Version API endpoint
This commit is contained in:
@@ -1686,6 +1686,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
|||||||
**Features:**
|
**Features:**
|
||||||
|
|
||||||
* Server: Support templating in the priority field ([#1426](https://github.com/binwiederhier/ntfy/issues/1426), thanks to [@seantomburke](https://github.com/seantomburke) for reporting)
|
* Server: Support templating in the priority field ([#1426](https://github.com/binwiederhier/ntfy/issues/1426), thanks to [@seantomburke](https://github.com/seantomburke) for reporting)
|
||||||
|
* Server: Add admin-only `GET /v1/version` endpoint returning server version, build commit, and date ([#1599](https://github.com/binwiederhier/ntfy/issues/1599), thanks to [@crivchri](https://github.com/crivchri) for reporting)
|
||||||
* Web: Show red notification dot on favicon when there are unread messages ([#1017](https://github.com/binwiederhier/ntfy/issues/1017), thanks to [@ad-si](https://github.com/ad-si) for reporting)
|
* Web: Show red notification dot on favicon when there are unread messages ([#1017](https://github.com/binwiederhier/ntfy/issues/1017), thanks to [@ad-si](https://github.com/ad-si) for reporting)
|
||||||
|
|
||||||
**Bug fixes + maintenance:**
|
**Bug fixes + maintenance:**
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ var (
|
|||||||
matrixPushPath = "/_matrix/push/v1/notify"
|
matrixPushPath = "/_matrix/push/v1/notify"
|
||||||
metricsPath = "/metrics"
|
metricsPath = "/metrics"
|
||||||
apiHealthPath = "/v1/health"
|
apiHealthPath = "/v1/health"
|
||||||
|
apiVersionPath = "/v1/version"
|
||||||
apiConfigPath = "/v1/config"
|
apiConfigPath = "/v1/config"
|
||||||
apiStatsPath = "/v1/stats"
|
apiStatsPath = "/v1/stats"
|
||||||
apiWebPushPath = "/v1/webpush"
|
apiWebPushPath = "/v1/webpush"
|
||||||
@@ -467,6 +468,8 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
|
|||||||
return s.ensureWebEnabled(s.handleEmpty)(w, r, v)
|
return s.ensureWebEnabled(s.handleEmpty)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == apiHealthPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == apiHealthPath {
|
||||||
return s.handleHealth(w, r, v)
|
return s.handleHealth(w, r, v)
|
||||||
|
} else if r.Method == http.MethodGet && r.URL.Path == apiVersionPath {
|
||||||
|
return s.ensureAdmin(s.handleVersion)(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == apiConfigPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == apiConfigPath {
|
||||||
return s.handleConfig(w, r, v)
|
return s.handleConfig(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
|
} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s *Server) handleVersion(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
return s.writeJSON(w, &apiVersionResponse{
|
||||||
|
Version: s.config.BuildVersion,
|
||||||
|
Commit: s.config.BuildCommit,
|
||||||
|
Date: s.config.BuildDate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) handleUsersGet(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
func (s *Server) handleUsersGet(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
users, err := s.userManager.Users()
|
users, err := s.userManager.Users()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/v2/user"
|
"heckel.io/ntfy/v2/user"
|
||||||
"heckel.io/ntfy/v2/util"
|
"heckel.io/ntfy/v2/util"
|
||||||
@@ -9,6 +10,41 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestVersion_Admin(t *testing.T) {
|
||||||
|
c := newTestConfigWithAuthFile(t)
|
||||||
|
c.BuildVersion = "1.2.3"
|
||||||
|
c.BuildCommit = "abcdef0"
|
||||||
|
c.BuildDate = "2026-02-08T00:00:00Z"
|
||||||
|
s := newTestServer(t, c)
|
||||||
|
defer s.closeDatabases()
|
||||||
|
|
||||||
|
// Create admin and regular user
|
||||||
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
|
||||||
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
|
||||||
|
|
||||||
|
// Admin can access /v1/version
|
||||||
|
rr := request(t, s, "GET", "/v1/version", "", map[string]string{
|
||||||
|
"Authorization": util.BasicAuth("phil", "phil"),
|
||||||
|
})
|
||||||
|
require.Equal(t, 200, rr.Code)
|
||||||
|
|
||||||
|
var versionResponse apiVersionResponse
|
||||||
|
require.Nil(t, json.NewDecoder(rr.Body).Decode(&versionResponse))
|
||||||
|
require.Equal(t, "1.2.3", versionResponse.Version)
|
||||||
|
require.Equal(t, "abcdef0", versionResponse.Commit)
|
||||||
|
require.Equal(t, "2026-02-08T00:00:00Z", versionResponse.Date)
|
||||||
|
|
||||||
|
// Non-admin user cannot access /v1/version
|
||||||
|
rr = request(t, s, "GET", "/v1/version", "", map[string]string{
|
||||||
|
"Authorization": util.BasicAuth("ben", "ben"),
|
||||||
|
})
|
||||||
|
require.Equal(t, 401, rr.Code)
|
||||||
|
|
||||||
|
// Unauthenticated user cannot access /v1/version
|
||||||
|
rr = request(t, s, "GET", "/v1/version", "", nil)
|
||||||
|
require.Equal(t, 401, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUser_AddRemove(t *testing.T) {
|
func TestUser_AddRemove(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
||||||
defer s.closeDatabases()
|
defer s.closeDatabases()
|
||||||
|
|||||||
@@ -319,6 +319,12 @@ type apiHealthResponse struct {
|
|||||||
Healthy bool `json:"healthy"`
|
Healthy bool `json:"healthy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type apiVersionResponse struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Commit string `json:"commit"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
type apiStatsResponse struct {
|
type apiStatsResponse struct {
|
||||||
Messages int64 `json:"messages"`
|
Messages int64 `json:"messages"`
|
||||||
MessagesRate float64 `json:"messages_rate"` // Average number of messages per second
|
MessagesRate float64 `json:"messages_rate"` // Average number of messages per second
|
||||||
|
|||||||
Reference in New Issue
Block a user