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,7 +1,25 @@
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';
export declare class AdminController {
private readonly moduleRegistry;
constructor(moduleRegistry: ModuleRegistryService);
private readonly guestService;
private readonly giftService;
private readonly todoService;
constructor(moduleRegistry: ModuleRegistryService, guestService: GuestService, giftService: GiftService, todoService: TodoService);
getModules(): import("../core/services/module-registry.service").AdminModuleInfo[];
private getTenantId;
listGuests(tenantId?: string): Promise<import("../guest/entities/guest.entity").Guest[]>;
createGuest(tenantId: string, body: any): Promise<import("../guest/entities/guest.entity").Guest>;
updateGuestRsvp(tenantId: string, id: string, body: any): Promise<import("../guest/entities/guest.entity").Guest | null>;
listTodos(tenantId?: string): Promise<import("../todo/entities/todo.entity").TodoItem[]>;
createTodo(tenantId: string, body: Record<string, any>): Promise<import("../todo/entities/todo.entity").TodoItem>;
completeTodo(tenantId: string, id: string): Promise<import("../todo/entities/todo.entity").TodoItem | null>;
listGifts(tenantId?: string): Promise<import("../gift/entities/gift.entity").Gift[]>;
createGift(tenantId: string, body: any): Promise<import("../gift/entities/gift.entity").Gift>;
getGift(tenantId: string, id: string): Promise<import("../gift/entities/gift.entity").Gift | null>;
createContribution(tenantId: string, id: string, body: any): Promise<import("../gift/entities/gift.entity").GiftContribution>;
listContributions(tenantId: string, id: string): Promise<import("../gift/entities/gift.entity").GiftContribution[]>;
}
//# sourceMappingURL=admin.controller.d.ts.map

View File

