Files
imen_pishghaman/apps/api/src/sections/sections.module.ts
T

102 lines
3.3 KiB
TypeScript
Raw Normal View History

import {
BadRequestException,
Body,
Controller,
ForbiddenException,
Get,
Injectable,
Module,
NotFoundException,
Param,
Put,
ServiceUnavailableException,
} from "@nestjs/common";
import {
isSectionKey,
SECTION_KEYS,
sectionSchemas,
type SiteSections,
toIssues,
} from "@imen/shared";
import { AdminAuth, type AuthUser, CurrentUser } from "../common/auth.js";
import { AuditService, RevalidateService } from "../common/common.module.js";
import { PrismaService } from "../prisma/prisma.service.js";
@Injectable()
export class SectionsService {
constructor(
private readonly prisma: PrismaService,
private readonly audit: AuditService,
private readonly revalidate: RevalidateService,
) {}
/** All sections, parsed through their schemas so missing optional fields get defaults */
async getAll(): Promise<SiteSections> {
const rows = await this.prisma.siteSection.findMany();
const byKey = new Map(rows.map((r) => [r.key, r.data]));
const missing = SECTION_KEYS.filter((k) => !byKey.has(k));
if (missing.length) {
throw new ServiceUnavailableException(
`محتوای سایت مقداردهی نشده است (${missing.join(", ")}) — pnpm db:seed را اجرا کنید`,
);
}
return Object.fromEntries(
SECTION_KEYS.map((k) => [k, sectionSchemas[k].parse(byKey.get(k))]),
) as SiteSections;
}
async get(key: string) {
if (!isSectionKey(key)) throw new NotFoundException("بخش پیدا نشد");
const row = await this.prisma.siteSection.findUnique({ where: { key } });
if (!row) throw new NotFoundException("بخش پیدا نشد");
return { key, data: sectionSchemas[key].parse(row.data), updatedAt: row.updatedAt };
}
async update(key: string, body: unknown, user: AuthUser) {
if (!isSectionKey(key)) throw new NotFoundException("بخش پیدا نشد");
// site-wide settings (contact info, SEO defaults) are limited to admins
if (key === "settings" && user.role !== "ADMIN") {
throw new ForbiddenException("فقط مدیر کل می‌تواند تنظیمات سایت را تغییر دهد");
}
const parsed = sectionSchemas[key].safeParse(body);
if (!parsed.success) {
throw new BadRequestException({
statusCode: 400,
message: "اطلاعات واردشده معتبر نیست",
issues: toIssues(parsed.error),
});
}
const row = await this.prisma.siteSection.upsert({
where: { key },
create: { key, data: parsed.data, updatedById: user.id },
update: { data: parsed.data, updatedById: user.id },
});
this.audit.log(user.id, "update", "section", key, key);
this.revalidate.trigger();
return { key, data: parsed.data, updatedAt: row.updatedAt };
}
}
@AdminAuth()
@Controller("admin/sections")
export class SectionsController {
constructor(private readonly sections: SectionsService) {}
@Get(":key")
get(@Param("key") key: string) {
return this.sections.get(key);
}
@Put(":key")
update(@Param("key") key: string, @Body() body: unknown, @CurrentUser() user: AuthUser) {
return this.sections.update(key, body, user);
}
}
@Module({
controllers: [SectionsController],
providers: [SectionsService],
exports: [SectionsService],
})
export class SectionsModule {}