Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new system info panel in webui settings #926

Merged
merged 12 commits into from
Jun 8, 2024
48 changes: 48 additions & 0 deletions docs/swagger/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,29 @@ const docTemplate = `{
}
}
},
"/api/v1/system/info": {
"get": {
"description": "Get general system information like Shiori version, database, and OS",
"produces": [
"application/json"
],
"tags": [
"system"
],
"summary": "Get general system information",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api_v1.infoResponse"
}
},
"403": {
"description": "Only owners can access this endpoint"
}
}
}
},
"/api/v1/tags": {
"get": {
"produces": [
Expand Down Expand Up @@ -228,6 +251,31 @@ const docTemplate = `{
}
},
"definitions": {
"api_v1.infoResponse": {
"type": "object",
"properties": {
"database": {
"type": "string"
},
"os": {
"type": "string"
},
"version": {
"type": "object",
"properties": {
"commit": {
"type": "string"
},
"date": {
"type": "string"
},
"tag": {
"type": "string"
}
}
}
}
},
"api_v1.loginRequestPayload": {
"type": "object",
"required": [
Expand Down
48 changes: 48 additions & 0 deletions docs/swagger/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,29 @@
}
}
},
"/api/v1/system/info": {
"get": {
"description": "Get general system information like Shiori version, database, and OS",
"produces": [
"application/json"
],
"tags": [
"system"
],
"summary": "Get general system information",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api_v1.infoResponse"
}
},
"403": {
"description": "Only owners can access this endpoint"
}
}
}
},
"/api/v1/tags": {
"get": {
"produces": [
Expand Down Expand Up @@ -217,6 +240,31 @@
}
},
"definitions": {
"api_v1.infoResponse": {
"type": "object",
"properties": {
"database": {
"type": "string"
},
"os": {
"type": "string"
},
"version": {
"type": "object",
"properties": {
"commit": {
"type": "string"
},
"date": {
"type": "string"
},
"tag": {
"type": "string"
}
}
}
}
},
"api_v1.loginRequestPayload": {
"type": "object",
"required": [
Expand Down
32 changes: 32 additions & 0 deletions docs/swagger/swagger.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
definitions:
api_v1.infoResponse:
properties:
database:
type: string
os:
type: string
version:
properties:
commit:
type: string
date:
type: string
tag:
type: string
type: object
type: object
api_v1.loginRequestPayload:
properties:
password:
Expand Down Expand Up @@ -239,6 +255,22 @@ paths:
summary: Get readable version of bookmark.
tags:
- Auth
/api/v1/system/info:
get:
description: Get general system information like Shiori version, database, and
OS
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api_v1.infoResponse'
"403":
description: Only owners can access this endpoint
summary: Get general system information
tags:
- system
/api/v1/tags:
get:
produces:
Expand Down
4 changes: 2 additions & 2 deletions internal/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func Connect(ctx context.Context, dbURL string) (DB, error) {
// DB is interface for accessing and manipulating data in database.
type DB interface {
// DBx is the underlying sqlx.DB
DBx() sqlx.DB
DBx() *sqlx.DB

// Migrate runs migrations for this database
Migrate(ctx context.Context) error
Expand Down Expand Up @@ -117,7 +117,7 @@ type DB interface {
}

type dbbase struct {
sqlx.DB
*sqlx.DB
}

func (db *dbbase) withTx(ctx context.Context, fn func(tx *sqlx.Tx) error) error {
Expand Down
4 changes: 2 additions & 2 deletions internal/database/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ func OpenMySQLDatabase(ctx context.Context, connString string) (mysqlDB *MySQLDa
db.SetMaxOpenConns(100)
db.SetConnMaxLifetime(time.Second) // in case mysql client has longer timeout (driver issue #674)

mysqlDB = &MySQLDatabase{dbbase: dbbase{*db}}
mysqlDB = &MySQLDatabase{dbbase: dbbase{db}}
return mysqlDB, err
}

// DBX returns the underlying sqlx.DB object
func (db *MySQLDatabase) DBx() sqlx.DB {
func (db *MySQLDatabase) DBx() *sqlx.DB {
return db.DB
}

Expand Down
4 changes: 2 additions & 2 deletions internal/database/pg.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ func OpenPGDatabase(ctx context.Context, connString string) (pgDB *PGDatabase, e
db.SetMaxOpenConns(100)
db.SetConnMaxLifetime(time.Second)

pgDB = &PGDatabase{dbbase: dbbase{*db}}
pgDB = &PGDatabase{dbbase: dbbase{db}}
return pgDB, err
}

// DBX returns the underlying sqlx.DB object
func (db *PGDatabase) DBx() sqlx.DB {
func (db *PGDatabase) DBx() *sqlx.DB {
return db.DB
}

Expand Down
2 changes: 1 addition & 1 deletion internal/database/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type tagContent struct {
}

// DBX returns the underlying sqlx.DB object
func (db *SQLiteDatabase) DBx() sqlx.DB {
func (db *SQLiteDatabase) DBx() *sqlx.DB {
return db.DB
}

Expand Down
2 changes: 1 addition & 1 deletion internal/database/sqlite_noncgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ func OpenSQLiteDatabase(ctx context.Context, databasePath string) (sqliteDB *SQL
return nil, errors.WithStack(err)
}

sqliteDB = &SQLiteDatabase{dbbase: dbbase{*db}}
sqliteDB = &SQLiteDatabase{dbbase: dbbase{db}}
return sqliteDB, nil
}
2 changes: 1 addition & 1 deletion internal/database/sqlite_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ func OpenSQLiteDatabase(ctx context.Context, databasePath string) (sqliteDB *SQL
return nil, errors.WithStack(err)
}

sqliteDB = &SQLiteDatabase{dbbase: dbbase{*db}}
sqliteDB = &SQLiteDatabase{dbbase: dbbase{db}}
return sqliteDB, nil
}
1 change: 1 addition & 0 deletions internal/http/routes/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
r.handle(g, "/auth", NewAuthAPIRoutes(r.logger, r.deps, r.loginHandler))
r.handle(g, "/bookmarks", NewBookmarksAPIRoutes(r.logger, r.deps))
r.handle(g, "/tags", NewTagsPIRoutes(r.logger, r.deps))
r.handle(g, "/system", NewSystemAPIRoutes(r.logger, r.deps))

Check warning on line 21 in internal/http/routes/api/v1/api.go

View check run for this annotation

Codecov / codecov/patch

internal/http/routes/api/v1/api.go#L21

Added line #L21 was not covered by tests

return r
}
Expand Down
4 changes: 2 additions & 2 deletions internal/http/routes/api/v1/bookmarks.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type readableResponseMessage struct {
// @Tags Auth
// @securityDefinitions.apikey ApiKeyAuth
// @Produce json
// @Success 200 {object} readableResponseMessage
// @Success 200 {object} readableResponseMessage
// @Failure 403 {object} nil "Token not provided/invalid"
// @Router /api/v1/bookmarks/id/readable [get]
func (r *BookmarksAPIRoutes) bookmarkReadable(c *gin.Context) {
Expand All @@ -120,7 +120,7 @@ func (r *BookmarksAPIRoutes) bookmarkReadable(c *gin.Context) {
// @Summary Update Cache and Ebook on server.
// @Tags Auth
// @securityDefinitions.apikey ApiKeyAuth
// @Param payload body updateCachePayload true "Update Cache Payload"`
// @Param payload body updateCachePayload true "Update Cache Payload"`
// @Produce json
// @Success 200 {object} model.BookmarkDTO
// @Failure 403 {object} nil "Token not provided/invalid"
Expand Down
74 changes: 74 additions & 0 deletions internal/http/routes/api/v1/system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package api_v1

import (
"net/http"
"runtime"

"github.com/gin-gonic/gin"
"github.com/go-shiori/shiori/internal/dependencies"
"github.com/go-shiori/shiori/internal/http/context"
"github.com/go-shiori/shiori/internal/http/middleware"
"github.com/go-shiori/shiori/internal/http/response"
"github.com/go-shiori/shiori/internal/model"
"github.com/sirupsen/logrus"
)

type SystemAPIRoutes struct {
logger *logrus.Logger
deps *dependencies.Dependencies
}

func (r *SystemAPIRoutes) Setup(g *gin.RouterGroup) model.Routes {
g.Use(middleware.AuthenticationRequired())
g.GET("/info", r.infoHandler)
return r
}

type infoResponse struct {
Version struct {
Tag string `json:"tag"`
Commit string `json:"commit"`
Date string `json:"date"`
} `json:"version"`
Database string `json:"database"`
OS string `json:"os"`
}

// System info API endpoint godoc
//
// @Summary Get general system information
// @Description Get general system information like Shiori version, database, and OS
// @Tags system
// @Produce json
// @securityDefinitions.apikey ApiKeyAuth
// @Success 200 {object} infoResponse
// @Failure 403 {object} nil "Only owners can access this endpoint"
// @Router /api/v1/system/info [get]
func (r *SystemAPIRoutes) infoHandler(c *gin.Context) {
ctx := context.NewContextFromGin(c)
if !ctx.GetAccount().Owner {
response.SendError(c, http.StatusForbidden, "Only owners can access this endpoint")
return
}

response.Send(c, 200, infoResponse{
Version: struct {
Tag string `json:"tag"`
Commit string `json:"commit"`
Date string `json:"date"`
}{
Tag: model.BuildVersion,
Commit: model.BuildCommit,
Date: model.BuildDate,
},
Database: r.deps.Database.DBx().DriverName(),
OS: runtime.GOOS + " (" + runtime.GOARCH + ")",
fmartingr marked this conversation as resolved.
Show resolved Hide resolved
})
}

func NewSystemAPIRoutes(logger *logrus.Logger, deps *dependencies.Dependencies) *SystemAPIRoutes {
return &SystemAPIRoutes{
logger: logger,
deps: deps,
}
}
56 changes: 56 additions & 0 deletions internal/http/routes/api/v1/system_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package api_v1

import (
"context"
"net/http"
"testing"

"github.com/go-shiori/shiori/internal/testutil"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)

func TestSystemRoute(t *testing.T) {
logger := logrus.New()
ctx := context.TODO()

t.Run("valid response", func(t *testing.T) {
g := testutil.NewGin()
g.Use(testutil.FakeAdminLoggedInMiddlewware)
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
router := NewSystemAPIRoutes(logger, deps)
router.Setup(g.Group("/"))
w := testutil.PerformRequest(g, http.MethodGet, "/info")
response, err := testutil.NewTestResponseFromReader(w.Body)
require.NoError(t, err)

response.AssertOk(t)
})

t.Run("requires authentication", func(t *testing.T) {
g := testutil.NewGin()
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
router := NewSystemAPIRoutes(logger, deps)
router.Setup(g.Group("/"))
w := testutil.PerformRequest(g, http.MethodGet, "/info")
response, err := testutil.NewTestResponseFromReader(w.Body)
require.NoError(t, err)

response.AssertNotOk(t)
require.Equal(t, http.StatusUnauthorized, w.Result().StatusCode)
})

t.Run("requires admin", func(t *testing.T) {
g := testutil.NewGin()
g.Use(testutil.FakeUserLoggedInMiddlewware)
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
router := NewSystemAPIRoutes(logger, deps)
router.Setup(g.Group("/"))
w := testutil.PerformRequest(g, http.MethodGet, "/info")
response, err := testutil.NewTestResponseFromReader(w.Body)
require.NoError(t, err)

response.AssertNotOk(t)
require.Equal(t, http.StatusForbidden, w.Result().StatusCode)
})
}
Loading
Loading