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",
|
"@prisma/client": "^6.18.0",
|
||||||
"@thallesp/nestjs-better-auth": "^2.1.0",
|
"@thallesp/nestjs-better-auth": "^2.1.0",
|
||||||
"better-auth": "^1.3.8",
|
"better-auth": "^1.3.8",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
"jose": "3.16.0",
|
"jose": "3.16.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
@@ -5029,6 +5030,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/class-validator": {
|
||||||
"version": "0.14.2",
|
"version": "0.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"@prisma/client": "^6.18.0",
|
"@prisma/client": "^6.18.0",
|
||||||
"@thallesp/nestjs-better-auth": "^2.1.0",
|
"@thallesp/nestjs-better-auth": "^2.1.0",
|
||||||
"better-auth": "^1.3.8",
|
"better-auth": "^1.3.8",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
"jose": "3.16.0",
|
"jose": "3.16.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"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;
|
||||||
+22
-6
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
}
|
}
|
||||||
@@ -7,7 +8,6 @@ datasource db {
|
|||||||
url = "postgresql://postgres:root@localhost:5432/zemenu?schema=public"
|
url = "postgresql://postgres:root@localhost:5432/zemenu?schema=public"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String?
|
name String?
|
||||||
@@ -16,11 +16,17 @@ model User {
|
|||||||
image String?
|
image String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
role String @default("user")
|
|
||||||
// Relations
|
// Relations
|
||||||
restaurants Restaurant[]
|
restaurants Restaurant[]
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
|
|
||||||
|
role String? @default("user")
|
||||||
|
banned Boolean? @default(false)
|
||||||
|
banReason String?
|
||||||
|
banExpires DateTime?
|
||||||
|
|
||||||
|
@@map("user")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
@@ -36,8 +42,13 @@ model Account {
|
|||||||
expiresAt DateTime?
|
expiresAt DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
idToken String?
|
||||||
|
accessTokenExpiresAt DateTime?
|
||||||
|
refreshTokenExpiresAt DateTime?
|
||||||
|
scope String?
|
||||||
|
|
||||||
|
@@map("account")
|
||||||
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
@@ -49,18 +60,23 @@ model Session {
|
|||||||
userAgent String?
|
userAgent String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
|
||||||
|
|
||||||
|
impersonatedBy String?
|
||||||
|
|
||||||
|
@@map("session")
|
||||||
|
}
|
||||||
|
|
||||||
model Verification {
|
model Verification {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
identifier String
|
identifier String
|
||||||
token String
|
token String?
|
||||||
expiresAt DateTime
|
expiresAt DateTime
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
value String
|
||||||
|
|
||||||
|
@@map("verification")
|
||||||
|
}
|
||||||
|
|
||||||
model Restaurant {
|
model Restaurant {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|||||||
+15
-2
@@ -1,7 +1,7 @@
|
|||||||
import { betterAuth } from 'better-auth';
|
import { betterAuth } from 'better-auth';
|
||||||
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
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 { PrismaClient } from '@prisma/client';
|
||||||
|
import { admin } from 'better-auth/plugins';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
@@ -9,12 +9,25 @@ export const auth = betterAuth({
|
|||||||
database: prismaAdapter(prisma, {
|
database: prismaAdapter(prisma, {
|
||||||
provider: 'postgresql',
|
provider: 'postgresql',
|
||||||
}),
|
}),
|
||||||
|
plugins: [admin()],
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
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: {
|
socialProviders: {
|
||||||
google: {
|
google: {
|
||||||
|
prompt: 'select_account',
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ import {
|
|||||||
Put,
|
Put,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} 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 { CategoryService } from './category.service';
|
||||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||||
import { UpdateCategoryDto } from './dto/update-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')
|
@Controller('categories')
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
export class CategoryController {
|
export class CategoryController {
|
||||||
constructor(private readonly service: CategoryService) {}
|
constructor(private readonly service: CategoryService) {}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(
|
async create(
|
||||||
@Body() dto: CreateCategoryDto,
|
@Body() dto: CreateCategoryDto,
|
||||||
@@ -28,8 +28,11 @@ export class CategoryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('restaurant/:restaurantId')
|
@Get('restaurant/:restaurantId')
|
||||||
async findAllForRestaurant(@Param('restaurantId') restaurantId: string) {
|
async findAllForRestaurant(
|
||||||
return await this.service.findAllForRestaurant(restaurantId);
|
@Param('restaurantId') restaurantId: string,
|
||||||
|
@Session() session: UserSession,
|
||||||
|
) {
|
||||||
|
return await this.service.findAllForRestaurant(restaurantId, session.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@@ -37,7 +40,6 @@ export class CategoryController {
|
|||||||
return await this.service.findOne(id);
|
return await this.service.findOne(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
async update(
|
async update(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@@ -47,9 +49,8 @@ export class CategoryController {
|
|||||||
return await this.service.update(id, dto, session.user.id);
|
return await this.service.update(id, dto, session.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
async remove(@Param('id') id: string, @Session() session: UserSession) {
|
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 { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { RestaurantService } from 'src/restaurant/restaurant.service';
|
||||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||||
import { UpdateCategoryDto } from './dto/update-category.dto';
|
import { UpdateCategoryDto } from './dto/update-category.dto';
|
||||||
import { RestaurantService } from 'src/restaurant/restaurant.service';
|
|
||||||
import { CategoryDto } from './dto/category.dto';
|
import { CategoryDto } from './dto/category.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -14,19 +14,25 @@ export class CategoryService {
|
|||||||
|
|
||||||
async create(dto: CreateCategoryDto, userId: string) {
|
async create(dto: CreateCategoryDto, userId: string) {
|
||||||
await this.restaurantService.verifyOwnership(userId, dto.restaurantId);
|
await this.restaurantService.verifyOwnership(userId, dto.restaurantId);
|
||||||
return this.prisma.category.create({
|
|
||||||
|
const category = await this.prisma.category.create({
|
||||||
data: {
|
data: {
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
restaurantId: dto.restaurantId,
|
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({
|
const categories = await this.prisma.category.findMany({
|
||||||
where: { restaurantId },
|
where: { restaurantId },
|
||||||
include: { items: true },
|
include: { items: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
return categories.map(CategoryDto.fromEntity);
|
return categories.map(CategoryDto.fromEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,19 +41,20 @@ export class CategoryService {
|
|||||||
where: { id },
|
where: { id },
|
||||||
include: { items: true },
|
include: { items: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!category) throw new NotFoundException('Category not found');
|
if (!category) throw new NotFoundException('Category not found');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return CategoryDto.fromEntity(category);
|
return CategoryDto.fromEntity(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, dto: UpdateCategoryDto, userId: string) {
|
async update(id: string, dto: UpdateCategoryDto, userId: string) {
|
||||||
const category = await this.findOne(id);
|
const category = await this.findOne(id);
|
||||||
if (!category) {
|
|
||||||
throw new NotFoundException('Category not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.restaurantService.verifyOwnership(userId, category.restaurantId);
|
await this.restaurantService.verifyOwnership(userId, category.restaurantId);
|
||||||
|
|
||||||
const savedCategory = await this.prisma.category.update({
|
const updated = await this.prisma.category.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
@@ -55,17 +62,14 @@ export class CategoryService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return CategoryDto.fromEntity(savedCategory);
|
return CategoryDto.fromEntity(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string, userId: string) {
|
async remove(id: string, userId: string) {
|
||||||
const category = await this.findOne(id);
|
const category = await this.findOne(id);
|
||||||
|
|
||||||
if (!category) {
|
|
||||||
throw new NotFoundException('Category not found');
|
|
||||||
}
|
|
||||||
await this.restaurantService.verifyOwnership(userId, category.restaurantId);
|
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 {
|
export class CreateCategoryDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
image?: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
restaurantId: string;
|
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()
|
@IsString()
|
||||||
image?: string;
|
image?: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsUUID()
|
|
||||||
categoryId?: string;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
restaurantId: string;
|
categoryId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-13
@@ -8,48 +8,48 @@ import {
|
|||||||
Put,
|
Put,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} 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 { ItemService } from './item.service';
|
||||||
import { CreateItemDto } from './dto/create-item.dto';
|
import { CreateItemDto } from './dto/create-item.dto';
|
||||||
import { UpdateItemDto } from './dto/update-item.dto';
|
import { UpdateItemDto } from './dto/update-item.dto';
|
||||||
import { MenuItemDto } from './dto/menu-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')
|
@Controller('items')
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
export class ItemController {
|
export class ItemController {
|
||||||
constructor(private readonly service: ItemService) {}
|
constructor(private readonly service: ItemService) {}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(@Body() dto: CreateItemDto, @Session() session: UserSession) {
|
async create(@Body() dto: CreateItemDto, @Session() session: UserSession) {
|
||||||
const entity = await this.service.create(dto, session.user.id);
|
return await this.service.create(dto, session.user.id);
|
||||||
return MenuItemDto.fromEntity(entity);
|
}
|
||||||
|
|
||||||
|
@Get('/category/:categoryId')
|
||||||
|
async findAllByCategory(@Param('categoryId') categoryId: string) {
|
||||||
|
const items = await this.service.findAllByCategory(categoryId);
|
||||||
|
return items.map(MenuItemDto.fromEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('restaurant/:restaurantId')
|
@Get('restaurant/:restaurantId')
|
||||||
async findAllForRestaurant(@Param('restaurantId') restaurantId: string) {
|
async findAllForRestaurant(@Param('restaurantId') restaurantId: string) {
|
||||||
const items = await this.service.findAllForRestaurant(restaurantId);
|
return await this.service.findAllForRestaurant(restaurantId);
|
||||||
return items.map(MenuItemDto.fromEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
async findOne(@Param('id') id: string) {
|
async findOne(@Param('id') id: string) {
|
||||||
const entity = await this.service.findOne(id);
|
return await this.service.findOne(id);
|
||||||
return MenuItemDto.fromEntity(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
async update(
|
async update(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() dto: UpdateItemDto,
|
@Body() dto: UpdateItemDto,
|
||||||
@Session() session: UserSession,
|
@Session() session: UserSession,
|
||||||
) {
|
) {
|
||||||
const entity = await this.service.update(id, dto, session.user.id);
|
return await this.service.update(id, dto, session.user.id);
|
||||||
return MenuItemDto.fromEntity(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
async remove(@Param('id') id: string, @Session() session: UserSession) {
|
async remove(@Param('id') id: string, @Session() session: UserSession) {
|
||||||
await this.service.remove(id, session.user.id);
|
await this.service.remove(id, session.user.id);
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { ItemController } from './item.controller';
|
|||||||
import { ItemService } from './item.service';
|
import { ItemService } from './item.service';
|
||||||
import { RestaurantModule } from 'src/restaurant/restaurant.module';
|
import { RestaurantModule } from 'src/restaurant/restaurant.module';
|
||||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
import { CategoryModule } from 'src/category/category.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RestaurantModule, PrismaModule],
|
imports: [RestaurantModule, CategoryModule, PrismaModule],
|
||||||
controllers: [ItemController],
|
controllers: [ItemController],
|
||||||
providers: [ItemService],
|
providers: [ItemService],
|
||||||
})
|
})
|
||||||
|
|||||||
+28
-19
@@ -1,32 +1,45 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { RestaurantService } from 'src/restaurant/restaurant.service';
|
||||||
import { CreateItemDto } from './dto/create-item.dto';
|
import { CreateItemDto } from './dto/create-item.dto';
|
||||||
import { UpdateItemDto } from './dto/update-item.dto';
|
import { UpdateItemDto } from './dto/update-item.dto';
|
||||||
import { RestaurantService } from 'src/restaurant/restaurant.service';
|
|
||||||
import { MenuItemDto } from './dto/menu-item.dto';
|
import { MenuItemDto } from './dto/menu-item.dto';
|
||||||
|
import { CategoryService } from 'src/category/category.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ItemService {
|
export class ItemService {
|
||||||
constructor(
|
constructor(
|
||||||
private prisma: PrismaService,
|
private prisma: PrismaService,
|
||||||
private readonly restaurantService: RestaurantService,
|
private readonly restaurantService: RestaurantService,
|
||||||
|
private readonly categoryService: CategoryService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(dto: CreateItemDto, userId: string) {
|
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({
|
const item = await this.prisma.menuItem.create({
|
||||||
data: {
|
data: {
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
description: dto.description,
|
description: dto.description,
|
||||||
price: dto.price,
|
price: dto.price,
|
||||||
image: dto.image,
|
image: dto.image,
|
||||||
categoryId: dto.categoryId ?? null,
|
categoryId: category.id,
|
||||||
restaurantId: dto.restaurantId,
|
restaurantId: category.restaurantId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return MenuItemDto.fromEntity(item);
|
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) {
|
async findAllForRestaurant(restaurantId: string) {
|
||||||
const items = await this.prisma.menuItem.findMany({
|
const items = await this.prisma.menuItem.findMany({
|
||||||
where: { restaurantId },
|
where: { restaurantId },
|
||||||
@@ -38,43 +51,39 @@ export class ItemService {
|
|||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
const item = await this.prisma.menuItem.findUnique({ where: { id } });
|
const item = await this.prisma.menuItem.findUnique({ where: { id } });
|
||||||
|
|
||||||
if (!item) throw new NotFoundException('Menu item not found');
|
if (!item) throw new NotFoundException('Menu item not found');
|
||||||
|
|
||||||
return MenuItemDto.fromEntity(item);
|
return MenuItemDto.fromEntity(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, dto: UpdateItemDto, userId: string) {
|
async update(id: string, dto: UpdateItemDto, userId: string) {
|
||||||
const item = await this.findOne(id);
|
const item = await this.findOne(id);
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
throw new NotFoundException('Menu item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.restaurantService.verifyOwnership(userId, item.restaurantId);
|
await this.restaurantService.verifyOwnership(userId, item.restaurantId);
|
||||||
|
|
||||||
const updatedItem = await this.prisma.menuItem.update({
|
const updated = await this.prisma.menuItem.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
description: dto.description,
|
description: dto.description,
|
||||||
price: dto.price,
|
price: dto.price,
|
||||||
image: dto.image,
|
image: dto.image,
|
||||||
categoryId: dto.categoryId ?? undefined,
|
categoryId: dto.categoryId
|
||||||
restaurantId: dto.restaurantId ?? undefined,
|
? (await this.categoryService.findOne(dto.categoryId)).id
|
||||||
|
: item.categoryId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return MenuItemDto.fromEntity(updatedItem);
|
return MenuItemDto.fromEntity(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string, userId: string) {
|
async remove(id: string, userId: string) {
|
||||||
const item = await this.findOne(id);
|
const item = await this.findOne(id);
|
||||||
|
|
||||||
if (!item) {
|
return await this.restaurantService.verifyOwnership(
|
||||||
throw new NotFoundException('Menu item not found');
|
userId,
|
||||||
}
|
item.restaurantId,
|
||||||
|
);
|
||||||
await this.restaurantService.verifyOwnership(userId, item.restaurantId);
|
|
||||||
|
|
||||||
return this.prisma.menuItem.delete({ where: { id } });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-3
@@ -1,10 +1,42 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
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() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule, {
|
// Disable NestJS's built-in body parser so we can control ordering
|
||||||
bodyParser: false,
|
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();
|
bootstrap();
|
||||||
|
|||||||
@@ -6,62 +6,71 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
Request,
|
|
||||||
UseGuards,
|
UseGuards,
|
||||||
|
Session,
|
||||||
} from '@nestjs/common';
|
} 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 { CreateRestaurantDto } from './dto/create-restaurant.dto';
|
||||||
import { UpdateRestaurantDto } from './dto/update-restaurant.dto';
|
import { UpdateRestaurantDto } from './dto/update-restaurant.dto';
|
||||||
import { AuthGuard, Session } from '@thallesp/nestjs-better-auth';
|
import { RestaurantService } from './restaurant.service';
|
||||||
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
|
||||||
|
|
||||||
@Controller('restaurants')
|
@Controller('restaurants')
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
export class RestaurantController {
|
export class RestaurantController {
|
||||||
constructor(private readonly service: RestaurantService) {}
|
constructor(private readonly service: RestaurantService) {}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(
|
async create(
|
||||||
@Body() dto: CreateRestaurantDto,
|
@Body() dto: CreateRestaurantDto,
|
||||||
@Session() session: UserSession,
|
@Session() session: SessionType,
|
||||||
) {
|
) {
|
||||||
const ownerId = session.user.id;
|
return await this.service.create(session.user.id, dto);
|
||||||
return await this.service.create(ownerId, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Get('my')
|
|
||||||
async myRestaurants(@Session() session) {
|
|
||||||
return await this.service.findByOwner(session.user.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async findAll() {
|
async findOwnerRestaurants(@Session() session: SessionType) {
|
||||||
return await this.service.findAll();
|
return await this.service.findByOwner(session.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
async findOne(@Param('id') id: string) {
|
async findOne(@Param('id') id: string, @Session() session: SessionType) {
|
||||||
return await this.service.findOne(id);
|
return await this.service.findMyRestaurant(id, session.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
async update(
|
async update(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body() dto: UpdateRestaurantDto,
|
@Body() dto: UpdateRestaurantDto,
|
||||||
@Session() session: UserSession,
|
@Session() session: SessionType,
|
||||||
) {
|
) {
|
||||||
return await this.service.update(id, dto, session.user.id);
|
return await this.service.update(id, dto, session.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
async remove(@Param('id') id: string, @Session() session: UserSession) {
|
async remove(@Param('id') id: string, @Session() session: SessionType) {
|
||||||
await this.service.remove(id, session.user.id);
|
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')
|
@Get('/public/:slug')
|
||||||
async publicBySlug(@Param('slug') slug: string) {
|
async publicBySlug(@Param('slug') slug: string) {
|
||||||
return await this.service.findBySlug(slug);
|
return await this.service.findBySlug(slug);
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import {
|
|||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { CreateRestaurantDto } from './dto/create-restaurant.dto';
|
import { CreateRestaurantDto } from './dto/create-restaurant.dto';
|
||||||
import { UpdateRestaurantDto } from './dto/update-restaurant.dto';
|
import { UpdateRestaurantDto } from './dto/update-restaurant.dto';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
|
||||||
import { RestaurantDto } from './dto/restaurant.dto';
|
import { RestaurantDto } from './dto/restaurant.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -13,11 +13,11 @@ export class RestaurantService {
|
|||||||
constructor(private prisma: PrismaService) {}
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
async verifyOwnership(userId: string, restaurantId: string) {
|
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 },
|
where: { id: restaurantId, ownerId: userId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!restaurant) {
|
if (!owned) {
|
||||||
throw new ForbiddenException('You do not own this restaurant');
|
throw new ForbiddenException('You do not own this restaurant');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,6 @@ export class RestaurantService {
|
|||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
const restaurants = await this.prisma.restaurant.findMany();
|
const restaurants = await this.prisma.restaurant.findMany();
|
||||||
|
|
||||||
return restaurants.map(RestaurantDto.fromEntity);
|
return restaurants.map(RestaurantDto.fromEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,11 +54,19 @@ export class RestaurantService {
|
|||||||
const restaurant = await this.prisma.restaurant.findUnique({
|
const restaurant = await this.prisma.restaurant.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
if (!restaurant) throw new NotFoundException('Restaurant not found');
|
|
||||||
|
if (!restaurant) {
|
||||||
|
throw new NotFoundException('Restaurant not found');
|
||||||
|
}
|
||||||
|
|
||||||
return RestaurantDto.fromEntity(restaurant);
|
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) {
|
async findBySlug(slug: string) {
|
||||||
const restaurant = await this.prisma.restaurant.findUnique({
|
const restaurant = await this.prisma.restaurant.findUnique({
|
||||||
where: { slug },
|
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) {
|
if (!restaurant) {
|
||||||
throw new NotFoundException('Restaurant not found');
|
throw new NotFoundException('Restaurant not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return RestaurantDto.fromEntity(restaurant);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, dto: UpdateRestaurantDto, userId: string) {
|
||||||
await this.verifyOwnership(userId, id);
|
await this.verifyOwnership(userId, id);
|
||||||
|
|
||||||
return this.prisma.restaurant.update({
|
const updated = await this.prisma.restaurant.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
@@ -92,15 +98,12 @@ export class RestaurantService {
|
|||||||
template: dto.template ?? undefined,
|
template: dto.template ?? undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return RestaurantDto.fromEntity(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string, userId: string) {
|
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);
|
await this.verifyOwnership(userId, id);
|
||||||
|
|
||||||
return this.prisma.restaurant.delete({ where: { id } });
|
return this.prisma.restaurant.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { Controller, Get, Param, Delete, UseGuards } from '@nestjs/common';
|
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 type { UserSession } from '@thallesp/nestjs-better-auth';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
@@ -8,11 +13,13 @@ import { UserService } from './user.service';
|
|||||||
export class UserController {
|
export class UserController {
|
||||||
constructor(private readonly userService: UserService) {}
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
|
// @AllowAnonymous()
|
||||||
@Get()
|
@Get()
|
||||||
async getAllUsers() {
|
async getAllUsers() {
|
||||||
return this.userService.getAllUsers();
|
return this.userService.getAllUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Roles(['admin'])
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
async getUserById(@Param('id') id: string) {
|
async getUserById(@Param('id') id: string) {
|
||||||
return this.userService.getUserById(id);
|
return this.userService.getUserById(id);
|
||||||
|
|||||||
Reference in New Issue
Block a user