From ae6b52cc9206d4178c4a78c55055d916174098e8 Mon Sep 17 00:00:00 2001 From: Cybervoid Date: Sun, 15 Dec 2024 06:00:08 +0100 Subject: [PATCH 1/2] feat: implement API routes for categories, conditions, and manufacturers with CRUD operations --- .gitignore | 1 + app/api/v2/auth/[...nextauth]/route.ts | 6 + app/api/v2/barometers/[slug]/route.ts | 77 ++++ app/api/v2/barometers/route.ts | 183 +++++++++ app/api/v2/categories/[name]/route.ts | 33 ++ app/api/v2/categories/route.ts | 30 ++ app/api/v2/conditions/route.ts | 30 ++ app/api/v2/manufacturers/[id]/route.ts | 60 +++ app/api/v2/manufacturers/route.ts | 52 +++ app/api/v2/parameters.ts | 22 ++ app/api/v2/search/route.ts | 81 ++++ package-lock.json | 353 +++++++++++++----- package.json | 6 + .../migration.sql | 130 +++++++ .../migration.sql | 3 + .../migration.sql | 8 + .../migration.sql | 16 + .../migration.sql | 2 + .../migration.sql | 8 + .../migration.sql | 8 + prisma/migrations/migration_lock.toml | 3 + prisma/prismaClient.ts | 17 + prisma/schema.prisma | 91 +++++ 23 files changed, 1128 insertions(+), 92 deletions(-) create mode 100644 app/api/v2/auth/[...nextauth]/route.ts create mode 100644 app/api/v2/barometers/[slug]/route.ts create mode 100644 app/api/v2/barometers/route.ts create mode 100644 app/api/v2/categories/[name]/route.ts create mode 100644 app/api/v2/categories/route.ts create mode 100644 app/api/v2/conditions/route.ts create mode 100644 app/api/v2/manufacturers/[id]/route.ts create mode 100644 app/api/v2/manufacturers/route.ts create mode 100644 app/api/v2/parameters.ts create mode 100644 app/api/v2/search/route.ts create mode 100644 prisma/migrations/20241214014308_create_schemas/migration.sql create mode 100644 prisma/migrations/20241214033702_make_image_description_and_name_optional/migration.sql create mode 100644 prisma/migrations/20241214040205_change_value_to_int/migration.sql create mode 100644 prisma/migrations/20241214041337_rename_type_to_category/migration.sql create mode 100644 prisma/migrations/20241214042023_make_date_desc_optional/migration.sql create mode 100644 prisma/migrations/20241214212323_make_date_required/migration.sql create mode 100644 prisma/migrations/20241214235347_add_unique_to_category_name/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/prismaClient.ts create mode 100644 prisma/schema.prisma diff --git a/.gitignore b/.gitignore index 66316e7c..7ec1afe1 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ web_modules/ .env.test.local .env.production.local .env.local +.env.production # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/app/api/v2/auth/[...nextauth]/route.ts b/app/api/v2/auth/[...nextauth]/route.ts new file mode 100644 index 00000000..b34ca82a --- /dev/null +++ b/app/api/v2/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import NextAuth from 'next-auth' +import { authConfig } from '@/utils/auth' + +const handler = NextAuth(authConfig) + +export { handler as GET, handler as POST } diff --git a/app/api/v2/barometers/[slug]/route.ts b/app/api/v2/barometers/[slug]/route.ts new file mode 100644 index 00000000..da257bee --- /dev/null +++ b/app/api/v2/barometers/[slug]/route.ts @@ -0,0 +1,77 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getPrismaClient } from '@/prisma/prismaClient' + +interface Params { + params: { + slug: string + } +} + +/** + * Get Barometer details by slug + */ +export async function GET(_req: NextRequest, { params: { slug } }: Params) { + const prisma = getPrismaClient() + try { + const barometer = await prisma.barometer.findFirst({ + where: { + slug: { + equals: slug, + mode: 'insensitive', + }, + }, + include: { + category: true, + condition: true, + manufacturer: true, + }, + }) + if (barometer === null) return NextResponse.json({}, { status: 404 }) + return NextResponse.json(barometer, { status: 200 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error retrieving barometer' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} + +/** + * Delete Barometer by slug + */ +export async function DELETE(_req: NextRequest, { params: { slug } }: Params) { + const prisma = getPrismaClient() + try { + const barometer = await prisma.barometer.findFirst({ + where: { + slug: { + equals: slug, + mode: 'insensitive', + }, + }, + }) + + if (!barometer) { + return NextResponse.json({ message: 'Barometer not found' }, { status: 404 }) + } + + await prisma.barometer.delete({ + where: { + id: barometer.id, + }, + }) + + return NextResponse.json({ message: 'Barometer deleted successfully' }, { status: 200 }) + } catch (error) { + return NextResponse.json( + { + message: error instanceof Error ? error.message : 'Error deleting barometer', + }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} diff --git a/app/api/v2/barometers/route.ts b/app/api/v2/barometers/route.ts new file mode 100644 index 00000000..6b112825 --- /dev/null +++ b/app/api/v2/barometers/route.ts @@ -0,0 +1,183 @@ +import { NextRequest, NextResponse } from 'next/server' +import { revalidatePath } from 'next/cache' +import type { Barometer, Prisma, PrismaClient } from '@prisma/client' +import { getPrismaClient } from '@/prisma/prismaClient' +import { cleanObject, slug as slugify } from '@/utils/misc' +import { type SortValue } from '@/app/collection/types/[type]/types' +import { DEFAULT_PAGE_SIZE } from '../parameters' + +function getSortCriteria( + sortBy: SortValue | null, + direction: 'asc' | 'desc' = 'asc', +): Prisma.BarometerOrderByWithRelationInput { + switch (sortBy) { + case 'manufacturer': + return { manufacturer: { name: direction } } + case 'name': + return { name: direction } + case 'date': + return { date: direction } + case 'cat-no': + return { collectionId: direction } + default: + return { date: direction } + } +} + +// which barometer fields to include in barometer group responses +const select: Prisma.BarometerSelect = { + name: true, + date: true, + slug: true, + collectionId: true, + manufacturer: { + select: { + name: true, + }, + }, + category: { + select: { + name: true, + }, + }, +} + +/** + * Find a list of barometers of a certain type using pagination + */ +async function getBarometersByParams( + prisma: PrismaClient, + categoryName: string, + page: number, + pageSize: number, + sortBy: SortValue | null, +) { + // perform case-insensitive compare with the stored categories + const category = await prisma.category.findFirst({ + where: { name: { equals: categoryName, mode: 'insensitive' } }, + }) + if (!category) throw new Error('Unknown barometer category') + + const skip = (page - 1) * pageSize + const orderBy = getSortCriteria(sortBy) + + const [barometers, totalItems] = await Promise.all([ + prisma.barometer.findMany({ + where: { categoryId: category.id }, + select, + skip, + take: pageSize, + orderBy, + }), + prisma.barometer.count({ where: { categoryId: category.id } }), + ]) + + return NextResponse.json( + { + barometers, + page, + totalItems, + pageSize, + totalPages: Math.ceil(totalItems / pageSize), + }, + { status: barometers.length > 0 ? 200 : 404 }, + ) +} + +/** + * List all barometers without pagination + */ +async function getAllBarometers(prisma: PrismaClient) { + const barometers = await prisma.barometer.findMany({ + select, + }) + return NextResponse.json(barometers, { status: 200 }) +} + +/** + * Get barometer list + * + * GET /api/barometers?category=aneroid + */ +export async function GET(req: NextRequest) { + const prisma = getPrismaClient() + try { + const { searchParams } = req.nextUrl + const category = searchParams.get('category') + const sortBy = searchParams.get('sort') as SortValue | null + const size = Math.max(Number(searchParams.get('size')) || DEFAULT_PAGE_SIZE, 1) + const page = Math.max(Number(searchParams.get('page')) || 1, 1) + // if `type` search param was not passed return all barometers list + if (!category || !category.trim()) return await getAllBarometers(prisma) + // type was passed + return await getBarometersByParams(prisma, category, page, size, sortBy) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Could not retrieve barometer list' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} + +//! Protect this function +/** + * Add new barometer + * + * POST /api/barometers + */ +export async function POST(req: NextRequest) { + const prisma = getPrismaClient() + try { + const barometerData: Barometer = await req.json() + const cleanData = cleanObject(barometerData) + const slug = slugify(cleanData.name) + const newBarometer = await prisma.barometer.create({ + data: { + ...cleanData, + slug, + dimensions: cleanData.dimensions?.toString(), + }, + }) + revalidatePath('/') + return NextResponse.json({ id: newBarometer.id }, { status: 201 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error adding new barometer' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} +//! Protect this function +/** + * Update barometer data + * + * PUT /api/barometers + */ +export async function PUT(req: NextRequest) { + const prisma = getPrismaClient() + try { + const barometerData: Barometer = await req.json() + const slug = slugify(barometerData.name) + await prisma.barometer.update({ + where: { id: barometerData.id }, + data: { + ...barometerData, + slug, + dimensions: barometerData.dimensions?.toString(), + }, + }) + revalidatePath(`/collection/items/${slug}`) + return NextResponse.json({ slug }, { status: 200 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error updating barometer' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} diff --git a/app/api/v2/categories/[name]/route.ts b/app/api/v2/categories/[name]/route.ts new file mode 100644 index 00000000..5bc05904 --- /dev/null +++ b/app/api/v2/categories/[name]/route.ts @@ -0,0 +1,33 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getPrismaClient } from '@/prisma/prismaClient' + +interface Params { + params: { + name: string + } +} + +/** + * Get Category details + */ +export async function GET(_req: NextRequest, { params: { name } }: Params) { + const prisma = getPrismaClient() + try { + const category = await prisma.category.findFirst({ + where: { + name: { + equals: name, + mode: 'insensitive', + }, + }, + }) + return NextResponse.json(category, { status: 200 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error retrieving category details' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} diff --git a/app/api/v2/categories/route.ts b/app/api/v2/categories/route.ts new file mode 100644 index 00000000..ea074135 --- /dev/null +++ b/app/api/v2/categories/route.ts @@ -0,0 +1,30 @@ +import { NextResponse } from 'next/server' +import { getPrismaClient } from '@/prisma/prismaClient' + +/** + * Get Categories list + */ +export async function GET() { + const prisma = getPrismaClient() + try { + const categories = await prisma.category.findMany({ + orderBy: { + order: 'asc', + }, + select: { + id: true, + name: true, + label: true, + order: true, + }, + }) + return NextResponse.json(categories, { status: 200 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error getting barometer categories' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} diff --git a/app/api/v2/conditions/route.ts b/app/api/v2/conditions/route.ts new file mode 100644 index 00000000..05f46d6f --- /dev/null +++ b/app/api/v2/conditions/route.ts @@ -0,0 +1,30 @@ +import { NextResponse } from 'next/server' +import { getPrismaClient } from '@/prisma/prismaClient' + +/** + * Get list of possible barometer Conditions + */ +export async function GET() { + const prisma = getPrismaClient() + try { + const conditions = await prisma.condition.findMany({ + orderBy: { + value: 'asc', + }, + select: { + id: true, + name: true, + value: true, + description: true, + }, + }) + return NextResponse.json(conditions, { status: 200 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error getting barometer conditions' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} diff --git a/app/api/v2/manufacturers/[id]/route.ts b/app/api/v2/manufacturers/[id]/route.ts new file mode 100644 index 00000000..43877853 --- /dev/null +++ b/app/api/v2/manufacturers/[id]/route.ts @@ -0,0 +1,60 @@ +import { NextResponse, NextRequest } from 'next/server' +import { getPrismaClient } from '@/prisma/prismaClient' + +interface Parameters { + params: { + id: string + } +} + +/** + * Query a specific manufacturer by ID + */ +export async function GET(req: NextRequest, { params: { id } }: Parameters) { + const prisma = getPrismaClient() + try { + const manufacturer = await prisma.manufacturer.findUnique({ + where: { + id, + }, + }) + return NextResponse.json(manufacturer, { status: 200 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error querying manufacturer' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} + +// !нужно как-то защитить от общего доступа к этой функции + +/** + * Delete manufacturer by ID + */ +export async function DELETE(req: NextRequest, { params: { id } }: Parameters) { + const prisma = getPrismaClient() + try { + const manufacturer = await prisma.manufacturer.findUnique({ where: { id } }) + if (!manufacturer) { + return NextResponse.json({ message: 'Manufacturer not found' }, { status: 404 }) + } + await prisma.manufacturer.delete({ + where: { + id, + }, + }) + return NextResponse.json({ message: 'Manufacturer deleted successfully' }, { status: 200 }) + } catch (error) { + return NextResponse.json( + { + message: error instanceof Error ? error.message : 'Cannot delete manufacturer', + }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} diff --git a/app/api/v2/manufacturers/route.ts b/app/api/v2/manufacturers/route.ts new file mode 100644 index 00000000..ce0da71d --- /dev/null +++ b/app/api/v2/manufacturers/route.ts @@ -0,0 +1,52 @@ +import { NextResponse, NextRequest } from 'next/server' +import { getPrismaClient } from '@/prisma/prismaClient' + +/** + * Retrieve a list of all Manufacturers + */ +export async function GET() { + const prisma = getPrismaClient() + try { + const manufacturers = await prisma.manufacturer.findMany({ + select: { + name: true, + id: true, + }, + orderBy: { + name: 'asc', + }, + }) + return NextResponse.json(manufacturers, { status: 200 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error getting manufacturers' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} + +//! Protect this function +/** + * Create a new Manufacturer + */ +export async function POST(req: NextRequest) { + const prisma = getPrismaClient() + try { + const manufData = await req.json() + + const newManufacturer = await prisma.manufacturer.create({ + data: manufData, + }) + + return NextResponse.json({ id: newManufacturer.id }, { status: 201 }) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Cannot add new manufacturer' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} diff --git a/app/api/v2/parameters.ts b/app/api/v2/parameters.ts new file mode 100644 index 00000000..9197a5c0 --- /dev/null +++ b/app/api/v2/parameters.ts @@ -0,0 +1,22 @@ +import { type Prisma } from '@prisma/client' + +// pagination page size +export const DEFAULT_PAGE_SIZE = 12 + +// which barometer fields to include in barometer group responses +export const select: Prisma.BarometerSelect = { + name: true, + date: true, + slug: true, + collectionId: true, + manufacturer: { + select: { + name: true, + }, + }, + category: { + select: { + name: true, + }, + }, +} diff --git a/app/api/v2/search/route.ts b/app/api/v2/search/route.ts new file mode 100644 index 00000000..bf1dfacb --- /dev/null +++ b/app/api/v2/search/route.ts @@ -0,0 +1,81 @@ +import { NextRequest, NextResponse } from 'next/server' +import { type PrismaClient } from '@prisma/client' +import { getPrismaClient } from '@/prisma/prismaClient' +import { DEFAULT_PAGE_SIZE, select } from '../parameters' + +/** + * Search barometers matching a query + */ +async function searchBarometers( + prisma: PrismaClient, + query: string, + page: number, + pageSize: number, +) { + const skip = (page - 1) * pageSize + + const [barometers, totalItems] = await Promise.all([ + prisma.barometer.findMany({ + where: { + OR: [ + { name: { contains: query, mode: 'insensitive' } }, + { description: { contains: query, mode: 'insensitive' } }, + ], + }, + select: { ...select, images: true }, + skip, + take: pageSize, + }), + prisma.barometer.count({ + where: { + OR: [ + { name: { contains: query, mode: 'insensitive' } }, + { description: { contains: query, mode: 'insensitive' } }, + ], + }, + }), + ]) + + // replace array of images with the first image + const barometersWithFirstImage = barometers.map(barometer => { + const { images, ...restBarometer } = barometer + return { + ...restBarometer, + image: images.at(0), + } + }) + + return NextResponse.json( + { + barometers: barometersWithFirstImage, + totalItems, + page, + totalPages: Math.ceil(totalItems / pageSize), + pageSize, + }, + { status: 200 }, + ) +} + +export async function GET(req: NextRequest) { + const prisma = getPrismaClient() + try { + const { searchParams } = req.nextUrl + const query = searchParams.get('q') + const pageSize = Math.max(Number(searchParams.get('pageSize')) || DEFAULT_PAGE_SIZE, 1) + const page = Math.max(Number(searchParams.get('page')) || 1, 1) + + if (!query) { + return NextResponse.json([], { status: 200 }) + } + + return await searchBarometers(prisma, query, page, pageSize) + } catch (error) { + return NextResponse.json( + { message: error instanceof Error ? error.message : 'Error searching barometers' }, + { status: 500 }, + ) + } finally { + await prisma.$disconnect() + } +} diff --git a/package-lock.json b/package-lock.json index f75b5df0..bd8c29e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,11 @@ "@mantine/form": "^7.13.4", "@mantine/hooks": "7.13.4", "@mantine/notifications": "^7.13.4", + "@neondatabase/serverless": "^0.10.4", "@next/bundle-analyzer": "^14.2.4", "@next/third-parties": "^15.0.3", + "@prisma/adapter-neon": "^6.0.1", + "@prisma/client": "^6.0.1", "@tabler/icons-react": "^3.6.0", "@tanstack/react-query": "^5.56.2", "@vercel/analytics": "^1.4.1", @@ -44,6 +47,7 @@ "slugify": "^1.6.6", "swiper": "^11.1.14", "traverse": "^0.6.10", + "undici": "^7.1.0", "uuid": "^10.0.0", "validator": "^13.12.0" }, @@ -67,6 +71,7 @@ "@types/react": "18.3.3", "@types/traverse": "^0.6.37", "@types/validator": "^13.12.2", + "@types/ws": "^8.5.13", "@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/parser": "^7.13.1", "babel-loader": "^9.1.3", @@ -85,6 +90,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "prettier": "^3.3.3", + "prisma": "^6.0.1", "storybook": "^8.2.9", "storybook-dark-mode": "^4.0.2", "stylelint": "^16.6.1", @@ -4003,6 +4009,15 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@neondatabase/serverless": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.10.4.tgz", + "integrity": "sha512-2nZuh3VUO9voBauuh+IGYRhGU/MskWHt1IuZvHcJw6GLjDgtqj/KViKo7SIrLdGLdot7vFbiRRw+BgEy3wT9HA==", + "license": "MIT", + "dependencies": { + "@types/pg": "8.11.6" + } + }, "node_modules/@next/bundle-analyzer": { "version": "14.2.16", "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-14.2.16.tgz", @@ -4614,6 +4629,95 @@ "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", "license": "MIT" }, + "node_modules/@prisma/adapter-neon": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@prisma/adapter-neon/-/adapter-neon-6.0.1.tgz", + "integrity": "sha512-H6WV/pKTa1ttEVfLh6WPldf7EKonFnul2J+U36WCDTWBGwcAKyzDCBYM3qTRrs0siz7FJ/CMxbOoubJqeMHBog==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "6.0.1", + "postgres-array": "3.0.2" + }, + "peerDependencies": { + "@neondatabase/serverless": "^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0" + } + }, + "node_modules/@prisma/client": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.0.1.tgz", + "integrity": "sha512-60w7kL6bUxz7M6Gs/V+OWMhwy94FshpngVmOY05TmGD0Lhk+Ac0ZgtjlL6Wll9TD4G03t4Sq1wZekNVy+Xdlbg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.0.1.tgz", + "integrity": "sha512-jQylgSOf7ibTVxqBacnAlVGvek6fQxJIYCQOeX2KexsfypNzXjJQSS2o5s+Mjj2Np93iSOQUaw6TvPj8syhG4w==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-6.0.1.tgz", + "integrity": "sha512-er0UqOPcqg/A15Ozf9+O9MQ0p81nbG1dI771VROSmSMlqnwVNOOoBRG51x6+4MRzeL50W0Xgu34pb87+kIoDew==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.0.1" + } + }, + "node_modules/@prisma/engines": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.0.1.tgz", + "integrity": "sha512-4hxzI+YQIR2uuDyVsDooFZGu5AtixbvM2psp+iayDZ4hRrAHo/YwgA17N23UWq7G6gRu18NvuNMb48qjP3DPQw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.0.1", + "@prisma/engines-version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", + "@prisma/fetch-engine": "6.0.1", + "@prisma/get-platform": "6.0.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e.tgz", + "integrity": "sha512-JmIds0Q2/vsOmnuTJYxY4LE+sajqjYKhLtdOT6y4imojqv5d/aeVEfbBGC74t8Be1uSp0OP8lxIj2OqoKbLsfQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.0.1.tgz", + "integrity": "sha512-T36bWFVGeGYYSyYOj9d+O9G3sBC+pAyMC+jc45iSL63/Haq1GrYjQPgPMxrEj9m739taXrupoysRedQ+VyvM/Q==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.0.1", + "@prisma/engines-version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", + "@prisma/get-platform": "6.0.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.0.1.tgz", + "integrity": "sha512-zspC9vlxAqx4E6epMPMLLBMED2VD8axDe8sPnquZ8GOsn6tiacWK0oxrGK4UAHYzYUVuMVUApJbdXB2dFpLhvg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.0.1" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -6088,6 +6192,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/pg": { + "version": "8.11.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz", + "integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -6240,6 +6355,16 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -8280,9 +8405,9 @@ } }, "node_modules/bson": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz", - "integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", + "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", "license": "Apache-2.0", "engines": { "node": ">=16.20.1" @@ -11878,38 +12003,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/gcp-metadata/node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -15358,13 +15451,13 @@ } }, "node_modules/mongodb": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", - "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", + "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -15372,7 +15465,7 @@ }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", @@ -15439,14 +15532,14 @@ } }, "node_modules/mongoose": { - "version": "8.7.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.3.tgz", - "integrity": "sha512-Xl6+dzU5ZpEcDoJ8/AyrIdAwTY099QwpolvV73PIytpK13XqwllLq/9XeVzzLEQgmyvwBVGVgjmMrKbuezxrIA==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.0.tgz", + "integrity": "sha512-b58zY3PLNBcoz6ZXFckr0leJcVVBMAOBvD+7Bj2ZjghAwntXmNnqwlDixTKQU3UYoQIGTv+AQx/0ThsvaeVrCA==", "license": "MIT", "dependencies": { - "bson": "^6.7.0", + "bson": "^6.10.1", "kareem": "2.6.3", - "mongodb": "6.9.0", + "mongodb": "~6.12.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -15460,52 +15553,6 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/mongodb": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", - "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -16101,6 +16148,12 @@ "dev": true, "license": "ISC" }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, "node_modules/oidc-token-hash": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", @@ -16487,6 +16540,48 @@ "node": ">=0.12" } }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-protocol": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz", + "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz", + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -16965,6 +17060,51 @@ "dev": true, "license": "MIT" }, + "node_modules/postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "license": "MIT", + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "license": "MIT" + }, "node_modules/preact": { "version": "10.24.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", @@ -17071,6 +17211,26 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prisma": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.0.1.tgz", + "integrity": "sha512-CaMNFHkf+DDq8zq3X/JJsQ4Koy7dyWwwtOKibkT/Am9j/tDxcfbg7+lB1Dzhx18G/+RQCMgjPYB61bhRqteNBQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "6.0.1" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -20419,6 +20579,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.1.0.tgz", + "integrity": "sha512-3+mdX2R31khuLCm2mKExSlMdJsfol7bJkIMH80tdXA74W34rT1jKemUTlYR7WY3TqsV4wfOgpatWmmB2Jl1+5g==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/package.json b/package.json index 98cf52b5..811e5bb7 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,11 @@ "@mantine/form": "^7.13.4", "@mantine/hooks": "7.13.4", "@mantine/notifications": "^7.13.4", + "@neondatabase/serverless": "^0.10.4", "@next/bundle-analyzer": "^14.2.4", "@next/third-parties": "^15.0.3", + "@prisma/adapter-neon": "^6.0.1", + "@prisma/client": "^6.0.1", "@tabler/icons-react": "^3.6.0", "@tanstack/react-query": "^5.56.2", "@vercel/analytics": "^1.4.1", @@ -59,6 +62,7 @@ "slugify": "^1.6.6", "swiper": "^11.1.14", "traverse": "^0.6.10", + "undici": "^7.1.0", "uuid": "^10.0.0", "validator": "^13.12.0" }, @@ -82,6 +86,7 @@ "@types/react": "18.3.3", "@types/traverse": "^0.6.37", "@types/validator": "^13.12.2", + "@types/ws": "^8.5.13", "@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/parser": "^7.13.1", "babel-loader": "^9.1.3", @@ -100,6 +105,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "prettier": "^3.3.3", + "prisma": "^6.0.1", "storybook": "^8.2.9", "storybook-dark-mode": "^4.0.2", "stylelint": "^16.6.1", diff --git a/prisma/migrations/20241214014308_create_schemas/migration.sql b/prisma/migrations/20241214014308_create_schemas/migration.sql new file mode 100644 index 00000000..0ac1486c --- /dev/null +++ b/prisma/migrations/20241214014308_create_schemas/migration.sql @@ -0,0 +1,130 @@ +-- CreateEnum +CREATE TYPE "AccessRole" AS ENUM ('USER', 'OWNER', 'ADMIN'); + +-- CreateTable +CREATE TABLE "Image" ( + "id" TEXT NOT NULL, + "url" TEXT NOT NULL, + "description" TEXT NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Image_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Category" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "label" TEXT NOT NULL, + "order" INTEGER NOT NULL, + "imageId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Category_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Barometer" ( + "id" TEXT NOT NULL, + "collectionId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "description" TEXT NOT NULL, + "typeId" TEXT NOT NULL, + "conditionId" TEXT NOT NULL, + "date" TIMESTAMP(3) NOT NULL, + "dateDescription" TEXT NOT NULL, + "manufacturerId" TEXT NOT NULL, + "dimensions" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Barometer_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Manufacturer" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "country" TEXT, + "city" TEXT, + "description" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Manufacturer_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Condition" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "value" DOUBLE PRECISION NOT NULL, + "description" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Condition_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT, + "name" TEXT NOT NULL, + "role" "AccessRole" NOT NULL, + "avatarURL" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_BarometerImages" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + + CONSTRAINT "_BarometerImages_AB_pkey" PRIMARY KEY ("A","B") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Category_imageId_key" ON "Category"("imageId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Barometer_collectionId_key" ON "Barometer"("collectionId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Barometer_name_key" ON "Barometer"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "Barometer_slug_key" ON "Barometer"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE INDEX "_BarometerImages_B_index" ON "_BarometerImages"("B"); + +-- AddForeignKey +ALTER TABLE "Category" ADD CONSTRAINT "Category_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "Image"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Barometer" ADD CONSTRAINT "Barometer_typeId_fkey" FOREIGN KEY ("typeId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Barometer" ADD CONSTRAINT "Barometer_conditionId_fkey" FOREIGN KEY ("conditionId") REFERENCES "Condition"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Barometer" ADD CONSTRAINT "Barometer_manufacturerId_fkey" FOREIGN KEY ("manufacturerId") REFERENCES "Manufacturer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_BarometerImages" ADD CONSTRAINT "_BarometerImages_A_fkey" FOREIGN KEY ("A") REFERENCES "Barometer"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_BarometerImages" ADD CONSTRAINT "_BarometerImages_B_fkey" FOREIGN KEY ("B") REFERENCES "Image"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20241214033702_make_image_description_and_name_optional/migration.sql b/prisma/migrations/20241214033702_make_image_description_and_name_optional/migration.sql new file mode 100644 index 00000000..6768cdf1 --- /dev/null +++ b/prisma/migrations/20241214033702_make_image_description_and_name_optional/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Image" ALTER COLUMN "description" DROP NOT NULL, +ALTER COLUMN "name" DROP NOT NULL; diff --git a/prisma/migrations/20241214040205_change_value_to_int/migration.sql b/prisma/migrations/20241214040205_change_value_to_int/migration.sql new file mode 100644 index 00000000..b6000502 --- /dev/null +++ b/prisma/migrations/20241214040205_change_value_to_int/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to alter the column `value` on the `Condition` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Integer`. + +*/ +-- AlterTable +ALTER TABLE "Condition" ALTER COLUMN "value" SET DATA TYPE INTEGER; diff --git a/prisma/migrations/20241214041337_rename_type_to_category/migration.sql b/prisma/migrations/20241214041337_rename_type_to_category/migration.sql new file mode 100644 index 00000000..50800491 --- /dev/null +++ b/prisma/migrations/20241214041337_rename_type_to_category/migration.sql @@ -0,0 +1,16 @@ +/* + Warnings: + + - You are about to drop the column `typeId` on the `Barometer` table. All the data in the column will be lost. + - Added the required column `categoryId` to the `Barometer` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "Barometer" DROP CONSTRAINT "Barometer_typeId_fkey"; + +-- AlterTable +ALTER TABLE "Barometer" DROP COLUMN "typeId", +ADD COLUMN "categoryId" TEXT NOT NULL; + +-- AddForeignKey +ALTER TABLE "Barometer" ADD CONSTRAINT "Barometer_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20241214042023_make_date_desc_optional/migration.sql b/prisma/migrations/20241214042023_make_date_desc_optional/migration.sql new file mode 100644 index 00000000..b23fc957 --- /dev/null +++ b/prisma/migrations/20241214042023_make_date_desc_optional/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Barometer" ALTER COLUMN "date" DROP NOT NULL; diff --git a/prisma/migrations/20241214212323_make_date_required/migration.sql b/prisma/migrations/20241214212323_make_date_required/migration.sql new file mode 100644 index 00000000..d3e707a5 --- /dev/null +++ b/prisma/migrations/20241214212323_make_date_required/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Made the column `date` on table `Barometer` required. This step will fail if there are existing NULL values in that column. + +*/ +-- AlterTable +ALTER TABLE "Barometer" ALTER COLUMN "date" SET NOT NULL; diff --git a/prisma/migrations/20241214235347_add_unique_to_category_name/migration.sql b/prisma/migrations/20241214235347_add_unique_to_category_name/migration.sql new file mode 100644 index 00000000..3e72dc88 --- /dev/null +++ b/prisma/migrations/20241214235347_add_unique_to_category_name/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[name]` on the table `Category` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..fbffa92c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/prismaClient.ts b/prisma/prismaClient.ts new file mode 100644 index 00000000..dcce9c3a --- /dev/null +++ b/prisma/prismaClient.ts @@ -0,0 +1,17 @@ +import { Pool, neonConfig } from '@neondatabase/serverless' +import { PrismaNeon } from '@prisma/adapter-neon' +import { PrismaClient } from '@prisma/client' +import { WebSocket } from 'undici' + +neonConfig.webSocketConstructor = WebSocket + +export function getPrismaClient(): PrismaClient { + const connectionString = `${process.env.DATABASE_URL}` + const pool = new Pool({ connectionString }) + const adapter = new PrismaNeon(pool) + + return new PrismaClient({ + adapter, + log: process.env.NODE_ENV === 'development' ? ['query', 'info', 'warn', 'error'] : [], + }) +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 00000000..71c284dc --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,91 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["driverAdapters"] +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Image { + id String @id @default(uuid()) + url String + description String? + name String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + categories Category? @relation("CategoryImage") + barometers Barometer[] @relation("BarometerImages") +} + +model Category { + id String @id @default(uuid()) + name String @unique + description String + label String + order Int + imageId String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + barometers Barometer[] + image Image @relation("CategoryImage", fields: [imageId], references: [id]) +} + +model Barometer { + id String @id @default(uuid()) + collectionId String @unique + name String @unique + slug String @unique + description String + conditionId String + date DateTime + dateDescription String + manufacturerId String + dimensions Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + categoryId String + category Category @relation(fields: [categoryId], references: [id]) + condition Condition @relation(fields: [conditionId], references: [id]) + manufacturer Manufacturer @relation(fields: [manufacturerId], references: [id]) + images Image[] @relation("BarometerImages") +} + +model Manufacturer { + id String @id @default(uuid()) + name String + country String? + city String? + description String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + barometers Barometer[] +} + +model Condition { + id String @id @default(uuid()) + name String + value Int + description String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + barometers Barometer[] +} + +model User { + id String @id @default(uuid()) + email String @unique + password String? + name String + role AccessRole + avatarURL String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +enum AccessRole { + USER + OWNER + ADMIN +} From 647ad39cc9ff91217c3213ab62c062eeebd07b01 Mon Sep 17 00:00:00 2001 From: Cybervoid Date: Mon, 16 Dec 2024 01:03:45 +0100 Subject: [PATCH 2/2] feat: add postinstall script to generate Prisma client --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 811e5bb7..14ec0208 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "scripts": { "dev": "next dev", "build": "next build", + "postinstall": "prisma generate", "analyze": "ANALYZE=true next build", "start": "next start -p $PORT", "typecheck": "tsc --noEmit",