Backend development for the Freyetag Absence Management App. Helps with API routes, Mongoose models, services, middleware, Graph API, Personio API, and Teams Bot integration. Use when building or modifying API endpoints, database models, or server-side services.
import { NextRequest, NextResponse } from 'next/server';
import { requireRole } from '@/lib/rbac';
import connectDB from '@/lib/mongodb';
export async function GET(request: NextRequest) {
try {
const { user, dbUser } = await requireRole(['employee', 'manager', 'hr_manager', 'admin']);
await connectDB();
// Business logic here
return NextResponse.json({ data });
} catch (error: any) {
console.error('Error:', error);
if (error.message === 'Unauthorized' || error.message.startsWith('Forbidden')) {
return NextResponse.json({ error: error.message }, { status: 403 });
}
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
| Route type | requireRole |
|---|---|
| Own data (absences, stats) | ['employee', 'teamlead', 'manager', 'hr_manager', 'admin'] |
| Team data (approvals, calendar) | ['manager', 'teamlead', 'hr_manager', 'admin'] |
| Analytics / reports | ['manager', 'hr_manager', 'admin'] |
| User management | ['hr_manager', 'admin'] |
| Department management | ['hr_manager', 'admin'] |
| System settings | ['admin'] |
| Integrations (Personio, etc.) | ['admin'] |
| Sync triggers | ['admin'] |
// Manager: only direct reports
if (dbUser.role === 'manager') {
const target = await User.findOne({ email: absence.userEmail });
if (target?.managerId !== dbUser.entraId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
}
// TeamLead: only same department
if (dbUser.role === 'teamlead') {
const target = await User.findOne({ email: absence.userEmail });
if (target?.department !== dbUser.department) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
}
// hr_manager + admin: no restriction
import mongoose, { Schema, Model } from 'mongoose';
interface IMyModel { name: string; isActive: boolean; }
interface IMyModelDocument extends IMyModel, mongoose.Document {}
const MySchema = new Schema<IMyModelDocument>({
name: { type: String, required: true, unique: true, trim: true },
isActive: { type: Boolean, default: true },
}, { timestamps: true });
const MyModel = (mongoose.models.MyModel as Model<IMyModelDocument>)
|| mongoose.model<IMyModelDocument>('MyModel', MySchema);
export default MyModel;
src/lib/graph-client.ts — getGraphUser, getUserManager, getUserDirectReports, getEntraGroups, getGroupMembers, getEntraUserssrc/lib/graph-client-delegated.ts — sendTeamsMessageDelegated (requires active session)src/lib/teams-bot.ts — sendApprovalNotification, sendHandoverNotification, Adaptive Cardssrc/lib/services/personioClient.ts — authenticate, getEmployees, getAbsenceBalance, createAbsencePeriod, deleteAbsencePeriodsrc/lib/services/personioSync.ts — syncPersonioEmployees, writeBackAbsenceToPersonio, cancelAbsenceInPersoniosrc/lib/services/entraGroupSync.ts — syncEntraGroups, fetchEntraGroupsPreviewsrc/lib/services/analyticsService.ts — calculateAnalytics, getAnalyticsByDepartmentAfter approval, 6 steps execute sequentially — each in own try/catch:
absence.approve() — set statusupdateVacationBalance() — local balancecreateCalendarEvent() — OutlooksetAutomaticReplies() — Auto-ReplysendTeamsNotification() — Adaptive CardwriteBackToPersonio() — LAST, non-blockingUse Zod for POST/PUT body validation:
import { z } from 'zod';
const schema = z.object({ name: z.string().min(1), bundesland: z.enum(['BW','BY',...]) });
const parsed = schema.safeParse(body);
if (!parsed.success) return NextResponse.json({ error: parsed.error.message }, { status: 400 });