Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
37fcec8
schema.prisma: Added DBML generator
michu2k May 29, 2025
8b8ba50
prisma.schema: Added ServiceAvailability model and updated Service sc…
michu2k May 30, 2025
0a63994
Updated README.md
michu2k May 30, 2025
55aebf8
Formatting fixes
michu2k May 30, 2025
93bcfb5
seed: Added status to Service, updated amount of seed records
michu2k May 31, 2025
7efcf75
schema.prisma: Renamed ServiceAvailability to ServiceSchedule
michu2k May 31, 2025
a8900a8
prisma: Added migration for ServiceSchedule model
michu2k Jun 2, 2025
ac3a630
seed: Moved prisma client to the utils, added console.time
michu2k Jun 6, 2025
8a4a4ee
schema.prisma: Changed schedule startTime and endTime columns to stri…
michu2k Jun 6, 2025
337f1a8
schema.prisma: startTime and endTime fields can be null
michu2k Jun 7, 2025
dbfd06a
seed: Added seed for ServiceSchedule model
michu2k Jun 7, 2025
34ce785
Updated README.md
michu2k Jun 7, 2025
89f52fd
prisma: Added schema.dbml file
michu2k Jun 7, 2025
18b87df
Updated service dto
michu2k Jun 7, 2025
ecb77aa
Updated service tests
michu2k Jun 7, 2025
39016db
Simplified values passed to ApiProperty
michu2k Jun 9, 2025
64a697c
schedule: Added schedule entity and dtos
michu2k Jun 10, 2025
feb1db1
seed: Renamed schedules file
michu2k Jun 12, 2025
162de67
Creating a new service will also create a full schedule
michu2k Jun 14, 2025
f706bd6
Added Schedule module
michu2k Jun 14, 2025
c454067
Simplified and moved mocks to the mock files
michu2k Jun 16, 2025
d395076
Updated schedule endpoints, adjusted mocks
michu2k Jun 19, 2025
b8c23ba
Added tests to schedule service and controller
michu2k Jun 19, 2025
7a6c741
Updated app version
michu2k Jun 19, 2025
2ec5965
Updated packages
michu2k Jun 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

A simple booking system built with NestJS 11 & Prisma. This project was created to test the NestJS framework, and additional changes or new features may appear over time.

The project uses a PostgreSQL database running in Docker. Athentication has been implemented using the Google Auth0 with the JWT tokens. Each module features multiple unit and e2e tests.
The project uses a PostgreSQL database running in Docker. Authentication has been implemented using the Google Auth0 with the JWT tokens. Each module features multiple unit and e2e tests.

## Requirements

