Add modular admin CRUD UI, fix MikroORM migrations/config and update static admin routing

This commit is contained in:
2026-03-18 16:52:05 -03:00
parent 56a926b10b
commit 9e60600a5d
19 changed files with 1921 additions and 563 deletions

View File

@@ -1,12 +1,113 @@
import { Controller, Get } from '@nestjs/common';
import { Body, Controller, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { GiftService } from '../gift/services/gift.service';
import { GuestService } from '../guest/services/guest.service';
import { TodoService } from '../todo/services/todo.service';
import { ModuleRegistryService } from '../core/services/module-registry.service';
const DEFAULT_TENANT = process.env.DEFAULT_TENANT_ID ?? 'default';
@Controller('admin')
export class AdminController {
constructor(private readonly moduleRegistry: ModuleRegistryService) {}
constructor(
private readonly moduleRegistry: ModuleRegistryService,
private readonly guestService: GuestService,
private readonly giftService: GiftService,
private readonly todoService: TodoService,
) {}
@Get('modules')
getModules() {
return this.moduleRegistry.getModules();
}
private getTenantId(tenantId?: string) {
return tenantId ?? DEFAULT_TENANT;
}
// Guests
@Get('guest')
listGuests(@Query('tenantId') tenantId?: string) {
return this.guestService.listGuests(this.getTenantId(tenantId));
}
@Post('guest')
createGuest(
@Query('tenantId') tenantId: string,
@Body() body: any,
) {
return this.guestService.createGuest(this.getTenantId(tenantId), body as any);
}
@Patch('guest/:id/rsvp')
updateGuestRsvp(
@Query('tenantId') tenantId: string,
@Param('id') id: string,
@Body() body: any,
) {
return this.guestService.updateRsvp(this.getTenantId(tenantId), id, body as any);
}
// Todos
@Get('todo')
listTodos(@Query('tenantId') tenantId?: string) {
return this.todoService.listTodos(this.getTenantId(tenantId));
}
@Post('todo')
createTodo(
@Query('tenantId') tenantId: string,
@Body() body: Record<string, any>,
) {
return this.todoService.createTodo(this.getTenantId(tenantId), body);
}
@Patch('todo/:id/complete')
completeTodo(
@Query('tenantId') tenantId: string,
@Param('id') id: string,
) {
return this.todoService.markComplete(this.getTenantId(tenantId), id);
}
// Gifts
@Get('gift')
listGifts(@Query('tenantId') tenantId?: string) {
return this.giftService.listGifts(this.getTenantId(tenantId));
}
@Post('gift')
createGift(
@Query('tenantId') tenantId: string,
@Body() body: any,
) {
return this.giftService.createGift(this.getTenantId(tenantId), body as any);
}
@Get('gift/:id')
getGift(
@Query('tenantId') tenantId: string,
@Param('id') id: string,
) {
return this.giftService.getGiftById(this.getTenantId(tenantId), id);
}
@Post('gift/:id/contribution')
createContribution(
@Query('tenantId') tenantId: string,
@Param('id') id: string,
@Body() body: any,
) {
return this.giftService.createContribution(this.getTenantId(tenantId), {
...(body as any),
giftId: id,
});
}
@Get('gift/:id/contributions')
listContributions(
@Query('tenantId') tenantId: string,
@Param('id') id: string,
) {
return this.giftService.listContributions(this.getTenantId(tenantId), id);
}
}

View File

@@ -1,13 +1,12 @@
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { CoreModule } from '../core/core.module';
import { GiftModule } from '../gift/gift.module';
import { GuestModule } from '../guest/guest.module';
import { TodoModule } from '../todo/todo.module';
import { AdminController } from './admin.controller';
import { ModuleRegistryService } from '../core/services/module-registry.service';
@Module({
imports: [CoreModule, MikroOrmModule.forFeature([])],
imports: [CoreModule, GiftModule, GuestModule, TodoModule],
controllers: [AdminController],
providers: [ModuleRegistryService],
exports: [ModuleRegistryService],
})
export class AdminModule {}

View File

@@ -12,6 +12,10 @@ import { AdminModule } from './admin/admin.module';
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'public'),
serveRoot: '/admin',
exclude: ['/admin/modules'],
serveStaticOptions: {
index: 'admin.html',
},
}),
CoreModule,
GiftModule,

View File

@@ -7,6 +7,13 @@ export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
// Allow the admin UI endpoints to be accessed without authentication.
// The UI is served from /admin and calls /api/admin/modules.
if (request.method === 'GET' && request.url?.startsWith('/api/admin')) {
return true;
}
const authHeader = request.headers['authorization'] || '';
const token = Array.isArray(authHeader) ? authHeader[0] : authHeader;
if (!token) {

View File

@@ -19,7 +19,7 @@ import { NotificationRuleSchema } from './entities/notification-rule.entity';
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
dbName: process.env.DB_NAME || 'planner',
autoLoadEntities: false,
autoLoadEntities: true,
migrations: {
path: 'dist/migrations',
transactional: true,