import { Injectable, NotFoundException } from '@nestjs/common'; import type { ResourceBizType, ResourceMediaType, ResourceOwnerType } from '@prisma/client'; import { Prisma } from '@prisma/client'; import { randomUUID } from 'crypto'; import { PrismaService } from '../../common/prisma/prisma.module'; import { serializeBigInt } from '../../common/decorators/current-user.decorator'; import type { ResourceListQueryDto } from './dto/common-query.dto'; import type { RegisterResourceDto, UpdateResourceDto, UploadTokenDto } from './dto/common-mutate.dto'; @Injectable() export class ResourceService { constructor(private readonly prisma: PrismaService) {} getUploadToken(dto: UploadTokenDto) { const bucket = process.env.OSS_BUCKET || 'mock-dukang'; const ext = dto.fileName.includes('.') ? dto.fileName.split('.').pop() : 'bin'; const key = `uploads/${dto.bizType.toLowerCase()}/${Date.now()}-${randomUUID().slice(0, 8)}.${ext}`; const cdnBase = process.env.OSS_CDN_BASE || 'https://mock-cdn.dukang.local'; return { bucket, region: process.env.OSS_REGION || 'oss-cn-hangzhou', ossKey: key, url: `${cdnBase}/${key}`, mock: true, expireAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(), mediaType: dto.mediaType, bizType: dto.bizType, }; } async register(dto: RegisterResourceDto) { const resource = await this.prisma.commonResource.create({ data: { ownerType: dto.ownerType as ResourceOwnerType, ownerId: BigInt(dto.ownerId), bizType: dto.bizType as ResourceBizType, mediaType: dto.mediaType as ResourceMediaType, ossBucket: dto.ossBucket ?? process.env.OSS_BUCKET ?? 'mock-dukang', ossKey: dto.ossKey, url: dto.url, fileName: dto.fileName, sortOrder: dto.sortOrder ?? 0, }, }); return serializeBigInt(resource); } async list(query: ResourceListQueryDto) { const page = query.page ?? 1; const pageSize = query.pageSize ?? 20; const where: Prisma.CommonResourceWhereInput = { status: (query.status ?? 'ACTIVE') as Prisma.EnumResourceStatusFilter['equals'], }; if (query.ownerType) where.ownerType = query.ownerType as Prisma.EnumResourceOwnerTypeFilter['equals']; if (query.ownerId) where.ownerId = BigInt(query.ownerId); if (query.bizType) where.bizType = query.bizType as Prisma.EnumResourceBizTypeFilter['equals']; const [items, total] = await Promise.all([ this.prisma.commonResource.findMany({ where, orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }], skip: (page - 1) * pageSize, take: pageSize, }), this.prisma.commonResource.count({ where }), ]); return serializeBigInt({ items, total, page, pageSize }); } async detail(id: bigint) { const resource = await this.prisma.commonResource.findUnique({ where: { id } }); if (!resource) throw new NotFoundException('资源不存在'); return serializeBigInt(resource); } async update(id: bigint, dto: UpdateResourceDto) { await this.detail(id); const resource = await this.prisma.commonResource.update({ where: { id }, data: { ...(dto.url !== undefined ? { url: dto.url, ossKey: dto.url } : {}), ...(dto.mediaType !== undefined ? { mediaType: dto.mediaType as 'IMAGE' | 'VIDEO' | 'FILE' } : {}), ...(dto.sortOrder !== undefined ? { sortOrder: dto.sortOrder } : {}), ...(dto.status !== undefined ? { status: dto.status as 'ACTIVE' | 'DELETED' } : {}), }, }); return serializeBigInt(resource); } async remove(id: bigint) { await this.detail(id); await this.prisma.commonResource.update({ where: { id }, data: { status: 'DELETED' }, }); return { ok: true }; } }