From 477b6920aba025345deca29c12721107879ae9ad Mon Sep 17 00:00:00 2001 From: fabiante Date: Wed, 20 Sep 2023 12:51:22 +0200 Subject: [PATCH 1/5] Add Stat method to ServiceInterface --- app/service.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/service.go b/app/service.go index 2127a97..72be695 100644 --- a/app/service.go +++ b/app/service.go @@ -16,4 +16,15 @@ type ServiceInterface interface { // // ErrBadRequest is returned if the domain already exists. CreateDomain(domain string) error + + // DetermineServiceStats calculates statistics about the service. + // + // This is potentially an expensive operation and should not be called + // frequently. + DetermineServiceStats() (*Stats, error) +} + +type Stats struct { + DomainsTotal int `json:"domains_total"` + PurlsTotal int `json:"purls_total"` } From 25a765d419393f5dba3a8e0d2df9e623ad382437 Mon Sep 17 00:00:00 2001 From: fabiante Date: Wed, 20 Sep 2023 12:51:36 +0200 Subject: [PATCH 2/5] Implement Stat method on Database type --- db/database.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/db/database.go b/db/database.go index a62cecf..5294867 100644 --- a/db/database.go +++ b/db/database.go @@ -101,6 +101,34 @@ func (db *Database) CreateDomain(domain string) error { } } +func (db *Database) DetermineServiceStats() (*app.Stats, error) { + stat := &app.Stats{} + var errs []error + var err error + + stat.DomainsTotal, err = db.countRows("domains", "id") + errs = append(errs, err) + + stat.PurlsTotal, err = db.countRows("purls", "id") + errs = append(errs, err) + + return stat, errors.Join(errs...) +} + +func (db *Database) countRows(table string, column string) (int, error) { + var count int + + found, err := db.db.From("domains").Select(goqu.COUNT(goqu.C("id"))).ScanVal(&count) + switch { + case err != nil: + return 0, fmt.Errorf("failed to count rows of table %s: %w", table, err) + case !found: + return 0, fmt.Errorf("failed to count rows of table %s, no rows found: %w", table, err) + } + + return count, nil +} + const ( pgErrUniqueKeyViolation = "23505" ) From d9568cf21b29c0e5bf9366587e99abb44d92b5f1 Mon Sep 17 00:00:00 2001 From: fabiante Date: Wed, 20 Sep 2023 12:52:07 +0200 Subject: [PATCH 3/5] Add endpoint in openAPI --- api/openapi.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/api/openapi.yml b/api/openapi.yml index 3168aea..debe28f 100644 --- a/api/openapi.yml +++ b/api/openapi.yml @@ -15,7 +15,7 @@ tags: - name: Admin description: Manage PURLs - name: System - description: Endpoints for interacting / managing the overal system + description: Endpoints for interacting / managing the overall system security: [] @@ -31,6 +31,24 @@ paths: 500: description: Service is not healthy + /s/stats/public: + get: + operationId: getPublicStats + summary: Get public stats about this system + tags: [System] + responses: + 200: + description: Returns stats + content: + application/json: + schema: + type: object + properties: + domains_total: + type: integer + purls_total: + type: integer + /r/{domain}/{name}: get: operationId: resolvePURL From ba94095a74b747feafb4481fa675d0891c43145f Mon Sep 17 00:00:00 2001 From: fabiante Date: Wed, 20 Sep 2023 12:52:42 +0200 Subject: [PATCH 4/5] Add stat endpoint --- api/server_routes.go | 2 ++ api/server_stats.go | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 api/server_stats.go diff --git a/api/server_routes.go b/api/server_routes.go index c0524db..8555bbb 100644 --- a/api/server_routes.go +++ b/api/server_routes.go @@ -57,5 +57,7 @@ func SetupRouting(r gin.IRouter, s *Server) { ctx.JSON(http.StatusOK, "service is ready to receive requests") }) + + sys.GET("/stats/public", s.GetPublicStats) } } diff --git a/api/server_stats.go b/api/server_stats.go new file mode 100644 index 0000000..14d8bdd --- /dev/null +++ b/api/server_stats.go @@ -0,0 +1,18 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func (s *Server) GetPublicStats(ctx *gin.Context) { + stats, err := s.service.DetermineServiceStats() + + switch true { + case err == nil: + ctx.JSON(http.StatusOK, stats) + default: + respondWithError(ctx, http.StatusInternalServerError, err) + } +} From 39b47ad09e1fd7f7736bca4e064dc9bdba227533 Mon Sep 17 00:00:00 2001 From: fabiante Date: Wed, 20 Sep 2023 12:53:01 +0200 Subject: [PATCH 5/5] Refactor admin endpoints into separate file --- api/server.go | 39 ------------------------------------ api/server_admin.go | 48 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 39 deletions(-) create mode 100644 api/server_admin.go diff --git a/api/server.go b/api/server.go index 9a5af2f..d996348 100644 --- a/api/server.go +++ b/api/server.go @@ -2,10 +2,8 @@ package api import ( "errors" - "fmt" "net/http" - "github.com/fabiante/persurl/api/res" "github.com/fabiante/persurl/app" "github.com/gin-gonic/gin" ) @@ -34,40 +32,3 @@ func (s *Server) Resolve(ctx *gin.Context) { respondWithError(ctx, http.StatusInternalServerError, err) } } - -func (s *Server) SavePURL(ctx *gin.Context) { - domain := ctx.Param("domain") - name := ctx.Param("name") - - var req res.SavePURL - if err := ctx.BindJSON(&req); err != nil { - ctx.Abort() - return - } - - err := s.service.SavePURL(domain, name, req.Target) - switch true { - case err == nil: - break - case errors.Is(err, app.ErrBadRequest): - respondWithError(ctx, http.StatusBadRequest, err) - default: - respondWithError(ctx, http.StatusInternalServerError, err) - } - - ctx.JSON(http.StatusOK, res.NewSavePURLResponse(fmt.Sprintf("/r/%s/%s", domain, name))) -} - -func (s *Server) CreateDomain(ctx *gin.Context) { - domain := ctx.Param("domain") - - err := s.service.CreateDomain(domain) - switch true { - case err == nil: - ctx.Status(http.StatusNoContent) - case errors.Is(err, app.ErrBadRequest): - respondWithError(ctx, http.StatusBadRequest, err) - default: - respondWithError(ctx, http.StatusInternalServerError, err) - } -} diff --git a/api/server_admin.go b/api/server_admin.go new file mode 100644 index 0000000..de787fa --- /dev/null +++ b/api/server_admin.go @@ -0,0 +1,48 @@ +package api + +import ( + "errors" + "fmt" + "net/http" + + "github.com/fabiante/persurl/api/res" + "github.com/fabiante/persurl/app" + "github.com/gin-gonic/gin" +) + +func (s *Server) SavePURL(ctx *gin.Context) { + domain := ctx.Param("domain") + name := ctx.Param("name") + + var req res.SavePURL + if err := ctx.BindJSON(&req); err != nil { + ctx.Abort() + return + } + + err := s.service.SavePURL(domain, name, req.Target) + switch true { + case err == nil: + break + case errors.Is(err, app.ErrBadRequest): + respondWithError(ctx, http.StatusBadRequest, err) + default: + respondWithError(ctx, http.StatusInternalServerError, err) + } + + ctx.JSON(http.StatusOK, res.NewSavePURLResponse(fmt.Sprintf("/r/%s/%s", domain, name))) +} + +func (s *Server) CreateDomain(ctx *gin.Context) { + domain := ctx.Param("domain") + + err := s.service.CreateDomain(domain) + switch true { + case err == nil: + ctx.Status(http.StatusNoContent) + case errors.Is(err, app.ErrBadRequest): + respondWithError(ctx, http.StatusBadRequest, err) + default: + respondWithError(ctx, http.StatusInternalServerError, err) + } +}