mirror of
https://github.com/axllent/mailpit.git
synced 2025-06-25 00:37:17 +02:00
Chore: Add swagger examples & API code restructure
This commit is contained in:
@ -2,692 +2,16 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/araddon/dateparse"
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/htmlcheck"
|
||||
"github.com/axllent/mailpit/internal/linkcheck"
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/mailpit/internal/spamassassin"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jhillyerd/enmime"
|
||||
)
|
||||
|
||||
// GetMessages returns a paginated list of messages as JSON
|
||||
func GetMessages(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/messages messages GetMessages
|
||||
//
|
||||
// # List messages
|
||||
//
|
||||
// Returns messages from the mailbox ordered from newest to oldest.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: start
|
||||
// in: query
|
||||
// description: Pagination offset
|
||||
// required: false
|
||||
// type: integer
|
||||
// default: 0
|
||||
// + name: limit
|
||||
// in: query
|
||||
// description: Limit results
|
||||
// required: false
|
||||
// type: integer
|
||||
// default: 50
|
||||
//
|
||||
// Responses:
|
||||
// 200: MessagesSummaryResponse
|
||||
// default: ErrorResponse
|
||||
start, beforeTS, limit := getStartLimit(r)
|
||||
|
||||
messages, err := storage.List(start, beforeTS, limit)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
stats := storage.StatsGet()
|
||||
|
||||
var res MessagesSummary
|
||||
|
||||
res.Start = start
|
||||
res.Messages = messages
|
||||
res.Count = float64(len(messages)) // legacy - now undocumented in API specs
|
||||
res.Total = stats.Total
|
||||
res.Unread = stats.Unread
|
||||
res.Tags = stats.Tags
|
||||
res.MessagesCount = stats.Total
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Search returns the latest messages as JSON
|
||||
func Search(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/search messages MessagesSummary
|
||||
//
|
||||
// # Search messages
|
||||
//
|
||||
// Returns messages matching [a search](https://mailpit.axllent.org/docs/usage/search-filters/), sorted by received date (descending).
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: query
|
||||
// in: query
|
||||
// description: Search query
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: start
|
||||
// in: query
|
||||
// description: Pagination offset
|
||||
// required: false
|
||||
// type: integer
|
||||
// default: 0
|
||||
// + name: limit
|
||||
// in: query
|
||||
// description: Limit results
|
||||
// required: false
|
||||
// type: integer
|
||||
// default: 50
|
||||
// + name: tz
|
||||
// in: query
|
||||
// description: [Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used specifically for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
// required: false
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: MessagesSummaryResponse
|
||||
// default: ErrorResponse
|
||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if search == "" {
|
||||
httpError(w, "Error: no search query")
|
||||
return
|
||||
}
|
||||
|
||||
start, beforeTS, limit := getStartLimit(r)
|
||||
|
||||
messages, results, err := storage.Search(search, r.URL.Query().Get("tz"), start, beforeTS, limit)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
stats := storage.StatsGet()
|
||||
|
||||
var res MessagesSummary
|
||||
|
||||
res.Start = start
|
||||
res.Messages = messages
|
||||
res.Count = float64(len(messages)) // legacy - now undocumented in API specs
|
||||
res.Total = stats.Total // total messages in mailbox
|
||||
res.MessagesCount = float64(results)
|
||||
res.Unread = stats.Unread
|
||||
res.Tags = stats.Tags
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSearch will delete all messages matching a search
|
||||
func DeleteSearch(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route DELETE /api/v1/search messages DeleteSearch
|
||||
//
|
||||
// # Delete messages by search
|
||||
//
|
||||
// Delete all messages matching [a search](https://mailpit.axllent.org/docs/usage/search-filters/).
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: query
|
||||
// in: query
|
||||
// description: Search query
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: tz
|
||||
// in: query
|
||||
// description: [Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used specifically for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
// required: false
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// default: ErrorResponse
|
||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if search == "" {
|
||||
httpError(w, "Error: no search query")
|
||||
return
|
||||
}
|
||||
|
||||
if err := storage.DeleteSearch(search, r.URL.Query().Get("tz")); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// GetMessage (method: GET) returns the Message as JSON
|
||||
func GetMessage(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID} message Message
|
||||
//
|
||||
// # Get message summary
|
||||
//
|
||||
// Returns the summary of a message, marking the message as read.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: ID
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: Message
|
||||
// default: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(msg); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadAttachment (method: GET) returns the attachment data
|
||||
func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/part/{PartID} message Attachment
|
||||
//
|
||||
// # Get message attachment
|
||||
//
|
||||
// This will return the attachment part using the appropriate Content-Type.
|
||||
//
|
||||
// Produces:
|
||||
// - application/*
|
||||
// - image/*
|
||||
// - text/*
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: ID
|
||||
// in: path
|
||||
// description: Message database ID
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: PartID
|
||||
// in: path
|
||||
// description: Attachment part ID
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: BinaryResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
partID := vars["partID"]
|
||||
|
||||
a, err := storage.GetAttachmentPart(id, partID)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
fileName := a.FileName
|
||||
if fileName == "" {
|
||||
fileName = a.ContentID
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", a.ContentType)
|
||||
w.Header().Set("Content-Disposition", "filename=\""+fileName+"\"")
|
||||
_, _ = w.Write(a.Content)
|
||||
}
|
||||
|
||||
// GetHeaders (method: GET) returns the message headers as JSON
|
||||
func GetHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/headers message Headers
|
||||
//
|
||||
// # Get message headers
|
||||
//
|
||||
// Returns the message headers as an array.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message headers.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: ID
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: MessageHeaders
|
||||
// default: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(data)
|
||||
m, err := mail.ReadMessage(reader)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(m.Header); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadRaw (method: GET) returns the full email source as plain text
|
||||
func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/raw message Raw
|
||||
//
|
||||
// # Get message source
|
||||
//
|
||||
// Returns the full email source as plain text.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message source.
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: ID
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: TextResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
dl := r.FormValue("dl")
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
if dl == "1" {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=\""+id+".eml\"")
|
||||
}
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
// DeleteMessages (method: DELETE) deletes all messages matching IDS.
|
||||
func DeleteMessages(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route DELETE /api/v1/messages messages DeleteMessages
|
||||
//
|
||||
// # Delete messages
|
||||
//
|
||||
// Delete individual or all messages. If no IDs are provided then all messages are deleted.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var data struct {
|
||||
IDs []string
|
||||
}
|
||||
err := decoder.Decode(&data)
|
||||
if err != nil || len(data.IDs) == 0 {
|
||||
if err := storage.DeleteAllMessages(); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := storage.DeleteMessages(data.IDs); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// SetReadStatus (method: PUT) will update the status to Read/Unread for all provided IDs
|
||||
// If no IDs are provided then all messages are updated.
|
||||
func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route PUT /api/v1/messages messages SetReadStatus
|
||||
//
|
||||
// # Set read status
|
||||
//
|
||||
// If no IDs are provided then all messages are updated.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var data struct {
|
||||
Read bool
|
||||
IDs []string
|
||||
}
|
||||
|
||||
err := decoder.Decode(&data)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ids := data.IDs
|
||||
|
||||
if len(ids) == 0 {
|
||||
if data.Read {
|
||||
err := storage.MarkAllRead()
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := storage.MarkAllUnread()
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if data.Read {
|
||||
for _, id := range ids {
|
||||
if err := storage.MarkRead(id); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, id := range ids {
|
||||
if err := storage.MarkUnread(id); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// HTMLCheck returns a summary of the HTML client support
|
||||
func HTMLCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/html-check Other HTMLCheck
|
||||
//
|
||||
// # HTML check
|
||||
//
|
||||
// Returns the summary of the message HTML checker.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: HTMLCheckResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
raw, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
e := bytes.NewReader(raw)
|
||||
|
||||
parser := enmime.NewParser(enmime.DisableCharacterDetection(true))
|
||||
|
||||
msg, err := parser.ReadEnvelope(e)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if msg.HTML == "" {
|
||||
httpError(w, "message does not contain HTML")
|
||||
return
|
||||
}
|
||||
|
||||
checks, err := htmlcheck.RunTests(msg.HTML)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(checks); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// LinkCheck returns a summary of links in the email
|
||||
func LinkCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/link-check Other LinkCheck
|
||||
//
|
||||
// # Link check
|
||||
//
|
||||
// Returns the summary of the message Link checker.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: LinkCheckResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
if config.DemoMode {
|
||||
httpError(w, "this functionality has been disabled for demonstration purposes")
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
f := r.URL.Query().Get("follow")
|
||||
followRedirects := f == "true" || f == "1"
|
||||
|
||||
summary, err := linkcheck.RunTests(msg, followRedirects)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(summary); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// SpamAssassinCheck returns a summary of SpamAssassin results (if enabled)
|
||||
func SpamAssassinCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/sa-check Other SpamAssassinCheck
|
||||
//
|
||||
// # SpamAssassin check
|
||||
//
|
||||
// Returns the SpamAssassin summary (if enabled) of the message.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: SpamAssassinResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
summary, err := spamassassin.Check(msg)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(summary); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// FourOFour returns a basic 404 message
|
||||
func fourOFour(w http.ResponseWriter) {
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
|
@ -6,8 +6,41 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/stats"
|
||||
)
|
||||
|
||||
// Application information
|
||||
// swagger:response AppInfoResponse
|
||||
type appInfoResponse struct {
|
||||
// Application information
|
||||
//
|
||||
// in: body
|
||||
Body stats.AppInformation
|
||||
}
|
||||
|
||||
// AppInfo returns some basic details about the running app, and latest release.
|
||||
func AppInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
// swagger:route GET /api/v1/info application AppInformation
|
||||
//
|
||||
// # Get application information
|
||||
//
|
||||
// Returns basic runtime information, message totals and latest release version.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: AppInfoResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(stats.Load()); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Response includes global web UI settings
|
||||
//
|
||||
// swagger:model WebUIConfiguration
|
||||
@ -38,6 +71,15 @@ type webUIConfiguration struct {
|
||||
DuplicatesIgnored bool
|
||||
}
|
||||
|
||||
// Web UI configuration response
|
||||
// swagger:response WebUIConfigurationResponse
|
||||
type webUIConfigurationResponse struct {
|
||||
// Web UI configuration settings
|
||||
//
|
||||
// in: body
|
||||
Body webUIConfiguration
|
||||
}
|
||||
|
||||
// WebUIConfig returns configuration settings for the web UI.
|
||||
func WebUIConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
// swagger:route GET /api/v1/webui application WebUIConfiguration
|
||||
@ -54,7 +96,8 @@ func WebUIConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
//
|
||||
// Responses:
|
||||
// 200: WebUIConfigurationResponse
|
||||
// default: ErrorResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
conf := webUIConfiguration{}
|
||||
|
||||
conf.Label = config.Label
|
@ -1,31 +0,0 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/axllent/mailpit/internal/stats"
|
||||
)
|
||||
|
||||
// AppInfo returns some basic details about the running app, and latest release.
|
||||
func AppInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
// swagger:route GET /api/v1/info application AppInformation
|
||||
//
|
||||
// # Get application information
|
||||
//
|
||||
// Returns basic runtime information, message totals and latest release version.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: InfoResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(stats.Load()); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
252
server/apiv1/message.go
Normal file
252
server/apiv1/message.go
Normal file
@ -0,0 +1,252 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// swagger:parameters GetMessageParams
|
||||
type getMessageParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
}
|
||||
|
||||
// GetMessage (method: GET) returns the Message as JSON
|
||||
func GetMessage(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID} message GetMessageParams
|
||||
//
|
||||
// # Get message summary
|
||||
//
|
||||
// Returns the summary of a message, marking the message as read.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: Message
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(msg); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters GetHeadersParams
|
||||
type getHeadersParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
}
|
||||
|
||||
// Message headers
|
||||
// swagger:model MessageHeadersResponse
|
||||
type messageHeaders map[string][]string
|
||||
|
||||
// GetHeaders (method: GET) returns the message headers as JSON
|
||||
func GetHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/headers message GetHeadersParams
|
||||
//
|
||||
// # Get message headers
|
||||
//
|
||||
// Returns the message headers as an array. Note that header keys are returned alphabetically.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message headers.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: MessageHeadersResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(data)
|
||||
m, err := mail.ReadMessage(reader)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(m.Header); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters AttachmentParams
|
||||
type attachmentParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
|
||||
// Attachment part ID
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 2
|
||||
PartID string
|
||||
}
|
||||
|
||||
// DownloadAttachment (method: GET) returns the attachment data
|
||||
func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/part/{PartID} message AttachmentParams
|
||||
//
|
||||
// # Get message attachment
|
||||
//
|
||||
// This will return the attachment part using the appropriate Content-Type.
|
||||
//
|
||||
// The ID can be set to `latest` to reference the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - application/*
|
||||
// - image/*
|
||||
// - text/*
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: BinaryResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
partID := vars["partID"]
|
||||
|
||||
a, err := storage.GetAttachmentPart(id, partID)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
fileName := a.FileName
|
||||
if fileName == "" {
|
||||
fileName = a.ContentID
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", a.ContentType)
|
||||
w.Header().Set("Content-Disposition", "filename=\""+fileName+"\"")
|
||||
_, _ = w.Write(a.Content)
|
||||
}
|
||||
|
||||
// swagger:parameters DownloadRawParams
|
||||
type downloadRawParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
}
|
||||
|
||||
// DownloadRaw (method: GET) returns the full email source as plain text
|
||||
func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/raw message DownloadRawParams
|
||||
//
|
||||
// # Get message source
|
||||
//
|
||||
// Returns the full email source as plain text.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message source.
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: TextResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
dl := r.FormValue("dl")
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
if dl == "1" {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=\""+id+".eml\"")
|
||||
}
|
||||
_, _ = w.Write(data)
|
||||
}
|
388
server/apiv1/messages.go
Normal file
388
server/apiv1/messages.go
Normal file
@ -0,0 +1,388 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
)
|
||||
|
||||
// swagger:parameters GetMessagesParams
|
||||
type getMessagesParams struct {
|
||||
// Pagination offset
|
||||
//
|
||||
// in: query
|
||||
// name: start
|
||||
// required: false
|
||||
// default: 0
|
||||
// type: integer
|
||||
// example: 100
|
||||
Start int `json:"start"`
|
||||
|
||||
// Limit number of results
|
||||
//
|
||||
// in: query
|
||||
// name: limit
|
||||
// required: false
|
||||
// default: 50
|
||||
// type: integer
|
||||
// example: 50
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
// Summary of messages
|
||||
// swagger:response MessagesSummaryResponse
|
||||
type messagesSummaryResponse struct {
|
||||
// The messages summary
|
||||
// in: body
|
||||
Body MessagesSummary
|
||||
}
|
||||
|
||||
// MessagesSummary is a summary of a list of messages
|
||||
type MessagesSummary struct {
|
||||
// Total number of messages in mailbox
|
||||
Total float64 `json:"total"`
|
||||
|
||||
// Total number of unread messages in mailbox
|
||||
Unread float64 `json:"unread"`
|
||||
|
||||
// Legacy - now undocumented in API specs but left for backwards compatibility.
|
||||
// Removed from API documentation 2023-07-12
|
||||
// swagger:ignore
|
||||
Count float64 `json:"count"`
|
||||
|
||||
// Total number of messages matching current query
|
||||
MessagesCount float64 `json:"messages_count"`
|
||||
|
||||
// Pagination offset
|
||||
Start int `json:"start"`
|
||||
|
||||
// All current tags
|
||||
Tags []string `json:"tags"`
|
||||
|
||||
// Messages summary
|
||||
// in: body
|
||||
Messages []storage.MessageSummary `json:"messages"`
|
||||
}
|
||||
|
||||
// GetMessages returns a paginated list of messages as JSON
|
||||
func GetMessages(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/messages messages GetMessagesParams
|
||||
//
|
||||
// # List messages
|
||||
//
|
||||
// Returns messages from the mailbox ordered from newest to oldest.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: MessagesSummaryResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
start, beforeTS, limit := getStartLimit(r)
|
||||
|
||||
messages, err := storage.List(start, beforeTS, limit)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
stats := storage.StatsGet()
|
||||
|
||||
var res MessagesSummary
|
||||
|
||||
res.Start = start
|
||||
res.Messages = messages
|
||||
res.Count = float64(len(messages)) // legacy - now undocumented in API specs
|
||||
res.Total = stats.Total
|
||||
res.Unread = stats.Unread
|
||||
res.Tags = stats.Tags
|
||||
res.MessagesCount = stats.Total
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SetReadStatusParams
|
||||
type setReadStatusParams struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
// Read status
|
||||
//
|
||||
// required: false
|
||||
// default: false
|
||||
// example: true
|
||||
Read bool
|
||||
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: false
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
}
|
||||
}
|
||||
|
||||
// SetReadStatus (method: PUT) will update the status to Read/Unread for all provided IDs
|
||||
// If no IDs are provided then all messages are updated.
|
||||
func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route PUT /api/v1/messages messages SetReadStatusParams
|
||||
//
|
||||
// # Set read status
|
||||
//
|
||||
// If no IDs are provided then all messages are updated.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
var data struct {
|
||||
Read bool
|
||||
IDs []string
|
||||
}
|
||||
|
||||
err := decoder.Decode(&data)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ids := data.IDs
|
||||
|
||||
if len(ids) == 0 {
|
||||
if data.Read {
|
||||
err := storage.MarkAllRead()
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := storage.MarkAllUnread()
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if data.Read {
|
||||
for _, id := range ids {
|
||||
if err := storage.MarkRead(id); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, id := range ids {
|
||||
if err := storage.MarkUnread(id); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteMessagesParams
|
||||
type deleteMessagesParams struct {
|
||||
// Delete request
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: false
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteMessages (method: DELETE) deletes all messages matching IDS.
|
||||
func DeleteMessages(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route DELETE /api/v1/messages messages DeleteMessagesParams
|
||||
//
|
||||
// # Delete messages
|
||||
//
|
||||
// Delete individual or all messages. If no IDs are provided then all messages are deleted.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var data struct {
|
||||
IDs []string
|
||||
}
|
||||
err := decoder.Decode(&data)
|
||||
if err != nil || len(data.IDs) == 0 {
|
||||
if err := storage.DeleteAllMessages(); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := storage.DeleteMessages(data.IDs); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// swagger:parameters SearchParams
|
||||
type searchParams struct {
|
||||
// Search query
|
||||
//
|
||||
// in: query
|
||||
// required: true
|
||||
// type: string
|
||||
// example: search words
|
||||
Query string `json:"query"`
|
||||
|
||||
// Pagination offset
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type integer
|
||||
// example: 100
|
||||
Start string `json:"start"`
|
||||
|
||||
// Limit results
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type integer
|
||||
// example: 50
|
||||
Limit string `json:"limit"`
|
||||
|
||||
// [Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type string
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
// Search returns the latest messages as JSON
|
||||
func Search(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/search messages SearchParams
|
||||
//
|
||||
// # Search messages
|
||||
//
|
||||
// Returns messages matching [a search](https://mailpit.axllent.org/docs/usage/search-filters/), sorted by received date (descending).
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: MessagesSummaryResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if search == "" {
|
||||
httpError(w, "Error: no search query")
|
||||
return
|
||||
}
|
||||
|
||||
start, beforeTS, limit := getStartLimit(r)
|
||||
|
||||
messages, results, err := storage.Search(search, r.URL.Query().Get("tz"), start, beforeTS, limit)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
stats := storage.StatsGet()
|
||||
|
||||
var res MessagesSummary
|
||||
|
||||
res.Start = start
|
||||
res.Messages = messages
|
||||
res.Count = float64(len(messages)) // legacy - now undocumented in API specs
|
||||
res.Total = stats.Total // total messages in mailbox
|
||||
res.MessagesCount = float64(results)
|
||||
res.Unread = stats.Unread
|
||||
res.Tags = stats.Tags
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteSearchParams
|
||||
type deleteSearchParams struct {
|
||||
// Search query
|
||||
//
|
||||
// in: query
|
||||
// required: true
|
||||
// type: string
|
||||
// example: search words
|
||||
Query string `json:"query"`
|
||||
|
||||
// [Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type string
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
// DeleteSearch will delete all messages matching a search
|
||||
func DeleteSearch(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route DELETE /api/v1/search messages DeleteSearchParams
|
||||
//
|
||||
// # Delete messages by search
|
||||
//
|
||||
// Delete all messages matching [a search](https://mailpit.axllent.org/docs/usage/search-filters/).
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if search == "" {
|
||||
httpError(w, "Error: no search query")
|
||||
return
|
||||
}
|
||||
|
||||
if err := storage.DeleteSearch(search, r.URL.Query().Get("tz")); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
238
server/apiv1/other.go
Normal file
238
server/apiv1/other.go
Normal file
@ -0,0 +1,238 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/htmlcheck"
|
||||
"github.com/axllent/mailpit/internal/linkcheck"
|
||||
"github.com/axllent/mailpit/internal/spamassassin"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jhillyerd/enmime"
|
||||
)
|
||||
|
||||
// swagger:parameters HTMLCheckParams
|
||||
type htmlCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
}
|
||||
|
||||
// HTMLCheckResponse summary response
|
||||
type HTMLCheckResponse = htmlcheck.Response
|
||||
|
||||
// HTMLCheck returns a summary of the HTML client support
|
||||
func HTMLCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/html-check Other HTMLCheckParams
|
||||
//
|
||||
// # HTML check
|
||||
//
|
||||
// Returns the summary of the message HTML checker.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: HTMLCheckResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
raw, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
e := bytes.NewReader(raw)
|
||||
|
||||
parser := enmime.NewParser(enmime.DisableCharacterDetection(true))
|
||||
|
||||
msg, err := parser.ReadEnvelope(e)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if msg.HTML == "" {
|
||||
httpError(w, "message does not contain HTML")
|
||||
return
|
||||
}
|
||||
|
||||
checks, err := htmlcheck.RunTests(msg.HTML)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(checks); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters LinkCheckParams
|
||||
type linkCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
|
||||
// Follow redirects
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// default: false
|
||||
// example: false
|
||||
Follow string `json:"follow"`
|
||||
}
|
||||
|
||||
// LinkCheckResponse summary response
|
||||
type LinkCheckResponse = linkcheck.Response
|
||||
|
||||
// LinkCheck returns a summary of links in the email
|
||||
func LinkCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/link-check Other LinkCheckParams
|
||||
//
|
||||
// # Link check
|
||||
//
|
||||
// Returns the summary of the message Link checker.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: LinkCheckResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
if config.DemoMode {
|
||||
httpError(w, "this functionality has been disabled for demonstration purposes")
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
f := r.URL.Query().Get("follow")
|
||||
followRedirects := f == "true" || f == "1"
|
||||
|
||||
summary, err := linkcheck.RunTests(msg, followRedirects)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(summary); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SpamAssassinCheckParams
|
||||
type spamAssassinCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
}
|
||||
|
||||
// SpamAssassinResponse summary response
|
||||
type SpamAssassinResponse = spamassassin.Result
|
||||
|
||||
// SpamAssassinCheck returns a summary of SpamAssassin results (if enabled)
|
||||
func SpamAssassinCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/sa-check Other SpamAssassinCheckParams
|
||||
//
|
||||
// # SpamAssassin check
|
||||
//
|
||||
// Returns the SpamAssassin summary (if enabled) of the message.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: SpamAssassinResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
fourOFour(w)
|
||||
return
|
||||
}
|
||||
|
||||
summary, err := spamassassin.Check(msg)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(summary); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
@ -17,14 +17,36 @@ import (
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
)
|
||||
|
||||
// swagger:parameters ReleaseMessageParams
|
||||
type releaseMessageParams struct {
|
||||
// Message database ID
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of email addresses to relay the message to
|
||||
//
|
||||
// required: true
|
||||
// example: ["user1@example.com", "user2@example.com"]
|
||||
To []string
|
||||
}
|
||||
}
|
||||
|
||||
// ReleaseMessage (method: POST) will release a message via a pre-configured external SMTP server.
|
||||
func ReleaseMessage(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route POST /api/v1/message/{ID}/release message ReleaseMessage
|
||||
// swagger:route POST /api/v1/message/{ID}/release message ReleaseMessageParams
|
||||
//
|
||||
// # Release message
|
||||
//
|
||||
// Release a message via a pre-configured external SMTP server. This is only enabled if message relaying has been configured.
|
||||
//
|
||||
// The ID can be set to `latest` to reference the latest message.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
@ -35,7 +57,8 @@ func ReleaseMessage(w http.ResponseWriter, r *http.Request) {
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// default: ErrorResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
if config.DemoMode {
|
||||
httpError(w, "this functionality has been disabled for demonstration purposes")
|
||||
@ -54,7 +77,9 @@ func ReleaseMessage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
data := releaseMessageRequestBody{}
|
||||
var data struct {
|
||||
To []string
|
||||
}
|
||||
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
httpError(w, err.Error())
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
"github.com/jhillyerd/enmime"
|
||||
)
|
||||
|
||||
// swagger:parameters SendMessage
|
||||
// swagger:parameters SendMessageParams
|
||||
type sendMessageParams struct {
|
||||
// in: body
|
||||
Body *SendRequest
|
||||
@ -108,13 +108,6 @@ type SendRequest struct {
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// SendMessageConfirmation struct
|
||||
type SendMessageConfirmation struct {
|
||||
// Database ID
|
||||
// example: iAfZVVe2UQFNSG5BAjgYwa
|
||||
ID string
|
||||
}
|
||||
|
||||
// JSONErrorMessage struct
|
||||
type JSONErrorMessage struct {
|
||||
// Error message
|
||||
@ -122,9 +115,25 @@ type JSONErrorMessage struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
// Confirmation message for HTTP send API
|
||||
// swagger:response sendMessageResponse
|
||||
type sendMessageResponse struct {
|
||||
// Response for sending messages via the HTTP API
|
||||
//
|
||||
// in: body
|
||||
Body SendMessageConfirmation
|
||||
}
|
||||
|
||||
// SendMessageConfirmation struct
|
||||
type SendMessageConfirmation struct {
|
||||
// Database ID
|
||||
// example: iAfZVVe2UQfNSG5BAjgYwa
|
||||
ID string
|
||||
}
|
||||
|
||||
// SendMessageHandler handles HTTP requests to send a new message
|
||||
func SendMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route POST /api/v1/send message SendMessage
|
||||
// swagger:route POST /api/v1/send message SendMessageParams
|
||||
//
|
||||
// # Send a message
|
||||
//
|
||||
@ -140,7 +149,7 @@ func SendMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
//
|
||||
// Responses:
|
||||
// 200: sendMessageResponse
|
||||
// default: jsonErrorResponse
|
||||
// 400: jsonErrorResponse
|
||||
|
||||
if config.DemoMode {
|
||||
httpJSONError(w, "this functionality has been disabled for demonstration purposes")
|
||||
|
@ -1,39 +1,9 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"github.com/axllent/mailpit/internal/htmlcheck"
|
||||
"github.com/axllent/mailpit/internal/linkcheck"
|
||||
"github.com/axllent/mailpit/internal/spamassassin"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
)
|
||||
|
||||
// MessagesSummary is a summary of a list of messages
|
||||
type MessagesSummary struct {
|
||||
// Total number of messages in mailbox
|
||||
Total float64 `json:"total"`
|
||||
|
||||
// Total number of unread messages in mailbox
|
||||
Unread float64 `json:"unread"`
|
||||
|
||||
// Legacy - now undocumented in API specs but left for backwards compatibility.
|
||||
// Removed from API documentation 2023-07-12
|
||||
// swagger:ignore
|
||||
Count float64 `json:"count"`
|
||||
|
||||
// Total number of messages matching current query
|
||||
MessagesCount float64 `json:"messages_count"`
|
||||
|
||||
// Pagination offset
|
||||
Start int `json:"start"`
|
||||
|
||||
// All current tags
|
||||
Tags []string `json:"tags"`
|
||||
|
||||
// Messages summary
|
||||
// in: body
|
||||
Messages []storage.MessageSummary `json:"messages"`
|
||||
}
|
||||
|
||||
// The following structs & aliases are provided for easy import
|
||||
// and understanding of the JSON structure.
|
||||
|
||||
@ -45,12 +15,3 @@ type Message = storage.Message
|
||||
|
||||
// Attachment summary
|
||||
type Attachment = storage.Attachment
|
||||
|
||||
// HTMLCheckResponse summary
|
||||
type HTMLCheckResponse = htmlcheck.Response
|
||||
|
||||
// LinkCheckResponse summary
|
||||
type LinkCheckResponse = linkcheck.Response
|
||||
|
||||
// SpamAssassinResponse summary
|
||||
type SpamAssassinResponse = spamassassin.Result
|
||||
|
@ -1,177 +1,8 @@
|
||||
package apiv1
|
||||
|
||||
import "github.com/axllent/mailpit/internal/stats"
|
||||
|
||||
// These structs are for the purpose of defining swagger HTTP parameters & responses
|
||||
|
||||
// Application information
|
||||
// swagger:response InfoResponse
|
||||
type infoResponse struct {
|
||||
// Application information
|
||||
//
|
||||
// in: body
|
||||
Body stats.AppInformation
|
||||
}
|
||||
|
||||
// Web UI configuration
|
||||
// swagger:response WebUIConfigurationResponse
|
||||
type webUIConfigurationResponse struct {
|
||||
// Web UI configuration settings
|
||||
//
|
||||
// in: body
|
||||
Body webUIConfiguration
|
||||
}
|
||||
|
||||
// Message summary
|
||||
// swagger:response MessagesSummaryResponse
|
||||
type messagesSummaryResponse struct {
|
||||
// The message summary
|
||||
// in: body
|
||||
Body MessagesSummary
|
||||
}
|
||||
|
||||
// Message headers
|
||||
// swagger:model MessageHeaders
|
||||
type messageHeaders map[string][]string
|
||||
|
||||
// swagger:parameters DeleteMessages
|
||||
type deleteMessagesParams struct {
|
||||
// in: body
|
||||
Body *deleteMessagesRequestBody
|
||||
}
|
||||
|
||||
// Delete request
|
||||
// swagger:model DeleteRequest
|
||||
type deleteMessagesRequestBody struct {
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: false
|
||||
// example: ["5dec4247-812e-4b77-9101-e25ad406e9ea", "8ac66bbc-2d9a-4c41-ad99-00aa75fa674e"]
|
||||
IDs []string
|
||||
}
|
||||
|
||||
// swagger:parameters SetReadStatus
|
||||
type setReadStatusParams struct {
|
||||
// in: body
|
||||
Body *setReadStatusRequestBody
|
||||
}
|
||||
|
||||
// Set read status request
|
||||
// swagger:model setReadStatusRequestBody
|
||||
type setReadStatusRequestBody struct {
|
||||
// Read status
|
||||
//
|
||||
// required: false
|
||||
// default: false
|
||||
// example: true
|
||||
Read bool
|
||||
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: false
|
||||
// example: ["5dec4247-812e-4b77-9101-e25ad406e9ea", "8ac66bbc-2d9a-4c41-ad99-00aa75fa674e"]
|
||||
IDs []string
|
||||
}
|
||||
|
||||
// swagger:parameters SetTags
|
||||
type setTagsParams struct {
|
||||
// in: body
|
||||
Body *setTagsRequestBody
|
||||
}
|
||||
|
||||
// Set tags request
|
||||
// swagger:model setTagsRequestBody
|
||||
type setTagsRequestBody struct {
|
||||
// Array of tag names to set
|
||||
//
|
||||
// required: true
|
||||
// example: ["Tag 1", "Tag 2"]
|
||||
Tags []string
|
||||
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: true
|
||||
// example: ["5dec4247-812e-4b77-9101-e25ad406e9ea", "8ac66bbc-2d9a-4c41-ad99-00aa75fa674e"]
|
||||
IDs []string
|
||||
}
|
||||
|
||||
// swagger:parameters RenameTag
|
||||
type renameTagParams struct {
|
||||
// in: body
|
||||
Body *renameTagRequestBody
|
||||
}
|
||||
|
||||
// Rename tag request
|
||||
// swagger:model renameTagRequestBody
|
||||
type renameTagRequestBody struct {
|
||||
// New name
|
||||
//
|
||||
// required: true
|
||||
// example: New name
|
||||
Name string
|
||||
}
|
||||
|
||||
// swagger:parameters ReleaseMessage
|
||||
type releaseMessageParams struct {
|
||||
// Message database ID
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// in: body
|
||||
Body *releaseMessageRequestBody
|
||||
}
|
||||
|
||||
// Release request
|
||||
// swagger:model releaseMessageRequestBody
|
||||
type releaseMessageRequestBody struct {
|
||||
// Array of email addresses to relay the message to
|
||||
// required: true
|
||||
// example: ["user1@example.com", "user2@example.com"]
|
||||
To []string
|
||||
}
|
||||
|
||||
// swagger:parameters HTMLCheck
|
||||
type htmlCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// swagger:parameters LinkCheck
|
||||
type linkCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// Follow redirects
|
||||
//
|
||||
// in: query
|
||||
// description: Follow redirects
|
||||
// required: false
|
||||
// default: false
|
||||
Follow string `json:"follow"`
|
||||
}
|
||||
|
||||
// swagger:parameters SpamAssassinCheck
|
||||
type spamAssassinCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// Binary data response inherits the attachment's content type.
|
||||
// Binary data response which inherits the attachment's content type.
|
||||
// swagger:response BinaryResponse
|
||||
type binaryResponse string
|
||||
|
||||
@ -183,11 +14,15 @@ type textResponse string
|
||||
// swagger:response HTMLResponse
|
||||
type htmlResponse string
|
||||
|
||||
// HTTP error response will return with a >= 400 response code
|
||||
// Server error will return with a 400 status code
|
||||
// with the error message in the body
|
||||
// swagger:response ErrorResponse
|
||||
// example: invalid request
|
||||
type errorResponse string
|
||||
|
||||
// Not found error will return a 404 status code
|
||||
// swagger:response NotFoundResponse
|
||||
type notFoundResponse string
|
||||
|
||||
// Plain text "ok" response
|
||||
// swagger:response OKResponse
|
||||
type okResponse string
|
||||
@ -196,15 +31,6 @@ type okResponse string
|
||||
// swagger:response ArrayResponse
|
||||
type arrayResponse []string
|
||||
|
||||
// Confirmation message for HTTP send API
|
||||
// swagger:response sendMessageResponse
|
||||
type sendMessageResponse struct {
|
||||
// Response for sending messages via the HTTP API
|
||||
//
|
||||
// in: body
|
||||
Body SendMessageConfirmation
|
||||
}
|
||||
|
||||
// JSON error response
|
||||
// swagger:response jsonErrorResponse
|
||||
type jsonErrorResponse struct {
|
||||
|
@ -24,7 +24,7 @@ func GetAllTags(w http.ResponseWriter, _ *http.Request) {
|
||||
//
|
||||
// Responses:
|
||||
// 200: ArrayResponse
|
||||
// default: ErrorResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(storage.GetAllTags()); err != nil {
|
||||
@ -32,9 +32,27 @@ func GetAllTags(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SetTagsParams
|
||||
type setTagsParams struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of tag names to set
|
||||
//
|
||||
// required: true
|
||||
// example: ["Tag 1", "Tag 2"]
|
||||
Tags []string
|
||||
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: true
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
}
|
||||
}
|
||||
|
||||
// SetMessageTags (method: PUT) will set the tags for all provided IDs
|
||||
func SetMessageTags(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route PUT /api/v1/tags tags SetTags
|
||||
// swagger:route PUT /api/v1/tags tags SetTagsParams
|
||||
//
|
||||
// # Set message tags
|
||||
//
|
||||
@ -50,7 +68,7 @@ func SetMessageTags(w http.ResponseWriter, r *http.Request) {
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// default: ErrorResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
@ -80,29 +98,42 @@ func SetMessageTags(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// swagger:parameters RenameTagParams
|
||||
type renameTagParams struct {
|
||||
// The url-encoded tag name to rename
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// type: string
|
||||
// example: Old name
|
||||
Tag string
|
||||
|
||||
// in: body
|
||||
Body struct {
|
||||
// New name
|
||||
//
|
||||
// required: true
|
||||
// example: New name
|
||||
Name string
|
||||
}
|
||||
}
|
||||
|
||||
// RenameTag (method: PUT) used to rename a tag
|
||||
func RenameTag(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route PUT /api/v1/tags/{tag} tags RenameTag
|
||||
// swagger:route PUT /api/v1/tags/{Tag} tags RenameTagParams
|
||||
//
|
||||
// # Rename a tag
|
||||
//
|
||||
// Renames a tag.
|
||||
// Renames an existing tag.
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: tag
|
||||
// in: path
|
||||
// description: The url-encoded tag name to rename
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// default: ErrorResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
@ -131,29 +162,32 @@ func RenameTag(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteTagParams
|
||||
type deleteTagParams struct {
|
||||
// The url-encoded tag name to delete
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: My tag
|
||||
Tag string
|
||||
}
|
||||
|
||||
// DeleteTag (method: DELETE) used to delete a tag
|
||||
func DeleteTag(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route DELETE /api/v1/tags/{tag} tags DeleteTag
|
||||
// swagger:route DELETE /api/v1/tags/{Tag} tags DeleteTagParams
|
||||
//
|
||||
// # Delete a tag
|
||||
//
|
||||
// Deletes a tag. This will not delete any messages with this tag.
|
||||
// Deletes a tag. This will not delete any messages with the tag, but will remove the tag from any messages containing the tag.
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: tag
|
||||
// in: path
|
||||
// description: The url-encoded tag name to delete
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: OKResponse
|
||||
// default: ErrorResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
|
159
server/apiv1/testing.go
Normal file
159
server/apiv1/testing.go
Normal file
@ -0,0 +1,159 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// swagger:parameters GetMessageHTMLParams
|
||||
type getMessageHTMLParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
}
|
||||
|
||||
// GetMessageHTML (method: GET) returns a rendered version of a message's HTML part
|
||||
func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /view/{ID}.html testing GetMessageHTMLParams
|
||||
//
|
||||
// # Render message HTML part
|
||||
//
|
||||
// Renders just the message's HTML part which can be used for UI integration testing.
|
||||
// Attached inline images are modified to link to the API provided they exist.
|
||||
// Note that is the message does not contain a HTML part then an 404 error is returned.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - text/html
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: HTMLResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "Message not found")
|
||||
return
|
||||
}
|
||||
if msg.HTML == "" {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "This message does not contain a HTML part")
|
||||
return
|
||||
}
|
||||
|
||||
html := linkInlineImages(msg)
|
||||
w.Header().Add("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write([]byte(html))
|
||||
}
|
||||
|
||||
// swagger:parameters GetMessageTextParams
|
||||
type getMessageTextParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
}
|
||||
|
||||
// GetMessageText (method: GET) returns a message's text part
|
||||
func GetMessageText(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /view/{ID}.txt testing GetMessageTextParams
|
||||
//
|
||||
// # Render message text part
|
||||
//
|
||||
// Renders just the message's text part which can be used for UI integration testing.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: TextResponse
|
||||
// 400: ErrorResponse
|
||||
// 404: NotFoundResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "Message not found")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
|
||||
_, _ = w.Write([]byte(msg.Text))
|
||||
}
|
||||
|
||||
// This will rewrite all inline image paths to API URLs
|
||||
func linkInlineImages(msg *storage.Message) string {
|
||||
html := msg.HTML
|
||||
|
||||
for _, a := range msg.Inline {
|
||||
if a.ContentID != "" {
|
||||
re := regexp.MustCompile(`(?i)(=["\']?)(cid:` + regexp.QuoteMeta(a.ContentID) + `)(["|\'|\\s|\\/|>|;])`)
|
||||
u := config.Webroot + "api/v1/message/" + msg.ID + "/part/" + a.PartID
|
||||
matches := re.FindAllStringSubmatch(html, -1)
|
||||
for _, m := range matches {
|
||||
html = strings.ReplaceAll(html, m[0], m[1]+u+m[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range msg.Attachments {
|
||||
if a.ContentID != "" {
|
||||
re := regexp.MustCompile(`(?i)(=["\']?)(cid:` + regexp.QuoteMeta(a.ContentID) + `)(["|\'|\\s|\\/|>|;])`)
|
||||
u := config.Webroot + "api/v1/message/" + msg.ID + "/part/" + a.PartID
|
||||
matches := re.FindAllStringSubmatch(html, -1)
|
||||
for _, m := range matches {
|
||||
html = strings.ReplaceAll(html, m[0], m[1]+u+m[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
@ -22,35 +22,43 @@ var (
|
||||
thumbHeight = 120
|
||||
)
|
||||
|
||||
// swagger:parameters ThumbnailParams
|
||||
type thumbnailParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 4oRBnPtCXgAqZniRhzLNmS
|
||||
ID string
|
||||
|
||||
// Attachment part ID
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// example: 2
|
||||
PartID string
|
||||
}
|
||||
|
||||
// Thumbnail returns a thumbnail image for an attachment (images only)
|
||||
func Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/part/{PartID}/thumb message Thumbnail
|
||||
// swagger:route GET /api/v1/message/{ID}/part/{PartID}/thumb message ThumbnailParams
|
||||
//
|
||||
// # Get an attachment image thumbnail
|
||||
//
|
||||
// This will return a cropped 180x120 JPEG thumbnail of an image attachment.
|
||||
// If the image is smaller than 180x120 then the image is padded. If the attachment is not an image then a blank image is returned.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - image/jpeg
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: ID
|
||||
// in: path
|
||||
// description: Database ID
|
||||
// required: true
|
||||
// type: string
|
||||
// + name: PartID
|
||||
// in: path
|
||||
// description: Attachment part ID
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: BinaryResponse
|
||||
// default: ErrorResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
@ -1,15 +1,12 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// RedirectToLatestMessage (method: GET) redirects the web UI to the latest message
|
||||
@ -44,142 +41,3 @@ func RedirectToLatestMessage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
http.Redirect(w, r, uri, 302)
|
||||
}
|
||||
|
||||
// GetMessageHTML (method: GET) returns a rendered version of a message's HTML part
|
||||
func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /view/{ID}.html testing GetMessageHTML
|
||||
//
|
||||
// # Render message HTML part
|
||||
//
|
||||
// Renders just the message's HTML part which can be used for UI integration testing.
|
||||
// Attached inline images are modified to link to the API provided they exist.
|
||||
// Note that is the message does not contain a HTML part then an 404 error is returned.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - text/html
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: ID
|
||||
// in: path
|
||||
// description: Database ID or latest
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: HTMLResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "Message not found")
|
||||
return
|
||||
}
|
||||
if msg.HTML == "" {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "This message does not contain a HTML part")
|
||||
return
|
||||
}
|
||||
|
||||
html := linkInlineImages(msg)
|
||||
w.Header().Add("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write([]byte(html))
|
||||
}
|
||||
|
||||
// GetMessageText (method: GET) returns a message's text part
|
||||
func GetMessageText(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /view/{ID}.txt testing GetMessageText
|
||||
//
|
||||
// # Render message text part
|
||||
//
|
||||
// Renders just the message's text part which can be used for UI integration testing.
|
||||
//
|
||||
// The ID can be set to `latest` to return the latest message.
|
||||
//
|
||||
// Produces:
|
||||
// - text/plain
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Parameters:
|
||||
// + name: ID
|
||||
// in: path
|
||||
// description: Database ID or latest
|
||||
// required: true
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: TextResponse
|
||||
// default: ErrorResponse
|
||||
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
if id == "latest" {
|
||||
var err error
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "Message not found")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain; charset=utf-8")
|
||||
_, _ = w.Write([]byte(msg.Text))
|
||||
}
|
||||
|
||||
// This will rewrite all inline image paths to API URLs
|
||||
func linkInlineImages(msg *storage.Message) string {
|
||||
html := msg.HTML
|
||||
|
||||
for _, a := range msg.Inline {
|
||||
if a.ContentID != "" {
|
||||
re := regexp.MustCompile(`(?i)(=["\']?)(cid:` + regexp.QuoteMeta(a.ContentID) + `)(["|\'|\\s|\\/|>|;])`)
|
||||
u := config.Webroot + "api/v1/message/" + msg.ID + "/part/" + a.PartID
|
||||
matches := re.FindAllStringSubmatch(html, -1)
|
||||
for _, m := range matches {
|
||||
html = strings.ReplaceAll(html, m[0], m[1]+u+m[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range msg.Attachments {
|
||||
if a.ContentID != "" {
|
||||
re := regexp.MustCompile(`(?i)(=["\']?)(cid:` + regexp.QuoteMeta(a.ContentID) + `)(["|\'|\\s|\\/|>|;])`)
|
||||
u := config.Webroot + "api/v1/message/" + msg.ID + "/part/" + a.PartID
|
||||
matches := re.FindAllStringSubmatch(html, -1)
|
||||
for _, m := range matches {
|
||||
html = strings.ReplaceAll(html, m[0], m[1]+u+m[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ func Listen() {
|
||||
r.HandleFunc(config.Webroot+"view/latest", middleWareFunc(handlers.RedirectToLatestMessage)).Methods("GET")
|
||||
|
||||
// frontend testing
|
||||
r.HandleFunc(config.Webroot+"view/{id}.html", middleWareFunc(handlers.GetMessageHTML)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"view/{id}.txt", middleWareFunc(handlers.GetMessageText)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"view/{id}.html", middleWareFunc(apiv1.GetMessageHTML)).Methods("GET")
|
||||
r.HandleFunc(config.Webroot+"view/{id}.txt", middleWareFunc(apiv1.GetMessageText)).Methods("GET")
|
||||
|
||||
// web UI via virtual index.html
|
||||
r.PathPrefix(config.Webroot + "view/").Handler(middleWareFunc(index)).Methods("GET")
|
||||
|
@ -40,9 +40,9 @@
|
||||
"operationId": "AppInformation",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/InfoResponse"
|
||||
"$ref": "#/responses/AppInfoResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -62,10 +62,11 @@
|
||||
"message"
|
||||
],
|
||||
"summary": "Get message summary",
|
||||
"operationId": "Message",
|
||||
"operationId": "GetMessageParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
@ -79,15 +80,18 @@
|
||||
"$ref": "#/definitions/Message"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/message/{ID}/headers": {
|
||||
"get": {
|
||||
"description": "Returns the message headers as an array.\n\nThe ID can be set to `latest` to return the latest message headers.",
|
||||
"description": "Returns the message headers as an array. Note that header keys are returned alphabetically.\n\nThe ID can be set to `latest` to return the latest message headers.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@ -99,10 +103,11 @@
|
||||
"message"
|
||||
],
|
||||
"summary": "Get message headers",
|
||||
"operationId": "Headers",
|
||||
"operationId": "GetHeadersParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
@ -111,20 +116,23 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "MessageHeaders",
|
||||
"description": "MessageHeadersResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/MessageHeaders"
|
||||
"$ref": "#/definitions/MessageHeadersResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/message/{ID}/html-check": {
|
||||
"get": {
|
||||
"description": "Returns the summary of the message HTML checker.",
|
||||
"description": "Returns the summary of the message HTML checker.\n\nThe ID can be set to `latest` to return the latest message.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@ -136,10 +144,11 @@
|
||||
"Other"
|
||||
],
|
||||
"summary": "HTML check",
|
||||
"operationId": "HTMLCheck",
|
||||
"operationId": "HTMLCheckParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
@ -153,15 +162,18 @@
|
||||
"$ref": "#/definitions/HTMLCheckResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/message/{ID}/link-check": {
|
||||
"get": {
|
||||
"description": "Returns the summary of the message Link checker.",
|
||||
"description": "Returns the summary of the message Link checker.\n\nThe ID can be set to `latest` to return the latest message.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@ -173,10 +185,11 @@
|
||||
"Other"
|
||||
],
|
||||
"summary": "Link check",
|
||||
"operationId": "LinkCheck",
|
||||
"operationId": "LinkCheckParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
@ -185,6 +198,7 @@
|
||||
{
|
||||
"type": "string",
|
||||
"default": "false",
|
||||
"example": "false",
|
||||
"x-go-name": "Follow",
|
||||
"description": "Follow redirects",
|
||||
"name": "follow",
|
||||
@ -198,15 +212,18 @@
|
||||
"$ref": "#/definitions/LinkCheckResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/message/{ID}/part/{PartID}": {
|
||||
"get": {
|
||||
"description": "This will return the attachment part using the appropriate Content-Type.",
|
||||
"description": "This will return the attachment part using the appropriate Content-Type.\n\nThe ID can be set to `latest` to reference the latest message.",
|
||||
"produces": [
|
||||
"application/*",
|
||||
"image/*",
|
||||
@ -220,17 +237,19 @@
|
||||
"message"
|
||||
],
|
||||
"summary": "Get message attachment",
|
||||
"operationId": "Attachment",
|
||||
"operationId": "AttachmentParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Message database ID",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "2",
|
||||
"description": "Attachment part ID",
|
||||
"name": "PartID",
|
||||
"in": "path",
|
||||
@ -241,15 +260,18 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/BinaryResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/message/{ID}/part/{PartID}/thumb": {
|
||||
"get": {
|
||||
"description": "This will return a cropped 180x120 JPEG thumbnail of an image attachment.\nIf the image is smaller than 180x120 then the image is padded. If the attachment is not an image then a blank image is returned.",
|
||||
"description": "This will return a cropped 180x120 JPEG thumbnail of an image attachment.\nIf the image is smaller than 180x120 then the image is padded. If the attachment is not an image then a blank image is returned.\n\nThe ID can be set to `latest` to return the latest message.",
|
||||
"produces": [
|
||||
"image/jpeg"
|
||||
],
|
||||
@ -261,17 +283,19 @@
|
||||
"message"
|
||||
],
|
||||
"summary": "Get an attachment image thumbnail",
|
||||
"operationId": "Thumbnail",
|
||||
"operationId": "ThumbnailParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Database ID",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "2",
|
||||
"description": "Attachment part ID",
|
||||
"name": "PartID",
|
||||
"in": "path",
|
||||
@ -282,7 +306,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/BinaryResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -302,10 +326,11 @@
|
||||
"message"
|
||||
],
|
||||
"summary": "Get message source",
|
||||
"operationId": "Raw",
|
||||
"operationId": "DownloadRawParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
@ -316,15 +341,18 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/TextResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/message/{ID}/release": {
|
||||
"post": {
|
||||
"description": "Release a message via a pre-configured external SMTP server. This is only enabled if message relaying has been configured.",
|
||||
"description": "Release a message via a pre-configured external SMTP server. This is only enabled if message relaying has been configured.\n\nThe ID can be set to `latest` to reference the latest message.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@ -339,10 +367,11 @@
|
||||
"message"
|
||||
],
|
||||
"summary": "Release message",
|
||||
"operationId": "ReleaseMessage",
|
||||
"operationId": "ReleaseMessageParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
@ -352,7 +381,23 @@
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/releaseMessageRequestBody"
|
||||
"type": "object",
|
||||
"required": [
|
||||
"To"
|
||||
],
|
||||
"properties": {
|
||||
"To": {
|
||||
"description": "Array of email addresses to relay the message to",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"user1@example.com",
|
||||
"user2@example.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -360,15 +405,18 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/OKResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/message/{ID}/sa-check": {
|
||||
"get": {
|
||||
"description": "Returns the SpamAssassin summary (if enabled) of the message.",
|
||||
"description": "Returns the SpamAssassin summary (if enabled) of the message.\n\nThe ID can be set to `latest` to return the latest message.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@ -380,10 +428,11 @@
|
||||
"Other"
|
||||
],
|
||||
"summary": "SpamAssassin check",
|
||||
"operationId": "SpamAssassinCheck",
|
||||
"operationId": "SpamAssassinCheckParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
@ -397,8 +446,11 @@
|
||||
"$ref": "#/definitions/SpamAssassinResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -417,19 +469,25 @@
|
||||
"messages"
|
||||
],
|
||||
"summary": "List messages",
|
||||
"operationId": "GetMessages",
|
||||
"operationId": "GetMessagesParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"default": 0,
|
||||
"example": 100,
|
||||
"x-go-name": "Start",
|
||||
"description": "Pagination offset",
|
||||
"name": "start",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"default": 50,
|
||||
"description": "Limit results",
|
||||
"example": 50,
|
||||
"x-go-name": "Limit",
|
||||
"description": "Limit number of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
@ -438,7 +496,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/MessagesSummaryResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -459,13 +517,32 @@
|
||||
"messages"
|
||||
],
|
||||
"summary": "Set read status",
|
||||
"operationId": "SetReadStatus",
|
||||
"operationId": "SetReadStatusParams",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/setReadStatusRequestBody"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"IDs": {
|
||||
"description": "Array of message database IDs",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"4oRBnPtCXgAqZniRhzLNmS",
|
||||
"hXayS6wnCgNnt6aFTvmOF6"
|
||||
]
|
||||
},
|
||||
"Read": {
|
||||
"description": "Read status",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"example": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -473,7 +550,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/OKResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -494,13 +571,27 @@
|
||||
"messages"
|
||||
],
|
||||
"summary": "Delete messages",
|
||||
"operationId": "DeleteMessages",
|
||||
"operationId": "DeleteMessagesParams",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Delete request",
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/DeleteRequest"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"IDs": {
|
||||
"description": "Array of message database IDs",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"4oRBnPtCXgAqZniRhzLNmS",
|
||||
"hXayS6wnCgNnt6aFTvmOF6"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -508,7 +599,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/OKResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -528,32 +619,37 @@
|
||||
"messages"
|
||||
],
|
||||
"summary": "Search messages",
|
||||
"operationId": "MessagesSummary",
|
||||
"operationId": "SearchParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "search words",
|
||||
"x-go-name": "Query",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"type": "string",
|
||||
"example": "100",
|
||||
"x-go-name": "Start",
|
||||
"description": "Pagination offset",
|
||||
"name": "start",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 50,
|
||||
"type": "string",
|
||||
"example": "50",
|
||||
"x-go-name": "Limit",
|
||||
"description": "Limit results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "[Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used specifically for `before:` \u0026 `after:` searches (eg: \"Pacific/Auckland\").",
|
||||
"x-go-name": "TZ",
|
||||
"description": "[Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` \u0026 `after:` searches (eg: \"Pacific/Auckland\").",
|
||||
"name": "tz",
|
||||
"in": "query"
|
||||
}
|
||||
@ -562,7 +658,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/MessagesSummaryResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -580,10 +676,12 @@
|
||||
"messages"
|
||||
],
|
||||
"summary": "Delete messages by search",
|
||||
"operationId": "DeleteSearch",
|
||||
"operationId": "DeleteSearchParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "search words",
|
||||
"x-go-name": "Query",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
@ -591,7 +689,8 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "[Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used specifically for `before:` \u0026 `after:` searches (eg: \"Pacific/Auckland\").",
|
||||
"x-go-name": "TZ",
|
||||
"description": "[Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` \u0026 `after:` searches (eg: \"Pacific/Auckland\").",
|
||||
"name": "tz",
|
||||
"in": "query"
|
||||
}
|
||||
@ -600,7 +699,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/OKResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -623,7 +722,7 @@
|
||||
"message"
|
||||
],
|
||||
"summary": "Send a message",
|
||||
"operationId": "SendMessage",
|
||||
"operationId": "SendMessageParams",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Body",
|
||||
@ -637,7 +736,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/sendMessageResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/jsonErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -662,7 +761,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/ArrayResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -683,13 +782,41 @@
|
||||
"tags"
|
||||
],
|
||||
"summary": "Set message tags",
|
||||
"operationId": "SetTags",
|
||||
"operationId": "SetTagsParams",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/setTagsRequestBody"
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Tags",
|
||||
"IDs"
|
||||
],
|
||||
"properties": {
|
||||
"IDs": {
|
||||
"description": "Array of message database IDs",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"4oRBnPtCXgAqZniRhzLNmS",
|
||||
"hXayS6wnCgNnt6aFTvmOF6"
|
||||
]
|
||||
},
|
||||
"Tags": {
|
||||
"description": "Array of tag names to set",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"Tag 1",
|
||||
"Tag 2"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -697,15 +824,15 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/OKResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/tags/{tag}": {
|
||||
"/api/v1/tags/{Tag}": {
|
||||
"put": {
|
||||
"description": "Renames a tag.",
|
||||
"description": "Renames an existing tag.",
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
@ -717,34 +844,45 @@
|
||||
"tags"
|
||||
],
|
||||
"summary": "Rename a tag",
|
||||
"operationId": "RenameTag",
|
||||
"operationId": "RenameTagParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "Old name",
|
||||
"description": "The url-encoded tag name to rename",
|
||||
"name": "Tag",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/renameTagRequestBody"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Name"
|
||||
],
|
||||
"properties": {
|
||||
"Name": {
|
||||
"description": "New name",
|
||||
"type": "string",
|
||||
"description": "The url-encoded tag name to rename",
|
||||
"name": "tag",
|
||||
"in": "path",
|
||||
"required": true
|
||||
"example": "New name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/OKResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes a tag. This will not delete any messages with this tag.",
|
||||
"description": "Deletes a tag. This will not delete any messages with the tag, but will remove the tag from any messages containing the tag.",
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
@ -756,12 +894,13 @@
|
||||
"tags"
|
||||
],
|
||||
"summary": "Delete a tag",
|
||||
"operationId": "DeleteTag",
|
||||
"operationId": "DeleteTagParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "My tag",
|
||||
"description": "The url-encoded tag name to delete",
|
||||
"name": "tag",
|
||||
"name": "Tag",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
@ -770,7 +909,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/OKResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -795,7 +934,7 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/WebUIConfigurationResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
}
|
||||
}
|
||||
@ -815,11 +954,12 @@
|
||||
"testing"
|
||||
],
|
||||
"summary": "Render message HTML part",
|
||||
"operationId": "GetMessageHTML",
|
||||
"operationId": "GetMessageHTMLParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Database ID or latest",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
@ -829,8 +969,11 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/HTMLResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -849,11 +992,12 @@
|
||||
"testing"
|
||||
],
|
||||
"summary": "Render message text part",
|
||||
"operationId": "GetMessageText",
|
||||
"operationId": "GetMessageTextParams",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Database ID or latest",
|
||||
"example": "4oRBnPtCXgAqZniRhzLNmS",
|
||||
"description": "Message database ID or \"latest\"",
|
||||
"name": "ID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
@ -863,8 +1007,11 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/TextResponse"
|
||||
},
|
||||
"default": {
|
||||
"400": {
|
||||
"$ref": "#/responses/ErrorResponse"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/NotFoundResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -996,25 +1143,6 @@
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/storage"
|
||||
},
|
||||
"DeleteRequest": {
|
||||
"description": "Delete request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"IDs": {
|
||||
"description": "Array of message database IDs",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"5dec4247-812e-4b77-9101-e25ad406e9ea",
|
||||
"8ac66bbc-2d9a-4c41-ad99-00aa75fa674e"
|
||||
]
|
||||
}
|
||||
},
|
||||
"x-go-name": "deleteMessagesRequestBody",
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"HTMLCheckResponse": {
|
||||
"description": "Response represents the HTML check response struct",
|
||||
"type": "object",
|
||||
@ -1337,7 +1465,7 @@
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/storage"
|
||||
},
|
||||
"MessageHeaders": {
|
||||
"MessageHeadersResponse": {
|
||||
"description": "Message headers",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@ -1503,7 +1631,7 @@
|
||||
"ID": {
|
||||
"description": "Database ID",
|
||||
"type": "string",
|
||||
"example": "iAfZVVe2UQFNSG5BAjgYwa"
|
||||
"example": "iAfZVVe2UQfNSG5BAjgYwa"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
@ -1745,102 +1873,15 @@
|
||||
},
|
||||
"x-go-name": "webUIConfiguration",
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"releaseMessageRequestBody": {
|
||||
"description": "Release request",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"To"
|
||||
],
|
||||
"properties": {
|
||||
"To": {
|
||||
"description": "Array of email addresses to relay the message to",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"user1@example.com",
|
||||
"user2@example.com"
|
||||
]
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"renameTagRequestBody": {
|
||||
"description": "Rename tag request",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Name"
|
||||
],
|
||||
"properties": {
|
||||
"Name": {
|
||||
"description": "New name",
|
||||
"type": "string",
|
||||
"example": "New name"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"setReadStatusRequestBody": {
|
||||
"description": "Set read status request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"IDs": {
|
||||
"description": "Array of message database IDs",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"5dec4247-812e-4b77-9101-e25ad406e9ea",
|
||||
"8ac66bbc-2d9a-4c41-ad99-00aa75fa674e"
|
||||
]
|
||||
},
|
||||
"Read": {
|
||||
"description": "Read status",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"example": true
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"setTagsRequestBody": {
|
||||
"description": "Set tags request",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Tags",
|
||||
"IDs"
|
||||
],
|
||||
"properties": {
|
||||
"IDs": {
|
||||
"description": "Array of message database IDs",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"5dec4247-812e-4b77-9101-e25ad406e9ea",
|
||||
"8ac66bbc-2d9a-4c41-ad99-00aa75fa674e"
|
||||
]
|
||||
},
|
||||
"Tags": {
|
||||
"description": "Array of tag names to set",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"Tag 1",
|
||||
"Tag 2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"AppInfoResponse": {
|
||||
"description": "Application information",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AppInformation"
|
||||
}
|
||||
},
|
||||
"ArrayResponse": {
|
||||
"description": "Plain JSON array response",
|
||||
"schema": {
|
||||
@ -1851,13 +1892,13 @@
|
||||
}
|
||||
},
|
||||
"BinaryResponse": {
|
||||
"description": "Binary data response inherits the attachment's content type.",
|
||||
"description": "Binary data response which inherits the attachment's content type.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"description": "HTTP error response will return with a \u003e= 400 response code",
|
||||
"description": "Server error will return with a 400 status code\nwith the error message in the body",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@ -1868,18 +1909,18 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"InfoResponse": {
|
||||
"description": "Application information",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AppInformation"
|
||||
}
|
||||
},
|
||||
"MessagesSummaryResponse": {
|
||||
"description": "Message summary",
|
||||
"description": "Summary of messages",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/MessagesSummary"
|
||||
}
|
||||
},
|
||||
"NotFoundResponse": {
|
||||
"description": "Not found error will return a 404 status code",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"OKResponse": {
|
||||
"description": "Plain text \"ok\" response",
|
||||
"schema": {
|
||||
@ -1893,7 +1934,7 @@
|
||||
}
|
||||
},
|
||||
"WebUIConfigurationResponse": {
|
||||
"description": "Web UI configuration",
|
||||
"description": "Web UI configuration response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/WebUIConfiguration"
|
||||
}
|
||||
|
Reference in New Issue
Block a user