feat(open_api): 统一开放接口命名并记录来源
- 将公共 API 相关处理器、路由与测试重命名为 Open API,统一接口语义。 - 新增发送来源 open_api,并在发送审计与队列中保存该来源。 - 前端发送队列来源标签新增 Open API 显示。
This commit is contained in:
@@ -1744,7 +1744,7 @@ func TestAPITokenManagementStoresHashAndRevokes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicAPIDomainAndMailboxCRUD(t *testing.T) {
|
||||
func TestOpenAPIDomainAndMailboxCRUD(t *testing.T) {
|
||||
a := newTestApp(t)
|
||||
ts := httptest.NewServer(a.Router())
|
||||
defer ts.Close()
|
||||
@@ -1764,7 +1764,7 @@ func TestPublicAPIDomainAndMailboxCRUD(t *testing.T) {
|
||||
|
||||
var domain Domain
|
||||
if code := openAdmin.do("POST", "/api/open/domains", map[string]string{"name": "api.example.test"}, &domain); code != http.StatusCreated {
|
||||
t.Fatalf("create public api domain code=%d domain=%+v", code, domain)
|
||||
t.Fatalf("create open api domain code=%d domain=%+v", code, domain)
|
||||
}
|
||||
if domain.Name != "api.example.test" || domain.DKIMPublicKey == "" {
|
||||
t.Fatalf("domain=%+v", domain)
|
||||
@@ -1773,20 +1773,20 @@ func TestPublicAPIDomainAndMailboxCRUD(t *testing.T) {
|
||||
Items []Domain `json:"items"`
|
||||
}
|
||||
if code := openAdmin.do("GET", "/api/open/domains", nil, &domains); code != http.StatusOK {
|
||||
t.Fatalf("list public api domains code=%d", code)
|
||||
t.Fatalf("list open api domains code=%d", code)
|
||||
}
|
||||
if len(domains.Items) < 2 {
|
||||
t.Fatalf("domains=%+v", domains.Items)
|
||||
}
|
||||
var disabled Domain
|
||||
if code := openAdmin.do("POST", "/api/open/domains/"+domain.ID, map[string]string{"status": "disabled"}, &disabled); code != http.StatusOK {
|
||||
t.Fatalf("update public api domain code=%d domain=%+v", code, disabled)
|
||||
t.Fatalf("update open api domain code=%d domain=%+v", code, disabled)
|
||||
}
|
||||
if disabled.Status != "disabled" {
|
||||
t.Fatalf("domain status=%q", disabled.Status)
|
||||
}
|
||||
if code := openAdmin.do("POST", "/api/open/domains/"+domain.ID, map[string]string{"status": "active"}, &domain); code != http.StatusOK {
|
||||
t.Fatalf("reactivate public api domain code=%d domain=%+v", code, domain)
|
||||
t.Fatalf("reactivate open api domain code=%d domain=%+v", code, domain)
|
||||
}
|
||||
|
||||
var mailbox Mailbox
|
||||
@@ -1797,7 +1797,7 @@ func TestPublicAPIDomainAndMailboxCRUD(t *testing.T) {
|
||||
"password": "Password123!",
|
||||
"quotaMb": 256,
|
||||
}, &mailbox); code != http.StatusCreated {
|
||||
t.Fatalf("create public api mailbox code=%d mailbox=%+v", code, mailbox)
|
||||
t.Fatalf("create open api mailbox code=%d mailbox=%+v", code, mailbox)
|
||||
}
|
||||
if mailbox.Address != "api-user@api.example.test" || mailbox.QuotaMB != 256 {
|
||||
t.Fatalf("mailbox=%+v", mailbox)
|
||||
@@ -1806,30 +1806,32 @@ func TestPublicAPIDomainAndMailboxCRUD(t *testing.T) {
|
||||
Items []Mailbox `json:"items"`
|
||||
}
|
||||
if code := openAdmin.do("GET", "/api/open/mailboxes", nil, &mailboxes); code != http.StatusOK {
|
||||
t.Fatalf("list public api mailboxes code=%d", code)
|
||||
t.Fatalf("list open api mailboxes code=%d", code)
|
||||
}
|
||||
if len(mailboxes.Items) < 2 {
|
||||
t.Fatalf("mailboxes=%+v", mailboxes.Items)
|
||||
}
|
||||
var updated Mailbox
|
||||
if code := openAdmin.do("POST", "/api/open/mailboxes/"+mailbox.ID, map[string]any{"displayName": "Renamed API User", "quotaMb": 512, "status": "disabled"}, &updated); code != http.StatusOK {
|
||||
t.Fatalf("update public api mailbox code=%d mailbox=%+v", code, updated)
|
||||
t.Fatalf("update open api mailbox code=%d mailbox=%+v", code, updated)
|
||||
}
|
||||
if updated.DisplayName != "Renamed API User" || updated.QuotaMB != 512 || updated.Status != "disabled" {
|
||||
t.Fatalf("updated mailbox=%+v", updated)
|
||||
}
|
||||
var ok map[string]any
|
||||
if code := openAdmin.do("DELETE", "/api/open/mailboxes/"+mailbox.ID, nil, &ok); code != http.StatusOK {
|
||||
t.Fatalf("delete public api mailbox code=%d body=%v", code, ok)
|
||||
t.Fatalf("delete open api mailbox code=%d body=%v", code, ok)
|
||||
}
|
||||
if code := openAdmin.do("DELETE", "/api/open/domains/"+domain.ID, nil, &ok); code != http.StatusOK {
|
||||
t.Fatalf("delete public api domain code=%d body=%v", code, ok)
|
||||
t.Fatalf("delete open api domain code=%d body=%v", code, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicAPISendStatusAndMailboxMessages(t *testing.T) {
|
||||
func TestOpenAPISendStatusAndMailboxMessages(t *testing.T) {
|
||||
a := newTestApp(t)
|
||||
stopTestWorkers(a)
|
||||
a.cfg.SMTPHost = "127.0.0.1"
|
||||
a.cfg.SMTPPort = "25"
|
||||
ts := httptest.NewServer(a.Router())
|
||||
defer ts.Close()
|
||||
admin := &testClient{t: t, server: ts}
|
||||
@@ -1839,9 +1841,9 @@ func TestPublicAPISendStatusAndMailboxMessages(t *testing.T) {
|
||||
t.Fatalf("admin login code=%d body=%v", code, login)
|
||||
}
|
||||
domainID := mustDefaultDomainID(t, a)
|
||||
sender := createTestMailbox(t, admin, domainID, "public-sender", "Public Sender", "Password123!", nil)
|
||||
recipient := createTestMailbox(t, admin, domainID, "public-recipient", "Public Recipient", "Password123!", nil)
|
||||
other := createTestMailbox(t, admin, domainID, "public-other", "Public Other", "Password123!", nil)
|
||||
sender := createTestMailbox(t, admin, domainID, "open-sender", "Open API Sender", "Password123!", nil)
|
||||
recipient := createTestMailbox(t, admin, domainID, "open-recipient", "Open API Recipient", "Password123!", nil)
|
||||
other := createTestMailbox(t, admin, domainID, "open-other", "Open API Other", "Password123!", nil)
|
||||
|
||||
senderClient := &testClient{t: t, server: ts}
|
||||
if code := senderClient.do("POST", "/api/auth/login", map[string]string{"email": sender.Address, "password": "Password123!"}, &login); code != http.StatusOK {
|
||||
@@ -1852,10 +1854,10 @@ func TestPublicAPISendStatusAndMailboxMessages(t *testing.T) {
|
||||
if code := senderClient.do("POST", "/api/open/send", map[string]any{
|
||||
"mailboxId": sender.ID,
|
||||
"to": []string{recipient.Address},
|
||||
"subject": "cookie-only public api send",
|
||||
"subject": "cookie-only open api send",
|
||||
"text": "this should not authenticate",
|
||||
}, &map[string]any{}); code != http.StatusUnauthorized {
|
||||
t.Fatalf("cookie-only public api send code=%d", code)
|
||||
t.Fatalf("cookie-only open api send code=%d", code)
|
||||
}
|
||||
var sent struct {
|
||||
ID string `json:"id"`
|
||||
@@ -1871,14 +1873,27 @@ func TestPublicAPISendStatusAndMailboxMessages(t *testing.T) {
|
||||
if code := senderOpen.do("POST", "/api/open/send", map[string]any{
|
||||
"mailboxId": sender.ID,
|
||||
"to": []string{recipient.Address},
|
||||
"subject": "public api send",
|
||||
"text": "hello from public api",
|
||||
"subject": "open api send",
|
||||
"text": "hello from open api",
|
||||
}, &sent); code != http.StatusCreated {
|
||||
t.Fatalf("public api send code=%d body=%+v", code, sent)
|
||||
t.Fatalf("open api send code=%d body=%+v", code, sent)
|
||||
}
|
||||
if sent.ID == "" || sent.Status != sendAuditAccepted || sent.MessageID == "" || sent.MailboxAddress != sender.Address {
|
||||
if sent.ID == "" || sent.QueueID == "" || sent.Status != sendQueueStatusQueued || sent.MessageID == "" || sent.MailboxAddress != sender.Address {
|
||||
t.Fatalf("sent response=%+v", sent)
|
||||
}
|
||||
var sendSource string
|
||||
if err := a.db.QueryRow(`SELECT source FROM send_audit_events WHERE sent_message_id=? AND event=?`, sent.MessageID, sendAuditAccepted).Scan(&sendSource); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sendSource != sendSourceOpenAPI {
|
||||
t.Fatalf("open api send audit source=%q, want %q", sendSource, sendSourceOpenAPI)
|
||||
}
|
||||
if err := a.db.QueryRow(`SELECT source FROM send_queue WHERE id=?`, sent.QueueID).Scan(&sendSource); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sendSource != sendSourceOpenAPI {
|
||||
t.Fatalf("open api send queue source=%q, want %q", sendSource, sendSourceOpenAPI)
|
||||
}
|
||||
|
||||
var status struct {
|
||||
ID string `json:"id"`
|
||||
@@ -1891,9 +1906,9 @@ func TestPublicAPISendStatusAndMailboxMessages(t *testing.T) {
|
||||
Subject string `json:"subject"`
|
||||
}
|
||||
if code := senderOpen.do("GET", "/api/open/send/"+sent.ID, nil, &status); code != http.StatusOK {
|
||||
t.Fatalf("public api send status code=%d status=%+v", code, status)
|
||||
t.Fatalf("open api send status code=%d status=%+v", code, status)
|
||||
}
|
||||
if status.ID != sent.ID || status.MessageID != sent.MessageID || status.Status != sendAuditAccepted {
|
||||
if status.ID != sent.QueueID || status.MessageID != sent.MessageID || status.Status != sendQueueStatusQueued {
|
||||
t.Fatalf("status=%+v sent=%+v", status, sent)
|
||||
}
|
||||
|
||||
@@ -1908,9 +1923,9 @@ func TestPublicAPISendStatusAndMailboxMessages(t *testing.T) {
|
||||
NextCursor string `json:"nextCursor"`
|
||||
}
|
||||
if code := recipientOpen.do("GET", "/api/open/mailboxes/"+recipient.ID+"/messages?folder=Inbox", nil, &inbox); code != http.StatusOK {
|
||||
t.Fatalf("public api mailbox messages code=%d inbox=%+v", code, inbox)
|
||||
t.Fatalf("open api mailbox messages code=%d inbox=%+v", code, inbox)
|
||||
}
|
||||
if len(inbox.Items) != 1 || inbox.Items[0].Subject != "public api send" || inbox.Items[0].From != sender.Address {
|
||||
if len(inbox.Items) != 1 || inbox.Items[0].Subject != "open api send" || inbox.Items[0].From != sender.Address {
|
||||
t.Fatalf("inbox=%+v", inbox.Items)
|
||||
}
|
||||
if code := recipientOpen.do("GET", "/api/open/mailboxes/"+other.ID+"/messages?folder=Inbox", nil, &map[string]any{}); code != http.StatusNotFound {
|
||||
|
||||
@@ -691,6 +691,14 @@ var errSenderNotAuthorized = errors.New("sender address is not authorized")
|
||||
var errMailboxQuotaExceeded = errors.New("mailbox quota exceeded")
|
||||
|
||||
func (a *App) sendMailNow(ctx context.Context, user *User, mb *Mailbox, req mailComposeInput) (*MailMessage, error) {
|
||||
return a.sendMailWithSource(ctx, user, mb, req, sendSourceWebmail)
|
||||
}
|
||||
|
||||
func (a *App) sendMailWithSource(ctx context.Context, user *User, mb *Mailbox, req mailComposeInput, source string) (*MailMessage, error) {
|
||||
source = strings.TrimSpace(source)
|
||||
if source == "" {
|
||||
source = sendSourceWebmail
|
||||
}
|
||||
if err := validateAttachmentLimit(req.Attachments, userLimits(user)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -742,8 +750,8 @@ func (a *App) sendMailNow(ctx context.Context, user *User, mb *Mailbox, req mail
|
||||
a.deleteMessage(ctx, sentID)
|
||||
return nil, fmt.Errorf("failed to store sent message in maildir: %w", err)
|
||||
}
|
||||
a.recordSendAudit(ctx, sendAuditAccepted, sendQueueStatusQueued, sendAuditInput{UserID: user.ID, MailboxID: mb.ID, SentMessageID: sentID, Source: sendSourceWebmail, MailFrom: fromAddress, HeaderFrom: fromAddress, Recipients: allRecipients})
|
||||
if _, err := a.enqueueSend(ctx, sendQueueInput{UserID: user.ID, MailboxID: mb.ID, SentMessageID: sentID, MessageID: messageID, Source: sendSourceWebmail, MailFrom: fromAddress, HeaderFrom: fromAddress, Recipients: allRecipients, MIMEBytes: mimeBytes, Now: now}); err != nil {
|
||||
a.recordSendAudit(ctx, sendAuditAccepted, sendQueueStatusQueued, sendAuditInput{UserID: user.ID, MailboxID: mb.ID, SentMessageID: sentID, Source: source, MailFrom: fromAddress, HeaderFrom: fromAddress, Recipients: allRecipients})
|
||||
if _, err := a.enqueueSend(ctx, sendQueueInput{UserID: user.ID, MailboxID: mb.ID, SentMessageID: sentID, MessageID: messageID, Source: source, MailFrom: fromAddress, HeaderFrom: fromAddress, Recipients: allRecipients, MIMEBytes: mimeBytes, Now: now}); err != nil {
|
||||
a.deleteMessage(ctx, sentID)
|
||||
return nil, fmt.Errorf("failed to enqueue delivery: %w", err)
|
||||
}
|
||||
|
||||
+30
-30
@@ -13,7 +13,7 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (a *App) handlePublicAPIListDomains(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIListDomains(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := a.db.QueryContext(r.Context(), `SELECT id,name,status,dkim_selector,dkim_public_key,dns_status,dns_checked_at,created_at FROM domains ORDER BY name`)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to list domains")
|
||||
@@ -36,7 +36,7 @@ func (a *App) handlePublicAPIListDomains(w http.ResponseWriter, r *http.Request)
|
||||
respondJSON(w, http.StatusOK, map[string]any{"items": items})
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPICreateDomain(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPICreateDomain(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func (a *App) handlePublicAPICreateDomain(w http.ResponseWriter, r *http.Request
|
||||
respondJSON(w, http.StatusCreated, domain)
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPIGetDomain(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIGetDomain(w http.ResponseWriter, r *http.Request) {
|
||||
domain, err := a.domainByID(r.Context(), chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, "domain not found")
|
||||
@@ -66,7 +66,7 @@ func (a *App) handlePublicAPIGetDomain(w http.ResponseWriter, r *http.Request) {
|
||||
respondJSON(w, http.StatusOK, domain)
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPIUpdateDomain(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIUpdateDomain(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func (a *App) handlePublicAPIUpdateDomain(w http.ResponseWriter, r *http.Request
|
||||
respondJSON(w, http.StatusOK, domain)
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPIDeleteDomain(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIDeleteDomain(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
var count int
|
||||
if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM mailboxes WHERE domain_id=?`, id).Scan(&count); err != nil {
|
||||
@@ -120,7 +120,7 @@ func (a *App) handlePublicAPIDeleteDomain(w http.ResponseWriter, r *http.Request
|
||||
respondJSON(w, http.StatusOK, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPIListMailboxes(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIListMailboxes(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := a.db.QueryContext(r.Context(), `SELECT mb.id,mb.user_id,u.email,mb.domain_id,mb.local_part,mb.address,mb.display_name,mb.quota_mb,mb.status,mb.created_at
|
||||
FROM mailboxes mb JOIN users u ON u.id=mb.user_id ORDER BY mb.address`)
|
||||
if err != nil {
|
||||
@@ -144,7 +144,7 @@ func (a *App) handlePublicAPIListMailboxes(w http.ResponseWriter, r *http.Reques
|
||||
respondJSON(w, http.StatusOK, map[string]any{"items": items})
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPICreateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPICreateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
DomainID string `json:"domainId"`
|
||||
LocalPart string `json:"localPart"`
|
||||
@@ -203,7 +203,7 @@ func (a *App) handlePublicAPICreateMailbox(w http.ResponseWriter, r *http.Reques
|
||||
respondJSON(w, http.StatusCreated, mailbox)
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPIGetMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIGetMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
mailbox, err := a.mailboxByID(r.Context(), chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, "mailbox not found")
|
||||
@@ -212,7 +212,7 @@ func (a *App) handlePublicAPIGetMailbox(w http.ResponseWriter, r *http.Request)
|
||||
respondJSON(w, http.StatusOK, mailbox)
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPIUpdateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIUpdateMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
current, err := a.mailboxByID(r.Context(), id)
|
||||
if err != nil {
|
||||
@@ -271,7 +271,7 @@ func (a *App) handlePublicAPIUpdateMailbox(w http.ResponseWriter, r *http.Reques
|
||||
respondJSON(w, http.StatusOK, mailbox)
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPIDeleteMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIDeleteMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
var owner string
|
||||
if err := a.db.QueryRowContext(r.Context(), `SELECT user_id FROM mailboxes WHERE id=?`, id).Scan(&owner); err != nil {
|
||||
@@ -318,7 +318,7 @@ func (a *App) handlePublicAPIDeleteMailbox(w http.ResponseWriter, r *http.Reques
|
||||
respondJSON(w, http.StatusOK, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPISendMail(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPISendMail(w http.ResponseWriter, r *http.Request) {
|
||||
var req mailComposeInput
|
||||
if err := decodeJSON(r, &req); err != nil {
|
||||
badRequest(w, err)
|
||||
@@ -329,26 +329,26 @@ func (a *App) handlePublicAPISendMail(w http.ResponseWriter, r *http.Request) {
|
||||
respondError(w, http.StatusNotFound, "mailbox not found")
|
||||
return
|
||||
}
|
||||
msg, err := a.sendMailNow(r.Context(), currentUser(r), mb, req)
|
||||
msg, err := a.sendMailWithSource(r.Context(), currentUser(r), mb, req, sendSourceOpenAPI)
|
||||
if err != nil {
|
||||
respondSendError(w, err)
|
||||
return
|
||||
}
|
||||
status := publicAPISendStatusFromMessage(msg, mb.Address)
|
||||
status := openAPISendStatusFromMessage(msg, mb.Address)
|
||||
if msg.SendQueueID != "" {
|
||||
if item, err := a.loadSendQueueEntryForUser(r.Context(), msg.SendQueueID, mb.UserID); err == nil {
|
||||
status = publicAPISendStatusFromQueue(item, mb.Address)
|
||||
status = openAPISendStatusFromQueue(item, mb.Address)
|
||||
}
|
||||
} else {
|
||||
item, err := a.loadLatestSendQueueForMailboxMessage(r.Context(), msg.ID, mb.ID)
|
||||
if err == nil {
|
||||
status = publicAPISendStatusFromQueue(item, mb.Address)
|
||||
status = openAPISendStatusFromQueue(item, mb.Address)
|
||||
}
|
||||
}
|
||||
respondJSON(w, http.StatusCreated, status)
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPISendStatus(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPISendStatus(w http.ResponseWriter, r *http.Request) {
|
||||
user := currentUser(r)
|
||||
id := strings.TrimSpace(chi.URLParam(r, "id"))
|
||||
item, err := a.loadSendQueueEntryForUser(r.Context(), id, user.ID)
|
||||
@@ -360,10 +360,10 @@ func (a *App) handlePublicAPISendStatus(w http.ResponseWriter, r *http.Request)
|
||||
if mb, mbErr := a.mailboxByID(r.Context(), item.MailboxID); mbErr == nil {
|
||||
mailboxAddress = mb.Address
|
||||
}
|
||||
respondJSON(w, http.StatusOK, publicAPISendStatusFromQueue(item, mailboxAddress))
|
||||
respondJSON(w, http.StatusOK, openAPISendStatusFromQueue(item, mailboxAddress))
|
||||
return
|
||||
}
|
||||
msg, err := a.loadPublicAPISentMessageForUser(r.Context(), id, user.ID)
|
||||
msg, err := a.loadOpenAPISentMessageForUser(r.Context(), id, user.ID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, "send item not found")
|
||||
return
|
||||
@@ -372,18 +372,18 @@ func (a *App) handlePublicAPISendStatus(w http.ResponseWriter, r *http.Request)
|
||||
if mb, mbErr := a.mailboxByID(r.Context(), msg.MailboxID); mbErr == nil {
|
||||
mailboxAddress = mb.Address
|
||||
}
|
||||
respondJSON(w, http.StatusOK, publicAPISendStatusFromMessage(msg, mailboxAddress))
|
||||
respondJSON(w, http.StatusOK, openAPISendStatusFromMessage(msg, mailboxAddress))
|
||||
}
|
||||
|
||||
func (a *App) handlePublicAPIMailboxMessages(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleOpenAPIMailboxMessages(w http.ResponseWriter, r *http.Request) {
|
||||
user := currentUser(r)
|
||||
mailboxID := strings.TrimSpace(chi.URLParam(r, "id"))
|
||||
if _, err := a.mailboxForUserByID(r.Context(), user.ID, mailboxID); err != nil {
|
||||
respondError(w, http.StatusNotFound, "mailbox not found")
|
||||
return
|
||||
}
|
||||
limit := parsePublicAPILimit(r, 30, 100)
|
||||
offset := parsePublicAPIOffset(r)
|
||||
limit := parseOpenAPILimit(r, 30, 100)
|
||||
offset := parseOpenAPIOffset(r)
|
||||
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
|
||||
if folder == "" {
|
||||
folder = "Inbox"
|
||||
@@ -458,7 +458,7 @@ func scanMailbox(row mailboxScanner) (Mailbox, error) {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
type publicAPISendStatus struct {
|
||||
type openAPISendStatus struct {
|
||||
ID string `json:"id"`
|
||||
QueueID string `json:"queueId,omitempty"`
|
||||
Status string `json:"status"`
|
||||
@@ -477,8 +477,8 @@ type publicAPISendStatus struct {
|
||||
DeliveredAt *time.Time `json:"deliveredAt,omitempty"`
|
||||
}
|
||||
|
||||
func publicAPISendStatusFromQueue(item SendQueueEntry, mailboxAddress string) publicAPISendStatus {
|
||||
return publicAPISendStatus{
|
||||
func openAPISendStatusFromQueue(item SendQueueEntry, mailboxAddress string) openAPISendStatus {
|
||||
return openAPISendStatus{
|
||||
ID: item.ID,
|
||||
QueueID: item.ID,
|
||||
Status: item.Status,
|
||||
@@ -498,10 +498,10 @@ func publicAPISendStatusFromQueue(item SendQueueEntry, mailboxAddress string) pu
|
||||
}
|
||||
}
|
||||
|
||||
func publicAPISendStatusFromMessage(msg *MailMessage, mailboxAddress string) publicAPISendStatus {
|
||||
func openAPISendStatusFromMessage(msg *MailMessage, mailboxAddress string) openAPISendStatus {
|
||||
recipients := append(append([]string{}, msg.To...), msg.CC...)
|
||||
recipients = append(recipients, msg.BCC...)
|
||||
return publicAPISendStatus{
|
||||
return openAPISendStatus{
|
||||
ID: msg.ID,
|
||||
Status: sendAuditAccepted,
|
||||
MessageID: msg.ID,
|
||||
@@ -623,7 +623,7 @@ func (a *App) loadSendQueueEntryForSentMessage(ctx context.Context, sentMessageI
|
||||
return a.loadLatestSendQueueForMessage(ctx, sentMessageID, userID)
|
||||
}
|
||||
|
||||
func (a *App) loadPublicAPISentMessageForUser(ctx context.Context, id, userID string) (*MailMessage, error) {
|
||||
func (a *App) loadOpenAPISentMessageForUser(ctx context.Context, id, userID string) (*MailMessage, error) {
|
||||
var messageID string
|
||||
err := a.db.QueryRowContext(ctx, `SELECT m.id
|
||||
FROM messages m JOIN mailboxes mb ON mb.id=m.mailbox_id JOIN folders f ON f.id=m.folder_id
|
||||
@@ -635,7 +635,7 @@ func (a *App) loadPublicAPISentMessageForUser(ctx context.Context, id, userID st
|
||||
return a.messageByID(ctx, messageID, false)
|
||||
}
|
||||
|
||||
func parsePublicAPILimit(r *http.Request, defaultLimit, maxLimit int) int {
|
||||
func parseOpenAPILimit(r *http.Request, defaultLimit, maxLimit int) int {
|
||||
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
if err != nil || limit <= 0 {
|
||||
return defaultLimit
|
||||
@@ -646,7 +646,7 @@ func parsePublicAPILimit(r *http.Request, defaultLimit, maxLimit int) int {
|
||||
return limit
|
||||
}
|
||||
|
||||
func parsePublicAPIOffset(r *http.Request) int {
|
||||
func parseOpenAPIOffset(r *http.Request) int {
|
||||
cursor := strings.TrimSpace(r.URL.Query().Get("cursor"))
|
||||
if cursor == "" {
|
||||
return 0
|
||||
@@ -77,19 +77,19 @@ func (a *App) Router() http.Handler {
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(a.requireAPIToken)
|
||||
r.With(a.requireAdminAccess, a.requireAnyPermission(PermissionDomainsView, PermissionDNSView, PermissionMailboxesView, PermissionAliasesView, PermissionSettingsView, PermissionTemplatesView)).Get("/open/domains", a.handlePublicAPIListDomains)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionDomainsCreate)).Post("/open/domains", a.handlePublicAPICreateDomain)
|
||||
r.With(a.requireAdminAccess, a.requireAnyPermission(PermissionDomainsView, PermissionDNSView, PermissionMailboxesView, PermissionAliasesView, PermissionSettingsView, PermissionTemplatesView)).Get("/open/domains/{id}", a.handlePublicAPIGetDomain)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionDomainsUpdate)).Post("/open/domains/{id}", a.handlePublicAPIUpdateDomain)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionDomainsDelete)).Delete("/open/domains/{id}", a.handlePublicAPIDeleteDomain)
|
||||
r.With(a.requireAdminAccess, a.requireAnyPermission(PermissionMailboxesView, PermissionMessagesView)).Get("/open/mailboxes", a.handlePublicAPIListMailboxes)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionMailboxesCreate)).Post("/open/mailboxes", a.handlePublicAPICreateMailbox)
|
||||
r.With(a.requireAdminAccess, a.requireAnyPermission(PermissionMailboxesView, PermissionMessagesView)).Get("/open/mailboxes/{id}", a.handlePublicAPIGetMailbox)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionMailboxesUpdate)).Post("/open/mailboxes/{id}", a.handlePublicAPIUpdateMailbox)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionMailboxesDelete)).Delete("/open/mailboxes/{id}", a.handlePublicAPIDeleteMailbox)
|
||||
r.With(a.requirePermission(PermissionMailSend)).Post("/open/send", a.handlePublicAPISendMail)
|
||||
r.With(a.requirePermission(PermissionMailRead)).Get("/open/send/{id}", a.handlePublicAPISendStatus)
|
||||
r.With(a.requirePermission(PermissionMailRead)).Get("/open/mailboxes/{id}/messages", a.handlePublicAPIMailboxMessages)
|
||||
r.With(a.requireAdminAccess, a.requireAnyPermission(PermissionDomainsView, PermissionDNSView, PermissionMailboxesView, PermissionAliasesView, PermissionSettingsView, PermissionTemplatesView)).Get("/open/domains", a.handleOpenAPIListDomains)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionDomainsCreate)).Post("/open/domains", a.handleOpenAPICreateDomain)
|
||||
r.With(a.requireAdminAccess, a.requireAnyPermission(PermissionDomainsView, PermissionDNSView, PermissionMailboxesView, PermissionAliasesView, PermissionSettingsView, PermissionTemplatesView)).Get("/open/domains/{id}", a.handleOpenAPIGetDomain)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionDomainsUpdate)).Post("/open/domains/{id}", a.handleOpenAPIUpdateDomain)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionDomainsDelete)).Delete("/open/domains/{id}", a.handleOpenAPIDeleteDomain)
|
||||
r.With(a.requireAdminAccess, a.requireAnyPermission(PermissionMailboxesView, PermissionMessagesView)).Get("/open/mailboxes", a.handleOpenAPIListMailboxes)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionMailboxesCreate)).Post("/open/mailboxes", a.handleOpenAPICreateMailbox)
|
||||
r.With(a.requireAdminAccess, a.requireAnyPermission(PermissionMailboxesView, PermissionMessagesView)).Get("/open/mailboxes/{id}", a.handleOpenAPIGetMailbox)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionMailboxesUpdate)).Post("/open/mailboxes/{id}", a.handleOpenAPIUpdateMailbox)
|
||||
r.With(a.requireAdminAccess, a.requirePermission(PermissionMailboxesDelete)).Delete("/open/mailboxes/{id}", a.handleOpenAPIDeleteMailbox)
|
||||
r.With(a.requirePermission(PermissionMailSend)).Post("/open/send", a.handleOpenAPISendMail)
|
||||
r.With(a.requirePermission(PermissionMailRead)).Get("/open/send/{id}", a.handleOpenAPISendStatus)
|
||||
r.With(a.requirePermission(PermissionMailRead)).Get("/open/mailboxes/{id}/messages", a.handleOpenAPIMailboxMessages)
|
||||
})
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
|
||||
@@ -27,6 +27,7 @@ const (
|
||||
|
||||
sendSourceWebmail = "webmail"
|
||||
sendSourceSubmission = "submission"
|
||||
sendSourceOpenAPI = "open_api"
|
||||
|
||||
sendQueueStaleAfter = 15 * time.Minute
|
||||
sendQueueConcurrency = 4
|
||||
|
||||
@@ -1921,6 +1921,7 @@ function sendQueueSourceLabel(source: string) {
|
||||
const normalized = source.toLowerCase()
|
||||
if (normalized === "submission") return "SMTP Submission"
|
||||
if (normalized === "webmail") return "Webmail"
|
||||
if (normalized === "open_api") return "Open API"
|
||||
if (normalized === "scheduled") return "定时发送"
|
||||
return source || "未知"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user