diff --git a/backend/app/api/handlers/v1/v1_ctrl_group.go b/backend/app/api/handlers/v1/v1_ctrl_group.go index a3e8992..b27622d 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_group.go +++ b/backend/app/api/handlers/v1/v1_ctrl_group.go @@ -24,6 +24,26 @@ type ( } ) +// HandleGroupGet godoc +// @Summary Get the current user's group +// @Tags Group +// @Produce json +// @Success 200 {object} repo.GroupStatistics +// @Router /v1/groups/statistics [Get] +// @Security Bearer +func (ctrl *V1Controller) HandleGroupStatistics() server.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) error { + ctx := services.NewContext(r.Context()) + + stats, err := ctrl.repo.Groups.GroupStatistics(ctx, ctx.GID) + if err != nil { + return validate.NewRequestError(err, http.StatusInternalServerError) + } + + return server.Respond(w, http.StatusOK, stats) + } +} + // HandleGroupGet godoc // @Summary Get the current user's group // @Tags Group diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 9074838..992e70e 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -76,6 +76,7 @@ func (a *app) mountRoutes(repos *repo.AllRepos) { a.server.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), a.mwAuthToken) a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), a.mwAuthToken) + a.server.Get(v1Base("/groups/statistics"), v1Ctrl.HandleGroupStatistics(), a.mwAuthToken) // TODO: I don't like /groups being the URL for users a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), a.mwAuthToken) diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index 7d19c80..a4105a1 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -113,6 +113,30 @@ const docTemplate = `{ } } }, + "/v1/groups/statistics": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Group" + ], + "summary": "Get the current user's group", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/repo.GroupStatistics" + } + } + } + } + }, "/v1/items": { "get": { "security": [ @@ -1178,6 +1202,23 @@ const docTemplate = `{ } } }, + "repo.GroupStatistics": { + "type": "object", + "properties": { + "totalItems": { + "type": "integer" + }, + "totalLabels": { + "type": "integer" + }, + "totalLocations": { + "type": "integer" + }, + "totalUsers": { + "type": "integer" + } + } + }, "repo.GroupUpdate": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 64bbf41..911f8f2 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -105,6 +105,30 @@ } } }, + "/v1/groups/statistics": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Group" + ], + "summary": "Get the current user's group", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/repo.GroupStatistics" + } + } + } + } + }, "/v1/items": { "get": { "security": [ @@ -1170,6 +1194,23 @@ } } }, + "repo.GroupStatistics": { + "type": "object", + "properties": { + "totalItems": { + "type": "integer" + }, + "totalLabels": { + "type": "integer" + }, + "totalLocations": { + "type": "integer" + }, + "totalUsers": { + "type": "integer" + } + } + }, "repo.GroupUpdate": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index eb80be3..b4f8c78 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -22,6 +22,17 @@ definitions: updatedAt: type: string type: object + repo.GroupStatistics: + properties: + totalItems: + type: integer + totalLabels: + type: integer + totalLocations: + type: integer + totalUsers: + type: integer + type: object repo.GroupUpdate: properties: currency: @@ -560,6 +571,20 @@ paths: summary: Get the current user tags: - Group + /v1/groups/statistics: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/repo.GroupStatistics' + security: + - Bearer: [] + summary: Get the current user's group + tags: + - Group /v1/items: get: parameters: diff --git a/backend/internal/data/repo/repo_group.go b/backend/internal/data/repo/repo_group.go index fa5b6b8..ea131a8 100644 --- a/backend/internal/data/repo/repo_group.go +++ b/backend/internal/data/repo/repo_group.go @@ -41,6 +41,12 @@ type ( Uses int `json:"uses"` Group Group `json:"group"` } + GroupStatistics struct { + TotalUsers int `json:"totalUsers"` + TotalItems int `json:"totalItems"` + TotalLocations int `json:"totalLocations"` + TotalLabels int `json:"totalLabels"` + } ) var ( @@ -70,6 +76,21 @@ func mapToGroupInvitation(g *ent.GroupInvitationToken) GroupInvitation { } } +func (r *GroupRepository) GroupStatistics(ctx context.Context, GID uuid.UUID) (GroupStatistics, error) { + q := ` + SELECT + (SELECT COUNT(*) FROM users WHERE group_users = ?) AS total_users, + (SELECT COUNT(*) FROM items WHERE group_items = ? AND items.archived = false) AS total_items, + (SELECT COUNT(*) FROM locations WHERE group_locations = ?) AS total_locations, + (SELECT COUNT(*) FROM labels WHERE group_labels = ?) AS total_labels +` + var stats GroupStatistics + + row := r.db.Sql().QueryRowContext(ctx, q, GID, GID, GID, GID) + row.Scan(&stats.TotalUsers, &stats.TotalItems, &stats.TotalLocations, &stats.TotalLabels) + return stats, nil +} + func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group, error) { return mapToGroupErr(r.db.Group.Create(). SetName(name). diff --git a/frontend/lib/api/classes/group.ts b/frontend/lib/api/classes/group.ts index 7468f09..9c8fefa 100644 --- a/frontend/lib/api/classes/group.ts +++ b/frontend/lib/api/classes/group.ts @@ -1,5 +1,5 @@ import { BaseAPI, route } from "../base"; -import { Group, GroupInvitation, GroupInvitationCreate, GroupUpdate } from "../types/data-contracts"; +import { Group, GroupInvitation, GroupInvitationCreate, GroupStatistics, GroupUpdate } from "../types/data-contracts"; export class GroupApi extends BaseAPI { createInvitation(data: GroupInvitationCreate) { @@ -21,4 +21,10 @@ export class GroupApi extends BaseAPI { url: route("/groups"), }); } + + statistics() { + return this.http.get({ + url: route("/groups/statistics"), + }); + } } diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts index e4304e3..7fd055c 100644 --- a/frontend/lib/api/types/data-contracts.ts +++ b/frontend/lib/api/types/data-contracts.ts @@ -24,6 +24,13 @@ export interface Group { updatedAt: Date; } +export interface GroupStatistics { + totalItems: number; + totalLabels: number; + totalLocations: number; + totalUsers: number; +} + export interface GroupUpdate { currency: string; name: string; diff --git a/frontend/pages/home.vue b/frontend/pages/home.vue index 8bf0a54..1adc178 100644 --- a/frontend/pages/home.vue +++ b/frontend/pages/home.vue @@ -1,6 +1,5 @@