Fixed files: - competitions-view.tsx: 10 strings (placeholders, labels) - subjects-view.tsx: 11 strings (types, actions, placeholders) - professional-calendar.tsx: 5 strings (event type labels) - illness-reporting-view.tsx: 3 strings (status badges) - student-detail-view.tsx: 4 strings (health form placeholders) - data-table.tsx: 2 strings (search, empty state) - compliance-wizard.tsx: 2 strings (name, state) - settings-view.tsx: 1 string (Skipped) - competence-flower-view.tsx: 1 string (Active) - disciplinary-view.tsx: 1 string (All clear) - district-management-view.tsx: 1 string (No recent activity) - competitions-view.tsx: 1 string (Stock) - ui/pagination.tsx: 3 strings (Previous, Next, More pages) - ui/breadcrumb.tsx: 1 string (More) - ui/carousel.tsx: 2 strings (Previous/Next slide) - ui/sidebar.tsx: 1 string (Sidebar title) Total: 44 hardcoded English strings replaced with German
409 lines
7.8 KiB
Markdown
Executable File
409 lines
7.8 KiB
Markdown
Executable File
# API Implementation Guide - Module B, F, H
|
|
|
|
All endpoints follow the standardized response format and use the middleware pattern from `src/lib/route-middleware.ts`.
|
|
|
|
---
|
|
|
|
## Module B: Emergency Signage API
|
|
|
|
### Signage Device Management
|
|
|
|
**POST `/api/v1/signage/displays`**
|
|
```typescript
|
|
Request: {
|
|
displayName: string,
|
|
signageType: "digital_display" | "led_board" | "tv_monitor",
|
|
location: string,
|
|
resolution?: string,
|
|
ipAddress?: string,
|
|
}
|
|
|
|
Response: {
|
|
success: true,
|
|
data: { id, displayName, signageType, location, isActive }
|
|
}
|
|
```
|
|
|
|
**GET `/api/v1/signage/displays`**
|
|
```typescript
|
|
Query: { schoolId: string, isActive?: boolean }
|
|
Response: {
|
|
success: true,
|
|
data: EmergencySignage[],
|
|
pagination: { page, total, limit }
|
|
}
|
|
```
|
|
|
|
**PATCH `/api/v1/signage/displays/{id}`**
|
|
```typescript
|
|
Request: {
|
|
brightness?: number,
|
|
volume?: number,
|
|
isActive?: boolean,
|
|
}
|
|
Response: { success: true, data: { id, ...updated } }
|
|
```
|
|
|
|
### Message Management
|
|
|
|
**POST `/api/v1/signage/messages`**
|
|
```typescript
|
|
Request: {
|
|
signageId: string,
|
|
title: string,
|
|
content: string,
|
|
messageType: "emergency" | "notification" | "schedule" | "announcement",
|
|
priority: "low" | "normal" | "high" | "critical",
|
|
displayDuration: number,
|
|
soundAlert?: boolean,
|
|
backgroundColor?: string,
|
|
textColor?: string,
|
|
startTime?: ISO8601,
|
|
endTime?: ISO8601,
|
|
}
|
|
|
|
Response: {
|
|
success: true,
|
|
data: { id, title, messageType, priority, createdAt }
|
|
}
|
|
```
|
|
|
|
**GET `/api/v1/signage/messages`**
|
|
```typescript
|
|
Query: { schoolId: string, signageId?: string, type?: string }
|
|
Response: {
|
|
success: true,
|
|
data: SignageMessage[],
|
|
}
|
|
```
|
|
|
|
### Scheduling
|
|
|
|
**POST `/api/v1/signage/schedules`**
|
|
```typescript
|
|
Request: {
|
|
signageId: string,
|
|
messageId?: string,
|
|
dayOfWeek: 0-6,
|
|
startTime: "HH:mm",
|
|
endTime: "HH:mm",
|
|
isRecurring: boolean,
|
|
}
|
|
|
|
Response: { success: true, data: { id, dayOfWeek, startTime, endTime } }
|
|
```
|
|
|
|
---
|
|
|
|
## Module F: Exam Mode API
|
|
|
|
### Exam Session Management
|
|
|
|
**POST `/api/v1/exams/sessions`**
|
|
```typescript
|
|
Request: {
|
|
assessmentId: string,
|
|
studentId: string,
|
|
durationMinutes: number,
|
|
lockdownEnabled: boolean,
|
|
cameraMonitor?: boolean,
|
|
}
|
|
|
|
Response: {
|
|
success: true,
|
|
data: {
|
|
id,
|
|
studentId,
|
|
status: "not_started",
|
|
startTime,
|
|
environmentNotes,
|
|
}
|
|
}
|
|
```
|
|
|
|
**GET `/api/v1/exams/sessions/{id}`**
|
|
```typescript
|
|
Response: {
|
|
success: true,
|
|
data: {
|
|
id,
|
|
status,
|
|
timeRemaining,
|
|
score,
|
|
answers: ExamAnswer[],
|
|
events: ExamEvent[],
|
|
warnings: ExamWarning[],
|
|
}
|
|
}
|
|
```
|
|
|
|
**GET `/api/v1/exams/active-sessions`**
|
|
```typescript
|
|
Query: { schoolId: string, classGroupId?: string }
|
|
Response: {
|
|
success: true,
|
|
data: ExamSession[] (with student, assessment, events)
|
|
}
|
|
```
|
|
|
|
### Answer Submission
|
|
|
|
**POST `/api/v1/exams/sessions/{id}/answers`**
|
|
```typescript
|
|
Request: {
|
|
questionId: string,
|
|
answer: string, // JSON serialized
|
|
}
|
|
|
|
Response: {
|
|
success: true,
|
|
data: { answerId, questionId, submittedAt }
|
|
}
|
|
```
|
|
|
|
**PATCH `/api/v1/exams/sessions/{id}/submit`**
|
|
```typescript
|
|
Request: { force?: boolean }
|
|
Response: {
|
|
success: true,
|
|
data: {
|
|
id,
|
|
status: "submitted",
|
|
submittedAt,
|
|
score,
|
|
totalPoints,
|
|
}
|
|
}
|
|
```
|
|
|
|
### Event Logging
|
|
|
|
**POST `/api/v1/exams/sessions/{id}/events`**
|
|
```typescript
|
|
Request: {
|
|
eventType: "tab_switch" | "copy_paste" | "window_blur" | "camera_detected_none" | "suspicious_movement",
|
|
severity: "info" | "warning" | "critical",
|
|
description?: string,
|
|
metadata?: Record<string, any>,
|
|
}
|
|
|
|
Response: {
|
|
success: true,
|
|
data: { eventId, eventType, severity, timestamp }
|
|
}
|
|
```
|
|
|
|
### Monitoring & Security
|
|
|
|
**GET `/api/v1/exams/{id}/security-report`**
|
|
```typescript
|
|
Response: {
|
|
success: true,
|
|
data: {
|
|
suspiciousEvents: number,
|
|
tabSwitches: number,
|
|
copyCounts: number,
|
|
focusLosses: number,
|
|
riskScore: 0-100,
|
|
events: ExamEvent[],
|
|
warnings: ExamWarning[],
|
|
}
|
|
}
|
|
```
|
|
|
|
**POST `/api/v1/exams/sessions/{id}/warnings`**
|
|
```typescript
|
|
Request: {
|
|
warningType: "time_low" | "behavior_suspicious" | "multiple_tabs" | "camera_issue",
|
|
message: string,
|
|
}
|
|
|
|
Response: { success: true, data: { id, warningType, createdAt } }
|
|
```
|
|
|
|
---
|
|
|
|
## Module H: Notifications API
|
|
|
|
### Notification Hub
|
|
|
|
**GET `/api/v1/notifications/hub`**
|
|
```typescript
|
|
Query: {
|
|
userId: string,
|
|
limit?: number,
|
|
category?: string,
|
|
}
|
|
|
|
Response: {
|
|
success: true,
|
|
data: NotificationHub[],
|
|
pagination: { unreadCount, totalCount }
|
|
}
|
|
```
|
|
|
|
**POST `/api/v1/notifications`**
|
|
```typescript
|
|
Request: {
|
|
userId: string,
|
|
category: "assessment" | "behavior" | "communication" | "wellness" | "system",
|
|
priority: "low" | "normal" | "high" | "critical",
|
|
title: string,
|
|
message: string,
|
|
actionUrl?: string,
|
|
actionLabel?: string,
|
|
sourceType?: string,
|
|
sourceId?: string,
|
|
expiresAt?: ISO8601,
|
|
}
|
|
|
|
Response: {
|
|
success: true,
|
|
data: { id, userId, category, priority, createdAt }
|
|
}
|
|
```
|
|
|
|
### Notification Actions
|
|
|
|
**PATCH `/api/v1/notifications/{id}/read`**
|
|
```typescript
|
|
Response: { success: true, data: { id, isRead: true, readAt } }
|
|
```
|
|
|
|
**PATCH `/api/v1/notifications/{id}/dismiss`**
|
|
```typescript
|
|
Response: { success: true, data: { id, isDismissed: true, dismissedAt } }
|
|
```
|
|
|
|
**PATCH `/api/v1/notifications/{id}/archive`**
|
|
```typescript
|
|
Response: { success: true, data: { id, archivedAt } }
|
|
```
|
|
|
|
### Bulk Operations
|
|
|
|
**POST `/api/v1/notifications/batch`**
|
|
```typescript
|
|
Request: {
|
|
userIds: string[],
|
|
category: string,
|
|
priority: string,
|
|
title: string,
|
|
message: string,
|
|
}
|
|
|
|
Response: {
|
|
success: true,
|
|
data: { createdCount: number }
|
|
}
|
|
```
|
|
|
|
**PATCH `/api/v1/notifications/batch/read`**
|
|
```typescript
|
|
Request: { notificationIds: string[] }
|
|
Response: { success: true, data: { updatedCount: number } }
|
|
```
|
|
|
|
### Preferences
|
|
|
|
**GET `/api/v1/notifications/preferences`**
|
|
```typescript
|
|
Query: { userId: string }
|
|
Response: {
|
|
success: true,
|
|
data: NotificationPreferences[]
|
|
}
|
|
```
|
|
|
|
**PATCH `/api/v1/notifications/preferences`**
|
|
```typescript
|
|
Request: {
|
|
category: string,
|
|
channel: "email" | "in_app" | "push" | "sms",
|
|
enabled: boolean,
|
|
}
|
|
|
|
Response: { success: true, data: { id, category, channel, enabled } }
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Template
|
|
|
|
All routes should follow this pattern:
|
|
|
|
```typescript
|
|
import { withAuth } from '@/lib/route-middleware';
|
|
import { successResponse, errorResponse } from '@/lib/api-response';
|
|
import { logComplianceAudit } from '@/lib/audit';
|
|
|
|
export const POST = withAuth(
|
|
['SCHOOL_ADMIN', 'TEACHER'],
|
|
async (req: Request, session: Session) => {
|
|
try {
|
|
// Validate input
|
|
const body = await req.json();
|
|
|
|
// Check authorization
|
|
if (body.schoolId !== session.user.schoolId) {
|
|
return errorResponse('Unauthorized', 403);
|
|
}
|
|
|
|
// Create resource
|
|
const resource = await db.model.create({
|
|
data: body,
|
|
});
|
|
|
|
// Log audit
|
|
await logComplianceAudit({
|
|
userId: session.user.id,
|
|
schoolId: session.user.schoolId,
|
|
action: 'CREATE',
|
|
entityType: 'Model',
|
|
entityId: resource.id,
|
|
});
|
|
|
|
return successResponse(resource);
|
|
} catch (error) {
|
|
return errorResponse('Internal server error', 500);
|
|
}
|
|
}
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
For each endpoint:
|
|
- [ ] Happy path (valid input)
|
|
- [ ] Missing required fields
|
|
- [ ] Invalid field types
|
|
- [ ] Unauthorized access (wrong school)
|
|
- [ ] Rate limiting
|
|
- [ ] Audit logging
|
|
- [ ] Response format compliance
|
|
|
|
---
|
|
|
|
## Performance Notes
|
|
|
|
**Indices needed:**
|
|
```sql
|
|
CREATE INDEX idx_exam_sessions_status ON exam_sessions(status);
|
|
CREATE INDEX idx_exam_events_session ON exam_events(examSessionId);
|
|
CREATE INDEX idx_notifications_user_read ON notification_hubs(userId, isRead);
|
|
CREATE INDEX idx_signage_school_active ON emergency_signages(schoolId, isActive);
|
|
```
|
|
|
|
**Caching strategy:**
|
|
- Signage devices: 5 minutes
|
|
- Active exams: 30 seconds
|
|
- Notifications: 1 minute (invalidate on create)
|
|
- User preferences: 1 hour
|
|
|
|
**Pagination defaults:**
|
|
- Default limit: 20
|
|
- Max limit: 100
|
|
- Default page: 1
|