feat(admin-tags): POST /api/admin/tags to create craft/cooperative tags

Admin-only route that validates a label + pool via zod, slugifies the
label, returns the existing tag if the slug already exists, otherwise
creates a new active Tag document.
This commit is contained in:
Jennie Robinson Faber 2026-05-21 17:51:01 +01:00
parent 4a05e91715
commit 384d3197ce
2 changed files with 45 additions and 0 deletions

View file

@ -0,0 +1,40 @@
import Tag from '../../../models/tag.js'
import { adminTagCreateSchema } from '../../../utils/schemas.js'
const slugify = (s) =>
s
.toLowerCase()
.trim()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
export default defineEventHandler(async (event) => {
await requireAdmin(event)
await connectDB()
const body = await validateBody(event, adminTagCreateSchema)
const slug = slugify(body.label)
if (!slug) {
throw createError({
statusCode: 400,
statusMessage: 'Tag label must contain at least one alphanumeric character'
})
}
const existing = await Tag.findOne({ slug })
if (existing) {
return { tag: { slug: existing.slug, label: existing.label, pool: existing.pool } }
}
const tag = await Tag.create({
slug,
label: body.label,
pool: body.pool,
active: true
})
return { tag: { slug: tag.slug, label: tag.label, pool: tag.pool } }
})

View file

@ -383,6 +383,11 @@ export const tagSuggestionSchema = z.object({
pool: z.enum(['craft', 'cooperative']) pool: z.enum(['craft', 'cooperative'])
}) })
export const adminTagCreateSchema = z.object({
label: z.string().trim().min(1).max(100),
pool: z.enum(['craft', 'cooperative'])
})
// --- Board post / channel schemas --- // --- Board post / channel schemas ---
export const boardPostCreateSchema = z.object({ export const boardPostCreateSchema = z.object({