cms.c2sgmbh/src/lib/validation/slug-validation.ts
Martin Porwoll d449da6915 fix: resolve TypeScript and lint errors in video feature
- Fix slug-validation.ts: Use proper Where type from Payload
- Fix processFeaturedVideo.ts: Remove TypeWithID constraint, use type casting
- Fix retention-worker.ts: Remove unused import cleanupExpiredConsentLogs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 11:50:32 +00:00

160 lines
3.8 KiB
TypeScript

/**
* Slug Validation Utilities
*
* Stellt sicher, dass Slugs innerhalb eines Tenants eindeutig sind.
*/
import type { Payload, Where } from 'payload'
import type { Config } from '@/payload-types'
type CollectionSlug = keyof Config['collections']
type LocaleType = 'de' | 'en' | 'all' | undefined
export interface SlugValidationOptions {
/** Collection slug */
collection: CollectionSlug
/** Field name for slug (default: 'slug') */
slugField?: string
/** Field name for tenant (default: 'tenant') */
tenantField?: string
/** Whether to check per locale (default: false) */
perLocale?: boolean
}
/**
* Validates that a slug is unique within a tenant
*
* @throws Error if slug already exists for this tenant
*/
export async function validateUniqueSlug(
payload: Payload,
data: Record<string, unknown>,
options: SlugValidationOptions & {
existingId?: number | string
locale?: string
}
): Promise<void> {
const {
collection,
slugField = 'slug',
tenantField = 'tenant',
perLocale = false,
existingId,
locale,
} = options
const slug = data[slugField]
const tenantId = data[tenantField]
// Skip if no slug provided
if (!slug || typeof slug !== 'string') {
return
}
// Build where clause
const conditions: Where[] = [{ [slugField]: { equals: slug } }]
// Add tenant filter if tenant is set
if (tenantId) {
conditions.push({ [tenantField]: { equals: tenantId } })
}
// Exclude current document when updating
if (existingId) {
conditions.push({ id: { not_equals: existingId } })
}
const where: Where = conditions.length > 1 ? { and: conditions } : conditions[0]
// Determine locale for query
const queryLocale: LocaleType = perLocale && locale ? (locale as LocaleType) : undefined
// Check for existing documents with same slug
const existing = await payload.find({
collection,
where,
limit: 1,
depth: 0,
locale: queryLocale,
})
if (existing.totalDocs > 0) {
const tenantInfo = tenantId ? ` für diesen Tenant` : ''
throw new Error(`Der Slug "${slug}" existiert bereits${tenantInfo}. Bitte wählen Sie einen anderen.`)
}
}
/**
* Creates a beforeValidate hook for slug uniqueness
*/
export function createSlugValidationHook(options: SlugValidationOptions) {
return async ({
data,
req,
operation,
originalDoc,
}: {
data?: Record<string, unknown>
req: { payload: Payload; locale?: string }
operation: 'create' | 'update'
originalDoc?: { id?: number | string }
}) => {
if (!data) return data
await validateUniqueSlug(req.payload, data, {
...options,
existingId: operation === 'update' ? originalDoc?.id : undefined,
locale: req.locale,
})
return data
}
}
/**
* Generates a unique slug by appending a number if necessary
*/
export async function generateUniqueSlug(
payload: Payload,
baseSlug: string,
options: SlugValidationOptions & {
existingId?: number | string
tenantId?: number | string
}
): Promise<string> {
const { collection, slugField = 'slug', tenantField = 'tenant', existingId, tenantId } = options
let slug = baseSlug
let counter = 1
let isUnique = false
while (!isUnique && counter < 100) {
const conditions: Where[] = [{ [slugField]: { equals: slug } }]
if (tenantId) {
conditions.push({ [tenantField]: { equals: tenantId } })
}
if (existingId) {
conditions.push({ id: { not_equals: existingId } })
}
const where: Where = conditions.length > 1 ? { and: conditions } : conditions[0]
const existing = await payload.find({
collection,
where,
limit: 1,
depth: 0,
})
if (existing.totalDocs === 0) {
isUnique = true
} else {
slug = `${baseSlug}-${counter}`
counter++
}
}
return slug
}