feat: 添加邮件服务器客户端库的基础功能
- 新增完整的Go客户端库实现,支持邮件服务器API的各种操作 - 实现账户管理、签名管理、邮件发送、审计、配额、通道等功能模块 - 提供ServiceAuth和AppAuth两种认证模式的客户端 - 添加详细的README文档,包含安装指南和使用示例 - 配置.gitignore文件以忽略构建产物和开发工具配置 - 支持分页查询、错误处理和客户端选项配置
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
|||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
vendor/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
# email-serverr-cli
|
||||||
|
|
||||||
|
Go client library for the Email Server API.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get gitea.s1f.ren/shiran/email-serverr-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Management Client (ServiceAuth)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
emailcli "gitea.s1f.ren/shiran/email-serverr-cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := emailcli.NewServiceClient(
|
||||||
|
"https://your-server.com",
|
||||||
|
"your-service-token",
|
||||||
|
)
|
||||||
|
|
||||||
|
// List accounts
|
||||||
|
accounts, err := client.ListAccounts(context.Background(), emailcli.AccountListQuery{
|
||||||
|
PaginationQuery: emailcli.PaginationQuery{Page: 1, PageSize: 20},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, a := range accounts.List {
|
||||||
|
fmt.Printf("Account: %s (ID: %d)\n", a.Name, a.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mail Sending Client (AppAuth)
|
||||||
|
|
||||||
|
```go
|
||||||
|
client := emailcli.NewAppClient(
|
||||||
|
"https://your-server.com",
|
||||||
|
"your-app-key",
|
||||||
|
"your-app-secret",
|
||||||
|
)
|
||||||
|
|
||||||
|
resp, err := client.SendMail(context.Background(), emailcli.SendMailReq{
|
||||||
|
To: []string{"recipient@example.com"},
|
||||||
|
Subject: "Hello",
|
||||||
|
Body: "<h1>Hello World</h1>",
|
||||||
|
Channel: "default",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Mail sent: log_id=%d status=%s\n", resp.MailLogID, resp.Status)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
| Mode | Constructor | Header | Use Case |
|
||||||
|
|------|-------------|--------|----------|
|
||||||
|
| ServiceAuth | `NewServiceClient` | `Authorization: Bearer <token>` | Management APIs |
|
||||||
|
| AppAuth | `NewAppClient` | `X-App-Key` + `X-App-Secret` | Mail sending |
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Mail (AppAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `SendMail` | Send an email |
|
||||||
|
|
||||||
|
### Accounts (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `CreateAccount` | Create mail account |
|
||||||
|
| `ListAccounts` | List accounts with filters |
|
||||||
|
| `GetAccount` | Get account details |
|
||||||
|
| `UpdateAccount` | Update account |
|
||||||
|
| `DeleteAccount` | Delete account |
|
||||||
|
| `ResetAccountSecret` | Reset app secret |
|
||||||
|
|
||||||
|
### Signatures (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `CreateSignature` | Create signature |
|
||||||
|
| `ListSignatures` | List signatures with filters |
|
||||||
|
| `GetSignature` | Get signature details |
|
||||||
|
| `UpdateSignature` | Update signature |
|
||||||
|
| `DeleteSignature` | Delete signature |
|
||||||
|
| `AuditSignature` | Approve or reject signature |
|
||||||
|
|
||||||
|
### Mail Logs (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `ListMailLogs` | List mail logs with filters |
|
||||||
|
| `GetMailLog` | Get mail log detail with body |
|
||||||
|
| `GetMailStats` | Get status statistics |
|
||||||
|
|
||||||
|
### Quotas (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `CreateQuota` | Create quota |
|
||||||
|
| `ListQuotas` | List quotas with filters |
|
||||||
|
| `GetQuotaSummary` | Get quota summary for account |
|
||||||
|
| `UpdateQuota` | Update quota |
|
||||||
|
| `DeleteQuota` | Delete quota |
|
||||||
|
|
||||||
|
### Channels (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `CreateChannel` | Create channel |
|
||||||
|
| `ListChannels` | List channels with filters |
|
||||||
|
| `UpdateChannel` | Update channel |
|
||||||
|
| `DeleteChannel` | Delete channel |
|
||||||
|
|
||||||
|
### Sender Accounts (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `CreateSender` | Create sender under channel |
|
||||||
|
| `ListSendersByChannel` | List senders for channel |
|
||||||
|
| `UpdateSender` | Update sender |
|
||||||
|
| `DeleteSender` | Delete sender |
|
||||||
|
|
||||||
|
### Audits (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `ListAuditPending` | List pending audit items |
|
||||||
|
| `GetAuditPendingDetail` | Get pending item detail |
|
||||||
|
| `ApproveAudit` | Approve single item |
|
||||||
|
| `RejectAudit` | Reject single item |
|
||||||
|
| `BatchApproveAudit` | Batch approve |
|
||||||
|
| `BatchRejectAudit` | Batch reject |
|
||||||
|
| `ListAuditLogs` | List audit history |
|
||||||
|
| `GetAuditStats` | Get audit statistics |
|
||||||
|
|
||||||
|
### Audit Rules (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `CreateAuditRule` | Create rule |
|
||||||
|
| `ListAuditRules` | List all rules |
|
||||||
|
| `GetAuditRule` | Get rule details |
|
||||||
|
| `UpdateAuditRule` | Update rule |
|
||||||
|
| `DeleteAuditRule` | Delete rule |
|
||||||
|
| `UpdateAuditRuleStatus` | Toggle rule status |
|
||||||
|
| `TestAuditRule` | Test rules against sample |
|
||||||
|
|
||||||
|
### Queue (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `GetQueueStatus` | Get queue lengths |
|
||||||
|
| `ListQueuePending` | List pending queue items |
|
||||||
|
| `CancelQueueItem` | Cancel queued mail |
|
||||||
|
| `RetryQueueItem` | Retry failed mail |
|
||||||
|
|
||||||
|
### Health Checks (ServiceAuth)
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `ListCheckLogs` | List check logs |
|
||||||
|
| `GetCheckSummary` | Get sender health summary |
|
||||||
|
| `TriggerCheck` | Trigger health check |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```go
|
||||||
|
resp, err := client.SendMail(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
var apiErr *emailcli.APIError
|
||||||
|
if errors.As(err, &apiErr) {
|
||||||
|
fmt.Printf("API error: code=%d message=%s\n", apiErr.Code, apiErr.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```go
|
||||||
|
client := emailcli.NewServiceClient(
|
||||||
|
"https://your-server.com",
|
||||||
|
"token",
|
||||||
|
emailcli.WithTimeout(60 * time.Second),
|
||||||
|
emailcli.WithHTTPClient(customClient),
|
||||||
|
)
|
||||||
|
```
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) CreateAccount(ctx context.Context, req CreateAccountReq) (*CreateAccountResp, error) {
|
||||||
|
return post[*CreateAccountResp](c, ctx, "/api/v1/accounts", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListAccounts(ctx context.Context, q AccountListQuery) (*PaginationResult[Account], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"user_id": q.UserID,
|
||||||
|
"status": q.Status,
|
||||||
|
"keyword": q.Keyword,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[Account]](c, ctx, "/api/v1/accounts", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetAccount(ctx context.Context, id uint) (*Account, error) {
|
||||||
|
return get[*Account](c, ctx, fmt.Sprintf("/api/v1/accounts/%d", id), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateAccount(ctx context.Context, id uint, req UpdateAccountReq) (*Account, error) {
|
||||||
|
return put[*Account](c, ctx, fmt.Sprintf("/api/v1/accounts/%d", id), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteAccount(ctx context.Context, id uint) error {
|
||||||
|
_, err := del[any](c, ctx, fmt.Sprintf("/api/v1/accounts/%d", id))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ResetAccountSecret(ctx context.Context, id uint) (*ResetSecretResp, error) {
|
||||||
|
return post[*ResetSecretResp](c, ctx, fmt.Sprintf("/api/v1/accounts/%d/reset-secret", id), nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) ListAuditPending(ctx context.Context, q AuditPendingQuery) (*PaginationResult[MailLog], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"user_id": q.UserID,
|
||||||
|
"account_id": q.AccountID,
|
||||||
|
"keyword": q.Keyword,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[MailLog]](c, ctx, "/api/v1/audits/pending", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetAuditPendingDetail(ctx context.Context, id uint) (*MailLogDetail, error) {
|
||||||
|
return get[*MailLogDetail](c, ctx, fmt.Sprintf("/api/v1/audits/pending/%d", id), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ApproveAudit(ctx context.Context, id uint) error {
|
||||||
|
_, err := post[any](c, ctx, fmt.Sprintf("/api/v1/audits/%d/approve", id), nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RejectAudit(ctx context.Context, id uint, req AuditRejectReq) error {
|
||||||
|
_, err := post[any](c, ctx, fmt.Sprintf("/api/v1/audits/%d/reject", id), req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BatchApproveAudit(ctx context.Context, req BatchAuditApproveReq) error {
|
||||||
|
_, err := post[any](c, ctx, "/api/v1/audits/batch/approve", req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BatchRejectAudit(ctx context.Context, req BatchAuditRejectReq) error {
|
||||||
|
_, err := post[any](c, ctx, "/api/v1/audits/batch/reject", req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListAuditLogs(ctx context.Context, q AuditLogQuery) (*PaginationResult[MailAudit], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"audit_type": q.AuditType,
|
||||||
|
"action": q.Action,
|
||||||
|
"user_id": q.UserID,
|
||||||
|
"start_date": q.StartDate,
|
||||||
|
"end_date": q.EndDate,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[MailAudit]](c, ctx, "/api/v1/audits/logs", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetAuditStats(ctx context.Context) (*AuditStats, error) {
|
||||||
|
return get[*AuditStats](c, ctx, "/api/v1/audits/stats", nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) CreateAuditRule(ctx context.Context, req CreateAuditRuleReq) (*AuditRule, error) {
|
||||||
|
return post[*AuditRule](c, ctx, "/api/v1/audit-rules", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListAuditRules(ctx context.Context) ([]AuditRule, error) {
|
||||||
|
return get[[]AuditRule](c, ctx, "/api/v1/audit-rules", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetAuditRule(ctx context.Context, id uint) (*AuditRule, error) {
|
||||||
|
return get[*AuditRule](c, ctx, fmt.Sprintf("/api/v1/audit-rules/%d", id), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateAuditRule(ctx context.Context, id uint, req UpdateAuditRuleReq) (*AuditRule, error) {
|
||||||
|
return put[*AuditRule](c, ctx, fmt.Sprintf("/api/v1/audit-rules/%d", id), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteAuditRule(ctx context.Context, id uint) error {
|
||||||
|
_, err := del[any](c, ctx, fmt.Sprintf("/api/v1/audit-rules/%d", id))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateAuditRuleStatus(ctx context.Context, id uint, status int8) (*AuditRule, error) {
|
||||||
|
return put[*AuditRule](c, ctx, fmt.Sprintf("/api/v1/audit-rules/%d/status", id), map[string]int8{"status": status})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TestAuditRule(ctx context.Context, req TestAuditRuleReq) (*TestAuditRuleResp, error) {
|
||||||
|
return post[*TestAuditRuleResp](c, ctx, "/api/v1/audit-rules/test", req)
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) CreateChannel(ctx context.Context, req CreateChannelReq) (*Channel, error) {
|
||||||
|
return post[*Channel](c, ctx, "/api/v1/channels", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListChannels(ctx context.Context, q ChannelListQuery) (*PaginationResult[Channel], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"status": q.Status,
|
||||||
|
"keyword": q.Keyword,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[Channel]](c, ctx, "/api/v1/channels", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateChannel(ctx context.Context, id uint, req UpdateChannelReq) (*Channel, error) {
|
||||||
|
return put[*Channel](c, ctx, fmt.Sprintf("/api/v1/channels/%d", id), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteChannel(ctx context.Context, id uint) error {
|
||||||
|
_, err := del[any](c, ctx, fmt.Sprintf("/api/v1/channels/%d", id))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) ListCheckLogs(ctx context.Context, q CheckLogQuery) (*PaginationResult[CheckLog], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"sender_account_id": q.SenderAccountID,
|
||||||
|
"start_date": q.StartDate,
|
||||||
|
"end_date": q.EndDate,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[CheckLog]](c, ctx, "/api/v1/check-logs", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetCheckSummary(ctx context.Context) ([]SenderHealth, error) {
|
||||||
|
return get[[]SenderHealth](c, ctx, "/api/v1/check-logs/summary", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TriggerCheck(ctx context.Context, senderAccountID uint) (*TriggerCheckResp, error) {
|
||||||
|
return post[*TriggerCheckResp](c, ctx, fmt.Sprintf("/api/v1/check-logs/trigger/%d", senderAccountID), nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
baseURL string
|
||||||
|
serviceToken string
|
||||||
|
appKey string
|
||||||
|
appSecret string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*Client)
|
||||||
|
|
||||||
|
func WithHTTPClient(hc *http.Client) Option {
|
||||||
|
return func(c *Client) { c.httpClient = hc }
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTimeout(d time.Duration) Option {
|
||||||
|
return func(c *Client) { c.httpClient.Timeout = d }
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServiceClient creates a client authenticated with a service token (management APIs).
|
||||||
|
func NewServiceClient(baseURL, serviceToken string, opts ...Option) *Client {
|
||||||
|
c := &Client{
|
||||||
|
baseURL: strings.TrimRight(baseURL, "/"),
|
||||||
|
serviceToken: serviceToken,
|
||||||
|
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppClient creates a client authenticated with AppKey/AppSecret (mail sending API).
|
||||||
|
func NewAppClient(baseURL, appKey, appSecret string, opts ...Option) *Client {
|
||||||
|
c := &Client{
|
||||||
|
baseURL: strings.TrimRight(baseURL, "/"),
|
||||||
|
appKey: appKey,
|
||||||
|
appSecret: appSecret,
|
||||||
|
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIResponse[T any] struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data T `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIError struct {
|
||||||
|
Code int
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *APIError) Error() string {
|
||||||
|
return fmt.Sprintf("api error %d: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) url(path string) string {
|
||||||
|
return c.baseURL + path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) setAuth(req *http.Request) {
|
||||||
|
if c.serviceToken != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+c.serviceToken)
|
||||||
|
}
|
||||||
|
if c.appKey != "" {
|
||||||
|
req.Header.Set("X-App-Key", c.appKey)
|
||||||
|
req.Header.Set("X-App-Secret", c.appSecret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequest[T any](c *Client, ctx context.Context, method, path string, body interface{}, query url.Values) (T, error) {
|
||||||
|
var zero T
|
||||||
|
|
||||||
|
var bodyReader io.Reader
|
||||||
|
if body != nil {
|
||||||
|
b, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return zero, fmt.Errorf("marshal body: %w", err)
|
||||||
|
}
|
||||||
|
bodyReader = bytes.NewReader(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqURL := c.url(path)
|
||||||
|
if len(query) > 0 {
|
||||||
|
reqURL += "?" + query.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
return zero, fmt.Errorf("new request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if body != nil {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
c.setAuth(req)
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return zero, fmt.Errorf("do request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return zero, fmt.Errorf("read response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResp APIResponse[T]
|
||||||
|
if err := json.Unmarshal(respBody, &apiResp); err != nil {
|
||||||
|
return zero, fmt.Errorf("unmarshal response (status %d): %w\nbody: %s", resp.StatusCode, err, string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiResp.Code != 200 {
|
||||||
|
return zero, &APIError{Code: apiResp.Code, Message: apiResp.Message}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiResp.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func get[T any](c *Client, ctx context.Context, path string, query url.Values) (T, error) {
|
||||||
|
return doRequest[T](c, ctx, http.MethodGet, path, nil, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func post[T any](c *Client, ctx context.Context, path string, body interface{}) (T, error) {
|
||||||
|
return doRequest[T](c, ctx, http.MethodPost, path, body, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func put[T any](c *Client, ctx context.Context, path string, body interface{}) (T, error) {
|
||||||
|
return doRequest[T](c, ctx, http.MethodPut, path, body, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func del[T any](c *Client, ctx context.Context, path string) (T, error) {
|
||||||
|
return doRequest[T](c, ctx, http.MethodDelete, path, nil, nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SendMail sends an email. Requires an AppClient (X-App-Key/X-App-Secret auth).
|
||||||
|
func (c *Client) SendMail(ctx context.Context, req SendMailReq) (*SendMailResp, error) {
|
||||||
|
return post[*SendMailResp](c, ctx, "/api/v1/mail/send", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMailLogs lists mail log records with filters (ServiceAuth).
|
||||||
|
func (c *Client) ListMailLogs(ctx context.Context, q MailLogListQuery) (*PaginationResult[MailLog], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"user_id": q.UserID,
|
||||||
|
"account_id": q.AccountID,
|
||||||
|
"status": q.Status,
|
||||||
|
"start_date": q.StartDate,
|
||||||
|
"end_date": q.EndDate,
|
||||||
|
"to": q.To,
|
||||||
|
"keyword": q.Keyword,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[MailLog]](c, ctx, "/api/v1/mail-logs", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetMailLog(ctx context.Context, id uint) (*MailLogDetail, error) {
|
||||||
|
return get[*MailLogDetail](c, ctx, fmt.Sprintf("/api/v1/mail-logs/%d", id), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetMailStats(ctx context.Context) ([]MailStatItem, error) {
|
||||||
|
return get[[]MailStatItem](c, ctx, "/api/v1/mail-logs/stats", nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildQuery(params map[string]interface{}) url.Values {
|
||||||
|
q := url.Values{}
|
||||||
|
for k, v := range params {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
if val != "" {
|
||||||
|
q.Set(k, val)
|
||||||
|
}
|
||||||
|
case int:
|
||||||
|
if val != 0 {
|
||||||
|
q.Set(k, fmt.Sprintf("%d", val))
|
||||||
|
}
|
||||||
|
case int8:
|
||||||
|
q.Set(k, fmt.Sprintf("%d", val))
|
||||||
|
case uint:
|
||||||
|
if val != 0 {
|
||||||
|
q.Set(k, fmt.Sprintf("%d", val))
|
||||||
|
}
|
||||||
|
case *int:
|
||||||
|
if val != nil {
|
||||||
|
q.Set(k, fmt.Sprintf("%d", *val))
|
||||||
|
}
|
||||||
|
case *int8:
|
||||||
|
if val != nil {
|
||||||
|
q.Set(k, fmt.Sprintf("%d", *val))
|
||||||
|
}
|
||||||
|
case *uint:
|
||||||
|
if val != nil {
|
||||||
|
q.Set(k, fmt.Sprintf("%d", *val))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
q.Set(k, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func paginationParams(p PaginationQuery) map[string]interface{} {
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
if p.Page > 0 {
|
||||||
|
m["page"] = p.Page
|
||||||
|
}
|
||||||
|
if p.PageSize > 0 {
|
||||||
|
m["page_size"] = p.PageSize
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeParams(maps ...map[string]interface{}) map[string]interface{} {
|
||||||
|
result := map[string]interface{}{}
|
||||||
|
for _, m := range maps {
|
||||||
|
for k, v := range m {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) GetQueueStatus(ctx context.Context) (*QueueStatusData, error) {
|
||||||
|
return get[*QueueStatusData](c, ctx, "/api/v1/queue/status", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListQueuePending(ctx context.Context, q QueuePendingQuery) (*PaginationResult[MailLog], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"channel_id": q.ChannelID,
|
||||||
|
"user_id": q.UserID,
|
||||||
|
"account_id": q.AccountID,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[MailLog]](c, ctx, "/api/v1/queue/pending", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) CancelQueueItem(ctx context.Context, mailLogID uint) error {
|
||||||
|
_, err := post[any](c, ctx, fmt.Sprintf("/api/v1/queue/%d/cancel", mailLogID), nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RetryQueueItem(ctx context.Context, mailLogID uint) error {
|
||||||
|
_, err := post[any](c, ctx, fmt.Sprintf("/api/v1/queue/%d/retry", mailLogID), nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) CreateQuota(ctx context.Context, req CreateQuotaReq) (*MailQuota, error) {
|
||||||
|
return post[*MailQuota](c, ctx, "/api/v1/quotas", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListQuotas(ctx context.Context, q QuotaListQuery) (*PaginationResult[MailQuota], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"account_id": q.AccountID,
|
||||||
|
"user_id": q.UserID,
|
||||||
|
"status": q.Status,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[MailQuota]](c, ctx, "/api/v1/quotas", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetQuotaSummary(ctx context.Context, accountID uint) (*QuotaSummary, error) {
|
||||||
|
return get[*QuotaSummary](c, ctx, fmt.Sprintf("/api/v1/quotas/summary/%d", accountID), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateQuota(ctx context.Context, id uint, req UpdateQuotaReq) (*MailQuota, error) {
|
||||||
|
return put[*MailQuota](c, ctx, fmt.Sprintf("/api/v1/quotas/%d", id), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteQuota(ctx context.Context, id uint) error {
|
||||||
|
_, err := del[any](c, ctx, fmt.Sprintf("/api/v1/quotas/%d", id))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) CreateSender(ctx context.Context, channelID uint, req CreateSenderReq) (*SenderAccount, error) {
|
||||||
|
return post[*SenderAccount](c, ctx, fmt.Sprintf("/api/v1/channels/%d/senders", channelID), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListSendersByChannel(ctx context.Context, channelID uint, q SenderListQuery) (*PaginationResult[SenderAccount], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"status": q.Status,
|
||||||
|
"keyword": q.Keyword,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[SenderAccount]](c, ctx, fmt.Sprintf("/api/v1/channels/%d/senders", channelID), buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateSender(ctx context.Context, id uint, req UpdateSenderReq) (*SenderAccount, error) {
|
||||||
|
return put[*SenderAccount](c, ctx, fmt.Sprintf("/api/v1/senders/%d", id), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteSender(ctx context.Context, id uint) error {
|
||||||
|
_, err := del[any](c, ctx, fmt.Sprintf("/api/v1/senders/%d", id))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) CreateSignature(ctx context.Context, req CreateSignatureReq) (*Signature, error) {
|
||||||
|
return post[*Signature](c, ctx, "/api/v1/signatures", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListSignatures(ctx context.Context, q SignatureListQuery) (*PaginationResult[Signature], error) {
|
||||||
|
params := mergeParams(paginationParams(q.PaginationQuery), map[string]interface{}{
|
||||||
|
"account_id": q.AccountID,
|
||||||
|
"status": q.Status,
|
||||||
|
"user_id": q.UserID,
|
||||||
|
"keyword": q.Keyword,
|
||||||
|
})
|
||||||
|
return get[*PaginationResult[Signature]](c, ctx, "/api/v1/signatures", buildQuery(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetSignature(ctx context.Context, id uint) (*Signature, error) {
|
||||||
|
return get[*Signature](c, ctx, fmt.Sprintf("/api/v1/signatures/%d", id), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateSignature(ctx context.Context, id uint, req UpdateSignatureReq) (*Signature, error) {
|
||||||
|
return put[*Signature](c, ctx, fmt.Sprintf("/api/v1/signatures/%d", id), req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteSignature(ctx context.Context, id uint) error {
|
||||||
|
_, err := del[any](c, ctx, fmt.Sprintf("/api/v1/signatures/%d", id))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AuditSignature(ctx context.Context, id uint, req AuditSignatureReq) error {
|
||||||
|
_, err := post[any](c, ctx, fmt.Sprintf("/api/v1/signatures/%d/audit", id), req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,484 @@
|
|||||||
|
package emailcli
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// PaginationResult is the generic paginated response wrapper.
|
||||||
|
type PaginationResult[T any] struct {
|
||||||
|
List []T `json:"list"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaginationQuery struct {
|
||||||
|
Page int `json:"page,omitempty"`
|
||||||
|
PageSize int `json:"page_size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GormModel fields ---
|
||||||
|
|
||||||
|
type GormModel struct {
|
||||||
|
ID uint `json:"ID"`
|
||||||
|
CreatedAt time.Time `json:"CreatedAt"`
|
||||||
|
UpdatedAt time.Time `json:"UpdatedAt"`
|
||||||
|
DeletedAt *time.Time `json:"DeletedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Account ---
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
GormModel
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AppKey string `json:"app_key"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
AuditMode int8 `json:"audit_mode"`
|
||||||
|
RateLimit int `json:"rate_limit"`
|
||||||
|
AllowedChannels string `json:"allowed_channels"`
|
||||||
|
DefaultSignatureID *uint `json:"default_signature_id"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateAccountReq struct {
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AuditMode *int8 `json:"audit_mode,omitempty"`
|
||||||
|
RateLimit *int `json:"rate_limit,omitempty"`
|
||||||
|
Remark string `json:"remark,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateAccountResp struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
AppKey string `json:"app_key"`
|
||||||
|
AppSecret string `json:"app_secret"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateAccountReq struct {
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
AuditMode *int8 `json:"audit_mode,omitempty"`
|
||||||
|
RateLimit *int `json:"rate_limit,omitempty"`
|
||||||
|
AllowedChannels *string `json:"allowed_channels,omitempty"`
|
||||||
|
DefaultSignatureID *uint `json:"default_signature_id,omitempty"`
|
||||||
|
Remark *string `json:"remark,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountListQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
UserID *int `json:"user_id,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
Keyword string `json:"keyword,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResetSecretResp struct {
|
||||||
|
AppSecret string `json:"app_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Signature ---
|
||||||
|
|
||||||
|
type Signature struct {
|
||||||
|
GormModel
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
AccountID *uint `json:"account_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
EnglishName string `json:"english_name"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Applicant string `json:"applicant"`
|
||||||
|
ApplicantInfo string `json:"applicant_info"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
RejectReason string `json:"reject_reason"`
|
||||||
|
Auditor string `json:"auditor"`
|
||||||
|
AuditedAt *time.Time `json:"audited_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSignatureReq struct {
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
AccountID *uint `json:"account_id,omitempty"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
EnglishName string `json:"english_name"`
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
Applicant string `json:"applicant,omitempty"`
|
||||||
|
ApplicantInfo string `json:"applicant_info,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateSignatureReq struct {
|
||||||
|
Title *string `json:"title,omitempty"`
|
||||||
|
EnglishName *string `json:"english_name,omitempty"`
|
||||||
|
Content *string `json:"content,omitempty"`
|
||||||
|
Applicant *string `json:"applicant,omitempty"`
|
||||||
|
ApplicantInfo *string `json:"applicant_info,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditSignatureReq struct {
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
RejectReason string `json:"reject_reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignatureListQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
AccountID *uint `json:"account_id,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
UserID *int `json:"user_id,omitempty"`
|
||||||
|
Keyword string `json:"keyword,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Mail ---
|
||||||
|
|
||||||
|
type SendMailReq struct {
|
||||||
|
To []string `json:"to"`
|
||||||
|
Cc []string `json:"cc,omitempty"`
|
||||||
|
Bcc []string `json:"bcc,omitempty"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
ContentType string `json:"content_type,omitempty"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
SignatureID *uint `json:"signature_id,omitempty"`
|
||||||
|
SignatureTitle string `json:"signature_title,omitempty"`
|
||||||
|
Attachments []AttachmentItem `json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttachmentItem struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendMailResp struct {
|
||||||
|
MailLogID uint `json:"mail_log_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Mail Log ---
|
||||||
|
|
||||||
|
type MailLog struct {
|
||||||
|
GormModel
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
AccountID uint `json:"account_id"`
|
||||||
|
ChannelID *uint `json:"channel_id"`
|
||||||
|
SenderAccountID *uint `json:"sender_account_id"`
|
||||||
|
SignatureID *uint `json:"signature_id"`
|
||||||
|
MessageID string `json:"message_id"`
|
||||||
|
FromAddress string `json:"from_address"`
|
||||||
|
ToAddresses string `json:"to_addresses"`
|
||||||
|
CcAddresses string `json:"cc_addresses"`
|
||||||
|
BccAddresses string `json:"bcc_addresses"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
HasAttachment bool `json:"has_attachment"`
|
||||||
|
SourceIP string `json:"source_ip"`
|
||||||
|
SourceType string `json:"source_type"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
RetryCount int `json:"retry_count"`
|
||||||
|
MaxRetry int `json:"max_retry"`
|
||||||
|
ErrorMessage string `json:"error_message"`
|
||||||
|
SentAt *time.Time `json:"sent_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MailLogListQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
UserID *int `json:"user_id,omitempty"`
|
||||||
|
AccountID *uint `json:"account_id,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
StartDate string `json:"start_date,omitempty"`
|
||||||
|
EndDate string `json:"end_date,omitempty"`
|
||||||
|
To string `json:"to,omitempty"`
|
||||||
|
Keyword string `json:"keyword,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MailLogDetail struct {
|
||||||
|
Log MailLog `json:"log"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MailStatItem struct {
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Quota ---
|
||||||
|
|
||||||
|
type MailQuota struct {
|
||||||
|
GormModel
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
AccountID uint `json:"account_id"`
|
||||||
|
QuotaType int8 `json:"quota_type"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Used int `json:"used"`
|
||||||
|
ExpireAt *time.Time `json:"expire_at"`
|
||||||
|
CycleUnit string `json:"cycle_unit"`
|
||||||
|
CycleResetAt *time.Time `json:"cycle_reset_at"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateQuotaReq struct {
|
||||||
|
AccountID uint `json:"account_id"`
|
||||||
|
QuotaType int8 `json:"quota_type"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
ExpireAt string `json:"expire_at,omitempty"`
|
||||||
|
CycleUnit string `json:"cycle_unit,omitempty"`
|
||||||
|
CycleResetAt string `json:"cycle_reset_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateQuotaReq struct {
|
||||||
|
Total *int `json:"total,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuotaListQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
AccountID *uint `json:"account_id,omitempty"`
|
||||||
|
UserID *int `json:"user_id,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuotaSummary struct {
|
||||||
|
AccountID uint `json:"account_id"`
|
||||||
|
TotalQuota int `json:"total_quota"`
|
||||||
|
TotalUsed int `json:"total_used"`
|
||||||
|
TotalRemaining int `json:"total_remaining"`
|
||||||
|
Details []MailQuota `json:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Channel ---
|
||||||
|
|
||||||
|
type Channel struct {
|
||||||
|
GormModel
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateChannelReq struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Strategy string `json:"strategy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateChannelReq struct {
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Strategy *string `json:"strategy,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelListQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
Keyword string `json:"keyword,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sender Account ---
|
||||||
|
|
||||||
|
type SenderAccount struct {
|
||||||
|
GormModel
|
||||||
|
ChannelID uint `json:"channel_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SmtpHost string `json:"smtp_host"`
|
||||||
|
SmtpPort int `json:"smtp_port"`
|
||||||
|
SmtpUser string `json:"smtp_user"`
|
||||||
|
SmtpSSL bool `json:"smtp_ssl"`
|
||||||
|
FromName string `json:"from_name"`
|
||||||
|
FromAddress string `json:"from_address"`
|
||||||
|
DailyLimit int `json:"daily_limit"`
|
||||||
|
DailySent int `json:"daily_sent"`
|
||||||
|
Weight int `json:"weight"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
LastCheckAt *time.Time `json:"last_check_at"`
|
||||||
|
LastCheckResult string `json:"last_check_result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSenderReq struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
SmtpHost string `json:"smtp_host"`
|
||||||
|
SmtpPort int `json:"smtp_port"`
|
||||||
|
SmtpUser string `json:"smtp_user"`
|
||||||
|
SmtpPassword string `json:"smtp_password"`
|
||||||
|
SmtpSSL *bool `json:"smtp_ssl,omitempty"`
|
||||||
|
FromName string `json:"from_name,omitempty"`
|
||||||
|
FromAddress string `json:"from_address"`
|
||||||
|
DailyLimit *int `json:"daily_limit,omitempty"`
|
||||||
|
Weight *int `json:"weight,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateSenderReq struct {
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
SmtpHost *string `json:"smtp_host,omitempty"`
|
||||||
|
SmtpPort *int `json:"smtp_port,omitempty"`
|
||||||
|
SmtpUser *string `json:"smtp_user,omitempty"`
|
||||||
|
SmtpPassword *string `json:"smtp_password,omitempty"`
|
||||||
|
SmtpSSL *bool `json:"smtp_ssl,omitempty"`
|
||||||
|
FromName *string `json:"from_name,omitempty"`
|
||||||
|
FromAddress *string `json:"from_address,omitempty"`
|
||||||
|
DailyLimit *int `json:"daily_limit,omitempty"`
|
||||||
|
Weight *int `json:"weight,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SenderListQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
Keyword string `json:"keyword,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Audit ---
|
||||||
|
|
||||||
|
type MailAudit struct {
|
||||||
|
GormModel
|
||||||
|
MailLogID uint `json:"mail_log_id"`
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
AccountID uint `json:"account_id"`
|
||||||
|
AuditType int8 `json:"audit_type"`
|
||||||
|
Action int8 `json:"action"`
|
||||||
|
RejectReason string `json:"reject_reason"`
|
||||||
|
HitRules string `json:"hit_rules"`
|
||||||
|
Auditor string `json:"auditor"`
|
||||||
|
AuditedAt time.Time `json:"audited_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditPendingQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
UserID *int `json:"user_id,omitempty"`
|
||||||
|
AccountID *uint `json:"account_id,omitempty"`
|
||||||
|
Keyword string `json:"keyword,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditLogQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
AuditType *int8 `json:"audit_type,omitempty"`
|
||||||
|
Action *int8 `json:"action,omitempty"`
|
||||||
|
UserID *int `json:"user_id,omitempty"`
|
||||||
|
StartDate string `json:"start_date,omitempty"`
|
||||||
|
EndDate string `json:"end_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditRejectReq struct {
|
||||||
|
RejectReason string `json:"reject_reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BatchAuditApproveReq struct {
|
||||||
|
MailLogIDs []uint `json:"mail_log_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BatchAuditRejectReq struct {
|
||||||
|
MailLogIDs []uint `json:"mail_log_ids"`
|
||||||
|
RejectReason string `json:"reject_reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditStats struct {
|
||||||
|
PendingCount int64 `json:"pending_count"`
|
||||||
|
TodayDetails []AuditDetailCount `json:"today_details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditDetailCount struct {
|
||||||
|
AuditType int8 `json:"audit_type"`
|
||||||
|
Action int8 `json:"action"`
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Audit Rule ---
|
||||||
|
|
||||||
|
type AuditRule struct {
|
||||||
|
GormModel
|
||||||
|
Name string `json:"name"`
|
||||||
|
RuleType string `json:"rule_type"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
Condition string `json:"condition"`
|
||||||
|
Action int8 `json:"action"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateAuditRuleReq struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
RuleType string `json:"rule_type"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
Condition string `json:"condition"`
|
||||||
|
Action int8 `json:"action"`
|
||||||
|
Priority *int `json:"priority,omitempty"`
|
||||||
|
Remark string `json:"remark,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateAuditRuleReq struct {
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
RuleType *string `json:"rule_type,omitempty"`
|
||||||
|
Target *string `json:"target,omitempty"`
|
||||||
|
Condition *string `json:"condition,omitempty"`
|
||||||
|
Action *int8 `json:"action,omitempty"`
|
||||||
|
Priority *int `json:"priority,omitempty"`
|
||||||
|
Status *int8 `json:"status,omitempty"`
|
||||||
|
Remark *string `json:"remark,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestAuditRuleReq struct {
|
||||||
|
Subject string `json:"subject,omitempty"`
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
To []string `json:"to,omitempty"`
|
||||||
|
From string `json:"from,omitempty"`
|
||||||
|
AccountID uint `json:"account_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestAuditRuleResp struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
HitRules []HitRuleEntry `json:"hit_rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HitRuleEntry struct {
|
||||||
|
RuleID uint `json:"rule_id"`
|
||||||
|
RuleName string `json:"rule_name"`
|
||||||
|
RuleType string `json:"rule_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Queue ---
|
||||||
|
|
||||||
|
type QueueStatusData struct {
|
||||||
|
Queues map[string]int `json:"queues"`
|
||||||
|
DelayQueue int `json:"delay_queue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueuePendingQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
ChannelID *uint `json:"channel_id,omitempty"`
|
||||||
|
UserID *int `json:"user_id,omitempty"`
|
||||||
|
AccountID *uint `json:"account_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Check ---
|
||||||
|
|
||||||
|
type CheckLog struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
SenderAccountID uint `json:"sender_account_id"`
|
||||||
|
VerificationCode string `json:"verification_code"`
|
||||||
|
SentAt string `json:"sent_at"`
|
||||||
|
Received bool `json:"received"`
|
||||||
|
ReceivedAt *string `json:"received_at"`
|
||||||
|
LatencyMs int `json:"latency_ms"`
|
||||||
|
ErrorMessage string `json:"error_message"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckLogQuery struct {
|
||||||
|
PaginationQuery
|
||||||
|
SenderAccountID *uint `json:"sender_account_id,omitempty"`
|
||||||
|
StartDate string `json:"start_date,omitempty"`
|
||||||
|
EndDate string `json:"end_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SenderHealth struct {
|
||||||
|
SenderAccountID uint `json:"sender_account_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
FromAddress string `json:"from_address"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
LastCheckResult string `json:"last_check_result"`
|
||||||
|
TotalChecks int64 `json:"total_checks"`
|
||||||
|
SuccessChecks int64 `json:"success_checks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TriggerCheckResp struct {
|
||||||
|
Result string `json:"result"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
CheckLog *CheckLog `json:"check_log,omitempty"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user