diff --git a/.env.example b/.env.example index 4b6ec2500455d9d90dbdb2eb2da08f22c69b6e80..ca8f27598d7b665e1076da6dff73a43eda71d61d 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,10 @@ SERVER_PORT=4000 DB_USER=admin DB_PASSWORD=password DB_NAME=film-db +DB_PORT=5432 + +# PRISMA +DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@postgres:${DB_PORT}/${DB_NAME}?schema=public # ADMINER ADMINER_PORT=8080 diff --git a/README.md b/README.md index 5657e0d3cd9e9602368906b3e8f9bfd487afe9c7..9429c9dfaa29b44dec43b7c5936e5d6ea6b888d0 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ With Adminer we can manage our PostgreSQL database. To login to Adminer in your browser you need to specify: * System: `PostgreSQL` * Server: `postgres` (docker-compose database service name) + * if you changed `DB_PORT` in your .env file from 5432 (which is the default for Postgres), the server must be specified as `postgres:DB_PORT` * Username: same as `DB_USER` in your .env file * Password: same as `DB_PASSWORD` in your .env file * Database: same as `DB_NAME` in your .env file diff --git a/backend/package-lock.json b/backend/package-lock.json index ca5a6ee8fbbf039a2b1660f247e2d90311f7c3da..5e9d492bbacf6bc20b17cc08169e94590c20224d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "@nestjs/config": "^2.1.0", "@nestjs/core": "^8.0.0", "@nestjs/platform-express": "^8.0.0", + "@prisma/client": "^3.15.1", "jshint": "^2.13.4", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", @@ -33,6 +34,7 @@ "eslint-plugin-prettier": "^4.0.0", "jest": "28.0.3", "prettier": "^2.3.2", + "prisma": "^3.15.1", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "28.0.1", @@ -1726,6 +1728,38 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@prisma/client": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.15.1.tgz", + "integrity": "sha512-Lsk7oupvO9g99mrIs07iE6BIMouHs46Yq/YY8itTsUQNKfecsPuZvVYvcKci0pqRQ0neOpvIvoA/ouZmIMBCrQ==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines-version": "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" + }, + "engines": { + "node": ">=12.6" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz", + "integrity": "sha512-NHlojO1DFTsSi3FtEleL9QWXeSF/UjhCW0fgpi7bumnNZ4wj/eQ+BJJ5n2pgoOliTOGv9nX2qXvmHap7rJMNmg==", + "devOptional": true, + "hasInstallScript": true + }, + "node_modules/@prisma/engines-version": { + "version": "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz", + "integrity": "sha512-e3k2Vd606efd1ZYy2NQKkT4C/pn31nehyLhVug6To/q8JT8FpiMrDy7zmm3KLF0L98NOQQcutaVtAPhzKhzn9w==" + }, "node_modules/@sinclair/typebox": { "version": "0.23.5", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", @@ -7152,6 +7186,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prisma": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.15.1.tgz", + "integrity": "sha512-MLO3JUGJpe5+EVisA/i47+zlyF8Ug0ivvGYG4B9oSXQcPiUHB1ccmnpxqR7o0Up5SQgmxkBiEU//HgR6UuIKOw==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" + }, + "bin": { + "prisma": "build/index.js", + "prisma2": "build/index.js" + }, + "engines": { + "node": ">=12.6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -10118,6 +10169,25 @@ } } }, + "@prisma/client": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.15.1.tgz", + "integrity": "sha512-Lsk7oupvO9g99mrIs07iE6BIMouHs46Yq/YY8itTsUQNKfecsPuZvVYvcKci0pqRQ0neOpvIvoA/ouZmIMBCrQ==", + "requires": { + "@prisma/engines-version": "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" + } + }, + "@prisma/engines": { + "version": "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz", + "integrity": "sha512-NHlojO1DFTsSi3FtEleL9QWXeSF/UjhCW0fgpi7bumnNZ4wj/eQ+BJJ5n2pgoOliTOGv9nX2qXvmHap7rJMNmg==", + "devOptional": true + }, + "@prisma/engines-version": { + "version": "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz", + "integrity": "sha512-e3k2Vd606efd1ZYy2NQKkT4C/pn31nehyLhVug6To/q8JT8FpiMrDy7zmm3KLF0L98NOQQcutaVtAPhzKhzn9w==" + }, "@sinclair/typebox": { "version": "0.23.5", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.5.tgz", @@ -14267,6 +14337,15 @@ } } }, + "prisma": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.15.1.tgz", + "integrity": "sha512-MLO3JUGJpe5+EVisA/i47+zlyF8Ug0ivvGYG4B9oSXQcPiUHB1ccmnpxqR7o0Up5SQgmxkBiEU//HgR6UuIKOw==", + "devOptional": true, + "requires": { + "@prisma/engines": "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" + } + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/backend/package.json b/backend/package.json index 21cdb97e6e3e69b5129c275f8a811eb986e64a49..36e70e70cf2424848c3337945fe3ea118be15533 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,6 +25,7 @@ "@nestjs/config": "^2.1.0", "@nestjs/core": "^8.0.0", "@nestjs/platform-express": "^8.0.0", + "@prisma/client": "^3.15.1", "jshint": "^2.13.4", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", @@ -45,6 +46,7 @@ "eslint-plugin-prettier": "^4.0.0", "jest": "28.0.3", "prettier": "^2.3.2", + "prisma": "^3.15.1", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "28.0.1", diff --git a/backend/prisma/migrations/20220614193457_init/migration.sql b/backend/prisma/migrations/20220614193457_init/migration.sql new file mode 100644 index 0000000000000000000000000000000000000000..f2518c13e509f56306e1968457ddd6e2ac688745 --- /dev/null +++ b/backend/prisma/migrations/20220614193457_init/migration.sql @@ -0,0 +1,70 @@ +-- CreateTable +CREATE TABLE "Movie" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3), + "name" TEXT NOT NULL, + "originalName" TEXT NOT NULL, + "intro" TEXT NOT NULL, + "picture" TEXT, + "publishedAt" TIMESTAMP(3) NOT NULL, + "runTimeMinutes" INTEGER NOT NULL, + "directorId" TEXT NOT NULL, + + CONSTRAINT "Movie_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Director" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3), + "name" TEXT NOT NULL, + "surname" TEXT NOT NULL, + "birthDate" TIMESTAMP(3), + + CONSTRAINT "Director_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Category" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deletedAt" TIMESTAMP(3), + "name" TEXT NOT NULL, + + CONSTRAINT "Category_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_MovieCategory" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "Movie_originalName_publishedAt_directorId_key" ON "Movie"("originalName", "publishedAt", "directorId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Director_name_surname_birthDate_key" ON "Director"("name", "surname", "birthDate"); + +-- CreateIndex +CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "_MovieCategory_AB_unique" ON "_MovieCategory"("A", "B"); + +-- CreateIndex +CREATE INDEX "_MovieCategory_B_index" ON "_MovieCategory"("B"); + +-- AddForeignKey +ALTER TABLE "Movie" ADD CONSTRAINT "Movie_directorId_fkey" FOREIGN KEY ("directorId") REFERENCES "Director"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_MovieCategory" ADD CONSTRAINT "_MovieCategory_A_fkey" FOREIGN KEY ("A") REFERENCES "Category"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_MovieCategory" ADD CONSTRAINT "_MovieCategory_B_fkey" FOREIGN KEY ("B") REFERENCES "Movie"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000000000000000000000000000000000000..fbffa92c2bb7c748d6fc78f9f9dcac604dabb87d --- /dev/null +++ b/backend/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/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000000000000000000000000000000000000..b81dd6653c87cb22fc310158dcadf4ee00067497 --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,57 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Movie { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + + name String + originalName String + intro String + picture String? + publishedAt DateTime + runTimeMinutes Int + + director Director @relation(fields: [directorId], references: [id]) + directorId String + categories Category[] @relation(name: "MovieCategory") + + @@unique([originalName, publishedAt, directorId]) +} + +model Director { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + + name String + surname String + birthDate DateTime? + + movies Movie[] + + @@unique([name, surname, birthDate]) +} + +model Category { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + + name String @unique + + movies Movie[] @relation(name: "MovieCategory") +} diff --git a/backend/src/main.ts b/backend/src/main.ts index 7ec8d4b3d866f07bcd7d37cf38ec08df84363467..4084ce75dbf659a0a90d934380c9d84a328a1a3a 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -6,7 +6,7 @@ async function bootstrap(): Promise<void> { const app = await NestFactory.create(AppModule); const configService = app.get(ConfigService); - const port = configService.get<number>('PORT'); + const port = configService.get<number>('PORT') | 4000; console.log(`NestJS server is listening on: http://localhost:${port}`); await app.listen(port); } diff --git a/backend/src/prisma.service.ts b/backend/src/prisma.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..c20b6872db255060e49503caeeaf2c7256c3e39e --- /dev/null +++ b/backend/src/prisma.service.ts @@ -0,0 +1,15 @@ +import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit { + async onModuleInit(): Promise<void> { + await this.$connect(); + } + + enableShutdownHooks(app: INestApplication): void { + this.$on('beforeExit', async () => { + await app.close(); + }); + } +} diff --git a/docker-compose.yml b/docker-compose.yml index b7466238cdfb6adb3d288bf0f49a47912eaaaa94..edd8a39a1895fcd5dda5b7554088266cdd729357 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,7 @@ services: - exclude-server:/usr/src/app/node_modules/ environment: - PORT=${SERVER_PORT} + - DATABASE_URL=${DATABASE_URL} restart: always depends_on: - postgres @@ -34,6 +35,7 @@ services: POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME} + PGPORT: ${DB_PORT} restart: always adminer: