Move model constants

This commit is contained in:
binwiederhier
2026-02-21 10:42:34 -05:00
parent c76e39bb0e
commit 4b6979aa89
12 changed files with 66 additions and 97 deletions

View File

@@ -136,7 +136,7 @@ func (p *actionParser) Parse() ([]*model.Action, error) {
// and then uses populateAction to interpret the keys/values. The function terminates // and then uses populateAction to interpret the keys/values. The function terminates
// when EOF or ";" is reached. // when EOF or ";" is reached.
func (p *actionParser) parseAction() (*model.Action, error) { func (p *actionParser) parseAction() (*model.Action, error) {
a := newAction() a := model.NewAction()
section := 0 section := 0
for { for {
key, value, last, err := p.parseSection() key, value, last, err := p.parseSection()

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"heckel.io/ntfy/v2/log" "heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/model"
"heckel.io/ntfy/v2/util" "heckel.io/ntfy/v2/util"
"io" "io"
"os" "os"
@@ -13,7 +14,7 @@ import (
) )
var ( var (
fileIDRegex = regexp.MustCompile(fmt.Sprintf(`^[-_A-Za-z0-9]{%d}$`, messageIDLength)) fileIDRegex = regexp.MustCompile(fmt.Sprintf(`^[-_A-Za-z0-9]{%d}$`, model.MessageIDLength))
errInvalidFileID = errors.New("invalid file ID") errInvalidFileID = errors.New("invalid file ID")
errFileExists = errors.New("file exists") errFileExists = errors.New("file exists")
) )

View File

@@ -770,7 +770,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, v *visitor)
// - avoid abuse (e.g. 1 uploader, 1k downloaders) // - avoid abuse (e.g. 1 uploader, 1k downloaders)
// - and also uses the higher bandwidth limits of a paying user // - and also uses the higher bandwidth limits of a paying user
m, err := s.messageCache.Message(messageID) m, err := s.messageCache.Message(messageID)
if errors.Is(err, errMessageNotFound) { if errors.Is(err, model.ErrMessageNotFound) {
if s.config.CacheBatchTimeout > 0 { if s.config.CacheBatchTimeout > 0 {
// Strange edge case: If we immediately after upload request the file (the web app does this for images), // Strange edge case: If we immediately after upload request the file (the web app does this for images),
// and messages are persisted asynchronously, retry fetching from the database // and messages are persisted asynchronously, retry fetching from the database
@@ -834,7 +834,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*model.Mess
if err != nil { if err != nil {
return nil, err return nil, err
} }
m := newDefaultMessage(t.ID, "") m := model.NewDefaultMessage(t.ID, "")
cache, firebase, email, call, template, unifiedpush, priorityStr, e := s.parsePublishParams(r, m) cache, firebase, email, call, template, unifiedpush, priorityStr, e := s.parsePublishParams(r, m)
if e != nil { if e != nil {
return nil, e.With(t) return nil, e.With(t)
@@ -977,11 +977,11 @@ func (s *Server) handlePublishMatrix(w http.ResponseWriter, r *http.Request, v *
} }
func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
return s.handleActionMessage(w, r, v, messageDeleteEvent) return s.handleActionMessage(w, r, v, model.MessageDeleteEvent)
} }
func (s *Server) handleClear(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleClear(w http.ResponseWriter, r *http.Request, v *visitor) error {
return s.handleActionMessage(w, r, v, messageClearEvent) return s.handleActionMessage(w, r, v, model.MessageClearEvent)
} }
func (s *Server) handleActionMessage(w http.ResponseWriter, r *http.Request, v *visitor, event string) error { func (s *Server) handleActionMessage(w http.ResponseWriter, r *http.Request, v *visitor, event string) error {
@@ -1001,7 +1001,7 @@ func (s *Server) handleActionMessage(w http.ResponseWriter, r *http.Request, v *
return e.With(t) return e.With(t)
} }
// Create an action message with the given event type // Create an action message with the given event type
m := newActionMessage(event, t.ID, sequenceID) m := model.NewActionMessage(event, t.ID, sequenceID)
m.Sender = v.IP() m.Sender = v.IP()
m.User = v.MaybeUserID() m.User = v.MaybeUserID()
m.Expires = time.Unix(m.Time, 0).Add(v.Limits().MessageExpiryDuration).Unix() m.Expires = time.Unix(m.Time, 0).Add(v.Limits().MessageExpiryDuration).Unix()
@@ -1017,7 +1017,7 @@ func (s *Server) handleActionMessage(w http.ResponseWriter, r *http.Request, v *
if s.config.WebPushPublicKey != "" { if s.config.WebPushPublicKey != "" {
go s.publishToWebPushEndpoints(v, m) go s.publishToWebPushEndpoints(v, m)
} }
if event == messageDeleteEvent { if event == model.MessageDeleteEvent {
// Delete any existing scheduled message with the same sequence ID // Delete any existing scheduled message with the same sequence ID
deletedIDs, err := s.messageCache.DeleteScheduledBySequenceID(t.ID, sequenceID) deletedIDs, err := s.messageCache.DeleteScheduledBySequenceID(t.ID, sequenceID)
if err != nil { if err != nil {
@@ -1246,7 +1246,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *model.Message) (cache bo
// 7. curl -T file.txt ntfy.sh/mytopic // 7. curl -T file.txt ntfy.sh/mytopic
// In all other cases, mostly if file.txt is > message limit, treat it as an attachment // In all other cases, mostly if file.txt is > message limit, treat it as an attachment
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *model.Message, body *util.PeekedReadCloser, template templateMode, unifiedpush bool, priorityStr string) error { func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *model.Message, body *util.PeekedReadCloser, template templateMode, unifiedpush bool, priorityStr string) error {
if m.Event == pollRequestEvent { // Case 1 if m.Event == model.PollRequestEvent { // Case 1
return s.handleBodyDiscard(body) return s.handleBodyDiscard(body)
} else if unifiedpush { } else if unifiedpush {
return s.handleBodyAsMessageAutoDetect(m, body) // Case 2 return s.handleBodyAsMessageAutoDetect(m, body) // Case 2
@@ -1466,7 +1466,7 @@ func (s *Server) handleSubscribeSSE(w http.ResponseWriter, r *http.Request, v *v
if err := json.NewEncoder(&buf).Encode(msg.ForJSON()); err != nil { if err := json.NewEncoder(&buf).Encode(msg.ForJSON()); err != nil {
return "", err return "", err
} }
if msg.Event != messageEvent && msg.Event != messageDeleteEvent && msg.Event != messageClearEvent { if msg.Event != model.MessageEvent && msg.Event != model.MessageDeleteEvent && msg.Event != model.MessageClearEvent {
return fmt.Sprintf("event: %s\ndata: %s\n", msg.Event, buf.String()), nil // Browser's .onmessage() does not fire on this! return fmt.Sprintf("event: %s\ndata: %s\n", msg.Event, buf.String()), nil // Browser's .onmessage() does not fire on this!
} }
return fmt.Sprintf("data: %s\n", buf.String()), nil return fmt.Sprintf("data: %s\n", buf.String()), nil
@@ -1476,7 +1476,7 @@ func (s *Server) handleSubscribeSSE(w http.ResponseWriter, r *http.Request, v *v
func (s *Server) handleSubscribeRaw(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleSubscribeRaw(w http.ResponseWriter, r *http.Request, v *visitor) error {
encoder := func(msg *model.Message) (string, error) { encoder := func(msg *model.Message) (string, error) {
if msg.Event == messageEvent { // only handle default events if msg.Event == model.MessageEvent { // only handle default events
return strings.ReplaceAll(msg.Message, "\n", " ") + "\n", nil return strings.ReplaceAll(msg.Message, "\n", " ") + "\n", nil
} }
return "\n", nil // "keepalive" and "open" events just send an empty line return "\n", nil // "keepalive" and "open" events just send an empty line
@@ -1554,7 +1554,7 @@ func (s *Server) handleSubscribeHTTP(w http.ResponseWriter, r *http.Request, v *
topics[i].Unsubscribe(subscriberID) // Order! topics[i].Unsubscribe(subscriberID) // Order!
} }
}() }()
if err := sub(v, newOpenMessage(topicsStr)); err != nil { // Send out open message if err := sub(v, model.NewOpenMessage(topicsStr)); err != nil { // Send out open message
return err return err
} }
if err := s.sendOldMessages(topics, since, scheduled, v, sub); err != nil { if err := s.sendOldMessages(topics, since, scheduled, v, sub); err != nil {
@@ -1577,7 +1577,7 @@ func (s *Server) handleSubscribeHTTP(w http.ResponseWriter, r *http.Request, v *
for _, t := range topics { for _, t := range topics {
t.Keepalive() t.Keepalive()
} }
if err := sub(v, newKeepaliveMessage(topicsStr)); err != nil { // Send keepalive message if err := sub(v, model.NewKeepaliveMessage(topicsStr)); err != nil { // Send keepalive message
return err return err
} }
} }
@@ -1703,7 +1703,7 @@ func (s *Server) handleSubscribeWS(w http.ResponseWriter, r *http.Request, v *vi
topics[i].Unsubscribe(subscriberID) // Order! topics[i].Unsubscribe(subscriberID) // Order!
} }
}() }()
if err := sub(v, newOpenMessage(topicsStr)); err != nil { // Send out open message if err := sub(v, model.NewOpenMessage(topicsStr)); err != nil { // Send out open message
return err return err
} }
if err := s.sendOldMessages(topics, since, scheduled, v, sub); err != nil { if err := s.sendOldMessages(topics, since, scheduled, v, sub); err != nil {
@@ -1834,26 +1834,26 @@ func parseSince(r *http.Request, poll bool) (model.SinceMarker, error) {
// Easy cases (empty, all, none) // Easy cases (empty, all, none)
if since == "" { if since == "" {
if poll { if poll {
return sinceAllMessages, nil return model.SinceAllMessages, nil
} }
return sinceNoMessages, nil return model.SinceNoMessages, nil
} else if since == "all" { } else if since == "all" {
return sinceAllMessages, nil return model.SinceAllMessages, nil
} else if since == "latest" { } else if since == "latest" {
return sinceLatestMessage, nil return model.SinceLatestMessage, nil
} else if since == "none" { } else if since == "none" {
return sinceNoMessages, nil return model.SinceNoMessages, nil
} }
// ID, timestamp, duration // ID, timestamp, duration
if validMessageID(since) { if model.ValidMessageID(since) {
return newSinceID(since), nil return model.NewSinceID(since), nil
} else if s, err := strconv.ParseInt(since, 10, 64); err == nil { } else if s, err := strconv.ParseInt(since, 10, 64); err == nil {
return newSinceTime(s), nil return model.NewSinceTime(s), nil
} else if d, err := time.ParseDuration(since); err == nil { } else if d, err := time.ParseDuration(since); err == nil {
return newSinceTime(time.Now().Add(-1 * d).Unix()), nil return model.NewSinceTime(time.Now().Add(-1 * d).Unix()), nil
} }
return sinceNoMessages, errHTTPBadRequestSinceInvalid return model.SinceNoMessages, errHTTPBadRequestSinceInvalid
} }
func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request, _ *visitor) error { func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request, _ *visitor) error {
@@ -2009,14 +2009,14 @@ func (s *Server) runFirebaseKeepaliver() {
for { for {
select { select {
case <-time.After(s.config.FirebaseKeepaliveInterval): case <-time.After(s.config.FirebaseKeepaliveInterval):
s.sendToFirebase(v, newKeepaliveMessage(firebaseControlTopic)) s.sendToFirebase(v, model.NewKeepaliveMessage(firebaseControlTopic))
/* /*
FIXME: Disable iOS polling entirely for now due to thundering herd problem (see #677) FIXME: Disable iOS polling entirely for now due to thundering herd problem (see #677)
To solve this, we'd have to shard the iOS poll topics to spread out the polling evenly. To solve this, we'd have to shard the iOS poll topics to spread out the polling evenly.
Given that it's not really necessary to poll, turning it off for now should not have any impact. Given that it's not really necessary to poll, turning it off for now should not have any impact.
case <-time.After(s.config.FirebasePollInterval): case <-time.After(s.config.FirebasePollInterval):
s.sendToFirebase(v, newKeepaliveMessage(firebasePollTopic)) s.sendToFirebase(v, model.NewKeepaliveMessage(firebasePollTopic))
*/ */
case <-s.closeChan: case <-s.closeChan:
return return

View File

@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"heckel.io/ntfy/v2/log" "heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/model"
"heckel.io/ntfy/v2/user" "heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util" "heckel.io/ntfy/v2/util"
"net/http" "net/http"
@@ -641,7 +642,7 @@ func (s *Server) publishSyncEvent(v *visitor) error {
if err != nil { if err != nil {
return err return err
} }
m := newDefaultMessage(syncTopic.ID, string(messageBytes)) m := model.NewDefaultMessage(syncTopic.ID, string(messageBytes))
if err := syncTopic.Publish(v, m); err != nil { if err := syncTopic.Publish(v, m); err != nil {
return err return err
} }

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"heckel.io/ntfy/v2/log" "heckel.io/ntfy/v2/log"
"heckel.io/ntfy/v2/model"
"heckel.io/ntfy/v2/user" "heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util" "heckel.io/ntfy/v2/util"
"io" "io"
@@ -715,12 +716,12 @@ func TestAccount_Reservation_Delete_Messages_And_Attachments(t *testing.T) {
require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID)) require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
// Pre-verify message count and file // Pre-verify message count and file
ms, err := s.messageCache.Messages("mytopic1", sinceAllMessages, false) ms, err := s.messageCache.Messages("mytopic1", model.SinceAllMessages, false)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 1, len(ms)) require.Equal(t, 1, len(ms))
require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID)) require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
ms, err = s.messageCache.Messages("mytopic2", sinceAllMessages, false) ms, err = s.messageCache.Messages("mytopic2", model.SinceAllMessages, false)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 1, len(ms)) require.Equal(t, 1, len(ms))
require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID)) require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
@@ -741,17 +742,17 @@ func TestAccount_Reservation_Delete_Messages_And_Attachments(t *testing.T) {
// Verify that messages and attachments were deleted // Verify that messages and attachments were deleted
// This does not explicitly call the manager! // This does not explicitly call the manager!
waitFor(t, func() bool { waitFor(t, func() bool {
ms, err := s.messageCache.Messages("mytopic1", sinceAllMessages, false) ms, err := s.messageCache.Messages("mytopic1", model.SinceAllMessages, false)
require.Nil(t, err) require.Nil(t, err)
return len(ms) == 0 && !util.FileExists(filepath.Join(s.config.AttachmentCacheDir, m1.ID)) return len(ms) == 0 && !util.FileExists(filepath.Join(s.config.AttachmentCacheDir, m1.ID))
}) })
ms, err = s.messageCache.Messages("mytopic1", sinceAllMessages, false) ms, err = s.messageCache.Messages("mytopic1", model.SinceAllMessages, false)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 0, len(ms)) require.Equal(t, 0, len(ms))
require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID)) require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
ms, err = s.messageCache.Messages("mytopic2", sinceAllMessages, false) ms, err = s.messageCache.Messages("mytopic2", model.SinceAllMessages, false)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 1, len(ms)) require.Equal(t, 1, len(ms))
require.Equal(t, m2.ID, ms[0].ID) require.Equal(t, m2.ID, ms[0].ID)

View File

@@ -126,7 +126,7 @@ func toFirebaseMessage(m *model.Message, auther user.Auther) (*messaging.Message
var data map[string]string // Mostly matches https://ntfy.sh/docs/subscribe/api/#json-message-format var data map[string]string // Mostly matches https://ntfy.sh/docs/subscribe/api/#json-message-format
var apnsConfig *messaging.APNSConfig var apnsConfig *messaging.APNSConfig
switch m.Event { switch m.Event {
case keepaliveEvent, openEvent: case model.KeepaliveEvent, model.OpenEvent:
data = map[string]string{ data = map[string]string{
"id": m.ID, "id": m.ID,
"time": fmt.Sprintf("%d", m.Time), "time": fmt.Sprintf("%d", m.Time),
@@ -134,7 +134,7 @@ func toFirebaseMessage(m *model.Message, auther user.Auther) (*messaging.Message
"topic": m.Topic, "topic": m.Topic,
} }
apnsConfig = createAPNSBackgroundConfig(data) apnsConfig = createAPNSBackgroundConfig(data)
case pollRequestEvent: case model.PollRequestEvent:
data = map[string]string{ data = map[string]string{
"id": m.ID, "id": m.ID,
"time": fmt.Sprintf("%d", m.Time), "time": fmt.Sprintf("%d", m.Time),
@@ -144,7 +144,7 @@ func toFirebaseMessage(m *model.Message, auther user.Auther) (*messaging.Message
"poll_id": m.PollID, "poll_id": m.PollID,
} }
apnsConfig = createAPNSAlertConfig(m, data) apnsConfig = createAPNSAlertConfig(m, data)
case messageDeleteEvent, messageClearEvent: case model.MessageDeleteEvent, model.MessageClearEvent:
data = map[string]string{ data = map[string]string{
"id": m.ID, "id": m.ID,
"time": fmt.Sprintf("%d", m.Time), "time": fmt.Sprintf("%d", m.Time),
@@ -153,7 +153,7 @@ func toFirebaseMessage(m *model.Message, auther user.Auther) (*messaging.Message
"sequence_id": m.SequenceID, "sequence_id": m.SequenceID,
} }
apnsConfig = createAPNSBackgroundConfig(data) apnsConfig = createAPNSBackgroundConfig(data)
case messageEvent: case model.MessageEvent:
if auther != nil { if auther != nil {
// If "anonymous read" for a topic is not allowed, we cannot send the message along // If "anonymous read" for a topic is not allowed, we cannot send the message along
// via Firebase. Instead, we send a "poll_request" message, asking the client to poll. // via Firebase. Instead, we send a "poll_request" message, asking the client to poll.

View File

@@ -64,7 +64,7 @@ func (s *testFirebaseSender) Messages() []*messaging.Message {
} }
func TestToFirebaseMessage_Keepalive(t *testing.T) { func TestToFirebaseMessage_Keepalive(t *testing.T) {
m := newKeepaliveMessage("mytopic") m := model.NewKeepaliveMessage("mytopic")
fbm, err := toFirebaseMessage(m, nil) fbm, err := toFirebaseMessage(m, nil)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, "mytopic", fbm.Topic) require.Equal(t, "mytopic", fbm.Topic)
@@ -95,7 +95,7 @@ func TestToFirebaseMessage_Keepalive(t *testing.T) {
} }
func TestToFirebaseMessage_Open(t *testing.T) { func TestToFirebaseMessage_Open(t *testing.T) {
m := newOpenMessage("mytopic") m := model.NewOpenMessage("mytopic")
fbm, err := toFirebaseMessage(m, nil) fbm, err := toFirebaseMessage(m, nil)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, "mytopic", fbm.Topic) require.Equal(t, "mytopic", fbm.Topic)
@@ -126,7 +126,7 @@ func TestToFirebaseMessage_Open(t *testing.T) {
} }
func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) { func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
m := newDefaultMessage("mytopic", "this is a message") m := model.NewDefaultMessage("mytopic", "this is a message")
m.Priority = 4 m.Priority = 4
m.Tags = []string{"tag 1", "tag2"} m.Tags = []string{"tag 1", "tag2"}
m.Click = "https://google.com" m.Click = "https://google.com"
@@ -220,7 +220,7 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
} }
func TestToFirebaseMessage_Message_Normal_Not_Allowed(t *testing.T) { func TestToFirebaseMessage_Message_Normal_Not_Allowed(t *testing.T) {
m := newDefaultMessage("mytopic", "this is a message") m := model.NewDefaultMessage("mytopic", "this is a message")
m.Priority = 5 m.Priority = 5
fbm, err := toFirebaseMessage(m, &testAuther{Allow: false}) // Not allowed! fbm, err := toFirebaseMessage(m, &testAuther{Allow: false}) // Not allowed!
require.Nil(t, err) require.Nil(t, err)

View File

@@ -2,6 +2,7 @@ package server
import ( import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"heckel.io/ntfy/v2/model"
"testing" "testing"
) )
@@ -25,6 +26,6 @@ func TestServer_Manager_Prune_Messages_Without_Attachments_DoesNotPanic(t *testi
// Actually deleted // Actually deleted
_, err := s.messageCache.Message(m.ID) _, err := s.messageCache.Message(m.ID)
require.Equal(t, errMessageNotFound, err) require.Equal(t, model.ErrMessageNotFound, err)
}) })
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stripe/stripe-go/v74" "github.com/stripe/stripe-go/v74"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"heckel.io/ntfy/v2/model"
"heckel.io/ntfy/v2/payments" "heckel.io/ntfy/v2/payments"
"heckel.io/ntfy/v2/user" "heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util" "heckel.io/ntfy/v2/util"
@@ -546,12 +547,12 @@ func TestPayments_Webhook_Subscription_Updated_Downgrade_From_PastDue_To_Active(
time.Sleep(time.Second) time.Sleep(time.Second)
s.execManager() s.execManager()
ms, err := s.messageCache.Messages("atopic", sinceAllMessages, false) ms, err := s.messageCache.Messages("atopic", model.SinceAllMessages, false)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 2, len(ms)) require.Equal(t, 2, len(ms))
require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, a2.ID)) require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, a2.ID))
ms, err = s.messageCache.Messages("ztopic", sinceAllMessages, false) ms, err = s.messageCache.Messages("ztopic", model.SinceAllMessages, false)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 0, len(ms)) require.Equal(t, 0, len(ms))
require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, z2.ID)) require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, z2.ID))

View File

@@ -163,14 +163,14 @@ func TestServer_SubscribeOpenAndKeepalive(t *testing.T) {
messages := toMessages(t, rr.Body.String()) messages := toMessages(t, rr.Body.String())
require.Equal(t, 2, len(messages)) require.Equal(t, 2, len(messages))
require.Equal(t, openEvent, messages[0].Event) require.Equal(t, model.OpenEvent, messages[0].Event)
require.Equal(t, "mytopic", messages[0].Topic) require.Equal(t, "mytopic", messages[0].Topic)
require.Equal(t, "", messages[0].Message) require.Equal(t, "", messages[0].Message)
require.Equal(t, "", messages[0].Title) require.Equal(t, "", messages[0].Title)
require.Equal(t, 0, messages[0].Priority) require.Equal(t, 0, messages[0].Priority)
require.Nil(t, messages[0].Tags) require.Nil(t, messages[0].Tags)
require.Equal(t, keepaliveEvent, messages[1].Event) require.Equal(t, model.KeepaliveEvent, messages[1].Event)
require.Equal(t, "mytopic", messages[1].Topic) require.Equal(t, "mytopic", messages[1].Topic)
require.Equal(t, "", messages[1].Message) require.Equal(t, "", messages[1].Message)
require.Equal(t, "", messages[1].Title) require.Equal(t, "", messages[1].Title)
@@ -201,9 +201,9 @@ func TestServer_PublishAndSubscribe(t *testing.T) {
subscribeCancel() subscribeCancel()
messages := toMessages(t, subscribeRR.Body.String()) messages := toMessages(t, subscribeRR.Body.String())
require.Equal(t, 3, len(messages)) require.Equal(t, 3, len(messages))
require.Equal(t, openEvent, messages[0].Event) require.Equal(t, model.OpenEvent, messages[0].Event)
require.Equal(t, messageEvent, messages[1].Event) require.Equal(t, model.MessageEvent, messages[1].Event)
require.Equal(t, "mytopic", messages[1].Topic) require.Equal(t, "mytopic", messages[1].Topic)
require.Equal(t, "my first message", messages[1].Message) require.Equal(t, "my first message", messages[1].Message)
require.Equal(t, "", messages[1].Title) require.Equal(t, "", messages[1].Title)
@@ -212,7 +212,7 @@ func TestServer_PublishAndSubscribe(t *testing.T) {
require.True(t, time.Now().Add(12*time.Hour-5*time.Second).Unix() < messages[1].Expires) require.True(t, time.Now().Add(12*time.Hour-5*time.Second).Unix() < messages[1].Expires)
require.True(t, time.Now().Add(12*time.Hour+5*time.Second).Unix() > messages[1].Expires) require.True(t, time.Now().Add(12*time.Hour+5*time.Second).Unix() > messages[1].Expires)
require.Equal(t, messageEvent, messages[2].Event) require.Equal(t, model.MessageEvent, messages[2].Event)
require.Equal(t, "mytopic", messages[2].Topic) require.Equal(t, "mytopic", messages[2].Topic)
require.Equal(t, "my other message", messages[2].Message) require.Equal(t, "my other message", messages[2].Message)
require.Equal(t, "This is a title", messages[2].Title) require.Equal(t, "This is a title", messages[2].Title)
@@ -426,7 +426,7 @@ func TestServer_PublishAt(t *testing.T) {
require.Equal(t, netip.Addr{}, messages[0].Sender) // Never return the sender! require.Equal(t, netip.Addr{}, messages[0].Sender) // Never return the sender!
var err error var err error
messages, err = s.messageCache.Messages("mytopic", sinceAllMessages, true) messages, err = s.messageCache.Messages("mytopic", model.SinceAllMessages, true)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 1, len(messages)) require.Equal(t, 1, len(messages))
require.Equal(t, "a message", messages[0].Message) require.Equal(t, "a message", messages[0].Message)
@@ -465,7 +465,7 @@ func TestServer_PublishAt_FromUser(t *testing.T) {
require.Equal(t, "a message", messages[0].Message) require.Equal(t, "a message", messages[0].Message)
var err error var err error
messages, err = s.messageCache.Messages("mytopic", sinceAllMessages, true) messages, err = s.messageCache.Messages("mytopic", model.SinceAllMessages, true)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, 1, len(messages)) require.Equal(t, 1, len(messages))
require.Equal(t, "a message", messages[0].Message) require.Equal(t, "a message", messages[0].Message)
@@ -607,8 +607,8 @@ func TestServer_PublishWithNopCache(t *testing.T) {
subscribeCancel() subscribeCancel()
messages := toMessages(t, subscribeRR.Body.String()) messages := toMessages(t, subscribeRR.Body.String())
require.Equal(t, 2, len(messages)) require.Equal(t, 2, len(messages))
require.Equal(t, openEvent, messages[0].Event) require.Equal(t, model.OpenEvent, messages[0].Event)
require.Equal(t, messageEvent, messages[1].Event) require.Equal(t, model.MessageEvent, messages[1].Event)
require.Equal(t, "my first message", messages[1].Message) require.Equal(t, "my first message", messages[1].Message)
response := request(t, s, "GET", "/mytopic/json?poll=1", "", nil) response := request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
@@ -654,7 +654,7 @@ func TestServer_PublishAndPollSince(t *testing.T) {
} }
func newMessageWithTimestamp(topic, msg string, timestamp int64) *model.Message { func newMessageWithTimestamp(topic, msg string, timestamp int64) *model.Message {
m := newDefaultMessage(topic, msg) m := model.NewDefaultMessage(topic, msg)
m.Time = timestamp m.Time = timestamp
return m return m
} }
@@ -935,10 +935,10 @@ func TestServer_SubscribeWithQueryFilters(t *testing.T) {
messages := toMessages(t, subscribeResponse.Body.String()) messages := toMessages(t, subscribeResponse.Body.String())
require.Equal(t, 3, len(messages)) require.Equal(t, 3, len(messages))
require.Equal(t, openEvent, messages[0].Event) require.Equal(t, model.OpenEvent, messages[0].Event)
require.Equal(t, messageEvent, messages[1].Event) require.Equal(t, model.MessageEvent, messages[1].Event)
require.Equal(t, "ZFS scrub failed", messages[1].Message) require.Equal(t, "ZFS scrub failed", messages[1].Message)
require.Equal(t, keepaliveEvent, messages[2].Event) require.Equal(t, model.KeepaliveEvent, messages[2].Event)
}) })
} }
@@ -2652,7 +2652,7 @@ func TestServer_PublishWhileUpdatingStatsWithLotsOfMessages(t *testing.T) {
topicID := fmt.Sprintf("topic%d", i) topicID := fmt.Sprintf("topic%d", i)
_, err := s.topicsFromIDs(topicID) // Add topic to internal s.topics array _, err := s.topicsFromIDs(topicID) // Add topic to internal s.topics array
require.Nil(t, err) require.Nil(t, err)
messages = append(messages, newDefaultMessage(topicID, "some message")) messages = append(messages, model.NewDefaultMessage(topicID, "some message"))
} }
require.Nil(t, s.messageCache.AddMessages(messages)) require.Nil(t, s.messageCache.AddMessages(messages))
log.Info("Done: Adding %d messages; took %s", count, time.Since(start).Round(time.Millisecond)) log.Info("Done: Adding %d messages; took %s", count, time.Since(start).Round(time.Millisecond))
@@ -4398,7 +4398,7 @@ func TestServer_SubscribeHTTP_NoWriteAfterHandlerReturn(t *testing.T) {
// how the panic occurs: the goroutine spawned by topic.Publish calls sub() after the // how the panic occurs: the goroutine spawned by topic.Publish calls sub() after the
// handler has already returned and Go has cleaned up the response writer. // handler has already returned and Go has cleaned up the response writer.
v := newVisitor(s.config, s.messageCache, s.userManager, netip.MustParseAddr("9.9.9.9"), nil) v := newVisitor(s.config, s.messageCache, s.userManager, netip.MustParseAddr("9.9.9.9"), nil)
msg := newDefaultMessage("mytopic", "straggler message") msg := model.NewDefaultMessage("mytopic", "straggler message")
_ = copiedSub(v, msg) _ = copiedSub(v, msg)
require.False(t, rw.wroteAfterClose.Load(), require.False(t, rw.wroteAfterClose.Load(),

View File

@@ -160,7 +160,7 @@ func (s *smtpSession) Data(r io.Reader) error {
if len(body) > conf.MessageSizeLimit { if len(body) > conf.MessageSizeLimit {
body = body[:conf.MessageSizeLimit] body = body[:conf.MessageSizeLimit]
} }
m := newDefaultMessage(s.topic, body) m := model.NewDefaultMessage(s.topic, body)
subject := strings.TrimSpace(msg.Header.Get("Subject")) subject := strings.TrimSpace(msg.Header.Get("Subject"))
if subject != "" { if subject != "" {
dec := mime.WordDecoder{} dec := mime.WordDecoder{}

View File

@@ -8,45 +8,9 @@ import (
"heckel.io/ntfy/v2/util" "heckel.io/ntfy/v2/util"
) )
// Event constants
const (
openEvent = model.OpenEvent
keepaliveEvent = model.KeepaliveEvent
messageEvent = model.MessageEvent
messageDeleteEvent = model.MessageDeleteEvent
messageClearEvent = model.MessageClearEvent
pollRequestEvent = model.PollRequestEvent
messageIDLength = model.MessageIDLength
)
// SinceMarker aliases
var (
sinceAllMessages = model.SinceAllMessages
sinceNoMessages = model.SinceNoMessages
sinceLatestMessage = model.SinceLatestMessage
)
// Error aliases
var (
errMessageNotFound = model.ErrMessageNotFound
)
// Constructors and helpers
var (
newMessage = model.NewMessage
newDefaultMessage = model.NewDefaultMessage
newOpenMessage = model.NewOpenMessage
newKeepaliveMessage = model.NewKeepaliveMessage
newActionMessage = model.NewActionMessage
newAction = model.NewAction
newSinceTime = model.NewSinceTime
newSinceID = model.NewSinceID
validMessageID = model.ValidMessageID
)
// newPollRequestMessage is a convenience method to create a poll request message // newPollRequestMessage is a convenience method to create a poll request message
func newPollRequestMessage(topic, pollID string) *model.Message { func newPollRequestMessage(topic, pollID string) *model.Message {
m := newMessage(pollRequestEvent, topic, newMessageBody) m := model.NewMessage(model.PollRequestEvent, topic, newMessageBody)
m.PollID = pollID m.PollID = pollID
return m return m
} }
@@ -106,7 +70,7 @@ func parseQueryFilters(r *http.Request) (*queryFilter, error) {
} }
func (q *queryFilter) Pass(msg *model.Message) bool { func (q *queryFilter) Pass(msg *model.Message) bool {
if msg.Event != messageEvent && msg.Event != messageDeleteEvent && msg.Event != messageClearEvent { if msg.Event != model.MessageEvent && msg.Event != model.MessageDeleteEvent && msg.Event != model.MessageClearEvent {
return true // filters only apply to messages return true // filters only apply to messages
} else if q.ID != "" && msg.ID != q.ID { } else if q.ID != "" && msg.ID != q.ID {
return false return false