🚀 Backend komplett implementiert
Features: - Auth mit JWT + Argon2 (Login, Register, Refresh) - Rollen-System (Chef/Disponent/Mitarbeiter) - User Management mit Berechtigungen - Aufträge mit Zuweisungen - Verfügbarkeitsplanung - Stundenzettel mit Foto-Upload Support - Modulares System mit Config - Entwickler-Panel Endpoints Tech: - Deno + Oak - PostgreSQL - CORS enabled
This commit is contained in:
211
src/routes/modules.ts
Normal file
211
src/routes/modules.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import { Router } from "@oak/oak";
|
||||
import { query, queryOne, execute } from "../db/postgres.ts";
|
||||
import { AppError } from "../middleware/error.ts";
|
||||
import { authMiddleware, requireChef } from "../middleware/auth.ts";
|
||||
import type { Module, OrganizationModule } from "../types/index.ts";
|
||||
|
||||
export const modulesRouter = new Router({ prefix: "/api/modules" });
|
||||
|
||||
// Get all available modules
|
||||
modulesRouter.get("/", authMiddleware, async (ctx) => {
|
||||
const modules = await query<Module>(
|
||||
`SELECT * FROM modules ORDER BY is_core DESC, name`
|
||||
);
|
||||
|
||||
ctx.response.body = { modules };
|
||||
});
|
||||
|
||||
// Get organization's module configuration
|
||||
modulesRouter.get("/org", authMiddleware, async (ctx) => {
|
||||
const { org_id: orgId } = ctx.state.auth.user;
|
||||
|
||||
const modules = await query<Module & { enabled: boolean; config: Record<string, unknown> }>(
|
||||
`SELECT m.*,
|
||||
COALESCE(om.enabled, false) as enabled,
|
||||
COALESCE(om.config, m.default_config) as config
|
||||
FROM modules m
|
||||
LEFT JOIN organization_modules om ON m.id = om.module_id AND om.org_id = $1
|
||||
ORDER BY m.is_core DESC, m.name`,
|
||||
[orgId]
|
||||
);
|
||||
|
||||
ctx.response.body = { modules };
|
||||
});
|
||||
|
||||
// Enable/disable module for organization (Chef only)
|
||||
modulesRouter.post("/:moduleId/toggle", requireChef, async (ctx) => {
|
||||
const { org_id: orgId } = ctx.state.auth.user;
|
||||
const moduleId = ctx.params.moduleId;
|
||||
const body = await ctx.request.body.json();
|
||||
const { enabled } = body;
|
||||
|
||||
if (enabled === undefined) {
|
||||
throw new AppError("enabled field required", 400);
|
||||
}
|
||||
|
||||
// Get module
|
||||
const module = await queryOne<Module>(
|
||||
`SELECT * FROM modules WHERE id = $1`,
|
||||
[moduleId]
|
||||
);
|
||||
|
||||
if (!module) {
|
||||
throw new AppError("Module not found", 404);
|
||||
}
|
||||
|
||||
// Cannot disable core modules
|
||||
if (module.is_core && !enabled) {
|
||||
throw new AppError("Cannot disable core module", 400);
|
||||
}
|
||||
|
||||
// Upsert organization_modules
|
||||
await execute(
|
||||
`INSERT INTO organization_modules (org_id, module_id, enabled, config)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (org_id, module_id)
|
||||
DO UPDATE SET enabled = $3, updated_at = NOW()`,
|
||||
[orgId, moduleId, enabled, module.default_config]
|
||||
);
|
||||
|
||||
ctx.response.body = {
|
||||
message: `Module ${enabled ? "enabled" : "disabled"}`,
|
||||
module: module.name,
|
||||
enabled
|
||||
};
|
||||
});
|
||||
|
||||
// Update module configuration (Chef only)
|
||||
modulesRouter.put("/:moduleId/config", requireChef, async (ctx) => {
|
||||
const { org_id: orgId } = ctx.state.auth.user;
|
||||
const moduleId = ctx.params.moduleId;
|
||||
const body = await ctx.request.body.json();
|
||||
const { config } = body;
|
||||
|
||||
if (!config || typeof config !== "object") {
|
||||
throw new AppError("config object required", 400);
|
||||
}
|
||||
|
||||
// Verify module exists
|
||||
const module = await queryOne<Module>(
|
||||
`SELECT * FROM modules WHERE id = $1`,
|
||||
[moduleId]
|
||||
);
|
||||
|
||||
if (!module) {
|
||||
throw new AppError("Module not found", 404);
|
||||
}
|
||||
|
||||
// Upsert with new config
|
||||
await execute(
|
||||
`INSERT INTO organization_modules (org_id, module_id, enabled, config)
|
||||
VALUES ($1, $2, true, $3)
|
||||
ON CONFLICT (org_id, module_id)
|
||||
DO UPDATE SET config = $3, updated_at = NOW()`,
|
||||
[orgId, moduleId, JSON.stringify(config)]
|
||||
);
|
||||
|
||||
ctx.response.body = { message: "Module configuration updated" };
|
||||
});
|
||||
|
||||
// Check if specific module is enabled for current org
|
||||
modulesRouter.get("/check/:moduleName", authMiddleware, async (ctx) => {
|
||||
const { org_id: orgId } = ctx.state.auth.user;
|
||||
const moduleName = ctx.params.moduleName;
|
||||
|
||||
const result = await queryOne<{ enabled: boolean; config: Record<string, unknown> }>(
|
||||
`SELECT om.enabled, COALESCE(om.config, m.default_config) as config
|
||||
FROM modules m
|
||||
LEFT JOIN organization_modules om ON m.id = om.module_id AND om.org_id = $2
|
||||
WHERE m.name = $1`,
|
||||
[moduleName, orgId]
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
throw new AppError("Module not found", 404);
|
||||
}
|
||||
|
||||
ctx.response.body = {
|
||||
module: moduleName,
|
||||
enabled: result.enabled ?? false,
|
||||
config: result.config
|
||||
};
|
||||
});
|
||||
|
||||
// ============ DEVELOPER PANEL ENDPOINTS ============
|
||||
// These require the 'developer' module to be enabled and special permissions
|
||||
|
||||
// Get system status
|
||||
modulesRouter.get("/developer/status", requireChef, async (ctx) => {
|
||||
const { org_id: orgId } = ctx.state.auth.user;
|
||||
|
||||
// Check if developer module is enabled
|
||||
const devModule = await queryOne<{ enabled: boolean }>(
|
||||
`SELECT om.enabled FROM organization_modules om
|
||||
JOIN modules m ON om.module_id = m.id
|
||||
WHERE m.name = 'developer' AND om.org_id = $1`,
|
||||
[orgId]
|
||||
);
|
||||
|
||||
if (!devModule?.enabled) {
|
||||
throw new AppError("Developer module not enabled", 403);
|
||||
}
|
||||
|
||||
// Get stats
|
||||
const stats = await queryOne<{
|
||||
user_count: number;
|
||||
order_count: number;
|
||||
timesheet_count: number;
|
||||
enabled_modules: number;
|
||||
}>(
|
||||
`SELECT
|
||||
(SELECT COUNT(*) FROM users WHERE org_id = $1) as user_count,
|
||||
(SELECT COUNT(*) FROM orders WHERE org_id = $1) as order_count,
|
||||
(SELECT COUNT(*) FROM timesheets t JOIN users u ON t.user_id = u.id WHERE u.org_id = $1) as timesheet_count,
|
||||
(SELECT COUNT(*) FROM organization_modules WHERE org_id = $1 AND enabled = true) as enabled_modules`,
|
||||
[orgId]
|
||||
);
|
||||
|
||||
ctx.response.body = {
|
||||
status: "ok",
|
||||
organization: orgId,
|
||||
stats,
|
||||
serverTime: new Date().toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
// Get audit logs
|
||||
modulesRouter.get("/developer/logs", requireChef, async (ctx) => {
|
||||
const { org_id: orgId } = ctx.state.auth.user;
|
||||
const limit = parseInt(ctx.request.url.searchParams.get("limit") || "50");
|
||||
const offset = parseInt(ctx.request.url.searchParams.get("offset") || "0");
|
||||
|
||||
// Check developer module
|
||||
const devModule = await queryOne<{ enabled: boolean }>(
|
||||
`SELECT om.enabled FROM organization_modules om
|
||||
JOIN modules m ON om.module_id = m.id
|
||||
WHERE m.name = 'developer' AND om.org_id = $1`,
|
||||
[orgId]
|
||||
);
|
||||
|
||||
if (!devModule?.enabled) {
|
||||
throw new AppError("Developer module not enabled", 403);
|
||||
}
|
||||
|
||||
const logs = await query<{
|
||||
id: string;
|
||||
action: string;
|
||||
entity_type: string;
|
||||
user_email: string;
|
||||
created_at: Date;
|
||||
}>(
|
||||
`SELECT al.id, al.action, al.entity_type, al.created_at, u.email as user_email
|
||||
FROM audit_logs al
|
||||
LEFT JOIN users u ON al.user_id = u.id
|
||||
WHERE al.org_id = $1
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT $2 OFFSET $3`,
|
||||
[orgId, limit, offset]
|
||||
);
|
||||
|
||||
ctx.response.body = { logs };
|
||||
});
|
||||
Reference in New Issue
Block a user