diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 012b51a..9cafa8a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,6 +14,193 @@ datasource db { url = env("DATABASE_URL") } +// Portfolio +model PortfolioImage { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + fileKey String @unique + originalFile String @unique + fileType String + name String + fileSize Int + needsWork Boolean @default(true) + nsfw Boolean @default(false) + published Boolean @default(false) + setAsHeader Boolean @default(false) + + altText String? + description String? + month Int? + year Int? + creationDate DateTime? + + albumId String? + typeId String? + album PortfolioAlbum? @relation(fields: [albumId], references: [id]) + type PortfolioType? @relation(fields: [typeId], references: [id]) + + metadata ImageMetadata? + + categories PortfolioCategory[] + colors ImageColor[] + sortContexts PortfolioSortContext[] + tags PortfolioTag[] + variants ImageVariant[] +} + +model PortfolioAlbum { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + name String @unique + slug String @unique + + description String? + + images PortfolioImage[] +} + +model PortfolioType { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + name String @unique + slug String @unique + + description String? + + images PortfolioImage[] +} + +model PortfolioCategory { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + name String @unique + slug String @unique + + description String? + + images PortfolioImage[] +} + +model PortfolioTag { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sortIndex Int @default(0) + + name String @unique + slug String @unique + + description String? + + images PortfolioImage[] +} + +model PortfolioSortContext { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + year String + albumId String + type String + group String + sortOrder Int + + imageId String + image PortfolioImage @relation(fields: [imageId], references: [id]) + + @@unique([imageId, year, albumId, type, group]) +} + +model Color { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + type String + + hex String? + blue Int? + green Int? + red Int? + + images ImageColor[] +} + +model ImageColor { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + imageId String + colorId String + type String + + image PortfolioImage @relation(fields: [imageId], references: [id]) + color Color @relation(fields: [colorId], references: [id]) + + @@unique([imageId, type]) +} + +model ImageMetadata { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + imageId String @unique + depth String + format String + space String + channels Int + height Int + width Int + + autoOrientH Int? + autoOrientW Int? + bitsPerSample Int? + density Int? + hasAlpha Boolean? + hasProfile Boolean? + isPalette Boolean? + isProgressive Boolean? + + image PortfolioImage @relation(fields: [imageId], references: [id]) +} + +model ImageVariant { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + imageId String + s3Key String + type String + height Int + width Int + + fileExtension String? + mimeType String? + url String? + sizeBytes Int? + + image PortfolioImage @relation(fields: [imageId], references: [id]) + + @@unique([imageId, type]) +} + model CommissionType { id String @id @default(cuid()) createdAt DateTime @default(now()) @@ -149,137 +336,3 @@ model CommissionRequest { option CommissionOption? @relation(fields: [optionId], references: [id]) type CommissionType? @relation(fields: [typeId], references: [id]) } - -model PortfolioImage { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - sortIndex Int @default(0) - - fileKey String @unique - originalFile String @unique - nsfw Boolean @default(false) - published Boolean @default(false) - setAsHeader Boolean @default(false) - - altText String? - description String? - fileType String? - name String? - slug String? - type String? - fileSize Int? - creationDate DateTime? - - metadata ImageMetadata? - - categories PortfolioCategory[] - colors ImageColor[] - tags PortfolioTag[] - variants ImageVariant[] -} - -model PortfolioCategory { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - sortIndex Int @default(0) - - name String @unique - - slug String? - description String? - - images PortfolioImage[] -} - -model PortfolioTag { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - sortIndex Int @default(0) - - name String @unique - - slug String? - description String? - - images PortfolioImage[] -} - -model Color { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - name String @unique - type String - - hex String? - blue Int? - green Int? - red Int? - - images ImageColor[] -} - -model ImageColor { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - imageId String - colorId String - type String - - image PortfolioImage @relation(fields: [imageId], references: [id]) - color Color @relation(fields: [colorId], references: [id]) - - @@unique([imageId, type]) -} - -model ImageMetadata { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - imageId String @unique - depth String - format String - space String - channels Int - height Int - width Int - - autoOrientH Int? - autoOrientW Int? - bitsPerSample Int? - density Int? - hasAlpha Boolean? - hasProfile Boolean? - isPalette Boolean? - isProgressive Boolean? - - image PortfolioImage @relation(fields: [imageId], references: [id]) -} - -model ImageVariant { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - imageId String - s3Key String - type String - height Int - width Int - - fileExtension String? - mimeType String? - url String? - sizeBytes Int? - - image PortfolioImage @relation(fields: [imageId], references: [id]) - - @@unique([imageId, type]) -} diff --git a/src/actions/portfolio/getJustifiedImages.ts b/src/actions/portfolio/getJustifiedImages.ts index 190189b..8b49590 100644 --- a/src/actions/portfolio/getJustifiedImages.ts +++ b/src/actions/portfolio/getJustifiedImages.ts @@ -1,40 +1,53 @@ -"use server"; +// "use server"; + +// import prisma from "@/lib/prisma"; + +// export async function getJustifiedImages() { +// const images = await prisma.portfolioImage.findMany({ +// where: { +// variants: { +// some: { type: "resized" }, +// }, +// }, +// include: { +// variants: true, +// colors: { include: { color: true } }, +// }, +// }); + +// return images +// .map((img) => { +// const variant = img.variants.find((v) => v.type === "resized"); +// if (!variant || !variant.width || !variant.height) return null; + +// const bg = img.colors.find((c) => c.type === "Vibrant")?.color.hex ?? "#e5e7eb"; + +// return { +// id: img.id, +// fileKey: img.fileKey, +// altText: img.altText ?? img.name ?? "", +// backgroundColor: bg, +// width: variant.width, +// height: variant.height, +// url: variant.url ?? `/api/image/resized/${img.fileKey}.webp`, +// }; +// }) +// .filter(Boolean) as JustifiedInputImage[]; +// } + +// export interface JustifiedInputImage { +// id: string; +// url: string; +// altText: string; +// backgroundColor: string; +// width: number; +// height: number; +// } + +"use server" import prisma from "@/lib/prisma"; -export async function getJustifiedImages() { - const images = await prisma.portfolioImage.findMany({ - where: { - variants: { - some: { type: "resized" }, - }, - }, - include: { - variants: true, - colors: { include: { color: true } }, - }, - }); - - return images - .map((img) => { - const variant = img.variants.find((v) => v.type === "resized"); - if (!variant || !variant.width || !variant.height) return null; - - const bg = img.colors.find((c) => c.type === "Vibrant")?.color.hex ?? "#e5e7eb"; - - return { - id: img.id, - fileKey: img.fileKey, - altText: img.altText ?? img.name ?? "", - backgroundColor: bg, - width: variant.width, - height: variant.height, - url: variant.url ?? `/api/image/resized/${img.fileKey}.webp`, - }; - }) - .filter(Boolean) as JustifiedInputImage[]; -} - export interface JustifiedInputImage { id: string; url: string; @@ -42,4 +55,165 @@ export interface JustifiedInputImage { backgroundColor: string; width: number; height: number; -} \ No newline at end of file + fileKey: string; +} + +interface Variant { + type: string; + url?: string | null; + width: number; + height: number; +} + +interface Color { + type: string; + color: { + hex: string | null; + }; +} + +interface PortfolioImage { + id: string; + fileKey: string; + name?: string | null; + altText?: string | null; + variants: Variant[]; + colors: Color[]; +} + +interface PortfolioImageSortContext { + image: PortfolioImage; + group: "highlighted" | "featured" | "default" | string; + sortOrder: number; +} + +// const groupPriority = { +// highlighted: 0, +// featured: 1, +// default: 2, +// } as const; + +function shuffleImages(arr: T[]): T[] { + const shuffled = [...arr]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } + return shuffled; +} + +// Stub: Replace this with your actual DB or API call +async function getImageSortContext(params: { + year?: string; + albumId?: string; + type: string; +}): Promise { + const { year, albumId, type } = params; + + const typeEntry = await prisma.portfolioType.findUnique({ + where: { slug: type }, + // select: { id: true }, + }); + if (!typeEntry) return []; + const typeId = typeEntry.id; + + const sortContexts = await prisma.portfolioSortContext.findMany({ + where: { + type: typeId, + ...(year ? { year: year } : {}), + ...(albumId ? { albumId } : {}), + image: { + // published: true, + }, + }, + include: { + image: { + include: { + variants: true, + colors: { + include: { + color: true, + }, + }, + }, + }, + }, + }); + + return sortContexts; +} + +export async function getJustifiedImages( + year: string | null | undefined, + albumId: string | null | undefined, + type: string, + shouldShuffle = false +): Promise { + if (!year && !albumId) return null; + + const sortContexts = await getImageSortContext({ + year: year || undefined, + albumId: albumId || undefined, + type, + }); + + // const validImages = sortContexts + // .filter((ctx) => ctx.image && ctx.image.variants.some((v) => v.type === "resized")) + // .sort((a, b) => { + // const groupA = groupPriority[a.group as keyof typeof groupPriority] ?? 3; + // const groupB = groupPriority[b.group as keyof typeof groupPriority] ?? 3; + // if (groupA !== groupB) return groupA - groupB; + // return a.sortOrder - b.sortOrder; + // }); + + type GroupName = "highlighted" | "featured" | "default"; + + const grouped: Record = { + highlighted: [], + featured: [], + default: [], + }; + + for (const ctx of sortContexts) { + const rawGroup = ctx.group?.toLowerCase() ?? "default"; + const group = ["highlighted", "featured", "default"].includes(rawGroup) + ? (rawGroup as GroupName) + : "default"; + + grouped[group].push(ctx); + } + + const processGroup = (list: PortfolioImageSortContext[]) => + shouldShuffle + ? shuffleImages(list) + : [...list].sort((a, b) => a.sortOrder - b.sortOrder); + + const finalList = [ + ...processGroup(grouped.highlighted), + ...processGroup(grouped.featured), + ...processGroup(grouped.default), + ]; + + const output: JustifiedInputImage[] = []; + + for (const ctx of finalList) { + const img = ctx.image; + const variant = img.variants.find((v) => v.type === "resized"); + + if (!variant || !variant.width || !variant.height) continue; + + const bg = img.colors.find((c) => c.type === "Vibrant")?.color.hex ?? "#e5e7eb"; + + output.push({ + id: img.id, + fileKey: img.fileKey, + altText: img.altText ?? img.name ?? "", + backgroundColor: bg, + width: variant.width, + height: variant.height, + url: variant.url ?? `/api/image/resized/${img.fileKey}.webp`, + }); + } + + return output; +} diff --git a/src/actions/portfolio/getPortfolioFilters.ts b/src/actions/portfolio/getPortfolioFilters.ts new file mode 100644 index 0000000..56b67c5 --- /dev/null +++ b/src/actions/portfolio/getPortfolioFilters.ts @@ -0,0 +1,42 @@ +"use server" + +import prisma from "@/lib/prisma"; + +export async function getPortfolioFilters() { + const albums = await prisma.portfolioAlbum.findMany({ + where: { + // images: { + // some: { + // published: true, + // }, + // }, + }, + select: { + id: true, + name: true, + }, + orderBy: { + sortIndex: "asc", + }, + }); + + const yearsRaw = await prisma.portfolioImage.findMany({ + where: { + // published: true, + year: { + not: null, + }, + }, + distinct: ["year"], + select: { + year: true, + }, + }); + + const years = yearsRaw + .map((y) => y.year!) + .sort((a, b) => b - a) + .map(String); + + return { albums, years }; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7c25185..d4698ca 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -42,13 +42,13 @@ export default function RootLayout({
-
+
-
+
{children}
-