import { and, count, desc, eq } from "drizzle-orm"; import { db } from "@/lib/db"; import { mediaAsset, mediaUsage } from "@/lib/db/schema"; import type { MediaAsset, MediaUsage } from "@/lib/db/schema"; import type { MediaKind, MediaSource, MediaUsageType } from "@/lib/db/enums"; export type MediaAssetView = Pick< MediaAsset, "id" | "source" | "kind" | "url" | "fileName" | "label" | "altText" | "mimeType" | "size" | "createdAt" > & { usages: Array< Pick >; }; export type MediaOption = Pick; export type PortfolioMediaBindings = { coverAssetId: string | null; sectionAssetIds: Record; assetIds: Record; }; function mapMediaAsset( asset: MediaAsset & { usages: MediaUsage[]; }, ): MediaAssetView { return { id: asset.id, source: asset.source, kind: asset.kind, url: asset.url, fileName: asset.fileName, label: asset.label, altText: asset.altText, mimeType: asset.mimeType, size: asset.size, createdAt: asset.createdAt, usages: asset.usages.map((usage) => ({ id: usage.id, usageType: usage.usageType, entityType: usage.entityType, entityId: usage.entityId, fieldKey: usage.fieldKey, })), }; } export async function getAdminMediaAssets() { const assets = await db.query.mediaAsset.findMany({ with: { usages: { orderBy: (usage, { desc: descOrder }) => [descOrder(usage.createdAt)], }, }, orderBy: (asset, { desc: descOrder }) => [descOrder(asset.createdAt)], }); return assets.map(mapMediaAsset); } export async function getMediaOptions(filters?: { kind?: MediaKind }) { const assets = await db .select({ id: mediaAsset.id, kind: mediaAsset.kind, url: mediaAsset.url, label: mediaAsset.label, source: mediaAsset.source, }) .from(mediaAsset) .where(filters?.kind ? eq(mediaAsset.kind, filters.kind) : undefined) .orderBy(desc(mediaAsset.createdAt)); return assets; } export async function getMediaAssetById(id: string) { const asset = await db.query.mediaAsset.findFirst({ where: eq(mediaAsset.id, id), with: { usages: true, }, }); return asset ? mapMediaAsset(asset) : null; } export async function createMediaAsset(input: { source: MediaSource; kind: MediaKind; url: string; fileName: string; label: string; altText?: string | null; mimeType?: string | null; size?: number | null; }) { const [asset] = await db .insert(mediaAsset) .values({ source: input.source, kind: input.kind, url: input.url, fileName: input.fileName, label: input.label, altText: input.altText ?? null, mimeType: input.mimeType ?? null, size: input.size ?? null, }) .returning(); return asset; } export async function replaceEntityMediaUsages(input: { entityType: string; entityId: string; usages: Array<{ assetId: string; usageType: MediaUsageType; fieldKey: string; }>; }) { await db.transaction(async (tx) => { await tx .delete(mediaUsage) .where( and( eq(mediaUsage.entityType, input.entityType), eq(mediaUsage.entityId, input.entityId), ), ); if (input.usages.length === 0) { return; } await tx.insert(mediaUsage).values( input.usages.map((usage) => ({ assetId: usage.assetId, usageType: usage.usageType, entityType: input.entityType, entityId: input.entityId, fieldKey: usage.fieldKey, })), ); }); } export async function deleteEntityMediaUsages(entityType: string, entityId: string) { await db .delete(mediaUsage) .where(and(eq(mediaUsage.entityType, entityType), eq(mediaUsage.entityId, entityId))); } export async function getPortfolioMediaBindings(projectId: string): Promise { const usages = await db .select({ assetId: mediaUsage.assetId, usageType: mediaUsage.usageType, fieldKey: mediaUsage.fieldKey, }) .from(mediaUsage) .where( and(eq(mediaUsage.entityType, "portfolio-project"), eq(mediaUsage.entityId, projectId)), ); return usages.reduce( (result, usage) => { if (usage.usageType === "PORTFOLIO_COVER") { result.coverAssetId = usage.assetId; } if (usage.usageType === "PORTFOLIO_SECTION") { result.sectionAssetIds[usage.fieldKey] = usage.assetId; } if (usage.usageType === "PORTFOLIO_ASSET") { result.assetIds[usage.fieldKey] = usage.assetId; } return result; }, { coverAssetId: null, sectionAssetIds: {}, assetIds: {}, }, ); } export async function countMediaUsageReferences(assetId: string) { const [row] = await db .select({ value: count() }) .from(mediaUsage) .where(eq(mediaUsage.assetId, assetId)); return row?.value ?? 0; }