forked from wangziqi/gongxue-base
feat: scaffold supabase multi-tenant backend
This commit is contained in:
111
apps/api/src/features/video/routes.ts
Normal file
111
apps/api/src/features/video/routes.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { HttpError, type RequestContext } from '../../core/http.js';
|
||||
import { intParam, optionalStringArray, readJsonBody, stringParam, tenantIdFrom } from '../../core/request.js';
|
||||
import { query } from '../../core/db.js';
|
||||
|
||||
interface QuestionVideoRow {
|
||||
questionId: string;
|
||||
videoType: string;
|
||||
order: number;
|
||||
id: string;
|
||||
legacyId: string | null;
|
||||
title: string;
|
||||
description: string | null;
|
||||
videoUrl: string | null;
|
||||
thumbnailUrl: string | null;
|
||||
duration: number | null;
|
||||
knowledgeTags: unknown[];
|
||||
isGeneral: boolean;
|
||||
subjectId: string | null;
|
||||
difficulty: number | null;
|
||||
}
|
||||
|
||||
function videoSelectSql() {
|
||||
return `
|
||||
select qv.question_id as "questionId", qv.video_type as "videoType",
|
||||
qv.sort_order as "order",
|
||||
v.id, v.legacy_id as "legacyId", v.title, v.description,
|
||||
v.video_url as "videoUrl", v.thumbnail_url as "thumbnailUrl",
|
||||
v.duration_seconds as "duration", v.knowledge_tags as "knowledgeTags",
|
||||
v.is_general as "isGeneral", v.subject_id as "subjectId",
|
||||
v.difficulty
|
||||
from public.question_videos qv
|
||||
join public.video_explanations v on v.id = qv.video_id and v.tenant_id = qv.tenant_id
|
||||
`;
|
||||
}
|
||||
|
||||
export async function questionVideosRoute(ctx: RequestContext) {
|
||||
const tenantId = tenantIdFrom(ctx);
|
||||
const questionId = stringParam(ctx, 'questionId');
|
||||
if (!questionId) {
|
||||
throw new HttpError(400, 'questionId is required', 'QUESTION_ID_REQUIRED');
|
||||
}
|
||||
|
||||
const videos = await query<QuestionVideoRow>(
|
||||
`
|
||||
${videoSelectSql()}
|
||||
where qv.tenant_id = $1 and qv.question_id = $2 and v.is_active = true
|
||||
order by qv.sort_order asc, v.sort_order asc, v.created_at asc
|
||||
`,
|
||||
[tenantId, questionId],
|
||||
);
|
||||
|
||||
return { videos, total: videos.length };
|
||||
}
|
||||
|
||||
export async function questionVideosBatchRoute(ctx: RequestContext) {
|
||||
const body = await readJsonBody(ctx);
|
||||
const tenantId = tenantIdFrom(ctx);
|
||||
const questionIds = optionalStringArray(body, 'questionIds').slice(0, 50);
|
||||
|
||||
if (!questionIds.length) {
|
||||
throw new HttpError(400, 'questionIds is required', 'QUESTION_IDS_REQUIRED');
|
||||
}
|
||||
|
||||
const rows = await query<QuestionVideoRow>(
|
||||
`
|
||||
${videoSelectSql()}
|
||||
where qv.tenant_id = $1 and qv.question_id = any($2::uuid[]) and v.is_active = true
|
||||
order by qv.question_id, qv.sort_order asc, v.sort_order asc
|
||||
`,
|
||||
[tenantId, questionIds],
|
||||
);
|
||||
|
||||
const data: Record<string, { hasVideo: boolean; videos: QuestionVideoRow[] }> = {};
|
||||
for (const row of rows) {
|
||||
data[row.questionId] ||= { hasVideo: true, videos: [] };
|
||||
data[row.questionId].videos.push(row);
|
||||
}
|
||||
|
||||
return { data };
|
||||
}
|
||||
|
||||
export async function videoSearchRoute(ctx: RequestContext) {
|
||||
const tenantId = tenantIdFrom(ctx);
|
||||
const subjectId = stringParam(ctx, 'subjectId');
|
||||
const tags = stringParam(ctx, 'tags')
|
||||
.split(',')
|
||||
.map(tag => tag.trim())
|
||||
.filter(Boolean);
|
||||
const limit = intParam(ctx, 'limit', 50, 200);
|
||||
|
||||
const videos = await query(
|
||||
`
|
||||
select id, legacy_id as "legacyId", title, description,
|
||||
video_url as "videoUrl", thumbnail_url as "thumbnailUrl",
|
||||
duration_seconds as "duration", knowledge_tags as "knowledgeTags",
|
||||
is_general as "isGeneral", subject_id as "subjectId",
|
||||
difficulty, sort_order as "order", created_at as "createdAt"
|
||||
from public.video_explanations
|
||||
where tenant_id = $1
|
||||
and is_active = true
|
||||
and is_general = true
|
||||
and ($2::uuid is null or subject_id = $2::uuid)
|
||||
and ($3::text[] = '{}'::text[] or knowledge_tags ?| $3::text[])
|
||||
order by sort_order asc, created_at desc
|
||||
limit $4
|
||||
`,
|
||||
[tenantId, subjectId || null, tags, limit],
|
||||
);
|
||||
|
||||
return { videos, total: videos.length };
|
||||
}
|
||||
Reference in New Issue
Block a user