group statistics endpoint

This commit is contained in:
Hayden 2022-11-01 00:06:42 -08:00
parent a886fa86ca
commit 68147e4426
No known key found for this signature in database
GPG key ID: 17CF79474E257545
9 changed files with 184 additions and 23 deletions

View file

@ -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

View file

@ -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)

View file

@ -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": {

View file

@ -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": {

View file

@ -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:

View file

@ -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).

View file

@ -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<GroupStatistics>({
url: route("/groups/statistics"),
});
}
}

View file

@ -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;

View file

@ -1,6 +1,5 @@
<script setup lang="ts">
import { useAuthStore } from "~~/stores/auth";
import { useItemStore } from "~~/stores/items";
import { useLabelStore } from "~~/stores/labels";
import { useLocationStore } from "~~/stores/locations";
@ -16,33 +15,33 @@
const auth = useAuthStore();
const itemsStore = useItemStore();
const items = computed(() => itemsStore.items);
const locationStore = useLocationStore();
const locations = computed(() => locationStore.locations);
const labelsStore = useLabelStore();
const labels = computed(() => labelsStore.labels);
const totalItems = computed(() => items.value?.length || 0);
const totalLocations = computed(() => locations.value?.length || 0);
const totalLabels = computed(() => labels.value?.length || 0);
const { data: statistics } = useAsyncData(async () => {
const { data } = await api.group.statistics();
return data;
});
const stats = [
const stats = computed(() => {
return [
{
label: "Locations",
value: totalLocations,
value: statistics.value?.totalLocations || 0,
},
{
label: "Items",
value: totalItems,
value: statistics.value?.totalItems || 0,
},
{
label: "Labels",
value: totalLabels,
value: statistics.value?.totalLabels || 0,
},
];
});
const importDialog = ref(false);
const importCsv = ref(null);
@ -141,7 +140,7 @@
class="grid grid-cols-1 divide-y divide-base-300 border-t border-base-300 sm:grid-cols-3 sm:divide-y-0 sm:divide-x"
>
<div v-for="stat in stats" :key="stat.label" class="px-6 py-5 text-center text-sm font-medium">
<span class="text-base-900 font-bold">{{ stat.value.value }}</span>
<span class="text-base-900 font-bold">{{ stat.value }}</span>
{{ " " }}
<span class="text-base-600">{{ stat.label }}</span>
</div>