Files
Dungeons-Ground/apps/web/server/utils/coauthor-question.ts
pavel444-byte f7ca158fe4
Some checks failed
CI / validate (pull_request) Has been cancelled
CI / validate (push) Successful in 32m54s
fix(gameplay): start campaigns in playable state
Co-authored-by: multica-agent <github@multica.ai>
2026-08-21 19:41:50 +05:00

64 lines
1.8 KiB
TypeScript

import { z } from 'zod'
const reservedOptionValues = new Set([
'answer',
'answers',
'label',
'option',
'options',
'question',
])
function normalized(value: string) {
return value.trim().toLocaleLowerCase().replace(/\s+/g, ' ')
}
export const CoauthorQuestionSchema = z.object({
question: z.string().trim().min(5).max(300),
options: z.array(z.string().trim().min(2).max(100)).length(3),
}).superRefine((value, context) => {
const question = normalized(value.question)
const seen = new Set<string>()
value.options.forEach((option, index) => {
const candidate = normalized(option)
if (!/[\p{L}\p{N}]/u.test(candidate) || reservedOptionValues.has(candidate)) {
context.addIssue({
code: 'custom',
message: 'Each option must be a meaningful answer.',
path: ['options', index],
})
}
if (seen.has(candidate)) {
context.addIssue({
code: 'custom',
message: 'Answer options must be distinct.',
path: ['options', index],
})
}
if (candidate === question || (candidate.length >= 20 && question.includes(candidate))) {
context.addIssue({
code: 'custom',
message: 'An answer option must not repeat the question.',
path: ['options', index],
})
}
seen.add(candidate)
})
})
export const StoredCoauthorQuestionSchema = z.object({
id: z.string().trim().min(1).max(80),
label: z.string().trim().min(5).max(300),
options: CoauthorQuestionSchema.shape.options,
}).superRefine((value, context) => {
const result = CoauthorQuestionSchema.safeParse({ question: value.label, options: value.options })
for (const issue of result.error?.issues ?? []) {
context.addIssue({
code: 'custom',
message: issue.message,
path: issue.path[0] === 'question' ? ['label', ...issue.path.slice(1)] : issue.path,
})
}
})