Add modular admin CRUD UI, fix MikroORM migrations/config and update static admin routing
This commit is contained in:
20
dist/admin/admin.controller.d.ts
vendored
20
dist/admin/admin.controller.d.ts
vendored
@@ -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';
|
import { ModuleRegistryService } from '../core/services/module-registry.service';
|
||||||
export declare class AdminController {
|
export declare class AdminController {
|
||||||
private readonly moduleRegistry;
|
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[];
|
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
|
//# sourceMappingURL=admin.controller.d.ts.map
|
||||||
2
dist/admin/admin.controller.d.ts.map
vendored
2
dist/admin/admin.controller.d.ts.map
vendored
@@ -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"}
|
||||||
146
dist/admin/admin.controller.js
vendored
146
dist/admin/admin.controller.js
vendored
@@ -8,17 +8,69 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|||||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(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 });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.AdminController = void 0;
|
exports.AdminController = void 0;
|
||||||
const common_1 = require("@nestjs/common");
|
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 module_registry_service_1 = require("../core/services/module-registry.service");
|
||||||
|
const DEFAULT_TENANT = process.env.DEFAULT_TENANT_ID ?? 'default';
|
||||||
let AdminController = class AdminController {
|
let AdminController = class AdminController {
|
||||||
constructor(moduleRegistry) {
|
constructor(moduleRegistry, guestService, giftService, todoService) {
|
||||||
this.moduleRegistry = moduleRegistry;
|
this.moduleRegistry = moduleRegistry;
|
||||||
|
this.guestService = guestService;
|
||||||
|
this.giftService = giftService;
|
||||||
|
this.todoService = todoService;
|
||||||
}
|
}
|
||||||
getModules() {
|
getModules() {
|
||||||
return this.moduleRegistry.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;
|
exports.AdminController = AdminController;
|
||||||
__decorate([
|
__decorate([
|
||||||
@@ -27,7 +79,97 @@ __decorate([
|
|||||||
__metadata("design:paramtypes", []),
|
__metadata("design:paramtypes", []),
|
||||||
__metadata("design:returntype", void 0)
|
__metadata("design:returntype", void 0)
|
||||||
], AdminController.prototype, "getModules", null);
|
], 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([
|
exports.AdminController = AdminController = __decorate([
|
||||||
(0, common_1.Controller)('admin'),
|
(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);
|
], AdminController);
|
||||||
|
|||||||
2
dist/admin/admin.module.d.ts.map
vendored
2
dist/admin/admin.module.d.ts.map
vendored
@@ -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"}
|
||||||
9
dist/admin/admin.module.js
vendored
9
dist/admin/admin.module.js
vendored
@@ -8,18 +8,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.AdminModule = void 0;
|
exports.AdminModule = void 0;
|
||||||
const common_1 = require("@nestjs/common");
|
const common_1 = require("@nestjs/common");
|
||||||
const nestjs_1 = require("@mikro-orm/nestjs");
|
|
||||||
const core_module_1 = require("../core/core.module");
|
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 admin_controller_1 = require("./admin.controller");
|
||||||
const module_registry_service_1 = require("../core/services/module-registry.service");
|
|
||||||
let AdminModule = class AdminModule {
|
let AdminModule = class AdminModule {
|
||||||
};
|
};
|
||||||
exports.AdminModule = AdminModule;
|
exports.AdminModule = AdminModule;
|
||||||
exports.AdminModule = AdminModule = __decorate([
|
exports.AdminModule = AdminModule = __decorate([
|
||||||
(0, common_1.Module)({
|
(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],
|
controllers: [admin_controller_1.AdminController],
|
||||||
providers: [module_registry_service_1.ModuleRegistryService],
|
|
||||||
exports: [module_registry_service_1.ModuleRegistryService],
|
|
||||||
})
|
})
|
||||||
], AdminModule);
|
], AdminModule);
|
||||||
|
|||||||
2
dist/app.module.d.ts.map
vendored
2
dist/app.module.d.ts.map
vendored
@@ -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
4
dist/app.module.js
vendored
@@ -24,6 +24,10 @@ exports.AppModule = AppModule = __decorate([
|
|||||||
serve_static_1.ServeStaticModule.forRoot({
|
serve_static_1.ServeStaticModule.forRoot({
|
||||||
rootPath: (0, path_1.join)(__dirname, '..', 'public'),
|
rootPath: (0, path_1.join)(__dirname, '..', 'public'),
|
||||||
serveRoot: '/admin',
|
serveRoot: '/admin',
|
||||||
|
exclude: ['/admin/modules'],
|
||||||
|
serveStaticOptions: {
|
||||||
|
index: 'admin.html',
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
core_module_1.CoreModule,
|
core_module_1.CoreModule,
|
||||||
gift_module_1.GiftModule,
|
gift_module_1.GiftModule,
|
||||||
|
|||||||
2
dist/core/auth.guard.d.ts.map
vendored
2
dist/core/auth.guard.d.ts.map
vendored
@@ -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"}
|
||||||
5
dist/core/auth.guard.js
vendored
5
dist/core/auth.guard.js
vendored
@@ -18,6 +18,11 @@ let AuthGuard = class AuthGuard {
|
|||||||
}
|
}
|
||||||
canActivate(context) {
|
canActivate(context) {
|
||||||
const request = context.switchToHttp().getRequest();
|
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 authHeader = request.headers['authorization'] || '';
|
||||||
const token = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
const token = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
|||||||
2
dist/core/core.module.js
vendored
2
dist/core/core.module.js
vendored
@@ -31,7 +31,7 @@ exports.CoreModule = CoreModule = __decorate([
|
|||||||
user: process.env.DB_USER || 'postgres',
|
user: process.env.DB_USER || 'postgres',
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
password: process.env.DB_PASSWORD || 'postgres',
|
||||||
dbName: process.env.DB_NAME || 'planner',
|
dbName: process.env.DB_NAME || 'planner',
|
||||||
autoLoadEntities: false,
|
autoLoadEntities: true,
|
||||||
migrations: {
|
migrations: {
|
||||||
path: 'dist/migrations',
|
path: 'dist/migrations',
|
||||||
transactional: true,
|
transactional: true,
|
||||||
|
|||||||
18
mikro-orm.config.js
Normal file
18
mikro-orm.config.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const { PostgreSqlDriver } = require('@mikro-orm/postgresql');
|
||||||
|
|
||||||
|
/** @type {import('@mikro-orm/core').MikroORMOptions} */
|
||||||
|
module.exports = {
|
||||||
|
driver: PostgreSqlDriver,
|
||||||
|
host: process.env.DB_HOST || 'localhost',
|
||||||
|
port: Number(process.env.DB_PORT) || 5432,
|
||||||
|
user: process.env.DB_USER || 'postgres',
|
||||||
|
password: process.env.DB_PASSWORD || 'postgres',
|
||||||
|
dbName: process.env.DB_NAME || 'planner',
|
||||||
|
autoLoadEntities: true,
|
||||||
|
entities: ['./dist/**/*.entity.js'],
|
||||||
|
migrations: {
|
||||||
|
path: './dist/migrations',
|
||||||
|
transactional: true,
|
||||||
|
emit: 'js',
|
||||||
|
},
|
||||||
|
};
|
||||||
1831
package-lock.json
generated
1831
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -5,19 +5,23 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node -r tsconfig-paths/register src/main.ts",
|
"start": "ts-node -r tsconfig-paths/register src/main.ts",
|
||||||
"start:dev": "npx nodemon --watch src --ext ts --exec \"npm run build && node dist/main.js\"",
|
"start:dev": "npx nodemon --watch src --ext ts --exec \"npm run build && node dist/main.js\"",
|
||||||
"build": "tsc -p tsconfig.build.json"
|
"build": "tsc -p tsconfig.build.json",
|
||||||
|
"migration:generate": "mikro-orm migration:create",
|
||||||
|
"migration:run": "mikro-orm migration:up",
|
||||||
|
"migration:down": "mikro-orm migration:down"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mikro-orm/core": "^7.0.2",
|
"@mikro-orm/core": "^7.0.3",
|
||||||
"@mikro-orm/decorators": "^7.0.2",
|
"@mikro-orm/decorators": "^7.0.3",
|
||||||
|
"@mikro-orm/migrations": "^7.0.3",
|
||||||
"@mikro-orm/nestjs": "^7.0.1",
|
"@mikro-orm/nestjs": "^7.0.1",
|
||||||
"@mikro-orm/postgresql": "^7.0.2",
|
"@mikro-orm/postgresql": "^7.0.3",
|
||||||
"@nestjs/common": "^11.1.17",
|
"@nestjs/common": "^11.1.17",
|
||||||
"@nestjs/core": "^11.1.17",
|
"@nestjs/core": "^11.1.17",
|
||||||
"@nestjs/event-emitter": "^3.0.1",
|
"@nestjs/event-emitter": "^3.0.1",
|
||||||
"@nestjs/platform-express": "^11.1.17",
|
"@nestjs/platform-express": "^11.1.17",
|
||||||
"@nestjs/serve-static": "^5.0.4",
|
"@nestjs/serve-static": "^5.0.4",
|
||||||
"express": "^4.18.0",
|
"express": "^5.2.1",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"pg": "^8.20.0",
|
"pg": "^8.20.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
@@ -25,6 +29,9 @@
|
|||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@mikro-orm/cli": "^7.0.3",
|
||||||
|
"@swc-node/register": "^1.11.1",
|
||||||
|
"@swc/core": "^1.15.18",
|
||||||
"@types/express": "^4.17.0",
|
"@types/express": "^4.17.0",
|
||||||
"@types/jsonwebtoken": "^9.0.0",
|
"@types/jsonwebtoken": "^9.0.0",
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
|
|||||||
@@ -33,17 +33,54 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>Admin Panel</h1>
|
<h1>Admin Panel</h1>
|
||||||
<p>Modular admin UI: cada módulo se registra automáticamente.</p>
|
<p>Modular admin UI: cada módulo se registra automáticamente.</p>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Tenant ID:
|
||||||
|
<input id="tenantId" value="default" />
|
||||||
|
<button id="refresh" style="margin-left: 0.5rem;">Actualizar módulos</button>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div id="modules"></div>
|
<div id="modules"></div>
|
||||||
|
|
||||||
|
<div id="module-detail"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
async function loadModules() {
|
const apiRoot = '/api/admin';
|
||||||
const res = await fetch('/admin/modules');
|
|
||||||
const modules = await res.json();
|
const tenantInput = document.getElementById('tenantId');
|
||||||
const container = document.getElementById('modules');
|
const modulesContainer = document.getElementById('modules');
|
||||||
container.innerHTML = '';
|
const detailContainer = document.getElementById('module-detail');
|
||||||
|
|
||||||
|
function withTenant(url) {
|
||||||
|
const tenantId = tenantInput.value || 'default';
|
||||||
|
const sep = url.includes('?') ? '&' : '?';
|
||||||
|
return `${url}${sep}tenantId=${encodeURIComponent(tenantId)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function api(path, options = {}) {
|
||||||
|
const res = await fetch(withTenant(`${apiRoot}${path}`), options);
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = data?.message ? data.message : res.statusText;
|
||||||
|
throw new Error(err || `HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatJson(value) {
|
||||||
|
return `<pre style="white-space: pre-wrap; word-break: break-word;">${JSON.stringify(value, null, 2)}</pre>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(err) {
|
||||||
|
detailContainer.innerHTML = `<div class="module"><strong>Error:</strong> ${err.message || err}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderModules(modules) {
|
||||||
|
modulesContainer.innerHTML = '';
|
||||||
|
detailContainer.innerHTML = '';
|
||||||
|
|
||||||
if (!modules.length) {
|
if (!modules.length) {
|
||||||
container.innerHTML = '<p>No hay módulos registrados aún.</p>';
|
modulesContainer.innerHTML = '<p>No hay módulos registrados aún.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,13 +91,253 @@
|
|||||||
title.textContent = mod.name;
|
title.textContent = mod.name;
|
||||||
const meta = document.createElement('p');
|
const meta = document.createElement('p');
|
||||||
meta.innerHTML = `<span class="badge">${mod.key}</span> ${mod.description || ''}`;
|
meta.innerHTML = `<span class="badge">${mod.key}</span> ${mod.description || ''}`;
|
||||||
const route = document.createElement('p');
|
const button = document.createElement('button');
|
||||||
route.textContent = mod.routePrefix ? `Route: ${mod.routePrefix}` : 'Sin ruta definida';
|
button.textContent = 'Administrar';
|
||||||
card.append(title, meta, route);
|
button.onclick = () => showModule(mod.key);
|
||||||
container.appendChild(card);
|
card.append(title, meta, button);
|
||||||
|
modulesContainer.appendChild(card);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadModules() {
|
||||||
|
modulesContainer.innerHTML = '<p>Cargando módulos...</p>';
|
||||||
|
try {
|
||||||
|
const modules = await api('/modules');
|
||||||
|
renderModules(modules);
|
||||||
|
} catch (err) {
|
||||||
|
showError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createButton(label, onClick) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.textContent = label;
|
||||||
|
btn.style.marginRight = '0.5rem';
|
||||||
|
btn.onclick = onClick;
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInput(name, placeholder = '', type = 'text') {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.name = name;
|
||||||
|
input.placeholder = placeholder;
|
||||||
|
input.type = type;
|
||||||
|
input.style.marginRight = '0.5rem';
|
||||||
|
input.style.marginBottom = '0.5rem';
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showModule(key) {
|
||||||
|
detailContainer.innerHTML = `<div class="module"><h2>Administrar: ${key}</h2><p>Cargando...</p></div>`;
|
||||||
|
|
||||||
|
if (key === 'guest') {
|
||||||
|
return showGuestAdmin();
|
||||||
|
}
|
||||||
|
if (key === 'todo') {
|
||||||
|
return showTodoAdmin();
|
||||||
|
}
|
||||||
|
if (key === 'gift') {
|
||||||
|
return showGiftAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
detailContainer.innerHTML = `<div class="module"><p>No hay operaciones definidas para este módulo.</p></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showGuestAdmin() {
|
||||||
|
detailContainer.innerHTML = '';
|
||||||
|
const panel = document.createElement('div');
|
||||||
|
panel.className = 'module';
|
||||||
|
panel.innerHTML = '<h3>Invitados</h3>';
|
||||||
|
|
||||||
|
const listBtn = createButton('Listar invitados', async () => {
|
||||||
|
try {
|
||||||
|
const guests = await api('/guest');
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(guests);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const createForm = document.createElement('div');
|
||||||
|
createForm.innerHTML = '<strong>Crear invitado</strong><br/>';
|
||||||
|
const nameInput = createInput('name', 'Nombre');
|
||||||
|
const emailInput = createInput('email', 'Email');
|
||||||
|
const phoneInput = createInput('phone', 'Teléfono');
|
||||||
|
const createBtn = createButton('Crear', async () => {
|
||||||
|
try {
|
||||||
|
const guest = await api('/guest', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: nameInput.value,
|
||||||
|
email: emailInput.value,
|
||||||
|
phone: phoneInput.value,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(guest);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rsvpForm = document.createElement('div');
|
||||||
|
rsvpForm.innerHTML = '<strong>Actualizar RSVP</strong><br/>';
|
||||||
|
const idInput = createInput('id', 'ID del invitado');
|
||||||
|
const rsvpInput = createInput('rsvp', 'true/false');
|
||||||
|
const updateBtn = createButton('Actualizar', async () => {
|
||||||
|
try {
|
||||||
|
const updated = await api(`/guest/${idInput.value}/rsvp`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ rsvp: rsvpInput.value === 'true' }),
|
||||||
|
});
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(updated);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createForm.append(nameInput, emailInput, phoneInput, createBtn);
|
||||||
|
rsvpForm.append(idInput, rsvpInput, updateBtn);
|
||||||
|
|
||||||
|
const result = document.createElement('div');
|
||||||
|
result.className = 'result';
|
||||||
|
result.style.marginTop = '1rem';
|
||||||
|
|
||||||
|
panel.append(listBtn, document.createElement('br'), createForm, rsvpForm, result);
|
||||||
|
detailContainer.appendChild(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showTodoAdmin() {
|
||||||
|
detailContainer.innerHTML = '';
|
||||||
|
const panel = document.createElement('div');
|
||||||
|
panel.className = 'module';
|
||||||
|
panel.innerHTML = '<h3>To-dos</h3>';
|
||||||
|
|
||||||
|
const listBtn = createButton('Listar To-dos', async () => {
|
||||||
|
try {
|
||||||
|
const todos = await api('/todo');
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(todos);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const createForm = document.createElement('div');
|
||||||
|
createForm.innerHTML = '<strong>Crear To-do</strong><br/>';
|
||||||
|
const titleInput = createInput('title', 'Título');
|
||||||
|
const descInput = createInput('description', 'Descripción');
|
||||||
|
const createBtn = createButton('Crear', async () => {
|
||||||
|
try {
|
||||||
|
const todo = await api('/todo', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
title: titleInput.value,
|
||||||
|
description: descInput.value,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(todo);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const completeForm = document.createElement('div');
|
||||||
|
completeForm.innerHTML = '<strong>Marcar completado</strong><br/>';
|
||||||
|
const todoIdInput = createInput('todoId', 'ID del To-do');
|
||||||
|
const completeBtn = createButton('Completar', async () => {
|
||||||
|
try {
|
||||||
|
const completed = await api(`/todo/${todoIdInput.value}/complete`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
});
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(completed);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
completeForm.append(todoIdInput, completeBtn);
|
||||||
|
|
||||||
|
const result = document.createElement('div');
|
||||||
|
result.className = 'result';
|
||||||
|
result.style.marginTop = '1rem';
|
||||||
|
|
||||||
|
panel.append(listBtn, document.createElement('br'), createForm, completeForm, result);
|
||||||
|
detailContainer.appendChild(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showGiftAdmin() {
|
||||||
|
detailContainer.innerHTML = '';
|
||||||
|
const panel = document.createElement('div');
|
||||||
|
panel.className = 'module';
|
||||||
|
panel.innerHTML = '<h3>Regalos</h3>';
|
||||||
|
|
||||||
|
const listBtn = createButton('Listar regalos', async () => {
|
||||||
|
try {
|
||||||
|
const gifts = await api('/gift');
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(gifts);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const createForm = document.createElement('div');
|
||||||
|
createForm.innerHTML = '<strong>Crear Regalo</strong><br/>';
|
||||||
|
const nameInput = createInput('name', 'Nombre');
|
||||||
|
const descInput = createInput('description', 'Descripción');
|
||||||
|
const priceInput = createInput('price', 'Precio', 'number');
|
||||||
|
const createBtn = createButton('Crear', async () => {
|
||||||
|
try {
|
||||||
|
const gift = await api('/gift', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: nameInput.value,
|
||||||
|
description: descInput.value,
|
||||||
|
price: Number(priceInput.value) || undefined,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(gift);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const contributionForm = document.createElement('div');
|
||||||
|
contributionForm.innerHTML = '<strong>Agregar contribución</strong><br/>';
|
||||||
|
const giftIdInput = createInput('giftId', 'ID del regalo');
|
||||||
|
const contribNameInput = createInput('contributorName', 'Nombre');
|
||||||
|
const amountInput = createInput('amount', 'Monto', 'number');
|
||||||
|
const contribBtn = createButton('Agregar', async () => {
|
||||||
|
try {
|
||||||
|
const contribution = await api(`/gift/${giftIdInput.value}/contribution`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
contributorName: contribNameInput.value,
|
||||||
|
amount: Number(amountInput.value) || 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
panel.querySelector('.result')!.innerHTML = formatJson(contribution);
|
||||||
|
} catch (err) {
|
||||||
|
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
contributionForm.append(giftIdInput, contribNameInput, amountInput, contribBtn);
|
||||||
|
|
||||||
|
const result = document.createElement('div');
|
||||||
|
result.className = 'result';
|
||||||
|
result.style.marginTop = '1rem';
|
||||||
|
|
||||||
|
panel.append(listBtn, document.createElement('br'), createForm, contributionForm, result);
|
||||||
|
detailContainer.appendChild(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('refresh').addEventListener('click', loadModules);
|
||||||
|
tenantInput.addEventListener('change', loadModules);
|
||||||
|
|
||||||
loadModules();
|
loadModules();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -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';
|
import { ModuleRegistryService } from '../core/services/module-registry.service';
|
||||||
|
|
||||||
|
const DEFAULT_TENANT = process.env.DEFAULT_TENANT_ID ?? 'default';
|
||||||
|
|
||||||
@Controller('admin')
|
@Controller('admin')
|
||||||
export class AdminController {
|
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')
|
@Get('modules')
|
||||||
getModules() {
|
getModules() {
|
||||||
return this.moduleRegistry.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
|
||||||
import { CoreModule } from '../core/core.module';
|
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 { AdminController } from './admin.controller';
|
||||||
import { ModuleRegistryService } from '../core/services/module-registry.service';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [CoreModule, MikroOrmModule.forFeature([])],
|
imports: [CoreModule, GiftModule, GuestModule, TodoModule],
|
||||||
controllers: [AdminController],
|
controllers: [AdminController],
|
||||||
providers: [ModuleRegistryService],
|
|
||||||
exports: [ModuleRegistryService],
|
|
||||||
})
|
})
|
||||||
export class AdminModule {}
|
export class AdminModule {}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import { AdminModule } from './admin/admin.module';
|
|||||||
ServeStaticModule.forRoot({
|
ServeStaticModule.forRoot({
|
||||||
rootPath: join(__dirname, '..', 'public'),
|
rootPath: join(__dirname, '..', 'public'),
|
||||||
serveRoot: '/admin',
|
serveRoot: '/admin',
|
||||||
|
exclude: ['/admin/modules'],
|
||||||
|
serveStaticOptions: {
|
||||||
|
index: 'admin.html',
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
CoreModule,
|
CoreModule,
|
||||||
GiftModule,
|
GiftModule,
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ export class AuthGuard implements CanActivate {
|
|||||||
|
|
||||||
canActivate(context: ExecutionContext): boolean {
|
canActivate(context: ExecutionContext): boolean {
|
||||||
const request = context.switchToHttp().getRequest();
|
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 authHeader = request.headers['authorization'] || '';
|
||||||
const token = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
const token = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { NotificationRuleSchema } from './entities/notification-rule.entity';
|
|||||||
user: process.env.DB_USER || 'postgres',
|
user: process.env.DB_USER || 'postgres',
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
password: process.env.DB_PASSWORD || 'postgres',
|
||||||
dbName: process.env.DB_NAME || 'planner',
|
dbName: process.env.DB_NAME || 'planner',
|
||||||
autoLoadEntities: false,
|
autoLoadEntities: true,
|
||||||
migrations: {
|
migrations: {
|
||||||
path: 'dist/migrations',
|
path: 'dist/migrations',
|
||||||
transactional: true,
|
transactional: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user