fix(course): stop capping public course slug length

Public course slugs carry a -<timestamp> uniqueness suffix that pushes
them past the 80-char ZSlug cap, so the org-site lookup returned a 400
and the page fell through to the landing page instead of redirecting to
the first lesson. The course.slug column is unbounded, so validate the
public-course route param with a no-max ZCourseSlug.
This commit is contained in:
Rotimi Best
2026-06-16 22:44:26 +01:00
parent bbf02df65b
commit 7022537471
2 changed files with 13 additions and 3 deletions
@@ -1,6 +1,6 @@
import * as z from 'zod';
import { ZSlug } from '../shared/slug';
import { ZCourseSlug, ZSlug } from '../shared/slug';
/**
* Route params + query schemas for the anonymous, SEO-indexable public course
@@ -8,12 +8,12 @@ import { ZSlug } from '../shared/slug';
*/
export const ZPublicCourseBySlugParam = z.object({
courseSlug: ZSlug
courseSlug: ZCourseSlug
});
export type TPublicCourseBySlugParam = z.infer<typeof ZPublicCourseBySlugParam>;
export const ZPublicCourseItemParam = z.object({
courseSlug: ZSlug,
courseSlug: ZCourseSlug,
itemSlug: ZSlug
});
export type TPublicCourseItemParam = z.infer<typeof ZPublicCourseItemParam>;
@@ -15,6 +15,16 @@ export const ZSlug = z
});
export type TSlug = z.infer<typeof ZSlug>;
/**
* Course slugs are `slugify(title)` (capped at 80) plus a `-<timestamp>` uniqueness
* suffix, so they routinely exceed the 80-char `ZSlug` cap. The `course.slug` column is
* unbounded `varchar` and there's no technical reason to cap the URL, so don't.
*/
export const ZCourseSlug = z.string().min(1, { message: 'Slug is required' }).regex(SLUG_REGEX, {
message: 'Slug must be lowercase letters, numbers, and single dashes'
});
export type TCourseSlug = z.infer<typeof ZCourseSlug>;
/**
* Generate a kebab-case slug from a free-form title. Does not enforce uniqueness;
* callers must resolve collisions against the target namespace (e.g. per-course).