Expand All @@ -22,6 +22,10 @@ This project requires [Node.js 20+](https://nodejs.org/en), [pnpm 9+](https://pn
6. If necessary, run prisma studio to check the db data `pnpm run prisma:studio`
7. You can also populate the database with test data using `pnpm run prisma:seed`

## Database diagram

Each time you run the `prisma:gen` command, a new DBML diagram will be generated in the `prisma/schema.dbml` file. You can use [dbdiagram.io](https://dbdiagram.io/home) to visualize the database schema as an Entity-Relationship diagram.

## License

This project is under the MIT license.
33 changes: 17 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nestjs-booking-system",
"version": "1.0.0",
"version": "1.1.0",
"license": "MIT",
"author": "Michał Strumpf",
"private": true,
Expand All @@ -26,15 +26,15 @@
"test:e2e:watch": "jest --config ./test/jest-e2e.json --watch"
},
"dependencies": {
"@nestjs/common": "^11.1.2",
"@nestjs/common": "^11.1.3",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.1.2",
"@nestjs/core": "^11.1.3",
"@nestjs/jwt": "^11.0.0",
"@nestjs/mapped-types": "^2.1.0",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.2",
"@nestjs/platform-express": "^11.1.3",
"@nestjs/swagger": "^11.2.0",
"@prisma/client": "6.8.2",
"@prisma/client": "6.10.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
Expand All @@ -50,32 +50,33 @@
"@faker-js/faker": "^9.8.0",
"@nestjs/cli": "^11.0.7",
"@nestjs/schematics": "^11.0.5",
"@nestjs/testing": "^11.1.2",
"@nestjs/testing": "^11.1.3",
"@types/bcrypt": "^5.0.2",
"@types/compression": "^1.8.0",
"@types/cookie-parser": "^1.4.8",
"@types/express": "^5.0.2",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.21",
"@types/compression": "^1.8.1",
"@types/cookie-parser": "^1.4.9",
"@types/express": "^5.0.3",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.3",
"@types/passport": "^1.0.17",
"@types/passport-google-oauth20": "^2.0.16",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.3",
"eslint": "^9.27.0",
"eslint": "^9.29.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "4.1.4",
"jest": "^29.7.0",
"jest": "^30.0.1",
"prettier": "^3.5.3",
"prisma": "^6.8.2",
"prisma": "^6.10.1",
"prisma-dbml-generator": "^0.12.0",
"source-map-support": "^0.5.21",
"supertest": "^7.1.1",
"ts-jest": "^29.3.4",
"ts-jest": "^29.4.0",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.1"
"typescript-eslint": "^8.34.1"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
Expand Down
3,524 changes: 2,435 additions & 1,089 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- CreateEnum
CREATE TYPE "ServiceStatus" AS ENUM ('ACTIVE', 'INACTIVE', 'UNAVAILABLE', 'DRAFT', 'ARCHIVED');

-- AlterTable
ALTER TABLE "Service" ADD COLUMN "status" "ServiceStatus" NOT NULL DEFAULT 'ACTIVE';

-- CreateTable
CREATE TABLE "ServiceSchedule" (
"id" SERIAL NOT NULL,
"day" INTEGER NOT NULL,
"startTime" TEXT,
"endTime" TEXT,
"serviceId" INTEGER NOT NULL,

CONSTRAINT "ServiceSchedule_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "ServiceSchedule_serviceId_day_key" ON "ServiceSchedule"("serviceId", "day");

-- AddForeignKey
ALTER TABLE "ServiceSchedule" ADD CONSTRAINT "ServiceSchedule_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service"("id") ON DELETE CASCADE ON UPDATE CASCADE;
4 changes: 2 additions & 2 deletions prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
106 changes: 106 additions & 0 deletions prisma/schema.dbml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//// ------------------------------------------------------
//// THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
//// ------------------------------------------------------

Table Account {
id Int [pk, increment]
userId Int [not null]
provider String [not null]
providerAccountId String [not null]
user User [not null]

indexes {
(provider, providerAccountId) [unique]
}
}

Table User {
id Int [pk, increment]
name String [not null]
email String [unique, not null]
phone String
role UserRole [not null]
refreshToken String
accounts Account [not null]
Booking Booking [not null]
}

Table Booking {
id Int [pk, increment]
from DateTime [not null]
to DateTime [not null]
createdAt DateTime [default: `now()`, not null]
updatedAt DateTime [default: `now()`, not null]
status BookingStatus [not null]
userId Int [not null]
serviceId Int [not null]
service Service [not null]
user User [not null]
}

Table Service {
id Int [pk, increment]
name String [not null]
description String [not null]
price Int [not null]
createdAt DateTime [default: `now()`, not null]
updatedAt DateTime [default: `now()`, not null]
status ServiceStatus [not null, default: 'ACTIVE']
locationId Int
Booking Booking [not null]
location Location
ServiceSchedule ServiceSchedule [not null]
}

Table ServiceSchedule {
id Int [pk, increment]
day Int [not null]
startTime String
endTime String
serviceId Int [not null]
service Service [not null]

indexes {
(serviceId, day) [unique]
}
}

Table Location {
id Int [pk, increment]
city String [not null]
address String [not null]
country String [not null]
lat Float
lng Float
Service Service [not null]
}

Enum UserRole {
ADMIN
MANAGER
USER
}

Enum BookingStatus {
CONFIRMED
PENDING
CANCELLED
}

Enum ServiceStatus {
ACTIVE
INACTIVE
UNAVAILABLE
DRAFT
ARCHIVED
}

Ref: Account.userId > User.id [delete: Cascade]

Ref: Booking.serviceId > Service.id [delete: Cascade]

Ref: Booking.userId > User.id [delete: Cascade]

Ref: Service.locationId > Location.id

Ref: ServiceSchedule.serviceId > Service.id [delete: Cascade]
46 changes: 36 additions & 10 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ generator client {
provider = "prisma-client-js"
}

generator dbml {
provider = "prisma-dbml-generator"
output = "./"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
Expand Down Expand Up @@ -34,23 +39,36 @@ model Booking {
to DateTime
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
status BookingStatus
userId Int
serviceId Int
status BookingStatus
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model Service {
id Int @id @default(autoincrement())
name String
description String
price Int
locationId Int?
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
Booking Booking[]
location Location? @relation(fields: [locationId], references: [id])
id Int @id @default(autoincrement())
name String
description String
price Int
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
status ServiceStatus @default(ACTIVE)
locationId Int?
Booking Booking[]
location Location? @relation(fields: [locationId], references: [id])
ServiceSchedule ServiceSchedule[]
}

model ServiceSchedule {
id Int @id @default(autoincrement())
day Int
startTime String?
endTime String?
serviceId Int
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)

@@unique([serviceId, day])
}

model Location {
Expand All @@ -74,3 +92,11 @@ enum BookingStatus {
PENDING
CANCELLED
}

enum ServiceStatus {
ACTIVE
INACTIVE
UNAVAILABLE
DRAFT
ARCHIVED
}
19 changes: 11 additions & 8 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { PrismaClient } from "@prisma/client";

import { prisma } from "./seed.utils";
import { seedBookings } from "./seed/booking.seed";
import { seedLocations } from "./seed/location.seed";
import { seedSchedules } from "./seed/schedule.seed";
import { seedServices } from "./seed/service.seed";
import { seedUsers } from "./seed/user.seed";

const prisma = new PrismaClient();

async function seed() {
await seedUsers(prisma);
await seedLocations(prisma);
await seedServices(prisma);
await seedBookings(prisma);
console.time("Seeding database");

await seedUsers();
await seedLocations();
await seedServices();
await seedSchedules();
await seedBookings();

console.timeEnd("Seeding database");
}

seed()
Expand Down
11 changes: 8 additions & 3 deletions prisma/seed.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { PrismaClient } from "@prisma/client";

/** Prisma client instance used for the seed operations. */
export const prisma = new PrismaClient();

export const SEED_RECORDS = {
USER: 10,
LOCATION: 2,
SERVICE: 3,
BOOKING: 10
LOCATION: 4,
SERVICE: 8,
BOOKING: 20
};
6 changes: 3 additions & 3 deletions prisma/seed/booking.seed.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { faker } from "@faker-js/faker";
import { BookingStatus, type Prisma, PrismaClient } from "@prisma/client";
import { BookingStatus, type Prisma } from "@prisma/client";

import { SEED_RECORDS } from "../seed.utils";
import { prisma, SEED_RECORDS } from "../seed.utils";

function createBooking(): Prisma.BookingCreateManyInput {
return {
Expand All @@ -13,7 +13,7 @@ function createBooking(): Prisma.BookingCreateManyInput {
};
}

export async function seedBookings(prisma: PrismaClient) {
export async function seedBookings() {
console.log("Seeding bookings...");

await prisma.booking.deleteMany();
Expand Down
6 changes: 3 additions & 3 deletions prisma/seed/location.seed.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { faker } from "@faker-js/faker";
import { type Prisma, PrismaClient } from "@prisma/client";
import { type Prisma } from "@prisma/client";

import { SEED_RECORDS } from "../seed.utils";
import { prisma, SEED_RECORDS } from "../seed.utils";

function createLocation(): Prisma.LocationCreateManyInput {
return {
Expand All @@ -13,7 +13,7 @@ function createLocation(): Prisma.LocationCreateManyInput {
};
}

export async function seedLocations(prisma: PrismaClient) {
export async function seedLocations() {
console.log("Seeding locations...");

await prisma.location.deleteMany();
Expand Down
Loading