mirror of
https://github.com/hay-kot/homebox.git
synced 2025-08-04 16:50:27 +00:00
group statistics endpoint
This commit is contained in:
parent
a886fa86ca
commit
68147e4426
9 changed files with 184 additions and 23 deletions
|
@ -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
|
// HandleGroupGet godoc
|
||||||
// @Summary Get the current user's group
|
// @Summary Get the current user's group
|
||||||
// @Tags Group
|
// @Tags Group
|
||||||
|
|
|
@ -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.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), a.mwAuthToken)
|
||||||
|
|
||||||
a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), 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
|
// TODO: I don't like /groups being the URL for users
|
||||||
a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), a.mwAuthToken)
|
a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), a.mwAuthToken)
|
||||||
|
|
|
@ -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": {
|
"/v1/items": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"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": {
|
"repo.GroupUpdate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -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": {
|
"/v1/items": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -1170,6 +1194,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"repo.GroupStatistics": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"totalItems": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"totalLabels": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"totalLocations": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"totalUsers": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repo.GroupUpdate": {
|
"repo.GroupUpdate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -22,6 +22,17 @@ definitions:
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
repo.GroupStatistics:
|
||||||
|
properties:
|
||||||
|
totalItems:
|
||||||
|
type: integer
|
||||||
|
totalLabels:
|
||||||
|
type: integer
|
||||||
|
totalLocations:
|
||||||
|
type: integer
|
||||||
|
totalUsers:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
repo.GroupUpdate:
|
repo.GroupUpdate:
|
||||||
properties:
|
properties:
|
||||||
currency:
|
currency:
|
||||||
|
@ -560,6 +571,20 @@ paths:
|
||||||
summary: Get the current user
|
summary: Get the current user
|
||||||
tags:
|
tags:
|
||||||
- Group
|
- 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:
|
/v1/items:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -41,6 +41,12 @@ type (
|
||||||
Uses int `json:"uses"`
|
Uses int `json:"uses"`
|
||||||
Group Group `json:"group"`
|
Group Group `json:"group"`
|
||||||
}
|
}
|
||||||
|
GroupStatistics struct {
|
||||||
|
TotalUsers int `json:"totalUsers"`
|
||||||
|
TotalItems int `json:"totalItems"`
|
||||||
|
TotalLocations int `json:"totalLocations"`
|
||||||
|
TotalLabels int `json:"totalLabels"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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) {
|
func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group, error) {
|
||||||
return mapToGroupErr(r.db.Group.Create().
|
return mapToGroupErr(r.db.Group.Create().
|
||||||
SetName(name).
|
SetName(name).
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { BaseAPI, route } from "../base";
|
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 {
|
export class GroupApi extends BaseAPI {
|
||||||
createInvitation(data: GroupInvitationCreate) {
|
createInvitation(data: GroupInvitationCreate) {
|
||||||
|
@ -21,4 +21,10 @@ export class GroupApi extends BaseAPI {
|
||||||
url: route("/groups"),
|
url: route("/groups"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statistics() {
|
||||||
|
return this.http.get<GroupStatistics>({
|
||||||
|
url: route("/groups/statistics"),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,13 @@ export interface Group {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GroupStatistics {
|
||||||
|
totalItems: number;
|
||||||
|
totalLabels: number;
|
||||||
|
totalLocations: number;
|
||||||
|
totalUsers: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GroupUpdate {
|
export interface GroupUpdate {
|
||||||
currency: string;
|
currency: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAuthStore } from "~~/stores/auth";
|
import { useAuthStore } from "~~/stores/auth";
|
||||||
import { useItemStore } from "~~/stores/items";
|
|
||||||
import { useLabelStore } from "~~/stores/labels";
|
import { useLabelStore } from "~~/stores/labels";
|
||||||
import { useLocationStore } from "~~/stores/locations";
|
import { useLocationStore } from "~~/stores/locations";
|
||||||
|
|
||||||
|
@ -16,33 +15,33 @@
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
|
|
||||||
const itemsStore = useItemStore();
|
|
||||||
const items = computed(() => itemsStore.items);
|
|
||||||
|
|
||||||
const locationStore = useLocationStore();
|
const locationStore = useLocationStore();
|
||||||
const locations = computed(() => locationStore.locations);
|
const locations = computed(() => locationStore.locations);
|
||||||
|
|
||||||
const labelsStore = useLabelStore();
|
const labelsStore = useLabelStore();
|
||||||
const labels = computed(() => labelsStore.labels);
|
const labels = computed(() => labelsStore.labels);
|
||||||
|
|
||||||
const totalItems = computed(() => items.value?.length || 0);
|
const { data: statistics } = useAsyncData(async () => {
|
||||||
const totalLocations = computed(() => locations.value?.length || 0);
|
const { data } = await api.group.statistics();
|
||||||
const totalLabels = computed(() => labels.value?.length || 0);
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
const stats = [
|
const stats = computed(() => {
|
||||||
{
|
return [
|
||||||
label: "Locations",
|
{
|
||||||
value: totalLocations,
|
label: "Locations",
|
||||||
},
|
value: statistics.value?.totalLocations || 0,
|
||||||
{
|
},
|
||||||
label: "Items",
|
{
|
||||||
value: totalItems,
|
label: "Items",
|
||||||
},
|
value: statistics.value?.totalItems || 0,
|
||||||
{
|
},
|
||||||
label: "Labels",
|
{
|
||||||
value: totalLabels,
|
label: "Labels",
|
||||||
},
|
value: statistics.value?.totalLabels || 0,
|
||||||
];
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
const importDialog = ref(false);
|
const importDialog = ref(false);
|
||||||
const importCsv = ref(null);
|
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"
|
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">
|
<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>
|
<span class="text-base-600">{{ stat.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue