Browse Source

Merge pull request #1216 from wunter8/since-latest-param

feat: add since=latest subscribe param
Philipp C. Heckel 9 months ago
parent
commit
3709ea689a

+ 1 - 0
docs/releases.md

@@ -1379,6 +1379,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
 
 
 * Add username/password auth to email publishing ([#1164](https://github.com/binwiederhier/ntfy/pull/1164), thanks to [@bishtawi](https://github.com/bishtawi) for implementing)
 * Add username/password auth to email publishing ([#1164](https://github.com/binwiederhier/ntfy/pull/1164), thanks to [@bishtawi](https://github.com/bishtawi) for implementing)
 * Write VAPID keys to file in `ntfy webpush --output-file` ([#1138](https://github.com/binwiederhier/ntfy/pull/1138), thanks to [@nogweii](https://github.com/nogweii) for implementing)
 * Write VAPID keys to file in `ntfy webpush --output-file` ([#1138](https://github.com/binwiederhier/ntfy/pull/1138), thanks to [@nogweii](https://github.com/nogweii) for implementing)
+* Add `latest` subscription param for grabbing just the most recent message (thanks to [@wunter8](https://github.com/wunter8))
 
 
 **Bug fixes + maintenance:**
 **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"
 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
 ### Fetch scheduled messages
 Messages that are [scheduled to be delivered](../publish.md#scheduled-delivery) at a later date are not typically 
 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 
 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)
 		WHERE topic = ? AND (id > ? OR published = 0)
 		ORDER BY time, id
 		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 = `
 	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
 		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 
 		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) {
 func (c *messageCache) Messages(topic string, since sinceMarker, scheduled bool) ([]*message, error) {
 	if since.IsNone() {
 	if since.IsNone() {
 		return make([]*message, 0), nil
 		return make([]*message, 0), nil
+	} else if since.IsLatest() {
+		return c.messagesLatest(topic)
 	} else if since.IsID() {
 	} else if since.IsID() {
 		return c.messagesSinceID(topic, since, scheduled)
 		return c.messagesSinceID(topic, since, scheduled)
 	}
 	}
@@ -462,6 +471,14 @@ func (c *messageCache) messagesSinceID(topic string, since sinceMarker, schedule
 	return readMessages(rows)
 	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) {
 func (c *messageCache) MessagesDue() ([]*message, error) {
 	rows, err := c.db.Query(selectMessagesDueQuery, time.Now().Unix())
 	rows, err := c.db.Query(selectMessagesDueQuery, time.Now().Unix())
 	if err != nil {
 	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, 1, len(messages))
 	require.Equal(t, "my other message", messages[0].Message)
 	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
 	// example: count
 	counts, err = c.MessageCounts()
 	counts, err = c.MessageCounts()
 	require.Nil(t, err)
 	require.Nil(t, err)

+ 4 - 2
server/server.go

@@ -1558,8 +1558,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.
 // 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) {
 func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
 	since := readParam(r, "x-since", "since", "si")
 	since := readParam(r, "x-since", "since", "si")
 
 
@@ -1571,6 +1571,8 @@ func parseSince(r *http.Request, poll bool) (sinceMarker, error) {
 		return sinceNoMessages, nil
 		return sinceNoMessages, nil
 	} else if since == "all" {
 	} else if since == "all" {
 		return sinceAllMessages, nil
 		return sinceAllMessages, nil
+	} else if since == "latest" {
+		return sinceLatestMessage, nil
 	} else if since == "none" {
 	} else if since == "none" {
 		return sinceNoMessages, nil
 		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, 1, len(messages))
 	require.Equal(t, "test 2", messages[0].Message)
 	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)
 	response = request(t, s, "GET", "/mytopic/json?poll=1&since=INVALID", "", nil)
 	require.Equal(t, 40008, toHTTPError(t, response.Body.String()).Code)
 	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
 	return t == sinceNoMessages
 }
 }
 
 
+func (t sinceMarker) IsLatest() bool {
+	return t == sinceLatestMessage
+}
+
 func (t sinceMarker) IsID() bool {
 func (t sinceMarker) IsID() bool {
-	return t.id != ""
+	return t.id != "" && t.id != "latest"
 }
 }
 
 
 func (t sinceMarker) Time() time.Time {
 func (t sinceMarker) Time() time.Time {
@@ -182,8 +186,9 @@ func (t sinceMarker) ID() string {
 }
 }
 
 
 var (
 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 {
 type queryFilter struct {