Files
GMW/services/backend/src/modules/materi/materi.repository.ts
T
mytheclipsebotreview 7f4196124d fix(ci): lint and a11y fixes unblocks GHA deploy
- materi/new/page.tsx: add htmlFor+id pairs for all 5 form labels (a11y)
- biome --write --unsafe: fix useTemplate, useLiteralKeys, import sort
  across materi module files (backend + frontend)
- These pre-existing lint errors from 0aa893a blocked the deploy pipeline
2026-08-20 18:45:09 +07:00

148 lines
3.9 KiB
TypeScript

import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
import { getDatabase } from "@/shared/database/index.js";
import {
type MateriDocument,
materiDocumentsTable,
} from "@/shared/database/schema.js";
import type { MateriQueryInput } from "./materi.schema.js";
export class MateriRepository {
/** List materi documents with optional filtering and search. */
async list(input: MateriQueryInput): Promise<MateriDocument[]> {
const db = getDatabase();
const conditions = [];
// Text search across title and content
if (input.search) {
const term = `%${input.search}%`;
conditions.push(
or(
ilike(materiDocumentsTable.title, term),
ilike(materiDocumentsTable.content, term),
),
);
}
// Category filter
if (input.category) {
conditions.push(eq(materiDocumentsTable.category, input.category));
}
// Owner filter
if (input.ownerId) {
conditions.push(eq(materiDocumentsTable.owner_user_id, input.ownerId));
}
// Only public (if requested)
if (input.onlyPublic) {
conditions.push(eq(materiDocumentsTable.is_public, true));
}
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
const result = await db
.select()
.from(materiDocumentsTable)
.where(whereClause)
.orderBy(desc(materiDocumentsTable.created_at))
.limit(input.limit);
return result;
}
/** Get a single materi by id. */
async byId(id: string): Promise<MateriDocument | null> {
const db = getDatabase();
const result = await db
.select()
.from(materiDocumentsTable)
.where(eq(materiDocumentsTable.id, id))
.limit(1);
return result[0] ?? null;
}
/** Create a new materi document. */
async create(data: {
title: string;
description?: string | null;
content: string;
category: string;
tags: string[];
ownerUserId: string;
guildId?: string | null;
channelId?: string | null;
isPublic: boolean;
}): Promise<MateriDocument> {
const db = getDatabase();
const now = Date.now();
const result = await db
.insert(materiDocumentsTable)
.values({
title: data.title,
description: data.description ?? null,
content: data.content,
category: data.category,
tags: data.tags,
owner_user_id: data.ownerUserId,
guild_id: data.guildId ?? null,
channel_id: data.channelId ?? null,
is_public: data.isPublic,
view_count: 0,
created_at: now,
updated_at: now,
})
.returning();
return result[0]!;
}
/** Update an existing materi. */
async update(
id: string,
data: Partial<{
title: string;
description?: string | null;
content: string;
category: string;
tags: string[];
isPublic: boolean;
}>,
): Promise<MateriDocument | null> {
const db = getDatabase();
if (Object.keys(data).length === 0) return this.byId(id);
const result = await db
.update(materiDocumentsTable)
.set({
...data,
updated_at: Date.now(),
})
.where(eq(materiDocumentsTable.id, id))
.returning();
return result[0] ?? null;
}
/** Delete a materi. */
async delete(id: string): Promise<boolean> {
const db = getDatabase();
const result = await db
.delete(materiDocumentsTable)
.where(eq(materiDocumentsTable.id, id))
.returning({ deletedId: materiDocumentsTable.id });
return result.length > 0;
}
/** Increment view count (for analytics). */
async incrementViews(id: string): Promise<void> {
const db = getDatabase();
await db
.update(materiDocumentsTable)
.set({
view_count: sql`${materiDocumentsTable.view_count} + 1`,
})
.where(eq(materiDocumentsTable.id, id));
}
}
export const materiRepository = new MateriRepository();