feat: add class-transformer dependency for better data transformation
refactor(prisma): update User model to include banned status and ban details refactor(prisma): modify Account model to include new authentication fields refactor(prisma): enhance Session model with impersonation tracking refactor(prisma): adjust Verification model to include value field feat(auth): integrate admin plugin and enhance email verification process refactor(category): enforce ownership checks in category service methods refactor(item): update item service to validate category ownership feat(item): add ability to fetch items by category refactor(restaurant): implement ownership checks and admin routes in restaurant controller fix(user): restrict user access to admin routes feat(exception): implement global exception filter for consistent error handling
This commit is contained in:
Generated
+7
@@ -15,6 +15,7 @@
|
||||
"@prisma/client": "^6.18.0",
|
||||
"@thallesp/nestjs-better-auth": "^2.1.0",
|
||||
"better-auth": "^1.3.8",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"jose": "3.16.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
@@ -5029,6 +5030,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-transformer": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-validator": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"@prisma/client": "^6.18.0",
|
||||
"@thallesp/nestjs-better-auth": "^2.1.0",
|
||||
"better-auth": "^1.3.8",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"jose": "3.16.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `accessToken` on the `Account` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `accountId` on the `Account` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `expiresAt` on the `Account` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `password` on the `Account` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `providerId` on the `Account` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `refreshToken` on the `Account` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[token]` on the table `Verification` will be added. If there are existing duplicate values, this will fail.
|
||||
- Added the required column `providerAccountId` to the `Account` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `type` to the `Account` table without a default value. This is not possible if the table is not empty.
|
||||
- Made the column `provider` on table `Account` required. This step will fail if there are existing NULL values in that column.
|
||||
- Added the required column `value` to the `Verification` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Account" DROP COLUMN "accessToken",
|
||||
DROP COLUMN "accountId",
|
||||
DROP COLUMN "expiresAt",
|
||||
DROP COLUMN "password",
|
||||
DROP COLUMN "providerId",
|
||||
DROP COLUMN "refreshToken",
|
||||
ADD COLUMN "access_token" TEXT,
|
||||
ADD COLUMN "expires_at" INTEGER,
|
||||
ADD COLUMN "id_token" TEXT,
|
||||
ADD COLUMN "providerAccountId" TEXT NOT NULL,
|
||||
ADD COLUMN "refresh_token" TEXT,
|
||||
ADD COLUMN "scope" TEXT,
|
||||
ADD COLUMN "session_state" TEXT,
|
||||
ADD COLUMN "token_type" TEXT,
|
||||
ADD COLUMN "type" TEXT NOT NULL,
|
||||
ALTER COLUMN "provider" SET NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verification" ADD COLUMN "value" TEXT NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Verification_token_key" ON "Verification"("token");
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `Account` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `Session` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `Verification` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Account" DROP CONSTRAINT "Account_userId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Restaurant" DROP CONSTRAINT "Restaurant_ownerId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Account";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Session";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "User";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Verification";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"image" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"role" TEXT NOT NULL DEFAULT 'user',
|
||||
|
||||
CONSTRAINT "user_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"providerAccountId" TEXT NOT NULL,
|
||||
"refresh_token" TEXT,
|
||||
"access_token" TEXT,
|
||||
"expires_at" INTEGER,
|
||||
"token_type" TEXT,
|
||||
"scope" TEXT,
|
||||
"id_token" TEXT,
|
||||
"session_state" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"accountId" TEXT NOT NULL,
|
||||
"providerId" TEXT NOT NULL,
|
||||
"accessToken" TEXT,
|
||||
"refreshToken" TEXT,
|
||||
"idToken" TEXT,
|
||||
"accessTokenExpiresAt" TIMESTAMP(3),
|
||||
"refreshTokenExpiresAt" TIMESTAMP(3),
|
||||
"password" TEXT,
|
||||
|
||||
CONSTRAINT "account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"token" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "verification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "verification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_email_key" ON "user"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "session_token_key" ON "session"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "verification_token_key" ON "verification"("token");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Restaurant" ADD CONSTRAINT "Restaurant_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `account` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `session` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `user` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `verification` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Restaurant" DROP CONSTRAINT "Restaurant_ownerId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "account" DROP CONSTRAINT "account_userId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "session" DROP CONSTRAINT "session_userId_fkey";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "account";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "session";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "user";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "verification";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"image" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"role" TEXT NOT NULL DEFAULT 'user',
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"providerId" TEXT NOT NULL,
|
||||
"provider" TEXT,
|
||||
"accountId" TEXT NOT NULL,
|
||||
"password" TEXT,
|
||||
"accessToken" TEXT,
|
||||
"refreshToken" TEXT,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"token" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Verification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Verification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Restaurant" ADD CONSTRAINT "Restaurant_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `Account` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `Session` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `Verification` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Account" DROP CONSTRAINT "Account_userId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Restaurant" DROP CONSTRAINT "Restaurant_ownerId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Account";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Session";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "User";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Verification";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"image" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"role" TEXT NOT NULL DEFAULT 'user',
|
||||
|
||||
CONSTRAINT "user_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"providerId" TEXT NOT NULL,
|
||||
"provider" TEXT,
|
||||
"accountId" TEXT NOT NULL,
|
||||
"password" TEXT,
|
||||
"accessToken" TEXT,
|
||||
"refreshToken" TEXT,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"idToken" TEXT,
|
||||
"accessTokenExpiresAt" TIMESTAMP(3),
|
||||
"refreshTokenExpiresAt" TIMESTAMP(3),
|
||||
"scope" TEXT,
|
||||
|
||||
CONSTRAINT "account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"token" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "verification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "verification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_email_key" ON "user"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "session_token_key" ON "session"("token");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Restaurant" ADD CONSTRAINT "Restaurant_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "session" ADD COLUMN "impersonatedBy" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "user" ADD COLUMN "banExpires" TIMESTAMP(3),
|
||||
ADD COLUMN "banReason" TEXT,
|
||||
ADD COLUMN "banned" BOOLEAN DEFAULT false,
|
||||
ALTER COLUMN "role" DROP NOT NULL;
|
||||
+82
-66
@@ -1,3 +1,4 @@
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
@@ -7,99 +8,114 @@ datasource db {
|
||||
url = "postgresql://postgres:root@localhost:5432/zemenu?schema=public"
|
||||
}
|
||||
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified Boolean @default(false)
|
||||
email String? @unique
|
||||
emailVerified Boolean @default(false)
|
||||
image String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
role String @default("user")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
// Relations
|
||||
restaurants Restaurant[]
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
restaurants Restaurant[]
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
|
||||
role String? @default("user")
|
||||
banned Boolean? @default(false)
|
||||
banReason String?
|
||||
banExpires DateTime?
|
||||
|
||||
@@map("user")
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
providerId String
|
||||
provider String?
|
||||
accountId String
|
||||
password String?
|
||||
accessToken String?
|
||||
refreshToken String?
|
||||
expiresAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
providerId String
|
||||
provider String?
|
||||
accountId String
|
||||
password String?
|
||||
accessToken String?
|
||||
refreshToken String?
|
||||
expiresAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
idToken String?
|
||||
accessTokenExpiresAt DateTime?
|
||||
refreshTokenExpiresAt DateTime?
|
||||
scope String?
|
||||
|
||||
@@map("account")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
expiresAt DateTime
|
||||
token String? @unique
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
expiresAt DateTime
|
||||
token String? @unique
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
impersonatedBy String?
|
||||
|
||||
@@map("session")
|
||||
}
|
||||
|
||||
model Verification {
|
||||
id String @id @default(cuid())
|
||||
identifier String
|
||||
token String
|
||||
token String?
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
value String
|
||||
|
||||
@@map("verification")
|
||||
}
|
||||
|
||||
|
||||
model Restaurant {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
slug String @unique // used as /domain.com/slug
|
||||
description String?
|
||||
logo String? // image URL
|
||||
ownerId String
|
||||
owner User @relation(fields: [ownerId], references: [id])
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
slug String @unique // used as /domain.com/slug
|
||||
description String?
|
||||
logo String? // image URL
|
||||
ownerId String
|
||||
owner User @relation(fields: [ownerId], references: [id])
|
||||
|
||||
template String?
|
||||
template String?
|
||||
|
||||
categories Category[]
|
||||
items MenuItem[]
|
||||
createdAt DateTime @default(now())
|
||||
categories Category[]
|
||||
items MenuItem[]
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model Category {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
|
||||
restaurantId String
|
||||
restaurant Restaurant @relation(fields: [restaurantId], references: [id])
|
||||
|
||||
items MenuItem[]
|
||||
}
|
||||
|
||||
model MenuItem {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
price Float
|
||||
image String?
|
||||
|
||||
categoryId String?
|
||||
category Category? @relation(fields: [categoryId], references: [id])
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
|
||||
restaurantId String
|
||||
restaurant Restaurant @relation(fields: [restaurantId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
items MenuItem[]
|
||||
}
|
||||
|
||||
model MenuItem {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
price Float
|
||||
image String?
|
||||
|
||||
categoryId String?
|
||||
category Category? @relation(fields: [categoryId], references: [id])
|
||||
|
||||
restaurantId String
|
||||
restaurant Restaurant @relation(fields: [restaurantId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
+15
-2
@@ -1,7 +1,7 @@
|
||||
import { betterAuth } from 'better-auth';
|
||||
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
||||
// If your Prisma file is located elsewhere, you can change the path
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { admin } from 'better-auth/plugins';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -9,12 +9,25 @@ export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: 'postgresql',
|
||||
}),
|
||||
plugins: [admin()],
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
// requireEmailVerification: true,
|
||||
requireEmailVerification: true,
|
||||
sendResetPassword: async ({ user, token }) => {
|
||||
console.log(`Send reset password email to ${user.email}: ${token}`);
|
||||
},
|
||||
},
|
||||
emailVerification: {
|
||||
sendVerificationEmail: async ({ user, token }) => {
|
||||
console.log(`Send verification email to ${user.email}: ${token}`);
|
||||
},
|
||||
sendOnSignIn: true,
|
||||
autoSignInAfterVerification: true,
|
||||
},
|
||||
trustedOrigins: ['http://localhost:5173', 'http://localhost:5174'],
|
||||
socialProviders: {
|
||||
google: {
|
||||
prompt: 'select_account',
|
||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||
},
|
||||
|
||||
@@ -8,17 +8,17 @@ import {
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard, Session } from '@thallesp/nestjs-better-auth';
|
||||
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
||||
import { CategoryService } from './category.service';
|
||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||
import { UpdateCategoryDto } from './dto/update-category.dto';
|
||||
import { AuthGuard, Session } from '@thallesp/nestjs-better-auth';
|
||||
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
||||
|
||||
@Controller('categories')
|
||||
@UseGuards(AuthGuard)
|
||||
export class CategoryController {
|
||||
constructor(private readonly service: CategoryService) {}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Post()
|
||||
async create(
|
||||
@Body() dto: CreateCategoryDto,
|
||||
@@ -28,8 +28,11 @@ export class CategoryController {
|
||||
}
|
||||
|
||||
@Get('restaurant/:restaurantId')
|
||||
async findAllForRestaurant(@Param('restaurantId') restaurantId: string) {
|
||||
return await this.service.findAllForRestaurant(restaurantId);
|
||||
async findAllForRestaurant(
|
||||
@Param('restaurantId') restaurantId: string,
|
||||
@Session() session: UserSession,
|
||||
) {
|
||||
return await this.service.findAllForRestaurant(restaurantId, session.user.id);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@@ -37,7 +40,6 @@ export class CategoryController {
|
||||
return await this.service.findOne(id);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Put(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@@ -47,9 +49,8 @@ export class CategoryController {
|
||||
return await this.service.update(id, dto, session.user.id);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Delete(':id')
|
||||
async remove(@Param('id') id: string, @Session() session: UserSession) {
|
||||
await this.service.remove(id, session.user.id);
|
||||
return await this.service.remove(id, session.user.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { RestaurantService } from 'src/restaurant/restaurant.service';
|
||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||
import { UpdateCategoryDto } from './dto/update-category.dto';
|
||||
import { RestaurantService } from 'src/restaurant/restaurant.service';
|
||||
import { CategoryDto } from './dto/category.dto';
|
||||
|
||||
@Injectable()
|
||||
@@ -14,19 +14,25 @@ export class CategoryService {
|
||||
|
||||
async create(dto: CreateCategoryDto, userId: string) {
|
||||
await this.restaurantService.verifyOwnership(userId, dto.restaurantId);
|
||||
return this.prisma.category.create({
|
||||
|
||||
const category = await this.prisma.category.create({
|
||||
data: {
|
||||
name: dto.name,
|
||||
restaurantId: dto.restaurantId,
|
||||
},
|
||||
});
|
||||
|
||||
return CategoryDto.fromEntity(category);
|
||||
}
|
||||
|
||||
async findAllForRestaurant(restaurantId: string) {
|
||||
async findAllForRestaurant(restaurantId: string, userId: string) {
|
||||
await this.restaurantService.verifyOwnership(userId, restaurantId);
|
||||
|
||||
const categories = await this.prisma.category.findMany({
|
||||
where: { restaurantId },
|
||||
include: { items: true },
|
||||
});
|
||||
|
||||
return categories.map(CategoryDto.fromEntity);
|
||||
}
|
||||
|
||||
@@ -35,19 +41,20 @@ export class CategoryService {
|
||||
where: { id },
|
||||
include: { items: true },
|
||||
});
|
||||
|
||||
if (!category) throw new NotFoundException('Category not found');
|
||||
|
||||
|
||||
|
||||
return CategoryDto.fromEntity(category);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateCategoryDto, userId: string) {
|
||||
const category = await this.findOne(id);
|
||||
if (!category) {
|
||||
throw new NotFoundException('Category not found');
|
||||
}
|
||||
|
||||
await this.restaurantService.verifyOwnership(userId, category.restaurantId);
|
||||
|
||||
const savedCategory = await this.prisma.category.update({
|
||||
const updated = await this.prisma.category.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: dto.name,
|
||||
@@ -55,17 +62,14 @@ export class CategoryService {
|
||||
},
|
||||
});
|
||||
|
||||
return CategoryDto.fromEntity(savedCategory);
|
||||
return CategoryDto.fromEntity(updated);
|
||||
}
|
||||
|
||||
async remove(id: string, userId: string) {
|
||||
const category = await this.findOne(id);
|
||||
|
||||
if (!category) {
|
||||
throw new NotFoundException('Category not found');
|
||||
}
|
||||
await this.restaurantService.verifyOwnership(userId, category.restaurantId);
|
||||
|
||||
this.prisma.category.delete({ where: { id } });
|
||||
return this.prisma.category.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
export class CreateCategoryDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
image?: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
restaurantId: string;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
|
||||
@Catch()
|
||||
export class AllExceptionsFilter implements ExceptionFilter {
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
let message = 'Internal server error';
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
status = exception.getStatus();
|
||||
const res = exception.getResponse();
|
||||
if (typeof res === 'string') message = res;
|
||||
else if (typeof res === 'object') message = (res as any).message || res;
|
||||
}
|
||||
|
||||
return response.status(status).json({
|
||||
success: false,
|
||||
status,
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,7 @@ export class CreateItemDto {
|
||||
@IsString()
|
||||
image?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
categoryId?: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
restaurantId: string;
|
||||
categoryId: string;
|
||||
}
|
||||
|
||||
+13
-13
@@ -8,48 +8,48 @@ import {
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard, Session } from '@thallesp/nestjs-better-auth';
|
||||
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
||||
import { ItemService } from './item.service';
|
||||
import { CreateItemDto } from './dto/create-item.dto';
|
||||
import { UpdateItemDto } from './dto/update-item.dto';
|
||||
import { MenuItemDto } from './dto/menu-item.dto';
|
||||
import { AuthGuard, Session } from '@thallesp/nestjs-better-auth';
|
||||
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
||||
|
||||
@Controller('items')
|
||||
@UseGuards(AuthGuard)
|
||||
export class ItemController {
|
||||
constructor(private readonly service: ItemService) {}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Post()
|
||||
async create(@Body() dto: CreateItemDto, @Session() session: UserSession) {
|
||||
const entity = await this.service.create(dto, session.user.id);
|
||||
return MenuItemDto.fromEntity(entity);
|
||||
return await this.service.create(dto, session.user.id);
|
||||
}
|
||||
|
||||
@Get('/category/:categoryId')
|
||||
async findAllByCategory(@Param('categoryId') categoryId: string) {
|
||||
const items = await this.service.findAllByCategory(categoryId);
|
||||
return items.map(MenuItemDto.fromEntity);
|
||||
}
|
||||
|
||||
@Get('restaurant/:restaurantId')
|
||||
async findAllForRestaurant(@Param('restaurantId') restaurantId: string) {
|
||||
const items = await this.service.findAllForRestaurant(restaurantId);
|
||||
return items.map(MenuItemDto.fromEntity);
|
||||
return await this.service.findAllForRestaurant(restaurantId);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
const entity = await this.service.findOne(id);
|
||||
return MenuItemDto.fromEntity(entity);
|
||||
return await this.service.findOne(id);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Put(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateItemDto,
|
||||
@Session() session: UserSession,
|
||||
) {
|
||||
const entity = await this.service.update(id, dto, session.user.id);
|
||||
return MenuItemDto.fromEntity(entity);
|
||||
return await this.service.update(id, dto, session.user.id);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Delete(':id')
|
||||
async remove(@Param('id') id: string, @Session() session: UserSession) {
|
||||
await this.service.remove(id, session.user.id);
|
||||
|
||||
@@ -3,9 +3,10 @@ import { ItemController } from './item.controller';
|
||||
import { ItemService } from './item.service';
|
||||
import { RestaurantModule } from 'src/restaurant/restaurant.module';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
import { CategoryModule } from 'src/category/category.module';
|
||||
|
||||
@Module({
|
||||
imports: [RestaurantModule, PrismaModule],
|
||||
imports: [RestaurantModule, CategoryModule, PrismaModule],
|
||||
controllers: [ItemController],
|
||||
providers: [ItemService],
|
||||
})
|
||||
|
||||
+28
-19
@@ -1,32 +1,45 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { RestaurantService } from 'src/restaurant/restaurant.service';
|
||||
import { CreateItemDto } from './dto/create-item.dto';
|
||||
import { UpdateItemDto } from './dto/update-item.dto';
|
||||
import { RestaurantService } from 'src/restaurant/restaurant.service';
|
||||
import { MenuItemDto } from './dto/menu-item.dto';
|
||||
import { CategoryService } from 'src/category/category.service';
|
||||
|
||||
@Injectable()
|
||||
export class ItemService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private readonly restaurantService: RestaurantService,
|
||||
private readonly categoryService: CategoryService,
|
||||
) {}
|
||||
|
||||
async create(dto: CreateItemDto, userId: string) {
|
||||
await this.restaurantService.verifyOwnership(userId, dto.restaurantId);
|
||||
const category = await this.categoryService.findOne(dto.categoryId);
|
||||
await this.restaurantService.verifyOwnership(userId, category.restaurantId);
|
||||
|
||||
const item = await this.prisma.menuItem.create({
|
||||
data: {
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
price: dto.price,
|
||||
image: dto.image,
|
||||
categoryId: dto.categoryId ?? null,
|
||||
restaurantId: dto.restaurantId,
|
||||
categoryId: category.id,
|
||||
restaurantId: category.restaurantId,
|
||||
},
|
||||
});
|
||||
|
||||
return MenuItemDto.fromEntity(item);
|
||||
}
|
||||
|
||||
async findAllByCategory(categoryId: string) {
|
||||
const items = await this.prisma.menuItem.findMany({
|
||||
where: { categoryId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
return items.map(MenuItemDto.fromEntity);
|
||||
}
|
||||
async findAllForRestaurant(restaurantId: string) {
|
||||
const items = await this.prisma.menuItem.findMany({
|
||||
where: { restaurantId },
|
||||
@@ -38,43 +51,39 @@ export class ItemService {
|
||||
|
||||
async findOne(id: string) {
|
||||
const item = await this.prisma.menuItem.findUnique({ where: { id } });
|
||||
|
||||
if (!item) throw new NotFoundException('Menu item not found');
|
||||
|
||||
return MenuItemDto.fromEntity(item);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateItemDto, userId: string) {
|
||||
const item = await this.findOne(id);
|
||||
|
||||
if (!item) {
|
||||
throw new NotFoundException('Menu item not found');
|
||||
}
|
||||
|
||||
await this.restaurantService.verifyOwnership(userId, item.restaurantId);
|
||||
|
||||
const updatedItem = await this.prisma.menuItem.update({
|
||||
const updated = await this.prisma.menuItem.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
price: dto.price,
|
||||
image: dto.image,
|
||||
categoryId: dto.categoryId ?? undefined,
|
||||
restaurantId: dto.restaurantId ?? undefined,
|
||||
categoryId: dto.categoryId
|
||||
? (await this.categoryService.findOne(dto.categoryId)).id
|
||||
: item.categoryId,
|
||||
},
|
||||
});
|
||||
|
||||
return MenuItemDto.fromEntity(updatedItem);
|
||||
return MenuItemDto.fromEntity(updated);
|
||||
}
|
||||
|
||||
async remove(id: string, userId: string) {
|
||||
const item = await this.findOne(id);
|
||||
|
||||
if (!item) {
|
||||
throw new NotFoundException('Menu item not found');
|
||||
}
|
||||
|
||||
await this.restaurantService.verifyOwnership(userId, item.restaurantId);
|
||||
|
||||
return this.prisma.menuItem.delete({ where: { id } });
|
||||
return await this.restaurantService.verifyOwnership(
|
||||
userId,
|
||||
item.restaurantId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+35
-3
@@ -1,10 +1,42 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { AuthService } from '@thallesp/nestjs-better-auth';
|
||||
import { toNodeHandler } from 'better-auth/node';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
bodyParser: false,
|
||||
// Disable NestJS's built-in body parser so we can control ordering
|
||||
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
||||
|
||||
app.enableCors({
|
||||
origin: ['http://localhost:5173', 'http://localhost:5174'],
|
||||
credentials: true,
|
||||
});
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
|
||||
// Access Express instance
|
||||
const expressApp = app.getHttpAdapter().getInstance();
|
||||
|
||||
// Access BetterAuth instance from AuthService
|
||||
const authService = app.get<AuthService>(AuthService);
|
||||
|
||||
// Mount BetterAuth before body parsers
|
||||
expressApp.all(
|
||||
/^\/api\/auth\/.*/,
|
||||
toNodeHandler(authService.instance.handler),
|
||||
);
|
||||
|
||||
// Re-enable Nest's JSON body parser AFTER mounting BetterAuth
|
||||
expressApp.use(require('express').json());
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}),
|
||||
);
|
||||
|
||||
app.setGlobalPrefix('api');
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -6,62 +6,71 @@ import {
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Request,
|
||||
UseGuards,
|
||||
Session,
|
||||
} from '@nestjs/common';
|
||||
import { RestaurantService } from './restaurant.service';
|
||||
import {
|
||||
AllowAnonymous,
|
||||
AuthGuard,
|
||||
Roles,
|
||||
Session as UserSession,
|
||||
} from '@thallesp/nestjs-better-auth';
|
||||
|
||||
import type { UserSession as SessionType } from '@thallesp/nestjs-better-auth';
|
||||
import { CreateRestaurantDto } from './dto/create-restaurant.dto';
|
||||
import { UpdateRestaurantDto } from './dto/update-restaurant.dto';
|
||||
import { AuthGuard, Session } from '@thallesp/nestjs-better-auth';
|
||||
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
||||
import { RestaurantService } from './restaurant.service';
|
||||
|
||||
@Controller('restaurants')
|
||||
@UseGuards(AuthGuard)
|
||||
export class RestaurantController {
|
||||
constructor(private readonly service: RestaurantService) {}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Post()
|
||||
async create(
|
||||
@Body() dto: CreateRestaurantDto,
|
||||
@Session() session: UserSession,
|
||||
@Session() session: SessionType,
|
||||
) {
|
||||
const ownerId = session.user.id;
|
||||
return await this.service.create(ownerId, dto);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('my')
|
||||
async myRestaurants(@Session() session) {
|
||||
return await this.service.findByOwner(session.user.id);
|
||||
return await this.service.create(session.user.id, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
async findAll() {
|
||||
return await this.service.findAll();
|
||||
async findOwnerRestaurants(@Session() session: SessionType) {
|
||||
return await this.service.findByOwner(session.user.id);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
return await this.service.findOne(id);
|
||||
async findOne(@Param('id') id: string, @Session() session: SessionType) {
|
||||
return await this.service.findMyRestaurant(id, session.user.id);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Put(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateRestaurantDto,
|
||||
@Session() session: UserSession,
|
||||
@Session() session: SessionType,
|
||||
) {
|
||||
return await this.service.update(id, dto, session.user.id);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Delete(':id')
|
||||
async remove(@Param('id') id: string, @Session() session: UserSession) {
|
||||
await this.service.remove(id, session.user.id);
|
||||
async remove(@Param('id') id: string, @Session() session: SessionType) {
|
||||
return await this.service.remove(id, session.user.id);
|
||||
}
|
||||
|
||||
// public menu by slug
|
||||
@Roles(['admin'])
|
||||
@Get('/admin/all')
|
||||
async adminFindAll() {
|
||||
return await this.service.findAll();
|
||||
}
|
||||
|
||||
@Roles(['admin'])
|
||||
@Get('/admin/:id')
|
||||
async adminFindOne(@Param('id') id: string) {
|
||||
return await this.service.findOne(id);
|
||||
}
|
||||
|
||||
@AllowAnonymous()
|
||||
@Get('/public/:slug')
|
||||
async publicBySlug(@Param('slug') slug: string) {
|
||||
return await this.service.findBySlug(slug);
|
||||
|
||||
@@ -3,9 +3,9 @@ import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { CreateRestaurantDto } from './dto/create-restaurant.dto';
|
||||
import { UpdateRestaurantDto } from './dto/update-restaurant.dto';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { RestaurantDto } from './dto/restaurant.dto';
|
||||
|
||||
@Injectable()
|
||||
@@ -13,11 +13,11 @@ export class RestaurantService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async verifyOwnership(userId: string, restaurantId: string) {
|
||||
const restaurant = await this.prisma.restaurant.findFirst({
|
||||
const owned = await this.prisma.restaurant.findFirst({
|
||||
where: { id: restaurantId, ownerId: userId },
|
||||
});
|
||||
|
||||
if (!restaurant) {
|
||||
if (!owned) {
|
||||
throw new ForbiddenException('You do not own this restaurant');
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,6 @@ export class RestaurantService {
|
||||
|
||||
async findAll() {
|
||||
const restaurants = await this.prisma.restaurant.findMany();
|
||||
|
||||
return restaurants.map(RestaurantDto.fromEntity);
|
||||
}
|
||||
|
||||
@@ -55,11 +54,19 @@ export class RestaurantService {
|
||||
const restaurant = await this.prisma.restaurant.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
if (!restaurant) throw new NotFoundException('Restaurant not found');
|
||||
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException('Restaurant not found');
|
||||
}
|
||||
|
||||
return RestaurantDto.fromEntity(restaurant);
|
||||
}
|
||||
|
||||
// find by slug with nested categories + items + template
|
||||
async findMyRestaurant(id: string, userId: string) {
|
||||
await this.verifyOwnership(userId, id);
|
||||
return this.findOne(id);
|
||||
}
|
||||
|
||||
async findBySlug(slug: string) {
|
||||
const restaurant = await this.prisma.restaurant.findUnique({
|
||||
where: { slug },
|
||||
@@ -70,19 +77,18 @@ export class RestaurantService {
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!restaurant) throw new NotFoundException('Restaurant not found');
|
||||
return RestaurantDto.fromEntity(restaurant);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateRestaurantDto, userId: string) {
|
||||
const restaurant = await this.findOne(id);
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException('Restaurant not found');
|
||||
}
|
||||
|
||||
return RestaurantDto.fromEntity(restaurant);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateRestaurantDto, userId: string) {
|
||||
await this.verifyOwnership(userId, id);
|
||||
|
||||
return this.prisma.restaurant.update({
|
||||
const updated = await this.prisma.restaurant.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: dto.name,
|
||||
@@ -92,15 +98,12 @@ export class RestaurantService {
|
||||
template: dto.template ?? undefined,
|
||||
},
|
||||
});
|
||||
|
||||
return RestaurantDto.fromEntity(updated);
|
||||
}
|
||||
|
||||
async remove(id: string, userId: string) {
|
||||
const restaurant = await this.findOne(id);
|
||||
|
||||
if (!restaurant) throw new NotFoundException('Restaurant not found');
|
||||
|
||||
await this.verifyOwnership(userId, id);
|
||||
|
||||
return this.prisma.restaurant.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { Controller, Get, Param, Delete, UseGuards } from '@nestjs/common';
|
||||
import { AuthGuard, Session } from '@thallesp/nestjs-better-auth';
|
||||
import {
|
||||
AuthGuard,
|
||||
Session,
|
||||
AllowAnonymous,
|
||||
Roles,
|
||||
} from '@thallesp/nestjs-better-auth';
|
||||
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
@@ -8,11 +13,13 @@ import { UserService } from './user.service';
|
||||
export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
// @AllowAnonymous()
|
||||
@Get()
|
||||
async getAllUsers() {
|
||||
return this.userService.getAllUsers();
|
||||
}
|
||||
|
||||
@Roles(['admin'])
|
||||
@Get(':id')
|
||||
async getUserById(@Param('id') id: string) {
|
||||
return this.userService.getUserById(id);
|
||||
|
||||
Reference in New Issue
Block a user