Skip to content

Commit 65909a1

Browse files
committed
feat: implement global settings management with group support and enhance UI alerts
1 parent e14cce4 commit 65909a1

File tree

8 files changed

+411
-9
lines changed

8 files changed

+411
-9
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ services/backend/tests/.test-context.json
6565
._*.js
6666
._*.json
6767
cookies.txt
68+
cookiejar.txt

services/backend/src/global-settings/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ export class GlobalSettingsInitService {
119119
const exists = await GlobalSettingsService.exists(setting.key);
120120

121121
if (!exists) {
122+
const groupIdForThisSetting = this.getGroupIdForSetting(setting.key);
122123
await GlobalSettingsService.set(setting.key, setting.defaultValue, {
123124
description: setting.description,
124125
encrypted: setting.encrypted,
125-
group_id: undefined // Temporarily set to undefined to avoid foreign key constraint
126+
group_id: groupIdForThisSetting === 'unknown' ? undefined : groupIdForThisSetting // Pass correct group_id
126127
});
127128

128129
result.created++;

services/backend/src/routes/globalSettings/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@ import {
1515
} from './schemas';
1616

1717
export default async function globalSettingsRoute(fastify: FastifyInstance) {
18+
// GET /api/settings/groups - List all groups with their settings (admin only)
19+
fastify.get('/api/settings/groups', {
20+
preHandler: requirePermission('settings.view'),
21+
}, async (request: FastifyRequest, reply: FastifyReply) => {
22+
try {
23+
const groupsWithSettings = await GlobalSettingsService.getAllGroupsWithSettings();
24+
return reply.status(200).send({
25+
success: true,
26+
data: groupsWithSettings,
27+
});
28+
} catch (error) {
29+
fastify.log.error(error, 'Error fetching all global setting groups with settings');
30+
return reply.status(500).send({
31+
success: false,
32+
error: 'Failed to fetch all global setting groups with settings',
33+
});
34+
}
35+
});
36+
1837
// GET /api/settings - List all global settings (admin only)
1938
fastify.get('/api/settings', {
2039
preHandler: requirePermission('settings.view'),

services/backend/src/services/globalSettingsService.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export interface GlobalSettingGroup {
2222
updated_at: Date;
2323
}
2424

25+
export interface GlobalSettingGroupWithSettings extends GlobalSettingGroup {
26+
settings: GlobalSetting[];
27+
}
28+
2529
export interface CreateGlobalSettingInput {
2630
key: string;
2731
value: string;
@@ -353,6 +357,42 @@ export class GlobalSettingsService {
353357
}
354358
}
355359

360+
/**
361+
* Get all groups with their metadata from the globalSettingGroups table.
362+
* Does not include the settings themselves.
363+
*/
364+
static async getAllGroupMetadata(): Promise<GlobalSettingGroup[]> {
365+
const db = getDb();
366+
const schema = getSchema();
367+
try {
368+
const results = await db
369+
.select()
370+
.from(schema.globalSettingGroups)
371+
.orderBy(schema.globalSettingGroups.sort_order, schema.globalSettingGroups.name); // Sort by sort_order, then name
372+
return results as GlobalSettingGroup[];
373+
} catch (error) {
374+
throw new Error(`Failed to get all group metadata: ${error instanceof Error ? error.message : 'Unknown error'}`);
375+
}
376+
}
377+
378+
/**
379+
* Get all groups with their metadata and their associated settings.
380+
*/
381+
static async getAllGroupsWithSettings(): Promise<GlobalSettingGroupWithSettings[]> {
382+
const groupMetadatas = await this.getAllGroupMetadata();
383+
const groupsWithSettings: GlobalSettingGroupWithSettings[] = [];
384+
385+
for (const groupMetadata of groupMetadatas) {
386+
const settings = await this.getByGroup(groupMetadata.id);
387+
groupsWithSettings.push({
388+
...groupMetadata,
389+
settings: settings,
390+
});
391+
}
392+
// The groups are already sorted by getAllGroupMetadata
393+
return groupsWithSettings;
394+
}
395+
356396
/**
357397
* Get a specific group by ID
358398
*/
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { useRoute } from 'vue-router'
4+
import { cn } from '@/lib/utils'
5+
import { Button } from '@/components/ui/button' // Adjusted path assuming shadcn/ui components are in @/components/ui
6+
7+
export interface Setting {
8+
key: string
9+
value: any
10+
description?: string
11+
is_encrypted?: boolean
12+
group_id?: string
13+
// Add other potential fields like 'required', 'type' if known/needed
14+
}
15+
16+
export interface GlobalSettingGroup {
17+
id: string
18+
name: string
19+
description?: string
20+
icon?: string
21+
sort_order?: number
22+
settings?: Setting[]
23+
}
24+
25+
interface NavItem {
26+
title: string
27+
href: string
28+
}
29+
30+
const props = defineProps<{
31+
groups: GlobalSettingGroup[]
32+
}>()
33+
34+
const $route = useRoute()
35+
36+
const sidebarNavItems = computed((): NavItem[] => {
37+
return props.groups.map(group => ({
38+
title: group.name,
39+
href: `/admin/settings/${group.id}`,
40+
})).sort((a, b) => {
41+
// Assuming groups are already sorted by sort_order by the parent or API
42+
// If not, and sort_order is available on GlobalSettingGroup, sort here
43+
return a.title.localeCompare(b.title) // Fallback sort by title
44+
})
45+
})
46+
</script>
47+
48+
<template>
49+
<nav class="flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1">
50+
<Button
51+
v-for="item in sidebarNavItems"
52+
:key="item.title"
53+
as="router-link"
54+
:to="item.href"
55+
variant="ghost"
56+
:class="cn(
57+
'w-full text-left justify-start',
58+
$route.path === item.href && 'bg-muted hover:bg-muted',
59+
)"
60+
>
61+
{{ item.title }}
62+
</Button>
63+
</nav>
64+
</template>

services/frontend/src/i18n/locales/en/globalSettings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@ export default {
22
globalSettings: {
33
title: 'Global Settings',
44
description: 'Manage global application settings',
5+
alerts: {
6+
successTitle: "Success!",
7+
saveSuccess: "Your settings have been saved successfully.",
8+
noChanges: "No changes detected. Settings are up to date."
9+
}
510
},
611
}

services/frontend/src/router/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ const routes = [
7575
},
7676
children: [
7777
{
78-
path: 'settings',
78+
path: 'settings/:groupId?', // Added :groupId? to make it optional
7979
name: 'AdminSettings',
8080
component: () => import('../views/GlobalSettings.vue'),
8181
},

0 commit comments

Comments
 (0)