Răsfoiți Sursa

feat: add subscribe param

Hunter Kehoe 1 an în urmă
părinte
comite
9241b0550c

+ 1 - 0
docs/releases.md

@@ -1378,6 +1378,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
 **Features:**
 
 * Add username/password auth to email publishing ([#1164](https://github.com/binwiederhier/ntfy/pull/1164), thanks to [@bishtawi](https://github.com/bishtawi))
+* Add `latest` subscription param for grabbing just the most recent message (thanks to [@wunter8](https://github.com/wunter8))
 
 **Bug fixes + maintenance:**
 

+ 8 - 0
docs/subscribe/api.md

@@ -257,6 +257,14 @@ curl -s "ntfy.sh/mytopic/json?since=1645970742"
 curl -s "ntfy.sh/mytopic/json?since=nFS3knfcQ1xe"
 ```
 
+### Fetch latest message
+If you only want the most recent message sent to a topic and do not have a message ID or timestamp to use with
+`since=`, you can use `since=latest` to grab the most recent message from the cache for a particular topic.
+
+```
+curl -s "ntfy.sh/mytopic/json?poll=1&since=latest"
+```
+
 ### Fetch scheduled messages
 Messages that are [scheduled to be delivered](../publish.md#scheduled-delivery) at a later date are not typically 
 returned when subscribing via the API, which makes sense, because after all, the messages have technically not been 

+ 17 - 0
server/message_cache.go

@@ -99,6 +99,13 @@ const (
 		WHERE topic = ? AND (id > ? OR published = 0)
 		ORDER BY time, id
 	`
+	selectMessagesLatestQuery = `
+		SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
+		FROM messages
+		WHERE topic = ? AND published = 1
+		ORDER BY time DESC, id DESC
+		LIMIT 1
+  `
 	selectMessagesDueQuery = `
 		SELECT mid, time, expires, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, user, content_type, encoding
 		FROM messages 
@@ -416,6 +423,8 @@ func (c *messageCache) addMessages(ms []*message) error {
 func (c *messageCache) Messages(topic string, since sinceMarker, scheduled bool) ([]*message, error) {
 	if since.IsNone() {
 		return make([]*message, 0), nil
+	} else if since.IsLatest() {
+		return c.messagesLatest(topic)
 	} else if since.IsID() {
 		return c.messagesSinceID(topic, since, scheduled)
 	}
@@ -462,6 +471,14 @@ func (c *messageCache) messagesSinceID(topic string, since sinceMarker, schedule
 	return readMessages(rows)
 }
 
+func (c *messageCache) messagesLatest(topic string) ([]*message, error) {
+	rows, err := c.db.Query(selectMessagesLatestQuery, topic)
+	if err != nil {
+		return nil, err
+	}
+	return readMessages(rows)
+}
+
 func (c *messageCache) MessagesDue() ([]*message, error) {
 	rows, err := c.db.Query(selectMessagesDueQuery, time.Now().Unix())
 	if err != nil {

+ 5 - 0
server/message_cache_test.go

@@ -66,6 +66,11 @@ func testCacheMessages(t *testing.T, c *messageCache) {
 	require.Equal(t, 1, len(messages))
 	require.Equal(t, "my other message", messages[0].Message)
 
+	// mytopic: latest
+	messages, _ = c.Messages("mytopic", sinceLatestMessage, false)
+	require.Equal(t, 1, len(messages))
+	require.Equal(t, "my other message", messages[0].Message)
+
 	// example: count
 	counts, err = c.MessageCounts()
 	require.Nil(t, err)

+ 4 - 2
server/server.go

@@ -1556,8 +1556,8 @@ func (s *Server) sendOldMessages(topics []*topic, since sinceMarker, scheduled b
 
 // parseSince returns a timestamp identifying the time span from which cached messages should be received.
 //
-// Values in the "since=..." parameter can be either a unix timestamp or a duration (e.g. 12h), or
-// "all" for all messages.
+// Values in the "since=..." parameter can be either a unix timestamp or a duration (e.g. 12h),
+// "all" for all messages, or "latest" for the most recent message for a topic
 func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
 	since := readParam(r, "x-since", "since", "si")
 
@@ -1569,6 +1569,8 @@ func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
 		return sinceNoMessages, nil
 	} else if since == "all" {
 		return sinceAllMessages, nil
+	} else if since == "latest" {
+		return sinceLatestMessage, nil
 	} else if since == "none" {
 		return sinceNoMessages, nil
 	}

+ 5 - 0
server/server_test.go

@@ -594,6 +594,11 @@ func TestServer_PublishAndPollSince(t *testing.T) {
 	require.Equal(t, 1, len(messages))
 	require.Equal(t, "test 2", messages[0].Message)
 
+	response = request(t, s, "GET", "/mytopic/json?poll=1&since=latest", "", nil)
+	messages = toMessages(t, response.Body.String())
+	require.Equal(t, 1, len(messages))
+	require.Equal(t, "test 2", messages[0].Message)
+
 	response = request(t, s, "GET", "/mytopic/json?poll=1&since=INVALID", "", nil)
 	require.Equal(t, 40008, toHTTPError(t, response.Body.String()).Code)
 }

+ 8 - 3
server/types.go

@@ -169,8 +169,12 @@ func (t sinceMarker) IsNone() bool {
 	return t == sinceNoMessages
 }
 
+func (t sinceMarker) IsLatest() bool {
+	return t == sinceLatestMessage
+}
+
 func (t sinceMarker) IsID() bool {
-	return t.id != ""
+	return t.id != "" && t.id != "latest"
 }
 
 func (t sinceMarker) Time() time.Time {
@@ -182,8 +186,9 @@ func (t sinceMarker) ID() string {
 }
 
 var (
-	sinceAllMessages = sinceMarker{time.Unix(0, 0), ""}
-	sinceNoMessages  = sinceMarker{time.Unix(1, 0), ""}
+	sinceAllMessages   = sinceMarker{time.Unix(0, 0), ""}
+	sinceNoMessages    = sinceMarker{time.Unix(1, 0), ""}
+	sinceLatestMessage = sinceMarker{time.Unix(0, 0), "latest"}
 )
 
 type queryFilter struct {