From 50617e55781613554a12f4bb2d668725fd8295ef Mon Sep 17 00:00:00 2001 From: Citali Date: Sat, 28 Jun 2025 16:41:01 +0200 Subject: [PATCH] Refactor colors and palettes --- .../migration.sql | 172 ++++++++++++++++ .../migration.sql | 11 ++ prisma/schema.prisma | 183 +++++++++++------- src/actions/albums/updateAlbum.ts | 3 +- src/actions/galleries/updateGallery.ts | 1 + src/actions/images/generateExtractColors.ts | 65 ++++--- src/actions/images/generateImageColors.ts | 74 ++++--- src/actions/images/generatePalette.ts | 19 +- src/app/albums/edit/[id]/page.tsx | 4 + src/app/albums/page.tsx | 2 +- src/app/artists/page.tsx | 5 +- src/app/categories/page.tsx | 3 +- src/app/galleries/edit/[id]/page.tsx | 3 +- src/app/galleries/page.tsx | 7 +- src/app/images/edit/[id]/page.tsx | 36 ++-- src/app/palettes/[id]/page.tsx | 31 +++ src/app/palettes/page.tsx | 23 +++ src/components/albums/edit/EditAlbumForm.tsx | 52 ++++- src/components/artists/list/ListArtists.tsx | 7 +- .../categories/list/ListCategories.tsx | 7 +- .../galleries/edit/EditGalleryForm.tsx | 61 +++++- .../galleries/list/ListGalleries.tsx | 11 +- src/components/global/TopNav.tsx | 10 + src/components/images/ImageCard.tsx | 39 ++++ src/components/images/edit/EditImageForm.tsx | 52 +++-- src/components/images/edit/ExtractColors.tsx | 17 +- src/components/images/edit/ImageColors.tsx | 23 ++- src/components/images/edit/ImagePalettes.tsx | 18 +- src/components/palettes/list/ListPalettes.tsx | 39 ++++ .../palettes/single/DisplayPalette.tsx | 45 +++++ src/schemas/albums/albumSchema.ts | 1 + src/schemas/galleries/gallerySchema.ts | 1 + src/schemas/images/imageSchema.ts | 6 +- src/utils/uploadHelper.ts | 84 ++++---- 34 files changed, 872 insertions(+), 243 deletions(-) create mode 100644 prisma/migrations/20250628110645_image_palette_type/migration.sql create mode 100644 prisma/migrations/20250628123020_image_palette_type/migration.sql create mode 100644 src/app/palettes/[id]/page.tsx create mode 100644 src/app/palettes/page.tsx create mode 100644 src/components/images/ImageCard.tsx create mode 100644 src/components/palettes/list/ListPalettes.tsx create mode 100644 src/components/palettes/single/DisplayPalette.tsx diff --git a/prisma/migrations/20250628110645_image_palette_type/migration.sql b/prisma/migrations/20250628110645_image_palette_type/migration.sql new file mode 100644 index 0000000..6269956 --- /dev/null +++ b/prisma/migrations/20250628110645_image_palette_type/migration.sql @@ -0,0 +1,172 @@ +/* + Warnings: + + - You are about to drop the column `type` on the `ColorPalette` table. All the data in the column will be lost. + - You are about to drop the column `blue` on the `ImageColor` table. All the data in the column will be lost. + - You are about to drop the column `green` on the `ImageColor` table. All the data in the column will be lost. + - You are about to drop the column `hex` on the `ImageColor` table. All the data in the column will be lost. + - You are about to drop the column `name` on the `ImageColor` table. All the data in the column will be lost. + - You are about to drop the column `red` on the `ImageColor` table. All the data in the column will be lost. + - You are about to drop the `PixelSummary` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `ThemeSeed` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `_ImagePalettes` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `_ImageToExtractColor` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `_ImageToImageColor` table. If the table is not empty, all the data it contains will be lost. + - A unique constraint covering the columns `[galleryId,slug]` on the table `Album` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[imageId,type]` on the table `ImageColor` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[imageId]` on the table `ImageMetadata` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[imageId]` on the table `ImageStats` will be added. If there are existing duplicate values, this will fail. + - Added the required column `colorId` to the `ImageColor` table without a default value. This is not possible if the table is not empty. + - Added the required column `imageId` to the `ImageColor` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "PixelSummary" DROP CONSTRAINT "PixelSummary_imageId_fkey"; + +-- DropForeignKey +ALTER TABLE "ThemeSeed" DROP CONSTRAINT "ThemeSeed_imageId_fkey"; + +-- DropForeignKey +ALTER TABLE "_ImagePalettes" DROP CONSTRAINT "_ImagePalettes_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_ImagePalettes" DROP CONSTRAINT "_ImagePalettes_B_fkey"; + +-- DropForeignKey +ALTER TABLE "_ImageToExtractColor" DROP CONSTRAINT "_ImageToExtractColor_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_ImageToExtractColor" DROP CONSTRAINT "_ImageToExtractColor_B_fkey"; + +-- DropForeignKey +ALTER TABLE "_ImageToImageColor" DROP CONSTRAINT "_ImageToImageColor_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_ImageToImageColor" DROP CONSTRAINT "_ImageToImageColor_B_fkey"; + +-- DropIndex +DROP INDEX "ImageColor_name_key"; + +-- AlterTable +ALTER TABLE "Album" ADD COLUMN "coverImageId" TEXT; + +-- AlterTable +ALTER TABLE "ColorPalette" DROP COLUMN "type"; + +-- AlterTable +ALTER TABLE "Gallery" ADD COLUMN "coverImageId" TEXT; + +-- AlterTable +ALTER TABLE "Image" ADD COLUMN "source" TEXT; + +-- AlterTable +ALTER TABLE "ImageColor" DROP COLUMN "blue", +DROP COLUMN "green", +DROP COLUMN "hex", +DROP COLUMN "name", +DROP COLUMN "red", +ADD COLUMN "colorId" TEXT NOT NULL, +ADD COLUMN "imageId" TEXT NOT NULL; + +-- DropTable +DROP TABLE "PixelSummary"; + +-- DropTable +DROP TABLE "ThemeSeed"; + +-- DropTable +DROP TABLE "_ImagePalettes"; + +-- DropTable +DROP TABLE "_ImageToExtractColor"; + +-- DropTable +DROP TABLE "_ImageToImageColor"; + +-- CreateTable +CREATE TABLE "Color" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "name" TEXT NOT NULL, + "type" TEXT NOT NULL, + "hex" TEXT, + "blue" INTEGER, + "green" INTEGER, + "red" INTEGER, + + CONSTRAINT "Color_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ImagePalette" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "imageId" TEXT NOT NULL, + "paletteId" TEXT NOT NULL, + "type" TEXT NOT NULL, + + CONSTRAINT "ImagePalette_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ImageExtractColor" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "imageId" TEXT NOT NULL, + "extractId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "colorId" TEXT, + + CONSTRAINT "ImageExtractColor_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Color_name_key" ON "Color"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "ImagePalette_imageId_type_key" ON "ImagePalette"("imageId", "type"); + +-- CreateIndex +CREATE UNIQUE INDEX "ImageExtractColor_imageId_type_key" ON "ImageExtractColor"("imageId", "type"); + +-- CreateIndex +CREATE UNIQUE INDEX "Album_galleryId_slug_key" ON "Album"("galleryId", "slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "ImageColor_imageId_type_key" ON "ImageColor"("imageId", "type"); + +-- CreateIndex +CREATE UNIQUE INDEX "ImageMetadata_imageId_key" ON "ImageMetadata"("imageId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ImageStats_imageId_key" ON "ImageStats"("imageId"); + +-- AddForeignKey +ALTER TABLE "Gallery" ADD CONSTRAINT "Gallery_coverImageId_fkey" FOREIGN KEY ("coverImageId") REFERENCES "Image"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Album" ADD CONSTRAINT "Album_coverImageId_fkey" FOREIGN KEY ("coverImageId") REFERENCES "Image"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ImagePalette" ADD CONSTRAINT "ImagePalette_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "Image"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ImagePalette" ADD CONSTRAINT "ImagePalette_paletteId_fkey" FOREIGN KEY ("paletteId") REFERENCES "ColorPalette"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ImageExtractColor" ADD CONSTRAINT "ImageExtractColor_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "Image"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ImageExtractColor" ADD CONSTRAINT "ImageExtractColor_extractId_fkey" FOREIGN KEY ("extractId") REFERENCES "ExtractColor"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ImageExtractColor" ADD CONSTRAINT "ImageExtractColor_colorId_fkey" FOREIGN KEY ("colorId") REFERENCES "Color"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ImageColor" ADD CONSTRAINT "ImageColor_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "Image"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ImageColor" ADD CONSTRAINT "ImageColor_colorId_fkey" FOREIGN KEY ("colorId") REFERENCES "Color"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20250628123020_image_palette_type/migration.sql b/prisma/migrations/20250628123020_image_palette_type/migration.sql new file mode 100644 index 0000000..89ec70c --- /dev/null +++ b/prisma/migrations/20250628123020_image_palette_type/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `colorId` on the `ImageExtractColor` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "ImageExtractColor" DROP CONSTRAINT "ImageExtractColor_colorId_fkey"; + +-- AlterTable +ALTER TABLE "ImageExtractColor" DROP COLUMN "colorId"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b2f05ac..398340b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,8 +24,8 @@ model Gallery { description String? - // coverImageId String? - // coverImage Image? @relation("GalleryCoverImage", fields: [coverImageId], references: [id]) + coverImageId String? + coverImage Image? @relation("GalleryCoverImage", fields: [coverImageId], references: [id]) albums Album[] } @@ -35,17 +35,19 @@ model Album { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - name String slug String + name String description String? - // coverImageId String? - galleryId String? - // coverImage Image? @relation("AlbumCoverImage", fields: [coverImageId], references: [id]) - gallery Gallery? @relation(fields: [galleryId], references: [id]) + coverImageId String? + galleryId String? + coverImage Image? @relation("AlbumCoverImage", fields: [coverImageId], references: [id]) + gallery Gallery? @relation(fields: [galleryId], references: [id]) images Image[] + + @@unique([galleryId, slug]) } model Artist { @@ -77,6 +79,30 @@ model Social { artist Artist? @relation(fields: [artistId], references: [id]) } +model Category { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + + description String? + + images Image[] @relation("ImageCategories") +} + +model Tag { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String @unique + + description String? + + images Image[] @relation("ImageTags") +} + model Image { id String @id @default(cuid()) createdAt DateTime @default(now()) @@ -91,6 +117,7 @@ model Image { description String? fileType String? imageData String? + source String? creationMonth Int? creationYear Int? fileSize Int? @@ -100,22 +127,22 @@ model Image { artistId String? album Album? @relation(fields: [albumId], references: [id]) artist Artist? @relation(fields: [artistId], references: [id]) - // sourceId String? - // source Source? @relation(fields: [sourceId], references: [id]) - metadata ImageMetadata[] - pixels PixelSummary[] - stats ImageStats[] - theme ThemeSeed[] - variants ImageVariant[] + metadata ImageMetadata? + stats ImageStats? - // albumCover Album[] @relation("AlbumCoverImage") - // galleryCover Gallery[] @relation("GalleryCoverImage") - categories Category[] @relation("ImageCategories") - colors ImageColor[] @relation("ImageToImageColor") - extractColors ExtractColor[] @relation("ImageToExtractColor") - palettes ColorPalette[] @relation("ImagePalettes") - tags Tag[] @relation("ImageTags") + colors ImageColor[] + extractColors ImageExtractColor[] + palettes ImagePalette[] + variants ImageVariant[] + // pixels PixelSummary[] + // theme ThemeSeed[] + albumCover Album[] @relation("AlbumCoverImage") + galleryCover Gallery[] @relation("GalleryCoverImage") + categories Category[] @relation("ImageCategories") + // colors ImageColor[] @relation("ImageToImageColor") + tags Tag[] @relation("ImageTags") + // palettes ColorPalette[] @relation("ImagePalettes") } model ImageMetadata { @@ -123,7 +150,7 @@ model ImageMetadata { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - imageId String + imageId String @unique depth String format String space String @@ -148,7 +175,7 @@ model ImageStats { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - imageId String + imageId String @unique entropy Float sharpness Float dominantB Int @@ -184,10 +211,10 @@ model ColorPalette { updatedAt DateTime @updatedAt name String - type String items ColorPaletteItem[] - images Image[] @relation("ImagePalettes") + images ImagePalette[] + // images Image[] @relation("ImagePalettes") } model ColorPaletteItem { @@ -217,10 +244,11 @@ model ExtractColor { hue Float? saturation Float? - images Image[] @relation("ImageToExtractColor") + // images Image[] @relation("ImageToExtractColor") + images ImageExtractColor[] } -model ImageColor { +model Color { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -233,53 +261,74 @@ model ImageColor { green Int? red Int? - images Image[] @relation("ImageToImageColor") + images ImageColor[] } -model ThemeSeed { +// model ThemeSeed { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt + +// imageId String +// seedHex String + +// image Image @relation(fields: [imageId], references: [id]) +// } + +// model PixelSummary { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt + +// imageId String +// channels Int +// height Int +// width Int + +// image Image @relation(fields: [imageId], references: [id]) +// } + +model ImagePalette { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + imageId String + paletteId String + type String + + image Image @relation(fields: [imageId], references: [id]) + palette ColorPalette @relation(fields: [paletteId], references: [id]) + + @@unique([imageId, type]) +} + +model ImageExtractColor { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + imageId String + extractId String + type String + + image Image @relation(fields: [imageId], references: [id]) + extract ExtractColor @relation(fields: [extractId], references: [id]) + + @@unique([imageId, type]) +} + +model ImageColor { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt imageId String - seedHex String + colorId String + type String image Image @relation(fields: [imageId], references: [id]) -} - -model PixelSummary { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - imageId String - channels Int - height Int - width Int - - image Image @relation(fields: [imageId], references: [id]) -} - -model Category { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - name String @unique - - description String? - - images Image[] @relation("ImageCategories") -} - -model Tag { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - name String @unique - - description String? - - images Image[] @relation("ImageTags") + color Color @relation(fields: [colorId], references: [id]) + + @@unique([imageId, type]) } diff --git a/src/actions/albums/updateAlbum.ts b/src/actions/albums/updateAlbum.ts index d3fc4d4..0e457a6 100644 --- a/src/actions/albums/updateAlbum.ts +++ b/src/actions/albums/updateAlbum.ts @@ -16,7 +16,8 @@ export async function updateAlbum( name: values.name, slug: values.slug, description: values.description, - galleryId: values.galleryId + galleryId: values.galleryId, + coverImageId: values.coverImageId } }) } \ No newline at end of file diff --git a/src/actions/galleries/updateGallery.ts b/src/actions/galleries/updateGallery.ts index 17cc2c7..1f00777 100644 --- a/src/actions/galleries/updateGallery.ts +++ b/src/actions/galleries/updateGallery.ts @@ -16,6 +16,7 @@ export async function updateGallery( name: values.name, slug: values.slug, description: values.description, + coverImageId: values.coverImageId } }) } \ No newline at end of file diff --git a/src/actions/images/generateExtractColors.ts b/src/actions/images/generateExtractColors.ts index d29d3a0..ddd2e7f 100644 --- a/src/actions/images/generateExtractColors.ts +++ b/src/actions/images/generateExtractColors.ts @@ -16,15 +16,16 @@ export async function generateExtractColors(imageId: string, fileKey: string) { metadata: true } }) - const buffer = await getImageBufferFromS3(fileKey); if(!image) throw new Error("Image not found"); - + + const buffer = await getImageBufferFromS3(fileKey); + const format = image.metadata?.format || "jpeg"; const imageDataUrl = `data:${image.fileType};base64,${buffer.toString("base64")}`; const pixels = await new Promise>((resolve, reject) => { - getPixels(imageDataUrl, 'image/' + image.metadata[0].format || "image/jpeg", (err, pixels) => { + getPixels(imageDataUrl, `image/${format}`, (err, result) => { if (err) reject(err); - else resolve(pixels); + else resolve(result); }); }); @@ -34,36 +35,46 @@ export async function generateExtractColors(imageId: string, fileKey: string) { height: pixels.shape[1] }); + let typeIndex = 0; + for (const c of extracted) { const name = generateExtractColorName(c.hex, c.hue, c.saturation, c.area); - await prisma.image.update({ - where: { id: imageId }, - data: { - extractColors: { - connectOrCreate: { - where: { name }, - create: { - name, - hex: c.hex, - red: c.red, - green: c.green, - blue: c.blue, - hue: c.hue, - saturation: c.saturation, - area: c.area, - }, - }, + const extract = await prisma.extractColor.upsert({ + where: { name }, + create: { + name, + hex: c.hex, + red: c.red, + green: c.green, + blue: c.blue, + hue: c.hue, + saturation: c.saturation, + area: c.area, + }, + update: {}, + }); + + await prisma.imageExtractColor.upsert({ + where: { + imageId_type: { + imageId, + type: `color-${typeIndex}`, }, }, + create: { + imageId, + extractId: extract.id, + type: `color-${typeIndex}`, + }, + update: {}, }); + + typeIndex++; } - return await prisma.extractColor.findMany({ - where: { - images: { - some: { id: imageId }, - }, - }, + return await prisma.imageExtractColor.findMany({ + where: { imageId }, + include: { extract: true }, }); } \ No newline at end of file diff --git a/src/actions/images/generateImageColors.ts b/src/actions/images/generateImageColors.ts index 6eebcec..7dea03d 100644 --- a/src/actions/images/generateImageColors.ts +++ b/src/actions/images/generateImageColors.ts @@ -10,45 +10,57 @@ export async function generateImageColors(imageId: string, fileKey: string) { const buffer = await getImageBufferFromS3(fileKey); const palette = await Vibrant.from(buffer).getPalette(); - const vibrantHexes = Object.fromEntries( - Object.entries(palette).map(([key, swatch]) => { - const castSwatch = swatch as VibrantSwatch | null; - const rgb = castSwatch?._rgb; - const hex = castSwatch?.hex || (rgb ? rgbToHex(rgb) : undefined); - return [key, hex]; - }) - ); + const vibrantHexes = Object.entries(palette).map(([key, swatch]) => { + const castSwatch = swatch as VibrantSwatch | null; + const rgb = castSwatch?._rgb; + const hex = castSwatch?.hex || (rgb ? rgbToHex(rgb) : undefined); + return { type: key, hex }; + }); - for (const [type, hex] of Object.entries(vibrantHexes)) { + for (const { type, hex } of vibrantHexes) { if (!hex) continue; + const [r, g, b] = hex.match(/\w\w/g)!.map((h) => parseInt(h, 16)); const name = generateColorName(hex); - await prisma.image.update({ - where: { id: imageId }, - data: { - colors: { - connectOrCreate: { - where: { name: name }, - create: { - name: name, - type: type, - hex: hex, - red: r, - green: g, - blue: b, - } - } - } + const color = await prisma.color.upsert({ + where: { name }, + create: { + name, + type, + hex, + red: r, + green: g, + blue: b, + }, + update: { + hex, + red: r, + green: g, + blue: b, }, }); - } + + await prisma.imageColor.upsert({ + where: { + imageId_type: { + imageId, + type, + }, + }, + create: { + imageId, + colorId: color.id, + type, + }, + update: { + colorId: color.id, + }, + }); + } return await prisma.imageColor.findMany({ - where: { - images: { - some: { id: imageId }, - }, - }, + where: { imageId }, + include: { color: true }, }); } \ No newline at end of file diff --git a/src/actions/images/generatePalette.ts b/src/actions/images/generatePalette.ts index e69b0e6..48bd2ee 100644 --- a/src/actions/images/generatePalette.ts +++ b/src/actions/images/generatePalette.ts @@ -44,15 +44,16 @@ export async function generatePaletteAction(imageId: string, fileKey: string) { await upsertPalettes(neutralVariantTones, imageId, "neutralVariant"); await upsertPalettes(errorTones, imageId, "error"); - await prisma.themeSeed.create({ - data: { - seedHex, - imageId, + return await prisma.imagePalette.findMany({ + where: { + imageId: imageId }, - }); - - return await prisma.colorPalette.findMany({ - where: { images: { some: { id: imageId } } }, - include: { items: true }, + include: { + palette: { + include: { + items: true + } + } + } }); } \ No newline at end of file diff --git a/src/app/albums/edit/[id]/page.tsx b/src/app/albums/edit/[id]/page.tsx index 5e21f6b..1456652 100644 --- a/src/app/albums/edit/[id]/page.tsx +++ b/src/app/albums/edit/[id]/page.tsx @@ -7,6 +7,10 @@ export default async function AlbumsEditPage({ params }: { params: { id: string const album = await prisma.album.findUnique({ where: { id, + }, + include: { + coverImage: true, + images: true } }); diff --git a/src/app/albums/page.tsx b/src/app/albums/page.tsx index 9c7f608..7497527 100644 --- a/src/app/albums/page.tsx +++ b/src/app/albums/page.tsx @@ -7,7 +7,7 @@ export default async function AlbumsPage() { const albums = await prisma.album.findMany( { include: { gallery: true, images: { select: { id: true } } }, - orderBy: { createdAt: "asc" } + orderBy: { name: "asc" } } ); diff --git a/src/app/artists/page.tsx b/src/app/artists/page.tsx index cc41442..0188c57 100644 --- a/src/app/artists/page.tsx +++ b/src/app/artists/page.tsx @@ -4,7 +4,10 @@ import { PlusCircleIcon } from "lucide-react"; import Link from "next/link"; export default async function ArtistsPage() { - const artists = await prisma.artist.findMany({ orderBy: { createdAt: "asc" } }); + const artists = await prisma.artist.findMany({ + orderBy: { createdAt: "asc" }, + include: { images: { select: { id: true } } } + }); return (
diff --git a/src/app/categories/page.tsx b/src/app/categories/page.tsx index a971250..71707bc 100644 --- a/src/app/categories/page.tsx +++ b/src/app/categories/page.tsx @@ -6,7 +6,8 @@ import Link from "next/link"; export default async function CategoriesPage() { const categories = await prisma.category.findMany( { - orderBy: { createdAt: "asc" } + orderBy: { createdAt: "asc" }, + include: { images: { select: { id: true } } } } ); diff --git a/src/app/galleries/edit/[id]/page.tsx b/src/app/galleries/edit/[id]/page.tsx index 2c0305d..fcbab13 100644 --- a/src/app/galleries/edit/[id]/page.tsx +++ b/src/app/galleries/edit/[id]/page.tsx @@ -9,7 +9,8 @@ export default async function GalleriesEditPage({ params }: { params: { id: stri id, }, include: { - albums: true + albums: { include: { images: true } }, + coverImage: true } }); diff --git a/src/app/galleries/page.tsx b/src/app/galleries/page.tsx index 441bdde..5c6af7a 100644 --- a/src/app/galleries/page.tsx +++ b/src/app/galleries/page.tsx @@ -4,7 +4,12 @@ import { PlusCircleIcon } from "lucide-react"; import Link from "next/link"; export default async function GalleriesPage() { - const galleries = await prisma.gallery.findMany({ orderBy: { createdAt: "asc" } }); + const galleries = await prisma.gallery.findMany({ + orderBy: { createdAt: "asc" }, + include: { + albums: { select: { id: true } } + } + }); return (
diff --git a/src/app/images/edit/[id]/page.tsx b/src/app/images/edit/[id]/page.tsx index e7e7beb..d1618e5 100644 --- a/src/app/images/edit/[id]/page.tsx +++ b/src/app/images/edit/[id]/page.tsx @@ -16,27 +16,39 @@ export default async function ImagesEditPage({ params }: { params: { id: string include: { album: true, artist: true, - colors: true, - extractColors: true, metadata: true, - pixels: true, stats: true, - theme: true, - variants: true, - palettes: { + colors: { include: { - items: true + color: true } }, + extractColors: { + include: { + extract: true + } + }, + palettes: { + include: { + palette: { + include: { + items: true + } + } + } + }, + variants: true, + categories: true, tags: true, - categories: true } }); - const artists = await prisma.artist.findMany({ orderBy: { createdAt: "asc" } }); - const albums = await prisma.album.findMany({ orderBy: { createdAt: "asc" }, include: { gallery: true } }); - const tags = await prisma.tag.findMany({ orderBy: { createdAt: "asc" } }); - const categories = await prisma.category.findMany({ orderBy: { createdAt: "asc" } }); + const albums = await prisma.album.findMany({ orderBy: { name: "asc" }, include: { gallery: { select: { name: true } } } }); + const artists = await prisma.artist.findMany({ orderBy: { displayName: "asc" } }); + const categories = await prisma.category.findMany({ orderBy: { name: "asc" } }); + const tags = await prisma.tag.findMany({ orderBy: { name: "asc" } }); + + console.log(image) return (
diff --git a/src/app/palettes/[id]/page.tsx b/src/app/palettes/[id]/page.tsx new file mode 100644 index 0000000..7f769ad --- /dev/null +++ b/src/app/palettes/[id]/page.tsx @@ -0,0 +1,31 @@ +import DisplayPalette from "@/components/palettes/single/DisplayPalette"; +import prisma from "@/lib/prisma"; + +export default async function PalettesPage({ params }: { params: { id: string } }) { + const { id } = await params; + + const palette = await prisma.colorPalette.findUnique({ + where: { + id, + }, + include: { + items: true, + images: { + include: { + image: { + include: { + variants: { where: { type: "thumbnail" } } + } + } + } + } + } + }); + + return ( +
+

Show palette

+ {palette ? : 'Palette not found...'} +
+ ); +} \ No newline at end of file diff --git a/src/app/palettes/page.tsx b/src/app/palettes/page.tsx new file mode 100644 index 0000000..835aabb --- /dev/null +++ b/src/app/palettes/page.tsx @@ -0,0 +1,23 @@ +import ListPalettes from "@/components/palettes/list/ListPalettes"; +import prisma from "@/lib/prisma"; + +export default async function PalettesPage() { + const palettes = await prisma.colorPalette.findMany( + { + orderBy: { name: "asc" }, + include: { + items: true, + images: { select: { id: true } } + } + } + ); + + return ( +
+
+

Palettes

+
+ {palettes.length > 0 ? :

No palettes found.

} +
+ ); +} \ No newline at end of file diff --git a/src/components/albums/edit/EditAlbumForm.tsx b/src/components/albums/edit/EditAlbumForm.tsx index 49ddfda..579f0c7 100644 --- a/src/components/albums/edit/EditAlbumForm.tsx +++ b/src/components/albums/edit/EditAlbumForm.tsx @@ -5,15 +5,21 @@ import { Button } from "@/components/ui/button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Album, Gallery } from "@/generated/prisma"; +import { Album, Gallery, Image } from "@/generated/prisma"; import { albumSchema } from "@/schemas/albums/albumSchema"; import { zodResolver } from "@hookform/resolvers/zod"; +import NextImage from "next/image"; import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import * as z from "zod/v4"; -export default function EditAlbumForm({ album, galleries }: { album: Album, galleries: Gallery[] }) { +type AlbumWithItems = Album & { + images: Image[], + coverImage: Image | null +} + +export default function EditAlbumForm({ album, galleries }: { album: AlbumWithItems, galleries: Gallery[] }) { const router = useRouter(); const form = useForm>({ resolver: zodResolver(albumSchema), @@ -22,6 +28,7 @@ export default function EditAlbumForm({ album, galleries }: { album: Album, gall slug: album.slug, description: album.description || "", galleryId: album.galleryId || "", + coverImageId: album.coverImage?.id || "", }, }) @@ -33,6 +40,8 @@ export default function EditAlbumForm({ album, galleries }: { album: Album, gall } } + const selectedImage = album.images.find(img => img.id === form.watch("coverImageId")); + return (
@@ -109,6 +118,45 @@ export default function EditAlbumForm({ album, galleries }: { album: Album, gall )} /> + ( + + Cover Image + + + Optional cover image shown in album previews. + + + + )} + /> + {selectedImage?.fileKey && ( +
+

Cover preview:

+ +
+ )}
diff --git a/src/components/artists/list/ListArtists.tsx b/src/components/artists/list/ListArtists.tsx index 9ba5ec2..9fdf2a1 100644 --- a/src/components/artists/list/ListArtists.tsx +++ b/src/components/artists/list/ListArtists.tsx @@ -2,7 +2,11 @@ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/componen import { Artist } from "@/generated/prisma"; import Link from "next/link"; -export default function ListArtists({ artists }: { artists: Artist[] }) { +type ArtistsWithItems = Artist & { + images: { id: string }[] +} + +export default function ListArtists({ artists }: { artists: ArtistsWithItems[] }) { return (
{artists.map((artist) => ( @@ -14,6 +18,7 @@ export default function ListArtists({ artists }: { artists: Artist[] }) { + Connected to {artist.images.length} image{artist.images.length !== 1 ? "s" : ""} diff --git a/src/components/categories/list/ListCategories.tsx b/src/components/categories/list/ListCategories.tsx index a70e795..6a77f3f 100644 --- a/src/components/categories/list/ListCategories.tsx +++ b/src/components/categories/list/ListCategories.tsx @@ -2,7 +2,11 @@ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/componen import { Category } from "@/generated/prisma"; import Link from "next/link"; -export default function ListCategories({ categories }: { categories: Category[] }) { +type CategoriesWithItems = Category & { + images: { id: string }[] +} + +export default function ListCategories({ categories }: { categories: CategoriesWithItems[] }) { return (
{categories.map((cat) => ( @@ -15,6 +19,7 @@ export default function ListCategories({ categories }: { categories: Category[] {cat.description &&

{cat.description}

} + Connected to {cat.images.length} image{cat.images.length !== 1 ? "s" : ""} diff --git a/src/components/galleries/edit/EditGalleryForm.tsx b/src/components/galleries/edit/EditGalleryForm.tsx index 8571ef9..0f2a616 100644 --- a/src/components/galleries/edit/EditGalleryForm.tsx +++ b/src/components/galleries/edit/EditGalleryForm.tsx @@ -5,15 +5,26 @@ import { updateGallery } from "@/actions/galleries/updateGallery"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { Album, Gallery } from "@/generated/prisma"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Album, Gallery, Image } from "@/generated/prisma"; import { gallerySchema } from "@/schemas/galleries/gallerySchema"; import { zodResolver } from "@hookform/resolvers/zod"; +import NextImage from "next/image"; import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import * as z from "zod/v4"; -export default function EditGalleryForm({ gallery }: { gallery: Gallery & { albums: Album[] } }) { +type GalleryWithItems = Gallery & { + albums: (Album & + { + images: Image[] + } + )[]; + coverImage: Image | null +} + +export default function EditGalleryForm({ gallery }: { gallery: GalleryWithItems }) { const router = useRouter(); const form = useForm>({ resolver: zodResolver(gallerySchema), @@ -21,6 +32,7 @@ export default function EditGalleryForm({ gallery }: { gallery: Gallery & { albu name: gallery.name, slug: gallery.slug, description: gallery.description || "", + coverImageId: gallery.coverImage?.id || "", }, }) @@ -32,6 +44,9 @@ export default function EditGalleryForm({ gallery }: { gallery: Gallery & { albu } } + const allGalleryImages = gallery.albums?.flatMap(a => a.images) || []; + const selectedImage = allGalleryImages.find(img => img.id === form.watch("coverImageId")); + return (
@@ -84,6 +99,44 @@ export default function EditGalleryForm({ gallery }: { gallery: Gallery & { albu )} /> + ( + + Gallery Cover Image + + + + )} + /> + {selectedImage?.fileKey && ( +
+

Cover preview:

+ +
+ )}
@@ -104,7 +157,7 @@ export default function EditGalleryForm({ gallery }: { gallery: Gallery & { albu
{/* Replace this with actual image count later */} - Images: 0 + Images: {album.images.length}
))} @@ -112,7 +165,7 @@ export default function EditGalleryForm({ gallery }: { gallery: Gallery & { albu )}

- Total images in this gallery: 0 + Total images in this gallery: {allGalleryImages.length}

{gallery.albums.length === 0 ? ( diff --git a/src/components/galleries/list/ListGalleries.tsx b/src/components/galleries/list/ListGalleries.tsx index 6828304..7cafe6a 100644 --- a/src/components/galleries/list/ListGalleries.tsx +++ b/src/components/galleries/list/ListGalleries.tsx @@ -1,10 +1,14 @@ // "use client" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Gallery } from "@/generated/prisma"; import Link from "next/link"; -export default function ListGalleries({ galleries }: { galleries: Gallery[] }) { +type GalleriesWithItems = Gallery & { + albums: { id: string }[] +} + +export default function ListGalleries({ galleries }: { galleries: GalleriesWithItems[] }) { return (
{galleries.map((gallery) => ( @@ -16,6 +20,9 @@ export default function ListGalleries({ galleries }: { galleries: Gallery[] }) { {gallery.description &&

{gallery.description}

}
+ + Connected to {gallery.albums.length} album{gallery.albums.length !== 1 ? "s" : ""} + ))} diff --git a/src/components/global/TopNav.tsx b/src/components/global/TopNav.tsx index 0890359..f1f3363 100644 --- a/src/components/global/TopNav.tsx +++ b/src/components/global/TopNav.tsx @@ -37,6 +37,16 @@ export default function TopNav() { Tags + + + Palettes + + + + + Colors + + Images diff --git a/src/components/images/ImageCard.tsx b/src/components/images/ImageCard.tsx new file mode 100644 index 0000000..8dc29f7 --- /dev/null +++ b/src/components/images/ImageCard.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { Image, ImagePalette, ImageVariant } from "@/generated/prisma"; +import ImageComponent from "next/image"; +import Link from "next/link"; + +type ImagePaletteWithItems = { + image: ImagePalette & { + image: Image & { + variants: ImageVariant[]; + } + }; +}; + +export default function ImageCard({ image }: ImagePaletteWithItems) { + const thumbnail = image.image.variants.find((v) => v.type === "thumbnail"); + + return ( + + {thumbnail?.s3Key ? ( + + ) : ( +
+ No Thumbnail +
+ )} +
{image.image.imageName} ({image.type})
+ + ); +} diff --git a/src/components/images/edit/EditImageForm.tsx b/src/components/images/edit/EditImageForm.tsx index d9db8c3..a160b41 100644 --- a/src/components/images/edit/EditImageForm.tsx +++ b/src/components/images/edit/EditImageForm.tsx @@ -9,7 +9,7 @@ import MultipleSelector from "@/components/ui/multiselect"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; -import { Album, Artist, Category, ColorPalette, ColorPaletteItem, ExtractColor, Gallery, Image, ImageColor, ImageMetadata, ImageStats, ImageVariant, PixelSummary, Tag, ThemeSeed } from "@/generated/prisma"; +import { Album, Artist, Category, Color, ColorPalette, ColorPaletteItem, ExtractColor, Image, ImageColor, ImageExtractColor, ImageMetadata, ImagePalette, ImageStats, ImageVariant, Tag } from "@/generated/prisma"; import { cn } from "@/lib/utils"; import { imageSchema } from "@/schemas/images/imageSchema"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -22,33 +22,43 @@ import * as z from "zod/v4"; type ImageWithItems = Image & { album: Album | null, artist: Artist | null, - colors: ImageColor[], - extractColors: ExtractColor[], - metadata: ImageMetadata[], - pixels: PixelSummary[], - stats: ImageStats[], - theme: ThemeSeed[], - variants: ImageVariant[], - tags: Tag[], - categories: Category[], - palettes: ( - ColorPalette & { - items: ColorPaletteItem[] + metadata: ImageMetadata | null, + stats: ImageStats | null, + colors: ( + ImageColor & { + color: Color } - )[] + )[], + extractColors: ( + ImageExtractColor & { + extract: ExtractColor + } + )[], + palettes: ( + ImagePalette & { + palette: ( + ColorPalette & { + items: ColorPaletteItem[] + } + ) + } + )[], + variants: ImageVariant[], + categories: Category[], + tags: Tag[], }; type AlbumsWithGallery = Album & { - gallery: Gallery | null + gallery: { name: string } | null } -export default function EditImageForm({ image, artists, albums, tags, categories }: +export default function EditImageForm({ image, albums, artists, categories, tags }: { image: ImageWithItems, - artists: Artist[], albums: AlbumsWithGallery[], - tags: Tag[], + artists: Artist[], categories: Category[] + tags: Tag[], }) { const router = useRouter(); const form = useForm>({ @@ -62,7 +72,7 @@ export default function EditImageForm({ image, artists, albums, tags, categories altText: image.altText || "", description: image.description || "", fileType: image.fileType || "", - imageData: image.imageData || "", + source: image.source || "", creationMonth: image.creationMonth || undefined, creationYear: image.creationYear || undefined, fileSize: image.fileSize || undefined, @@ -192,10 +202,10 @@ export default function EditImageForm({ image, artists, albums, tags, categories /> ( - imageData + source diff --git a/src/components/images/edit/ExtractColors.tsx b/src/components/images/edit/ExtractColors.tsx index 2077870..a048ce3 100644 --- a/src/components/images/edit/ExtractColors.tsx +++ b/src/components/images/edit/ExtractColors.tsx @@ -2,11 +2,15 @@ import { generateExtractColors } from "@/actions/images/generateExtractColors"; import { Button } from "@/components/ui/button"; -import { ExtractColor } from "@/generated/prisma"; +import { ExtractColor, ImageExtractColor } from "@/generated/prisma"; import { useState, useTransition } from "react"; import { toast } from "sonner"; -export default function ExtractColors({ colors: initialColors, imageId, fileKey }: { colors: ExtractColor[], imageId: string, fileKey: string }) { +type ExtractWithJoin = ImageExtractColor & { + extract: ExtractColor; +}; + +export default function ExtractColors({ colors: initialColors, imageId, fileKey }: { colors: ExtractWithJoin[], imageId: string, fileKey: string }) { const [colors, setColors] = useState(initialColors); const [isPending, startTransition] = useTransition(); @@ -32,8 +36,13 @@ export default function ExtractColors({ colors: initialColors, imageId, fileKey
- {colors.map((color, index) => ( -
+ {colors.map((item) => ( +
))}
diff --git a/src/components/images/edit/ImageColors.tsx b/src/components/images/edit/ImageColors.tsx index 5b191d9..c8049e2 100644 --- a/src/components/images/edit/ImageColors.tsx +++ b/src/components/images/edit/ImageColors.tsx @@ -2,11 +2,15 @@ import { generateImageColors } from "@/actions/images/generateImageColors"; import { Button } from "@/components/ui/button"; -import { ImageColor } from "@/generated/prisma"; +import { Color, ImageColor } from "@/generated/prisma"; import { useState, useTransition } from "react"; import { toast } from "sonner"; -export default function ImageColors({ colors: initialColors, imageId, fileKey }: { colors: ImageColor[], imageId: string, fileKey: string }) { +type ColorWithItems = ImageColor & { + color: Color +}; + +export default function ImageColors({ colors: initialColors, imageId, fileKey }: { colors: ColorWithItems[], imageId: string, fileKey: string }) { const [colors, setColors] = useState(initialColors); const [isPending, startTransition] = useTransition(); @@ -31,15 +35,14 @@ export default function ImageColors({ colors: initialColors, imageId, fileKey }: {isPending ? "Extracting..." : "Generate Palette"}
- {/*

Extracted Colors

- {extractColors.map((color, index) => ( -
- ))} -
*/} -
- {colors.map((color, index) => ( -
+ {colors.map((item) => ( +
))}
diff --git a/src/components/images/edit/ImagePalettes.tsx b/src/components/images/edit/ImagePalettes.tsx index 3cb9336..c1de475 100644 --- a/src/components/images/edit/ImagePalettes.tsx +++ b/src/components/images/edit/ImagePalettes.tsx @@ -2,12 +2,14 @@ import { generatePaletteAction } from "@/actions/images/generatePalette"; import { Button } from "@/components/ui/button"; -import { ColorPalette, ColorPaletteItem } from "@/generated/prisma"; +import { ColorPalette, ColorPaletteItem, ImagePalette } from "@/generated/prisma"; import { useState, useTransition } from "react"; import { toast } from "sonner"; -type PaletteWithItems = ColorPalette & { - items: ColorPaletteItem[]; +type PaletteWithItems = ImagePalette & { + palette: ColorPalette & { + items: ColorPaletteItem[] + }; }; export default function ImagePalettes({ palettes: initialPalettes, imageId, fileKey }: { palettes: PaletteWithItems[], imageId: string, fileKey: string }) { @@ -37,12 +39,12 @@ export default function ImagePalettes({ palettes: initialPalettes, imageId, file
- {palettes.map((palette) => ( - palette.type != 'error' ? -
-
{palette.type}
+ {palettes.map((imagePalette) => ( + imagePalette.type != 'error' ? +
+
{imagePalette.type}
- {palette.items + {imagePalette.palette.items .filter((item) => item.tone !== null && item.hex !== null) .sort((a, b) => (a.tone ?? 0) - (b.tone ?? 0)) .map((item) => ( diff --git a/src/components/palettes/list/ListPalettes.tsx b/src/components/palettes/list/ListPalettes.tsx new file mode 100644 index 0000000..16f8e8c --- /dev/null +++ b/src/components/palettes/list/ListPalettes.tsx @@ -0,0 +1,39 @@ +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { ColorPalette, ColorPaletteItem } from "@/generated/prisma"; +import Link from "next/link"; + +type PalettesWithItems = ColorPalette & { + items: ColorPaletteItem[], + images: { id: string }[] +} + +export default function ListPalettes({ palettes }: { palettes: PalettesWithItems[] }) { + return ( +
+ {palettes.map((palette) => ( + + + + {palette.name} + + +
+ {palette.items.map((item) => ( +
+ ))} +
+ + + Used by {palette.images.length} image{palette.images.length !== 1 ? "s" : ""} + + + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/components/palettes/single/DisplayPalette.tsx b/src/components/palettes/single/DisplayPalette.tsx new file mode 100644 index 0000000..61eb974 --- /dev/null +++ b/src/components/palettes/single/DisplayPalette.tsx @@ -0,0 +1,45 @@ + +import ImageCard from "@/components/images/ImageCard"; +import { ColorPalette, ColorPaletteItem, Image, ImagePalette, ImageVariant } from "@/generated/prisma"; + +type PaletteWithItems = ColorPalette & { + items: ColorPaletteItem[], + images: (ImagePalette & { + image: Image & { + variants: ImageVariant[] + } + })[] +} + +export default function DisplayPalette({ palette }: { palette: PaletteWithItems }) { + + + return ( +
+
+

{palette.name}

+ {/*

Type: {palette.type}

*/} +
+ +
+ {palette.items.map((item) => ( +
+ ))} +
+ +
+

Used by {palette.images.length} image(s)

+
+ {palette.images.map((image) => ( + + ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/schemas/albums/albumSchema.ts b/src/schemas/albums/albumSchema.ts index 1d1f0d5..b745075 100644 --- a/src/schemas/albums/albumSchema.ts +++ b/src/schemas/albums/albumSchema.ts @@ -5,5 +5,6 @@ export const albumSchema = z.object({ slug: z.string().min(3, "Slug is required. Min 3 characters.").regex(/^[a-z]+$/, "Only lowercase letters are allowed (no numbers, spaces, or uppercase)"), galleryId: z.string().min(1, "Please select a gallery"), description: z.string().optional(), + coverImageId: z.string().optional(), }) diff --git a/src/schemas/galleries/gallerySchema.ts b/src/schemas/galleries/gallerySchema.ts index a1b842e..f3a731f 100644 --- a/src/schemas/galleries/gallerySchema.ts +++ b/src/schemas/galleries/gallerySchema.ts @@ -4,4 +4,5 @@ export const gallerySchema = z.object({ name: z.string().min(3, "Name is required. Min 3 characters."), slug: z.string().min(3, "Slug is required. Min 3 characters.").regex(/^[a-z]+$/, "Only lowercase letters are allowed (no numbers, spaces, or uppercase)"), description: z.string().optional(), + coverImageId: z.string().optional(), }) \ No newline at end of file diff --git a/src/schemas/images/imageSchema.ts b/src/schemas/images/imageSchema.ts index 33845b5..14d4646 100644 --- a/src/schemas/images/imageSchema.ts +++ b/src/schemas/images/imageSchema.ts @@ -18,15 +18,15 @@ export const imageSchema = z.object({ altText: z.string().optional(), description: z.string().optional(), fileType: z.string().optional(), - imageData: z.string().optional(), + source: z.string().optional(), creationMonth: z.number().min(1).max(12).optional(), creationYear: z.number().min(1900).max(2100).optional(), fileSize: z.number().optional(), creationDate: z.date().optional(), - artistId: z.string().optional(), albumId: z.string().optional(), - tagIds: z.array(z.string()).optional(), + artistId: z.string().optional(), categoryIds: z.array(z.string()).optional(), + tagIds: z.array(z.string()).optional(), }) \ No newline at end of file diff --git a/src/utils/uploadHelper.ts b/src/utils/uploadHelper.ts index 2163bc5..b4ead33 100644 --- a/src/utils/uploadHelper.ts +++ b/src/utils/uploadHelper.ts @@ -44,42 +44,56 @@ export async function upsertPalettes(tones: Tone[], imageId: string, type: strin const existingPalette = await prisma.colorPalette.findFirst({ where: { name: paletteName }, - include: { images: { select: { id: true } } }, - }); - - if (existingPalette) { - const alreadyLinked = existingPalette.images.some(img => img.id === imageId); - - if (!alreadyLinked) { - await prisma.colorPalette.update({ - where: { id: existingPalette.id }, - data: { - images: { - connect: { id: imageId }, - }, - }, - }); - } - - return existingPalette; - } - - const newPalette = await prisma.colorPalette.create({ - data: { - name: paletteName, - type: type, - items: { - create: tones.map(t => ({ - tone: t.tone, - hex: t.hex, - })), - }, - images: { - connect: { id: imageId }, - }, - }, include: { items: true }, }); - return newPalette; + // + const palette = existingPalette ?? await prisma.colorPalette.create({ + data: { + name: paletteName, + items: { + create: tones.map(tone => ({ + tone: tone.tone, + hex: tone.hex, + })) + } + } + }); + + await prisma.imagePalette.upsert({ + where: { + imageId_type: { + imageId, + type, + } + }, + update: { + paletteId: palette.id + }, + create: { + imageId, + paletteId: palette.id, + type, + } + }); + + + // const newPalette = await prisma.colorPalette.create({ + // data: { + // name: paletteName, + // type: type, + // items: { + // create: tones.map(t => ({ + // tone: t.tone, + // hex: t.hex, + // })), + // }, + // images: { + // connect: { id: imageId }, + // }, + // }, + // include: { items: true }, + // }); + + return palette; } \ No newline at end of file