@@ -1 +1 @@
{"version":3,"file":"admin.controller.d.ts","sourceRoot":"","sources":["../../src/admin/admin.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAEjF,qBACa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,cAAc;gBAAd,cAAc,EAAE,qBAAqB;IAGlE,UAAU;CAGX"}
{"version":3,"file":"admin.controller.d.ts","sourceRoot":"","sources":["../../src/admin/admin.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAIjF,qBACa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAHX,cAAc,EAAE,qBAAqB,EACrC,YAAY,EAAE,YAAY,EAC1B,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,WAAW;IAI3C,UAAU;IAIV,OAAO,CAAC,WAAW;IAMnB,UAAU,CAAoB,QAAQ,CAAC,EAAE,MAAM;IAK/C,WAAW,CACU,QAAQ,EAAE,MAAM,EAC3B,IAAI,EAAE,GAAG;IAMnB,eAAe,CACM,QAAQ,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACf,IAAI,EAAE,GAAG;IAOnB,SAAS,CAAoB,QAAQ,CAAC,EAAE,MAAM;IAK9C,UAAU,CACW,QAAQ,EAAE,MAAM,EAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAMnC,YAAY,CACS,QAAQ,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM;IAOzB,SAAS,CAAoB,QAAQ,CAAC,EAAE,MAAM;IAK9C,UAAU,CACW,QAAQ,EAAE,MAAM,EAC3B,IAAI,EAAE,GAAG;IAMnB,OAAO,CACc,QAAQ,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM;IAMzB,kBAAkB,CACG,QAAQ,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,EACf,IAAI,EAAE,GAAG;IASnB,iBAAiB,CACI,QAAQ,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM;CAI1B"}

View File

@@ -8,17 +8,69 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminController = void 0;
const common_1 = require("@nestjs/common");
const gift_service_1 = require("../gift/services/gift.service");
const guest_service_1 = require("../guest/services/guest.service");
const todo_service_1 = require("../todo/services/todo.service");
const module_registry_service_1 = require("../core/services/module-registry.service");
const DEFAULT_TENANT = process.env.DEFAULT_TENANT_ID ?? 'default';
let AdminController = class AdminController {
constructor(moduleRegistry) {
constructor(moduleRegistry, guestService, giftService, todoService) {
this.moduleRegistry = moduleRegistry;
this.guestService = guestService;
this.giftService = giftService;
this.todoService = todoService;
}
getModules() {
return this.moduleRegistry.getModules();
}
getTenantId(tenantId) {
return tenantId ?? DEFAULT_TENANT;
}
// Guests
listGuests(tenantId) {
return this.guestService.listGuests(this.getTenantId(tenantId));
}
createGuest(tenantId, body) {
return this.guestService.createGuest(this.getTenantId(tenantId), body);
}
updateGuestRsvp(tenantId, id, body) {
return this.guestService.updateRsvp(this.getTenantId(tenantId), id, body);
}
// Todos
listTodos(tenantId) {
return this.todoService.listTodos(this.getTenantId(tenantId));
}
createTodo(tenantId, body) {
return this.todoService.createTodo(this.getTenantId(tenantId), body);
}
completeTodo(tenantId, id) {
return this.todoService.markComplete(this.getTenantId(tenantId), id);
}
// Gifts
listGifts(tenantId) {
return this.giftService.listGifts(this.getTenantId(tenantId));
}
createGift(tenantId, body) {
return this.giftService.createGift(this.getTenantId(tenantId), body);
}
getGift(tenantId, id) {
return this.giftService.getGiftById(this.getTenantId(tenantId), id);
}
createContribution(tenantId, id, body) {
return this.giftService.createContribution(this.getTenantId(tenantId), {
...body,
giftId: id,
});
}
listContributions(tenantId, id) {
return this.giftService.listContributions(this.getTenantId(tenantId), id);
}
};
exports.AdminController = AdminController;
__decorate([
@@ -27,7 +79,97 @@ __decorate([
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], AdminController.prototype, "getModules", null);
__decorate([
(0, common_1.Get)('guest'),
__param(0, (0, common_1.Query)('tenantId')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "listGuests", null);
__decorate([
(0, common_1.Post)('guest'),
__param(0, (0, common_1.Query)('tenantId')),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "createGuest", null);
__decorate([
(0, common_1.Patch)('guest/:id/rsvp'),
__param(0, (0, common_1.Query)('tenantId')),
__param(1, (0, common_1.Param)('id')),
__param(2, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, Object]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "updateGuestRsvp", null);
__decorate([
(0, common_1.Get)('todo'),
__param(0, (0, common_1.Query)('tenantId')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "listTodos", null);
__decorate([
(0, common_1.Post)('todo'),
__param(0, (0, common_1.Query)('tenantId')),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "createTodo", null);
__decorate([
(0, common_1.Patch)('todo/:id/complete'),
__param(0, (0, common_1.Query)('tenantId')),
__param(1, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "completeTodo", null);
__decorate([
(0, common_1.Get)('gift'),
__param(0, (0, common_1.Query)('tenantId')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "listGifts", null);
__decorate([
(0, common_1.Post)('gift'),
__param(0, (0, common_1.Query)('tenantId')),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "createGift", null);
__decorate([
(0, common_1.Get)('gift/:id'),
__param(0, (0, common_1.Query)('tenantId')),
__param(1, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "getGift", null);
__decorate([
(0, common_1.Post)('gift/:id/contribution'),
__param(0, (0, common_1.Query)('tenantId')),
__param(1, (0, common_1.Param)('id')),
__param(2, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, Object]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "createContribution", null);
__decorate([
(0, common_1.Get)('gift/:id/contributions'),
__param(0, (0, common_1.Query)('tenantId')),
__param(1, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", void 0)
], AdminController.prototype, "listContributions", null);
exports.AdminController = AdminController = __decorate([
(0, common_1.Controller)('admin'),
__metadata("design:paramtypes", [module_registry_service_1.ModuleRegistryService])
__metadata("design:paramtypes", [module_registry_service_1.ModuleRegistryService,
guest_service_1.GuestService,
gift_service_1.GiftService,
todo_service_1.TodoService])
], AdminController);

View File

@@ -1 +1 @@
{"version":3,"file":"admin.module.d.ts","sourceRoot":"","sources":["../../src/admin/admin.module.ts"],"names":[],"mappings":"AAMA,qBAMa,WAAW;CAAG"}
{"version":3,"file":"admin.module.d.ts","sourceRoot":"","sources":["../../src/admin/admin.module.ts"],"names":[],"mappings":"AAOA,qBAIa,WAAW;CAAG"}

View File

@@ -8,18 +8,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminModule = void 0;
const common_1 = require("@nestjs/common");
const nestjs_1 = require("@mikro-orm/nestjs");
const core_module_1 = require("../core/core.module");
const gift_module_1 = require("../gift/gift.module");
const guest_module_1 = require("../guest/guest.module");
const todo_module_1 = require("../todo/todo.module");
const admin_controller_1 = require("./admin.controller");
const module_registry_service_1 = require("../core/services/module-registry.service");
let AdminModule = class AdminModule {
};
exports.AdminModule = AdminModule;
exports.AdminModule = AdminModule = __decorate([
(0, common_1.Module)({
imports: [core_module_1.CoreModule, nestjs_1.MikroOrmModule.forFeature([])],
imports: [core_module_1.CoreModule, gift_module_1.GiftModule, guest_module_1.GuestModule, todo_module_1.TodoModule],
controllers: [admin_controller_1.AdminController],
providers: [module_registry_service_1.ModuleRegistryService],
exports: [module_registry_service_1.ModuleRegistryService],
})
], AdminModule);

View File

@@ -1 +1 @@
{"version":3,"file":"app.module.d.ts","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":"AASA,qBAaa,SAAS;CAAG"}
{"version":3,"file":"app.module.d.ts","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":"AASA,qBAiBa,SAAS;CAAG"}

4
dist/app.module.js vendored
View File

@@ -24,6 +24,10 @@ exports.AppModule = AppModule = __decorate([
serve_static_1.ServeStaticModule.forRoot({
rootPath: (0, path_1.join)(__dirname, '..', 'public'),
serveRoot: '/admin',
exclude: ['/admin/modules'],
serveStaticOptions: {
index: 'admin.html',
},
}),
core_module_1.CoreModule,
gift_module_1.GiftModule,

View File

@@ -1 +1 @@
{"version":3,"file":"auth.guard.d.ts","sourceRoot":"","sources":["../../src/core/auth.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAqC,MAAM,gBAAgB,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,qBACa,SAAU,YAAW,WAAW;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAErD,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO;CAgBhD"}
{"version":3,"file":"auth.guard.d.ts","sourceRoot":"","sources":["../../src/core/auth.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAqC,MAAM,gBAAgB,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,qBACa,SAAU,YAAW,WAAW;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAErD,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO;CAuBhD"}

View File

@@ -18,6 +18,11 @@ let AuthGuard = class AuthGuard {
}
canActivate(context) {
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

@@ -31,7 +31,7 @@ exports.CoreModule = CoreModule = __decorate([
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,