mirror of
https://github.com/multipleof4/ntfy.git
synced 2026-10-15 16:07:55 +00:00
support delete via GET
This commit is contained in:
+23
-4
@@ -2777,16 +2777,20 @@ Here's an example of a dead man's switch that sends an alert if the script stops
|
|||||||
### Canceling scheduled notifications
|
### Canceling scheduled notifications
|
||||||
|
|
||||||
You can cancel a scheduled message before it is delivered by sending a DELETE request to the
|
You can cancel a scheduled message before it is delivered by sending a DELETE request to the
|
||||||
`/<topic>/<sequence_id>` endpoint, just like [deleting notifications](#deleting-notifications). This will remove the
|
`/<topic>/<sequence_id>` endpoint, just like [deleting notifications](#deleting-notifications). Alternatively, you can send a `GET`
|
||||||
scheduled message from the server so it will never be delivered, and emit a `message_delete` event to any subscribers.
|
request to `/<topic>/<sequence_id>/delete`. This will remove the scheduled message from the server so it will never be delivered,
|
||||||
|
and emit a `message_delete` event to any subscribers.
|
||||||
|
|
||||||
=== "Command line (curl)"
|
=== "Command line (curl)"
|
||||||
```bash
|
```bash
|
||||||
# Schedule a reminder for 2 hours from now
|
# Schedule a reminder for 2 hours from now
|
||||||
curl -H "In: 2h" -d "Take a break!" ntfy.sh/mytopic/break-reminder
|
curl -H "In: 2h" -d "Take a break!" ntfy.sh/mytopic/break-reminder
|
||||||
|
|
||||||
# Changed your mind? Cancel the scheduled message
|
# Changed your mind? Cancel the scheduled message via DELETE
|
||||||
curl -X DELETE ntfy.sh/mytopic/break-reminder
|
curl -X DELETE ntfy.sh/mytopic/break-reminder
|
||||||
|
|
||||||
|
# Or cancel it via GET
|
||||||
|
curl ntfy.sh/mytopic/break-reminder/delete
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "ntfy CLI"
|
=== "ntfy CLI"
|
||||||
@@ -4154,25 +4158,40 @@ An example response from the server with the `message_clear` event may look like
|
|||||||
### Deleting notifications
|
### Deleting notifications
|
||||||
Deleting a notification means **removing it from the notification drawer and from the client's database**.
|
Deleting a notification means **removing it from the notification drawer and from the client's database**.
|
||||||
|
|
||||||
To do this, send a DELETE request to the `/<topic>/<sequence_id>` endpoint. This will emit a `message_delete` event
|
To do this, send a `DELETE` request to the `/<topic>/<sequence_id>` endpoint. This will emit a `message_delete` event
|
||||||
that is used by the clients (web app and Android app) to remove the notification entirely.
|
that is used by the clients (web app and Android app) to remove the notification entirely.
|
||||||
|
|
||||||
|
Alternatively, if your client has limited HTTP support (e.g. webhooks or IoT devices), you can also delete a message by sending
|
||||||
|
a `GET` request to `/<topic>/<sequence_id>/delete`.
|
||||||
|
|
||||||
=== "Command line (curl)"
|
=== "Command line (curl)"
|
||||||
```bash
|
```bash
|
||||||
|
# Via DELETE method
|
||||||
curl -X DELETE ntfy.sh/mytopic/my-download-123
|
curl -X DELETE ntfy.sh/mytopic/my-download-123
|
||||||
|
|
||||||
|
# Via GET method
|
||||||
|
curl ntfy.sh/mytopic/my-download-123/delete
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "HTTP"
|
=== "HTTP"
|
||||||
``` http
|
``` http
|
||||||
DELETE /mytopic/my-download-123 HTTP/1.1
|
DELETE /mytopic/my-download-123 HTTP/1.1
|
||||||
Host: ntfy.sh
|
Host: ntfy.sh
|
||||||
|
|
||||||
|
# Or using GET
|
||||||
|
GET /mytopic/my-download-123/delete HTTP/1.1
|
||||||
|
Host: ntfy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "JavaScript"
|
=== "JavaScript"
|
||||||
``` javascript
|
``` javascript
|
||||||
|
// Via DELETE method
|
||||||
await fetch('https://ntfy.sh/mytopic/my-download-123', {
|
await fetch('https://ntfy.sh/mytopic/my-download-123', {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Via GET method
|
||||||
|
await fetch('https://ntfy.sh/mytopic/my-download-123/delete');
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Go"
|
=== "Go"
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ var (
|
|||||||
publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/(publish|send|trigger)$`)
|
publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/(publish|send|trigger)$`)
|
||||||
updatePathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/[-_A-Za-z0-9]{1,64}$`)
|
updatePathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/[-_A-Za-z0-9]{1,64}$`)
|
||||||
clearPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/[-_A-Za-z0-9]{1,64}/(read|clear)$`)
|
clearPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/[-_A-Za-z0-9]{1,64}/(read|clear)$`)
|
||||||
|
deletePathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/[-_A-Za-z0-9]{1,64}/delete$`)
|
||||||
sequenceIDRegex = topicRegex
|
sequenceIDRegex = topicRegex
|
||||||
|
|
||||||
webConfigPath = "/config.js"
|
webConfigPath = "/config.js"
|
||||||
@@ -645,6 +646,8 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
|
|||||||
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublish))(w, r, v)
|
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handlePublish))(w, r, v)
|
||||||
} else if r.Method == http.MethodDelete && updatePathRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodDelete && updatePathRegex.MatchString(r.URL.Path) {
|
||||||
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handleDelete))(w, r, v)
|
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handleDelete))(w, r, v)
|
||||||
|
} else if r.Method == http.MethodGet && deletePathRegex.MatchString(r.URL.Path) {
|
||||||
|
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handleDelete))(w, r, v)
|
||||||
} else if r.Method == http.MethodPut && clearPathRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodPut && clearPathRegex.MatchString(r.URL.Path) {
|
||||||
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handleClear))(w, r, v)
|
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handleClear))(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && publishPathRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && publishPathRegex.MatchString(r.URL.Path) {
|
||||||
|
|||||||
@@ -4026,6 +4026,40 @@ func TestServer_DeleteMessage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_DeleteMessage_GET(t *testing.T) {
|
||||||
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
|
t.Parallel()
|
||||||
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
|
|
||||||
|
// Publish a message with a sequence ID
|
||||||
|
response := request(t, s, "PUT", "/mytopic/seq123", "original message", nil)
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
msg := toMessage(t, response.Body.String())
|
||||||
|
require.Equal(t, "seq123", msg.SequenceID)
|
||||||
|
require.Equal(t, "message", msg.Event)
|
||||||
|
|
||||||
|
// Delete the message using GET method (/topic/seq/delete)
|
||||||
|
response = request(t, s, "GET", "/mytopic/seq123/delete", "", nil)
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
deleteMsg := toMessage(t, response.Body.String())
|
||||||
|
require.Equal(t, "seq123", deleteMsg.SequenceID)
|
||||||
|
require.Equal(t, "message_delete", deleteMsg.Event)
|
||||||
|
|
||||||
|
// Poll and verify both messages are returned
|
||||||
|
response = request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
lines := strings.Split(strings.TrimSpace(response.Body.String()), "\n")
|
||||||
|
require.Equal(t, 2, len(lines))
|
||||||
|
|
||||||
|
msg1 := toMessage(t, lines[0])
|
||||||
|
msg2 := toMessage(t, lines[1])
|
||||||
|
require.Equal(t, "message", msg1.Event)
|
||||||
|
require.Equal(t, "message_delete", msg2.Event)
|
||||||
|
require.Equal(t, "seq123", msg1.SequenceID)
|
||||||
|
require.Equal(t, "seq123", msg2.SequenceID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_ClearMessage(t *testing.T) {
|
func TestServer_ClearMessage(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -4286,6 +4320,41 @@ func TestServer_DeleteScheduledMessage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_DeleteScheduledMessage_GET(t *testing.T) {
|
||||||
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
|
t.Parallel()
|
||||||
|
s := newTestServer(t, newTestConfig(t, databaseURL))
|
||||||
|
|
||||||
|
// Publish a scheduled message (future delivery)
|
||||||
|
response := request(t, s, "PUT", "/mytopic/delete-sched-seq?delay=1h", "scheduled message to delete", nil)
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
msg := toMessage(t, response.Body.String())
|
||||||
|
require.Equal(t, "delete-sched-seq", msg.SequenceID)
|
||||||
|
|
||||||
|
// Verify scheduled message exists
|
||||||
|
response = request(t, s, "GET", "/mytopic/json?poll=1&scheduled=1", "", nil)
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
messages := toMessages(t, response.Body.String())
|
||||||
|
require.Equal(t, 1, len(messages))
|
||||||
|
require.Equal(t, "scheduled message to delete", messages[0].Message)
|
||||||
|
|
||||||
|
// Delete the scheduled message using GET method (/topic/seq/delete)
|
||||||
|
response = request(t, s, "GET", "/mytopic/delete-sched-seq/delete", "", nil)
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
deleteMsg := toMessage(t, response.Body.String())
|
||||||
|
require.Equal(t, "delete-sched-seq", deleteMsg.SequenceID)
|
||||||
|
require.Equal(t, "message_delete", deleteMsg.Event)
|
||||||
|
|
||||||
|
// Verify scheduled message was deleted, only delete event remains
|
||||||
|
response = request(t, s, "GET", "/mytopic/json?poll=1&scheduled=1", "", nil)
|
||||||
|
require.Equal(t, 200, response.Code)
|
||||||
|
messages = toMessages(t, response.Body.String())
|
||||||
|
require.Equal(t, 1, len(messages))
|
||||||
|
require.Equal(t, "message_delete", messages[0].Event)
|
||||||
|
require.Equal(t, "delete-sched-seq", messages[0].SequenceID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_UpdateScheduledMessage_TopicScoped(t *testing.T) {
|
func TestServer_UpdateScheduledMessage_TopicScoped(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|||||||
Reference in New Issue
Block a user