mirror of
https://github.com/axllent/mailpit.git
synced 2025-03-21 21:47:19 +02:00
Merge branch 'release/v1.15.1'
This commit is contained in:
commit
ebe9195075
10
CHANGELOG.md
10
CHANGELOG.md
@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
Notable changes to Mailpit will be documented in this file.
|
Notable changes to Mailpit will be documented in this file.
|
||||||
|
|
||||||
|
## [v1.15.1]
|
||||||
|
|
||||||
|
### Chore
|
||||||
|
- Code cleanup, remove redundant functionality
|
||||||
|
- Add labels to Docker image ([#267](https://github.com/axllent/mailpit/issues/267))
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
- Add readyz subcommand for Docker healthcheck ([#270](https://github.com/axllent/mailpit/issues/270))
|
||||||
|
|
||||||
|
|
||||||
## [v1.15.0]
|
## [v1.15.0]
|
||||||
|
|
||||||
### Chore
|
### Chore
|
||||||
|
@ -12,10 +12,19 @@ CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/axllent/mailpit/config.Vers
|
|||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="Mailpit" \
|
||||||
|
org.opencontainers.image.description="An email and SMTP testing tool with API for developers" \
|
||||||
|
org.opencontainers.image.source="https://github.com/axllent/mailpit" \
|
||||||
|
org.opencontainers.image.url="https://mailpit.axllent.org" \
|
||||||
|
org.opencontainers.image.documentation="https://mailpit.axllent.org/docs/" \
|
||||||
|
org.opencontainers.image.licenses="MIT"
|
||||||
|
|
||||||
COPY --from=builder /mailpit /mailpit
|
COPY --from=builder /mailpit /mailpit
|
||||||
|
|
||||||
RUN apk add --no-cache tzdata
|
RUN apk add --no-cache tzdata
|
||||||
|
|
||||||
EXPOSE 1025/tcp 1110/tcp 8025/tcp
|
EXPOSE 1025/tcp 1110/tcp 8025/tcp
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=15s CMD /mailpit readyz
|
||||||
|
|
||||||
ENTRYPOINT ["/mailpit"]
|
ENTRYPOINT ["/mailpit"]
|
||||||
|
@ -49,9 +49,7 @@ The --recent flag will only consider files with a modification date within the l
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
info.ModTime()
|
if ingestRecent > 0 && time.Since(info.ModTime()) > time.Duration(ingestRecent)*24*time.Hour {
|
||||||
|
|
||||||
if ingestRecent > 0 && time.Now().Sub(info.ModTime()) > time.Duration(ingestRecent)*24*time.Hour {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
75
cmd/readyz.go
Normal file
75
cmd/readyz.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/axllent/mailpit/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
useHTTPS bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// readyzCmd represents the healthcheck command
|
||||||
|
var readyzCmd = &cobra.Command{
|
||||||
|
Use: "readyz",
|
||||||
|
Short: "Run a healthcheck to test if Mailpit is running",
|
||||||
|
Long: `This command connects to the /readyz endpoint of a running Mailpit server
|
||||||
|
and exits with a status of 0 if the connection is successful, else with a
|
||||||
|
status 1 if unhealthy.
|
||||||
|
|
||||||
|
If running within Docker, it should automatically detect environment
|
||||||
|
settings to determine the HTTP bind interface & port.
|
||||||
|
`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
webroot := strings.TrimRight(path.Join("/", config.Webroot, "/"), "/") + "/"
|
||||||
|
proto := "http"
|
||||||
|
if useHTTPS {
|
||||||
|
proto = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("%s://%s%sreadyz", proto, config.HTTPListen, webroot)
|
||||||
|
|
||||||
|
conf := &http.Transport{
|
||||||
|
IdleConnTimeout: time.Second * 5,
|
||||||
|
ExpectContinueTimeout: time.Second * 5,
|
||||||
|
TLSHandshakeTimeout: time.Second * 5,
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: conf}
|
||||||
|
|
||||||
|
res, err := client.Get(uri)
|
||||||
|
if err != nil || res.StatusCode != 200 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(readyzCmd)
|
||||||
|
|
||||||
|
if len(os.Getenv("MP_UI_BIND_ADDR")) > 0 {
|
||||||
|
config.HTTPListen = os.Getenv("MP_UI_BIND_ADDR")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(os.Getenv("MP_WEBROOT")) > 0 {
|
||||||
|
config.Webroot = os.Getenv("MP_WEBROOT")
|
||||||
|
}
|
||||||
|
|
||||||
|
config.UITLSCert = os.Getenv("MP_UI_TLS_CERT")
|
||||||
|
|
||||||
|
if config.UITLSCert != "" {
|
||||||
|
useHTTPS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
readyzCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "Set the HTTP bind interface & port")
|
||||||
|
readyzCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
|
||||||
|
readyzCmd.Flags().BoolVar(&useHTTPS, "https", useHTTPS, "Connect via HTTPS (ignores HTTPS validation)")
|
||||||
|
}
|
@ -17,8 +17,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfgFile string
|
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "mailpit",
|
Use: "mailpit",
|
||||||
@ -91,7 +89,7 @@ func init() {
|
|||||||
rootCmd.Flags().BoolVarP(&logger.VerboseLogging, "verbose", "v", logger.VerboseLogging, "Verbose logging")
|
rootCmd.Flags().BoolVarP(&logger.VerboseLogging, "verbose", "v", logger.VerboseLogging, "Verbose logging")
|
||||||
|
|
||||||
// Web UI / API
|
// Web UI / API
|
||||||
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for UI")
|
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface & port for UI")
|
||||||
rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
|
rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
|
||||||
rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI & API authentication")
|
rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI & API authentication")
|
||||||
rootCmd.Flags().StringVar(&config.UITLSCert, "ui-tls-cert", config.UITLSCert, "TLS certificate for web UI (HTTPS) - requires ui-tls-key")
|
rootCmd.Flags().StringVar(&config.UITLSCert, "ui-tls-cert", config.UITLSCert, "TLS certificate for web UI (HTTPS) - requires ui-tls-key")
|
||||||
|
@ -32,9 +32,7 @@ func RunTests(msg *storage.Message, followRedirects bool) (Response, error) {
|
|||||||
func extractTextLinks(msg *storage.Message) []string {
|
func extractTextLinks(msg *storage.Message) []string {
|
||||||
links := []string{}
|
links := []string{}
|
||||||
|
|
||||||
for _, match := range linkRe.FindAllString(msg.Text, -1) {
|
links = append(links, linkRe.FindAllString(msg.Text, -1)...)
|
||||||
links = append(links, match)
|
|
||||||
}
|
|
||||||
|
|
||||||
return links
|
return links
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ func pruneMessages() {
|
|||||||
ids := []string{}
|
ids := []string{}
|
||||||
var prunedSize int64
|
var prunedSize int64
|
||||||
var size int
|
var size int
|
||||||
if err := q.Query(nil, db, func(row *sql.Rows) {
|
if err := q.Query(context.TODO(), db, func(row *sql.Rows) {
|
||||||
var id string
|
var id string
|
||||||
|
|
||||||
if err := row.Scan(&id, &size); err != nil {
|
if err := row.Scan(&id, &size); err != nil {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -114,6 +115,11 @@ func Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ping the database connection and return an error if unsuccessful
|
||||||
|
func Ping() error {
|
||||||
|
return db.Ping()
|
||||||
|
}
|
||||||
|
|
||||||
// StatsGet returns the total/unread statistics for a mailbox
|
// StatsGet returns the total/unread statistics for a mailbox
|
||||||
func StatsGet() MailboxStats {
|
func StatsGet() MailboxStats {
|
||||||
var (
|
var (
|
||||||
@ -137,7 +143,7 @@ func CountTotal() int {
|
|||||||
|
|
||||||
_ = sqlf.From("mailbox").
|
_ = sqlf.From("mailbox").
|
||||||
Select("COUNT(*)").To(&total).
|
Select("COUNT(*)").To(&total).
|
||||||
QueryRowAndClose(nil, db)
|
QueryRowAndClose(context.TODO(), db)
|
||||||
|
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
@ -146,11 +152,10 @@ func CountTotal() int {
|
|||||||
func CountUnread() int {
|
func CountUnread() int {
|
||||||
var total int
|
var total int
|
||||||
|
|
||||||
q := sqlf.From("mailbox").
|
_ = sqlf.From("mailbox").
|
||||||
Select("COUNT(*)").To(&total).
|
Select("COUNT(*)").To(&total).
|
||||||
Where("Read = ?", 0)
|
Where("Read = ?", 0).
|
||||||
|
QueryRowAndClose(context.TODO(), db)
|
||||||
_ = q.QueryRowAndClose(nil, db)
|
|
||||||
|
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
@ -159,26 +164,23 @@ func CountUnread() int {
|
|||||||
func CountRead() int {
|
func CountRead() int {
|
||||||
var total int
|
var total int
|
||||||
|
|
||||||
q := sqlf.From("mailbox").
|
_ = sqlf.From("mailbox").
|
||||||
Select("COUNT(*)").To(&total).
|
Select("COUNT(*)").To(&total).
|
||||||
Where("Read = ?", 1)
|
Where("Read = ?", 1).
|
||||||
|
QueryRowAndClose(context.TODO(), db)
|
||||||
_ = q.QueryRowAndClose(nil, db)
|
|
||||||
|
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUnread returns the number of emails in the database that are unread.
|
// IsUnread returns whether a message is unread or not.
|
||||||
// If an ID is supplied, then it is just limited to that message.
|
|
||||||
func IsUnread(id string) bool {
|
func IsUnread(id string) bool {
|
||||||
var unread int
|
var unread int
|
||||||
|
|
||||||
q := sqlf.From("mailbox").
|
_ = sqlf.From("mailbox").
|
||||||
Select("COUNT(*)").To(&unread).
|
Select("COUNT(*)").To(&unread).
|
||||||
Where("Read = ?", 0).
|
Where("Read = ?", 0).
|
||||||
Where("ID = ?", id)
|
Where("ID = ?", id).
|
||||||
|
QueryRowAndClose(context.TODO(), db)
|
||||||
_ = q.QueryRowAndClose(nil, db)
|
|
||||||
|
|
||||||
return unread == 1
|
return unread == 1
|
||||||
}
|
}
|
||||||
@ -187,11 +189,10 @@ func IsUnread(id string) bool {
|
|||||||
func MessageIDExists(id string) bool {
|
func MessageIDExists(id string) bool {
|
||||||
var total int
|
var total int
|
||||||
|
|
||||||
q := sqlf.From("mailbox").
|
_ = sqlf.From("mailbox").
|
||||||
Select("COUNT(*)").To(&total).
|
Select("COUNT(*)").To(&total).
|
||||||
Where("MessageID = ?", id)
|
Where("MessageID = ?", id).
|
||||||
|
QueryRowAndClose(context.TODO(), db)
|
||||||
_ = q.QueryRowAndClose(nil, db)
|
|
||||||
|
|
||||||
return total != 0
|
return total != 0
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ func List(start, limit int) ([]MessageSummary, error) {
|
|||||||
Limit(limit).
|
Limit(limit).
|
||||||
Offset(start)
|
Offset(start)
|
||||||
|
|
||||||
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
|
if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
var created int64
|
var created int64
|
||||||
var id string
|
var id string
|
||||||
var messageID string
|
var messageID string
|
||||||
@ -245,7 +245,7 @@ func GetMessage(id string) (*Message, error) {
|
|||||||
Select(`Created`).
|
Select(`Created`).
|
||||||
Where(`ID = ?`, id)
|
Where(`ID = ?`, id)
|
||||||
|
|
||||||
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
|
if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
var created int64
|
var created int64
|
||||||
|
|
||||||
if err := row.Scan(&created); err != nil {
|
if err := row.Scan(&created); err != nil {
|
||||||
@ -564,7 +564,7 @@ func DeleteAllMessages() error {
|
|||||||
|
|
||||||
_ = sqlf.From("mailbox").
|
_ = sqlf.From("mailbox").
|
||||||
Select("COUNT(*)").To(&total).
|
Select("COUNT(*)").To(&total).
|
||||||
QueryRowAndClose(nil, db)
|
QueryRowAndClose(context.TODO(), db)
|
||||||
|
|
||||||
// begin a transaction to ensure both the message
|
// begin a transaction to ensure both the message
|
||||||
// summaries and data are deleted successfully
|
// summaries and data are deleted successfully
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
@ -140,7 +141,7 @@ func migrateTagsToManyMany() {
|
|||||||
Where("Tags != ?", "[]").
|
Where("Tags != ?", "[]").
|
||||||
Where("Tags IS NOT NULL")
|
Where("Tags IS NOT NULL")
|
||||||
|
|
||||||
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
|
if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
var id string
|
var id string
|
||||||
var jsonTags string
|
var jsonTags string
|
||||||
if err := row.Scan(&id, &jsonTags); err != nil {
|
if err := row.Scan(&id, &jsonTags); err != nil {
|
||||||
@ -169,7 +170,7 @@ func migrateTagsToManyMany() {
|
|||||||
if _, err := sqlf.Update("mailbox").
|
if _, err := sqlf.Update("mailbox").
|
||||||
Set("Tags", nil).
|
Set("Tags", nil).
|
||||||
Where("ID = ?", id).
|
Where("ID = ?", id).
|
||||||
ExecAndClose(nil, db); err != nil {
|
ExecAndClose(context.TODO(), db); err != nil {
|
||||||
logger.Log().Errorf("[migration] %s", err.Error())
|
logger.Log().Errorf("[migration] %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,7 +183,7 @@ func migrateTagsToManyMany() {
|
|||||||
if _, err := sqlf.Update("mailbox").
|
if _, err := sqlf.Update("mailbox").
|
||||||
Set("Tags", nil).
|
Set("Tags", nil).
|
||||||
Where("Tags = ?", "[]").
|
Where("Tags = ?", "[]").
|
||||||
ExecAndClose(nil, db); err != nil {
|
ExecAndClose(context.TODO(), db); err != nil {
|
||||||
logger.Log().Errorf("[migration] %s", err.Error())
|
logger.Log().Errorf("[migration] %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func ReindexAll() {
|
|||||||
err := sqlf.Select("ID").To(&i).
|
err := sqlf.Select("ID").To(&i).
|
||||||
From("mailbox").
|
From("mailbox").
|
||||||
OrderBy("Created DESC").
|
OrderBy("Created DESC").
|
||||||
QueryAndClose(nil, db, func(row *sql.Rows) {
|
QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
ids = append(ids, i)
|
ids = append(ids, i)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ func Search(search string, start, limit int) ([]MessageSummary, int, error) {
|
|||||||
q := searchQueryBuilder(search)
|
q := searchQueryBuilder(search)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
|
if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
var created int64
|
var created int64
|
||||||
var id string
|
var id string
|
||||||
var messageID string
|
var messageID string
|
||||||
@ -101,7 +101,7 @@ func DeleteSearch(search string) error {
|
|||||||
ids := []string{}
|
ids := []string{}
|
||||||
deleteSize := 0
|
deleteSize := 0
|
||||||
|
|
||||||
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
|
if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
var created int64
|
var created int64
|
||||||
var id string
|
var id string
|
||||||
var messageID string
|
var messageID string
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/axllent/mailpit/internal/logger"
|
"github.com/axllent/mailpit/internal/logger"
|
||||||
@ -14,7 +15,7 @@ func SettingGet(k string) string {
|
|||||||
Select("Value").To(&result).
|
Select("Value").To(&result).
|
||||||
Where("Key = ?", k).
|
Where("Key = ?", k).
|
||||||
Limit(1).
|
Limit(1).
|
||||||
QueryAndClose(nil, db, func(row *sql.Rows) {})
|
QueryAndClose(context.TODO(), db, func(row *sql.Rows) {})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log().Errorf("[db] %s", err.Error())
|
logger.Log().Errorf("[db] %s", err.Error())
|
||||||
return ""
|
return ""
|
||||||
@ -40,7 +41,7 @@ func getDeletedSize() int64 {
|
|||||||
Select("Value").To(&result).
|
Select("Value").To(&result).
|
||||||
Where("Key = ?", "DeletedSize").
|
Where("Key = ?", "DeletedSize").
|
||||||
Limit(1).
|
Limit(1).
|
||||||
QueryAndClose(nil, db, func(row *sql.Rows) {})
|
QueryAndClose(context.TODO(), db, func(row *sql.Rows) {})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log().Errorf("[db] %s", err.Error())
|
logger.Log().Errorf("[db] %s", err.Error())
|
||||||
return 0
|
return 0
|
||||||
@ -54,7 +55,7 @@ func totalMessagesSize() int64 {
|
|||||||
var result sql.NullInt64
|
var result sql.NullInt64
|
||||||
err := sqlf.From("mailbox").
|
err := sqlf.From("mailbox").
|
||||||
Select("SUM(Size)").To(&result).
|
Select("SUM(Size)").To(&result).
|
||||||
QueryAndClose(nil, db, func(row *sql.Rows) {})
|
QueryAndClose(context.TODO(), db, func(row *sql.Rows) {})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log().Errorf("[db] %s", err.Error())
|
logger.Log().Errorf("[db] %s", err.Error())
|
||||||
return 0
|
return 0
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
@ -64,14 +65,14 @@ func AddMessageTag(id, name string) error {
|
|||||||
Where("Name = ?", name)
|
Where("Name = ?", name)
|
||||||
|
|
||||||
// tag exists - add tag to message
|
// tag exists - add tag to message
|
||||||
if err := q.QueryRowAndClose(nil, db); err == nil {
|
if err := q.QueryRowAndClose(context.TODO(), db); err == nil {
|
||||||
// check message does not already have this tag
|
// check message does not already have this tag
|
||||||
var count int
|
var count int
|
||||||
if _, err := sqlf.From("message_tags").
|
if _, err := sqlf.From("message_tags").
|
||||||
Select("COUNT(ID)").To(&count).
|
Select("COUNT(ID)").To(&count).
|
||||||
Where("ID = ?", id).
|
Where("ID = ?", id).
|
||||||
Where("TagID = ?", tagID).
|
Where("TagID = ?", tagID).
|
||||||
ExecAndClose(nil, db); err != nil {
|
ExecAndClose(context.TODO(), db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if count != 0 {
|
if count != 0 {
|
||||||
@ -84,7 +85,7 @@ func AddMessageTag(id, name string) error {
|
|||||||
_, err := sqlf.InsertInto("message_tags").
|
_, err := sqlf.InsertInto("message_tags").
|
||||||
Set("ID", id).
|
Set("ID", id).
|
||||||
Set("TagID", tagID).
|
Set("TagID", tagID).
|
||||||
ExecAndClose(nil, db)
|
ExecAndClose(context.TODO(), db)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ func AddMessageTag(id, name string) error {
|
|||||||
if err := sqlf.InsertInto("tags").
|
if err := sqlf.InsertInto("tags").
|
||||||
Set("Name", name).
|
Set("Name", name).
|
||||||
Returning("ID").To(&tagID).
|
Returning("ID").To(&tagID).
|
||||||
QueryRowAndClose(nil, db); err != nil {
|
QueryRowAndClose(context.TODO(), db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +105,7 @@ func AddMessageTag(id, name string) error {
|
|||||||
Select("COUNT(ID)").To(&count).
|
Select("COUNT(ID)").To(&count).
|
||||||
Where("ID = ?", id).
|
Where("ID = ?", id).
|
||||||
Where("TagID = ?", tagID).
|
Where("TagID = ?", tagID).
|
||||||
ExecAndClose(nil, db); err != nil {
|
ExecAndClose(context.TODO(), db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if count != 0 {
|
if count != 0 {
|
||||||
@ -115,7 +116,7 @@ func AddMessageTag(id, name string) error {
|
|||||||
_, err := sqlf.InsertInto("message_tags").
|
_, err := sqlf.InsertInto("message_tags").
|
||||||
Set("ID", id).
|
Set("ID", id).
|
||||||
Set("TagID", tagID).
|
Set("TagID", tagID).
|
||||||
ExecAndClose(nil, db)
|
ExecAndClose(context.TODO(), db)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +125,7 @@ func DeleteMessageTag(id, name string) error {
|
|||||||
if _, err := sqlf.DeleteFrom("message_tags").
|
if _, err := sqlf.DeleteFrom("message_tags").
|
||||||
Where("message_tags.ID = ?", id).
|
Where("message_tags.ID = ?", id).
|
||||||
Where(`message_tags.Key IN (SELECT Key FROM message_tags LEFT JOIN tags ON TagID=tags.ID WHERE Name = ?)`, name).
|
Where(`message_tags.Key IN (SELECT Key FROM message_tags LEFT JOIN tags ON TagID=tags.ID WHERE Name = ?)`, name).
|
||||||
ExecAndClose(nil, db); err != nil {
|
ExecAndClose(context.TODO(), db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +136,7 @@ func DeleteMessageTag(id, name string) error {
|
|||||||
func DeleteAllMessageTags(id string) error {
|
func DeleteAllMessageTags(id string) error {
|
||||||
if _, err := sqlf.DeleteFrom("message_tags").
|
if _, err := sqlf.DeleteFrom("message_tags").
|
||||||
Where("message_tags.ID = ?", id).
|
Where("message_tags.ID = ?", id).
|
||||||
ExecAndClose(nil, db); err != nil {
|
ExecAndClose(context.TODO(), db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +152,7 @@ func GetAllTags() []string {
|
|||||||
Select(`DISTINCT Name`).
|
Select(`DISTINCT Name`).
|
||||||
From("tags").To(&name).
|
From("tags").To(&name).
|
||||||
OrderBy("Name").
|
OrderBy("Name").
|
||||||
QueryAndClose(nil, db, func(row *sql.Rows) {
|
QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
tags = append(tags, name)
|
tags = append(tags, name)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logger.Log().Errorf("[db] %s", err.Error())
|
logger.Log().Errorf("[db] %s", err.Error())
|
||||||
@ -173,7 +174,7 @@ func GetAllTagsCount() map[string]int64 {
|
|||||||
LeftJoin("message_tags", "tags.ID = message_tags.TagID").
|
LeftJoin("message_tags", "tags.ID = message_tags.TagID").
|
||||||
GroupBy("message_tags.TagID").
|
GroupBy("message_tags.TagID").
|
||||||
OrderBy("Name").
|
OrderBy("Name").
|
||||||
QueryAndClose(nil, db, func(row *sql.Rows) {
|
QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
tags[name] = total
|
tags[name] = total
|
||||||
// tags = append(tags, name)
|
// tags = append(tags, name)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -192,7 +193,7 @@ func pruneUnusedTags() error {
|
|||||||
|
|
||||||
toDel := []int{}
|
toDel := []int{}
|
||||||
|
|
||||||
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
|
if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
var n string
|
var n string
|
||||||
var id int
|
var id int
|
||||||
var c int
|
var c int
|
||||||
@ -214,7 +215,7 @@ func pruneUnusedTags() error {
|
|||||||
for _, id := range toDel {
|
for _, id := range toDel {
|
||||||
if _, err := sqlf.DeleteFrom("tags").
|
if _, err := sqlf.DeleteFrom("tags").
|
||||||
Where("ID = ?", id).
|
Where("ID = ?", id).
|
||||||
ExecAndClose(nil, db); err != nil {
|
ExecAndClose(context.TODO(), db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,7 +283,7 @@ func getMessageTags(id string) []string {
|
|||||||
LeftJoin("message_tags", "Tags.ID=message_tags.TagID").
|
LeftJoin("message_tags", "Tags.ID=message_tags.TagID").
|
||||||
Where(`message_tags.ID = ?`, id).
|
Where(`message_tags.ID = ?`, id).
|
||||||
OrderBy("Name").
|
OrderBy("Name").
|
||||||
QueryAndClose(nil, db, func(row *sql.Rows) {
|
QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||||
tags = append(tags, name)
|
tags = append(tags, name)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logger.Log().Errorf("[tags] %s", err.Error())
|
logger.Log().Errorf("[tags] %s", err.Error())
|
||||||
|
@ -103,42 +103,3 @@ func inArray(k string, arr []string) bool {
|
|||||||
func escPercentChar(s string) string {
|
func escPercentChar(s string) string {
|
||||||
return strings.ReplaceAll(s, "%", "%%")
|
return strings.ReplaceAll(s, "%", "%%")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape certain characters in search phrases
|
|
||||||
func escSearch(str string) string {
|
|
||||||
dest := make([]byte, 0, 2*len(str))
|
|
||||||
var escape byte
|
|
||||||
for i := 0; i < len(str); i++ {
|
|
||||||
c := str[i]
|
|
||||||
|
|
||||||
escape = 0
|
|
||||||
|
|
||||||
switch c {
|
|
||||||
case 0: /* Must be escaped for 'mysql' */
|
|
||||||
escape = '0'
|
|
||||||
break
|
|
||||||
case '\n': /* Must be escaped for logs */
|
|
||||||
escape = 'n'
|
|
||||||
break
|
|
||||||
case '\r':
|
|
||||||
escape = 'r'
|
|
||||||
break
|
|
||||||
case '\\':
|
|
||||||
escape = '\\'
|
|
||||||
break
|
|
||||||
case '\'':
|
|
||||||
escape = '\''
|
|
||||||
break
|
|
||||||
case '\032': //十进制26,八进制32,十六进制1a, /* This gives problems on Win32 */
|
|
||||||
escape = 'Z'
|
|
||||||
}
|
|
||||||
|
|
||||||
if escape != 0 {
|
|
||||||
dest = append(dest, '\\', escape)
|
|
||||||
} else {
|
|
||||||
dest = append(dest, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(dest)
|
|
||||||
}
|
|
||||||
|
@ -98,53 +98,6 @@ func makeAbsolute(inputFilePath, outputFilePath string) (string, string, error)
|
|||||||
return inputFilePath, outputFilePath, err
|
return inputFilePath, outputFilePath, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write path without the prefix in subPath to tar writer.
|
|
||||||
func writeTarGz(path string, tarWriter *tar.Writer, fileInfo os.FileInfo, subPath string) error {
|
|
||||||
file, err := os.Open(filepath.Clean(path))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
fmt.Printf("Error closing file: %s\n", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
evaledPath, err := filepath.EvalSymlinks(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
subPath, err = filepath.EvalSymlinks(subPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
link := ""
|
|
||||||
if evaledPath != path {
|
|
||||||
link = evaledPath
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := tar.FileInfoHeader(fileInfo, link)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
header.Name = evaledPath[len(subPath):]
|
|
||||||
|
|
||||||
err = tarWriter.WriteHeader(header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(tarWriter, file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the file in filePath to directory.
|
// Extract the file in filePath to directory.
|
||||||
func extract(filePath string, directory string) error {
|
func extract(filePath string, directory string) error {
|
||||||
file, err := os.Open(filepath.Clean(filePath))
|
file, err := os.Open(filepath.Clean(filePath))
|
||||||
@ -200,7 +153,7 @@ func extract(filePath string, directory string) error {
|
|||||||
|
|
||||||
// set file ownership (if allowed)
|
// set file ownership (if allowed)
|
||||||
// Chtimes() && Chmod() only set after once extraction is complete
|
// Chtimes() && Chmod() only set after once extraction is complete
|
||||||
os.Chown(filename, header.Uid, header.Gid) // #nosec
|
_ = os.Chown(filename, header.Uid, header.Gid)
|
||||||
|
|
||||||
// add directory info to slice to process afterwards
|
// add directory info to slice to process afterwards
|
||||||
postExtraction = append(postExtraction, DirInfo{filename, header})
|
postExtraction = append(postExtraction, DirInfo{filename, header})
|
||||||
@ -249,15 +202,15 @@ func extract(filePath string, directory string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set file permissions, timestamps & uid/gid
|
// set file permissions, timestamps & uid/gid
|
||||||
os.Chmod(filename, os.FileMode(header.Mode)) // #nosec
|
_ = os.Chmod(filename, os.FileMode(header.Mode))
|
||||||
os.Chtimes(filename, header.AccessTime, header.ModTime) // #nosec
|
_ = os.Chtimes(filename, header.AccessTime, header.ModTime)
|
||||||
os.Chown(filename, header.Uid, header.Gid) // #nosec
|
_ = os.Chown(filename, header.Uid, header.Gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(postExtraction) > 0 {
|
if len(postExtraction) > 0 {
|
||||||
for _, dir := range postExtraction {
|
for _, dir := range postExtraction {
|
||||||
os.Chtimes(dir.Path, dir.Header.AccessTime, dir.Header.ModTime) // #nosec
|
_ = os.Chtimes(dir.Path, dir.Header.AccessTime, dir.Header.ModTime)
|
||||||
os.Chmod(dir.Path, dir.Header.FileInfo().Mode().Perm()) // #nosec
|
_ = os.Chmod(dir.Path, dir.Header.FileInfo().Mode().Perm())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,16 +335,6 @@ func mkDirIfNotExists(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFile returns if a path is a file
|
|
||||||
func isFile(path string) bool {
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if os.IsNotExist(err) || !info.Mode().IsRegular() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns if a path is a directory
|
// IsDir returns if a path is a directory
|
||||||
func isDir(path string) bool {
|
func isDir(path string) bool {
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
|
@ -114,7 +114,7 @@ func blankImage(a *enmime.Part, w http.ResponseWriter) {
|
|||||||
rect := image.Rect(0, 0, thumbWidth, thumbHeight)
|
rect := image.Rect(0, 0, thumbWidth, thumbHeight)
|
||||||
img := image.NewRGBA(rect)
|
img := image.NewRGBA(rect)
|
||||||
background := color.RGBA{255, 255, 255, 255}
|
background := color.RGBA{255, 255, 255, 255}
|
||||||
draw.Draw(img, img.Bounds(), &image.Uniform{background}, image.ZP, draw.Src)
|
draw.Draw(img, img.Bounds(), &image.Uniform{background}, image.Point{}, draw.Src)
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
foo := bufio.NewWriter(&b)
|
foo := bufio.NewWriter(&b)
|
||||||
dstImageFill := imaging.Fill(img, thumbWidth, thumbHeight, imaging.Center, imaging.Lanczos)
|
dstImageFill := imaging.Fill(img, thumbWidth, thumbHeight, imaging.Center, imaging.Lanczos)
|
||||||
|
@ -3,12 +3,14 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/axllent/mailpit/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadyzHandler is a ready probe that signals k8s to be able to retrieve traffic
|
// ReadyzHandler is a ready probe that signals k8s to be able to retrieve traffic
|
||||||
func ReadyzHandler(isReady *atomic.Value) http.HandlerFunc {
|
func ReadyzHandler(isReady *atomic.Value) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, _ *http.Request) {
|
return func(w http.ResponseWriter, _ *http.Request) {
|
||||||
if isReady == nil || !isReady.Load().(bool) {
|
if isReady == nil || !isReady.Load().(bool) || storage.Ping() != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,9 @@ func Run() {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if config.POP3TLSCert != "" {
|
if config.POP3TLSCert != "" {
|
||||||
cer, err := tls.LoadX509KeyPair(config.POP3TLSCert, config.POP3TLSKey)
|
cer, err2 := tls.LoadX509KeyPair(config.POP3TLSCert, config.POP3TLSKey)
|
||||||
if err != nil {
|
if err2 != nil {
|
||||||
logger.Log().Errorf("[pop3] %s", err.Error())
|
logger.Log().Errorf("[pop3] %s", err2.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,6 +273,10 @@ func handleClient(conn net.Conn) {
|
|||||||
|
|
||||||
m := messages[nr-1]
|
m := messages[nr-1]
|
||||||
headers, body, err := getTop(m.ID, lines)
|
headers, body, err := getTop(m.ID, lines)
|
||||||
|
if err != nil {
|
||||||
|
sendResponse(conn, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sendData(conn, "+OK Top of message follows")
|
sendData(conn, "+OK Top of message follows")
|
||||||
sendData(conn, headers+"\r\n")
|
sendData(conn, headers+"\r\n")
|
||||||
|
@ -47,8 +47,6 @@ func TestAPIv1Messages(t *testing.T) {
|
|||||||
insertEmailData(t)
|
insertEmailData(t)
|
||||||
assertStatsEqual(t, ts.URL+"/api/v1/messages", 100, 100)
|
assertStatsEqual(t, ts.URL+"/api/v1/messages", 100, 100)
|
||||||
|
|
||||||
// store this for later tests
|
|
||||||
|
|
||||||
m, err = fetchMessages(ts.URL + "/api/v1/messages")
|
m, err = fetchMessages(ts.URL + "/api/v1/messages")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf(err.Error())
|
t.Errorf(err.Error())
|
||||||
@ -56,7 +54,6 @@ func TestAPIv1Messages(t *testing.T) {
|
|||||||
|
|
||||||
// read first 10 messages
|
// read first 10 messages
|
||||||
t.Log("Read first 10 messages including raw & headers")
|
t.Log("Read first 10 messages including raw & headers")
|
||||||
putIDS := []string{}
|
|
||||||
for idx, msg := range m.Messages {
|
for idx, msg := range m.Messages {
|
||||||
if idx == 10 {
|
if idx == 10 {
|
||||||
break
|
break
|
||||||
@ -71,13 +68,10 @@ func TestAPIv1Messages(t *testing.T) {
|
|||||||
t.Errorf(err.Error())
|
t.Errorf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// het headers
|
// get headers
|
||||||
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID + "/headers"); err != nil {
|
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID + "/headers"); err != nil {
|
||||||
t.Errorf(err.Error())
|
t.Errorf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// store for later
|
|
||||||
putIDS = append(putIDS, msg.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10 should be marked as read
|
// 10 should be marked as read
|
||||||
|
@ -22,14 +22,10 @@ const (
|
|||||||
|
|
||||||
// Send pings to peer with this period. Must be less than pongWait.
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
pingPeriod = (pongWait * 9) / 10
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
|
||||||
// Maximum message size allowed from peer.
|
|
||||||
maxMessageSize = 512
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
newline = []byte{'\n'}
|
newline = []byte{'\n'}
|
||||||
space = []byte{' '}
|
|
||||||
|
|
||||||
// MessageHub global
|
// MessageHub global
|
||||||
MessageHub *Hub
|
MessageHub *Hub
|
||||||
|
Loading…
x
Reference in New Issue
Block a user