Public Access
feat: Implement API modules for admin, auth, exam, result, and setup functionalities
- Created adminApi.ts for admin-related operations including user management and site settings. - Developed authApi.ts for authentication processes such as registration, login, and profile management. - Added examApi.ts for handling exam CRUD operations. - Introduced resultApi.ts for managing results submission and retrieval. - Established setupApi.ts for initial setup processes and status checks. - Created ErrorBoundary component to handle rendering errors gracefully in the frontend. - Re-exported shared API utilities in index.ts for easier access.
This commit is contained in:
1 parent
62af83ce53
commit
c64cdcd9e3
46 files changed
+2227
-1125
No files matched your search
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const updateUserRoleSchema = z.object({
|
||||
user_id: z.number().min(1),
|
||||
role: z.enum(['user', 'admin']),
|
||||
})
|
||||
|
||||
export const updateSettingSchema = z.object({
|
||||
key: z.string().min(1),
|
||||
value: z.string().min(1),
|
||||
})
|
||||
|
||||
export const listUsersQuerySchema = z.object({
|
||||
page: z.string().regex(/^\d+$/).optional().transform(v => (v ? Number(v) : 1)),
|
||||
search: z.string().optional(),
|
||||
})
|
||||
|
||||
export const userIdParamSchema = z.object({
|
||||
id: z.string().regex(/^\d+$/, 'ID must be a number'),
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const registerSchema = z.object({
|
||||
email: z.string().email().max(255),
|
||||
password: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
displayName: z.string().min(1, 'Display name is required').max(100),
|
||||
})
|
||||
|
||||
export const loginSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
})
|
||||
|
||||
export const verifyEmailSchema = z.object({
|
||||
token: z.string().min(1, 'Token is required'),
|
||||
})
|
||||
|
||||
export const resetPasswordSchema = z.object({
|
||||
token: z.string().min(1, 'Token is required'),
|
||||
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
})
|
||||
|
||||
export const updateProfileSchema = z.object({
|
||||
displayName: z.string().min(1, 'Display name is required').max(100),
|
||||
})
|
||||
|
||||
export const changePasswordSchema = z.object({
|
||||
currentPassword: z.string(),
|
||||
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
})
|
||||
|
||||
export const forgotPasswordSchema = z.object({
|
||||
email: z.string().email().optional(),
|
||||
})
|
||||
|
||||
export type RegisterInput = z.infer<typeof registerSchema>
|
||||
export type LoginInput = z.infer<typeof loginSchema>
|
||||
export type VerifyEmailInput = z.infer<typeof verifyEmailSchema>
|
||||
export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>
|
||||
export type UpdateProfileInput = z.infer<typeof updateProfileSchema>
|
||||
export type ChangePasswordInput = z.infer<typeof changePasswordSchema>
|
||||
@@ -0,0 +1,93 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const choiceSchema = z.object({
|
||||
id: z.string(),
|
||||
text: z.string(),
|
||||
})
|
||||
|
||||
const matchItemSchema = z.object({
|
||||
id: z.string(),
|
||||
text: z.string(),
|
||||
})
|
||||
|
||||
const questionImageSchema = z.object({
|
||||
src: z.string(),
|
||||
alt: z.string().optional(),
|
||||
caption: z.string().optional(),
|
||||
})
|
||||
|
||||
const questionAudioSchema = z.object({
|
||||
src: z.string(),
|
||||
})
|
||||
|
||||
const questionCodeSchema = z.object({
|
||||
language: z.string(),
|
||||
content: z.string(),
|
||||
})
|
||||
|
||||
const questionTableSchema = z.object({
|
||||
headers: z.array(z.string()),
|
||||
rows: z.array(z.array(z.string())),
|
||||
})
|
||||
|
||||
const questionSchema = z.object({
|
||||
id: z.string(),
|
||||
type: z.enum([
|
||||
'multiple-choice',
|
||||
'multiple-select',
|
||||
'short-answer',
|
||||
'translation',
|
||||
'math-input',
|
||||
'essay',
|
||||
'match',
|
||||
'order',
|
||||
]),
|
||||
category: z.string().optional(),
|
||||
points: z.number().optional(),
|
||||
prompt: z.string(),
|
||||
explanation: z.string().optional(),
|
||||
image: questionImageSchema.optional(),
|
||||
audio: questionAudioSchema.optional(),
|
||||
code: questionCodeSchema.optional(),
|
||||
table: questionTableSchema.optional(),
|
||||
svg: z.string().optional(),
|
||||
answer: z.union([z.string(), z.array(z.string()), z.record(z.string(), z.string())]).optional(),
|
||||
acceptedForms: z.array(z.string()).optional(),
|
||||
caseSensitive: z.boolean().optional(),
|
||||
ignorePunctuation: z.boolean().optional(),
|
||||
ignoreAccents: z.boolean().optional(),
|
||||
sourceLanguage: z.string().optional(),
|
||||
targetLanguage: z.string().optional(),
|
||||
choices: z.array(choiceSchema).optional(),
|
||||
minWords: z.number().optional(),
|
||||
rubric: z.array(z.string()).optional(),
|
||||
left: z.array(matchItemSchema).optional(),
|
||||
right: z.array(matchItemSchema).optional(),
|
||||
items: z.array(matchItemSchema).optional(),
|
||||
})
|
||||
|
||||
export const examSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
title: z.string().min(1, 'Title is required'),
|
||||
subtitle: z.string().optional(),
|
||||
author: z.string().optional(),
|
||||
timeLimitMinutes: z.number().nullable().optional(),
|
||||
feedbackMode: z.enum(['end', 'immediate']).optional(),
|
||||
passingScore: z.number().optional(),
|
||||
categories: z.array(z.string()).optional(),
|
||||
questions: z.array(questionSchema).min(1, 'At least one question is required'),
|
||||
})
|
||||
|
||||
export const saveExamSchema = z.object({
|
||||
exam: examSchema,
|
||||
})
|
||||
|
||||
export const updateExamSchema = z.object({
|
||||
exam: examSchema,
|
||||
})
|
||||
|
||||
export const examIdParamSchema = z.object({
|
||||
id: z.string().regex(/^\d+$/, 'ID must be a number'),
|
||||
})
|
||||
|
||||
export type ExamInput = z.infer<typeof examSchema>
|
||||
@@ -0,0 +1,40 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const answerValueSchema = z.union([z.string(), z.array(z.string()), z.record(z.string(), z.string())])
|
||||
|
||||
export const submitResultSchema = z.object({
|
||||
exam_id: z.number().nullable().optional(),
|
||||
title: z.string().min(1),
|
||||
score_points: z.number(),
|
||||
total_points: z.number(),
|
||||
correct_count: z.number(),
|
||||
graded_count: z.number(),
|
||||
passing_score: z.number().optional(),
|
||||
elapsed_ms: z.number(),
|
||||
state: z.object({
|
||||
answers: z.record(z.string(), answerValueSchema),
|
||||
gradedAnswers: z.record(z.string(), z.object({
|
||||
correct: z.boolean(),
|
||||
points: z.number(),
|
||||
explanation: z.string().optional(),
|
||||
})),
|
||||
answeredCount: z.number(),
|
||||
score: z.number(),
|
||||
correctCount: z.number(),
|
||||
gradedCount: z.number(),
|
||||
timeExpired: z.boolean().optional(),
|
||||
}),
|
||||
exam: z.object({
|
||||
id: z.string().optional(),
|
||||
title: z.string(),
|
||||
subtitle: z.string().optional(),
|
||||
questions: z.array(z.any()),
|
||||
timeLimitMinutes: z.number().nullable().optional(),
|
||||
passingScore: z.number().optional(),
|
||||
feedbackMode: z.enum(['end', 'immediate']).optional(),
|
||||
}).passthrough(),
|
||||
})
|
||||
|
||||
export const resultIdParamSchema = z.object({
|
||||
id: z.string().regex(/^\d+$/, 'ID must be a number'),
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const setupSchema = z.object({
|
||||
email: z.string().email().max(255),
|
||||
password: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
displayName: z.string().min(1, 'Display name is required').max(100),
|
||||
siteTitle: z.string().min(1, 'Site title is required').max(255),
|
||||
language: z.enum(['en', 'nl']),
|
||||
})
|
||||
|
||||
export const setupStatusSchema = z.object({
|
||||
email: z.string().email().max(255).optional(),
|
||||
password: z.string().min(8).optional(),
|
||||
})
|
||||
Reference in new issue
Block a user