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