🚀 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:
2026-02-20 15:12:06 +00:00
parent a07c2ad858
commit ee19e45171
16 changed files with 2079 additions and 2 deletions

62
src/middleware/auth.ts Normal file
View File

@@ -0,0 +1,62 @@
import { Context, Next } from "@oak/oak";
import { verifyToken } from "../utils/auth.ts";
import { AppError } from "./error.ts";
import type { UserRole, AuthContext } from "../types/index.ts";
// Extend Oak context with auth
declare module "@oak/oak" {
interface State {
auth: AuthContext;
}
}
// Auth middleware - requires valid JWT
export async function authMiddleware(ctx: Context, next: Next): Promise<void> {
const authHeader = ctx.request.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
throw new AppError("No token provided", 401);
}
const token = authHeader.slice(7);
const payload = await verifyToken(token);
if (!payload) {
throw new AppError("Invalid or expired token", 401);
}
ctx.state.auth = {
user: {
id: payload.sub,
org_id: payload.org,
role: payload.role,
email: payload.email,
},
};
await next();
}
// Role-based access control middleware
export function requireRole(...allowedRoles: UserRole[]) {
return async (ctx: Context, next: Next): Promise<void> => {
await authMiddleware(ctx, async () => {
const userRole = ctx.state.auth.user.role;
if (!allowedRoles.includes(userRole)) {
throw new AppError("Insufficient permissions", 403);
}
await next();
});
};
}
// Chef only
export const requireChef = requireRole("chef");
// Chef or Disponent
export const requireDisponentOrHigher = requireRole("chef", "disponent");
// Any authenticated user
export const requireAuth = authMiddleware;

28
src/middleware/error.ts Normal file
View File

@@ -0,0 +1,28 @@
import { Context, Next } from "@oak/oak";
export class AppError extends Error {
status: number;
constructor(message: string, status = 500) {
super(message);
this.status = status;
}
}
export async function errorHandler(ctx: Context, next: Next): Promise<void> {
try {
await next();
} catch (err) {
if (err instanceof AppError) {
ctx.response.status = err.status;
ctx.response.body = { error: err.message };
} else if (err instanceof Error) {
console.error("Unhandled error:", err);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
} else {
ctx.response.status = 500;
ctx.response.body = { error: "Unknown error" };
}
}
}

17
src/middleware/logger.ts Normal file
View File

@@ -0,0 +1,17 @@
import { Context, Next } from "@oak/oak";
export async function requestLogger(ctx: Context, next: Next): Promise<void> {
const start = Date.now();
await next();
const ms = Date.now() - start;
const status = ctx.response.status;
const method = ctx.request.method;
const url = ctx.request.url.pathname;
const color = status >= 500 ? "\x1b[31m" : status >= 400 ? "\x1b[33m" : "\x1b[32m";
const reset = "\x1b[0m";
console.log(`${color}${status}${reset} ${method} ${url} - ${ms}ms`);
}