first commit
This commit is contained in:
+58
@@ -0,0 +1,58 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# temp directory
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
/generated/prisma
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||||
|
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||||
|
|
||||||
|
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||||
|
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||||
|
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||||
|
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||||
|
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
|
||||||
|
</p>
|
||||||
|
<!--[](https://opencollective.com/nest#backer)
|
||||||
|
[](https://opencollective.com/nest#sponsor)-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compile and run the project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# development
|
||||||
|
$ npm run start
|
||||||
|
|
||||||
|
# watch mode
|
||||||
|
$ npm run start:dev
|
||||||
|
|
||||||
|
# production mode
|
||||||
|
$ npm run start:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# unit tests
|
||||||
|
$ npm run test
|
||||||
|
|
||||||
|
# e2e tests
|
||||||
|
$ npm run test:e2e
|
||||||
|
|
||||||
|
# test coverage
|
||||||
|
$ npm run test:cov
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||||
|
|
||||||
|
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install -g @nestjs/mau
|
||||||
|
$ mau deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
Check out a few resources that may come in handy when working with NestJS:
|
||||||
|
|
||||||
|
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
||||||
|
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
||||||
|
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
||||||
|
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
||||||
|
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
||||||
|
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
||||||
|
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
||||||
|
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||||
|
|
||||||
|
## Stay in touch
|
||||||
|
|
||||||
|
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
||||||
|
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||||
|
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// @ts-check
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: ['eslint.config.mjs'],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.jest,
|
||||||
|
},
|
||||||
|
sourceType: 'commonjs',
|
||||||
|
parserOptions: {
|
||||||
|
projectService: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-floating-promises': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||||
|
"prettier/prettier": ["error", { endOfLine: "auto" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+11476
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"name": "zemenu-api",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:cov": "jest --coverage",
|
||||||
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/common": "^11.0.1",
|
||||||
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
"@prisma/client": "^6.18.0",
|
||||||
|
"@thallesp/nestjs-better-auth": "^2.1.0",
|
||||||
|
"better-auth": "^1.3.8",
|
||||||
|
"class-validator": "^0.14.2",
|
||||||
|
"jose": "3.16.0",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"rxjs": "^7.8.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
|
"@eslint/js": "^9.18.0",
|
||||||
|
"@nestjs/cli": "^11.0.0",
|
||||||
|
"@nestjs/schematics": "^11.0.0",
|
||||||
|
"@nestjs/testing": "^11.0.1",
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/jest": "^30.0.0",
|
||||||
|
"@types/node": "^22.10.7",
|
||||||
|
"@types/supertest": "^6.0.2",
|
||||||
|
"eslint": "^9.18.0",
|
||||||
|
"eslint-config-prettier": "^10.0.1",
|
||||||
|
"eslint-plugin-prettier": "^5.2.2",
|
||||||
|
"globals": "^16.0.0",
|
||||||
|
"jest": "^30.0.0",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"prisma": "^6.18.0",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"supertest": "^7.0.0",
|
||||||
|
"ts-jest": "^29.2.5",
|
||||||
|
"ts-loader": "^9.5.2",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.20.0"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig, env } from '@prisma/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: 'prisma/schema.prisma',
|
||||||
|
migrations: {
|
||||||
|
path: 'prisma/migrations',
|
||||||
|
},
|
||||||
|
engine: 'classic',
|
||||||
|
datasource: {
|
||||||
|
url: 'postgresql://postgres:root@localhost:5432/zemenu?schema=public',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"firstName" TEXT NOT NULL,
|
||||||
|
"middleName" TEXT NOT NULL,
|
||||||
|
"lastName" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"emailVerified" BOOLEAN,
|
||||||
|
"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 "Session" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Restaurant" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"slug" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"logo" TEXT,
|
||||||
|
"ownerId" TEXT NOT NULL,
|
||||||
|
"template" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "Restaurant_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Category" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"restaurantId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "MenuItem" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"price" DOUBLE PRECISION NOT NULL,
|
||||||
|
"image" TEXT,
|
||||||
|
"categoryId" TEXT,
|
||||||
|
"restaurantId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "MenuItem_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Restaurant_slug_key" ON "Restaurant"("slug");
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Category" ADD CONSTRAINT "Category_restaurantId_fkey" FOREIGN KEY ("restaurantId") REFERENCES "Restaurant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MenuItem" ADD CONSTRAINT "MenuItem_restaurantId_fkey" FOREIGN KEY ("restaurantId") REFERENCES "Restaurant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `firstName` on the `User` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `lastName` on the `User` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `middleName` on the `User` table. All the data in the column will be lost.
|
||||||
|
- Added the required column `name` to the `User` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" DROP COLUMN "firstName",
|
||||||
|
DROP COLUMN "lastName",
|
||||||
|
DROP COLUMN "middleName",
|
||||||
|
ADD COLUMN "name" TEXT NOT NULL;
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `createdAt` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `expiresAt` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `updatedAt` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- The `emailVerified` column on the `User` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||||
|
- A unique constraint covering the columns `[sessionToken]` on the table `Session` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- Added the required column `expires` to the `Session` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `sessionToken` to the `Session` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Session" DROP COLUMN "createdAt",
|
||||||
|
DROP COLUMN "expiresAt",
|
||||||
|
DROP COLUMN "updatedAt",
|
||||||
|
ADD COLUMN "expires" TIMESTAMP(3) NOT NULL,
|
||||||
|
ADD COLUMN "sessionToken" TEXT NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "image" TEXT,
|
||||||
|
ADD COLUMN "password" TEXT,
|
||||||
|
DROP COLUMN "emailVerified",
|
||||||
|
ADD COLUMN "emailVerified" TIMESTAMP(3),
|
||||||
|
ALTER COLUMN "name" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Account" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"provider" TEXT NOT NULL,
|
||||||
|
"providerAccountId" TEXT NOT NULL,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"refresh_token" TEXT,
|
||||||
|
"access_token" TEXT,
|
||||||
|
"expires_at" INTEGER,
|
||||||
|
"token_type" TEXT,
|
||||||
|
"scope" TEXT,
|
||||||
|
"id_token" TEXT,
|
||||||
|
"session_state" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "VerificationToken" (
|
||||||
|
"identifier" TEXT NOT NULL,
|
||||||
|
"token" TEXT NOT NULL,
|
||||||
|
"expires" TIMESTAMP(3) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The `emailVerified` column on the `User` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" DROP COLUMN "emailVerified",
|
||||||
|
ADD COLUMN "emailVerified" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `updatedAt` to the `Account` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Account" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "password" TEXT,
|
||||||
|
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `access_token` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `expires_at` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `id_token` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `providerAccountId` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `refresh_token` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `scope` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `session_state` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `token_type` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `type` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `expires` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `password` on the `User` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `VerificationToken` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- Added the required column `accountId` to the `Account` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `providerId` to the `Account` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `expiresAt` to the `Session` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `updatedAt` to the `Session` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Account" DROP CONSTRAINT "Account_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "Account_provider_providerAccountId_key";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Account" DROP COLUMN "access_token",
|
||||||
|
DROP COLUMN "expires_at",
|
||||||
|
DROP COLUMN "id_token",
|
||||||
|
DROP COLUMN "providerAccountId",
|
||||||
|
DROP COLUMN "refresh_token",
|
||||||
|
DROP COLUMN "scope",
|
||||||
|
DROP COLUMN "session_state",
|
||||||
|
DROP COLUMN "token_type",
|
||||||
|
DROP COLUMN "type",
|
||||||
|
ADD COLUMN "accessToken" TEXT,
|
||||||
|
ADD COLUMN "accountId" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "expiresAt" TIMESTAMP(3),
|
||||||
|
ADD COLUMN "providerId" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "refreshToken" TEXT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Session" DROP COLUMN "expires",
|
||||||
|
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "expiresAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" DROP COLUMN "password",
|
||||||
|
ALTER COLUMN "email" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "VerificationToken";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Verification" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"identifier" TEXT NOT NULL,
|
||||||
|
"token" 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")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 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;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Account" ALTER COLUMN "provider" DROP NOT NULL;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Session" ALTER COLUMN "sessionToken" DROP NOT NULL;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Session" ADD COLUMN "ipAddress" TEXT,
|
||||||
|
ADD COLUMN "token" TEXT,
|
||||||
|
ADD COLUMN "userAgent" TEXT;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `ipAddress` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `token` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `userAgent` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- Made the column `sessionToken` on table `Session` required. This step will fail if there are existing NULL values in that column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Session" DROP COLUMN "ipAddress",
|
||||||
|
DROP COLUMN "token",
|
||||||
|
DROP COLUMN "userAgent",
|
||||||
|
ALTER COLUMN "sessionToken" SET NOT NULL;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `sessionToken` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- A unique constraint covering the columns `[token]` on the table `Session` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "Session_sessionToken_key";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Session" DROP COLUMN "sessionToken",
|
||||||
|
ADD COLUMN "ipAddress" TEXT,
|
||||||
|
ADD COLUMN "token" TEXT,
|
||||||
|
ADD COLUMN "userAgent" TEXT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token");
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = "postgresql://postgres:root@localhost:5432/zemenu?schema=public"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String?
|
||||||
|
email String? @unique
|
||||||
|
emailVerified Boolean @default(false)
|
||||||
|
image String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
role String @default("user")
|
||||||
|
// Relations
|
||||||
|
restaurants Restaurant[]
|
||||||
|
accounts Account[]
|
||||||
|
sessions Session[]
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
model Verification {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
identifier String
|
||||||
|
token String
|
||||||
|
expiresAt DateTime
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
template String?
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
restaurantId String
|
||||||
|
restaurant Restaurant @relation(fields: [restaurantId], references: [id])
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { auth } from './auth/auth';
|
||||||
|
import { AuthModule } from '@thallesp/nestjs-better-auth';
|
||||||
|
import { UserModule } from './user/user.module';
|
||||||
|
import { PrismaModule } from './prisma/prisma.module';
|
||||||
|
import { CategoryModule } from './category/category.module';
|
||||||
|
import { RestaurantModule } from './restaurant/restaurant.module';
|
||||||
|
import { ItemModule } from './item/item.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
AuthModule.forRoot({ auth }),
|
||||||
|
PrismaModule,
|
||||||
|
UserModule,
|
||||||
|
CategoryModule,
|
||||||
|
RestaurantModule,
|
||||||
|
ItemModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: prismaAdapter(prisma, {
|
||||||
|
provider: 'postgresql',
|
||||||
|
}),
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true,
|
||||||
|
// requireEmailVerification: true,
|
||||||
|
},
|
||||||
|
socialProviders: {
|
||||||
|
google: {
|
||||||
|
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||||
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
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')
|
||||||
|
export class CategoryController {
|
||||||
|
constructor(private readonly service: CategoryService) {}
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
@Post()
|
||||||
|
async create(
|
||||||
|
@Body() dto: CreateCategoryDto,
|
||||||
|
@Session() session: UserSession,
|
||||||
|
) {
|
||||||
|
return await this.service.create(dto, session.user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('restaurant/:restaurantId')
|
||||||
|
async findAllForRestaurant(@Param('restaurantId') restaurantId: string) {
|
||||||
|
return await this.service.findAllForRestaurant(restaurantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(@Param('id') id: string) {
|
||||||
|
return await this.service.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
@Put(':id')
|
||||||
|
async update(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() dto: UpdateCategoryDto,
|
||||||
|
@Session() session: UserSession,
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { CategoryController } from './category.controller';
|
||||||
|
import { CategoryService } from './category.service';
|
||||||
|
import { RestaurantModule } from 'src/restaurant/restaurant.module';
|
||||||
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [RestaurantModule, PrismaModule],
|
||||||
|
controllers: [CategoryController],
|
||||||
|
providers: [CategoryService],
|
||||||
|
exports: [CategoryService],
|
||||||
|
})
|
||||||
|
export class CategoryModule {}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../prisma/prisma.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()
|
||||||
|
export class CategoryService {
|
||||||
|
constructor(
|
||||||
|
private prisma: PrismaService,
|
||||||
|
private readonly restaurantService: RestaurantService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(dto: CreateCategoryDto, userId: string) {
|
||||||
|
await this.restaurantService.verifyOwnership(userId, dto.restaurantId);
|
||||||
|
return this.prisma.category.create({
|
||||||
|
data: {
|
||||||
|
name: dto.name,
|
||||||
|
restaurantId: dto.restaurantId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllForRestaurant(restaurantId: string) {
|
||||||
|
const categories = await this.prisma.category.findMany({
|
||||||
|
where: { restaurantId },
|
||||||
|
include: { items: true },
|
||||||
|
});
|
||||||
|
return categories.map(CategoryDto.fromEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: string) {
|
||||||
|
const category = await this.prisma.category.findUnique({
|
||||||
|
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({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name: dto.name,
|
||||||
|
restaurantId: dto.restaurantId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return CategoryDto.fromEntity(savedCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 } });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { MenuItemDto } from "src/item/dto/menu-item.dto";
|
||||||
|
|
||||||
|
export class CategoryDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
restaurantId: string;
|
||||||
|
items?: MenuItemDto[];
|
||||||
|
|
||||||
|
static fromEntity(entity: any): CategoryDto {
|
||||||
|
return {
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name,
|
||||||
|
restaurantId: entity.restaurantId,
|
||||||
|
items: entity.items?.map(MenuItemDto.fromEntity) ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateCategoryDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsUUID()
|
||||||
|
restaurantId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateCategoryDto } from './create-category.dto';
|
||||||
|
|
||||||
|
export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
IsUUID,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateItemDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsNumber()
|
||||||
|
price: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
image?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsUUID()
|
||||||
|
categoryId?: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsUUID()
|
||||||
|
restaurantId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export class MenuItemDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
price: number;
|
||||||
|
image?: string;
|
||||||
|
categoryId?: string;
|
||||||
|
restaurantId: string;
|
||||||
|
createdAt?: Date;
|
||||||
|
|
||||||
|
static fromEntity(entity: any): MenuItemDto {
|
||||||
|
return {
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name,
|
||||||
|
description: entity.description ?? undefined,
|
||||||
|
price: entity.price,
|
||||||
|
image: entity.image ?? undefined,
|
||||||
|
categoryId: entity.categoryId ?? undefined,
|
||||||
|
restaurantId: entity.restaurantId,
|
||||||
|
createdAt: entity.createdAt ? new Date(entity.createdAt) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateItemDto } from './create-item.dto';
|
||||||
|
|
||||||
|
export class UpdateItemDto extends PartialType(CreateItemDto) {}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
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')
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('restaurant/:restaurantId')
|
||||||
|
async findAllForRestaurant(@Param('restaurantId') restaurantId: string) {
|
||||||
|
const items = await this.service.findAllForRestaurant(restaurantId);
|
||||||
|
return items.map(MenuItemDto.fromEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(@Param('id') id: string) {
|
||||||
|
const entity = await this.service.findOne(id);
|
||||||
|
return MenuItemDto.fromEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
@Delete(':id')
|
||||||
|
async remove(@Param('id') id: string, @Session() session: UserSession) {
|
||||||
|
await this.service.remove(id, session.user.id);
|
||||||
|
return { id };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ItemController } from './item.controller';
|
||||||
|
import { ItemService } from './item.service';
|
||||||
|
import { RestaurantModule } from 'src/restaurant/restaurant.module';
|
||||||
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [RestaurantModule, PrismaModule],
|
||||||
|
controllers: [ItemController],
|
||||||
|
providers: [ItemService],
|
||||||
|
})
|
||||||
|
export class ItemModule {}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../prisma/prisma.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';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ItemService {
|
||||||
|
constructor(
|
||||||
|
private prisma: PrismaService,
|
||||||
|
private readonly restaurantService: RestaurantService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(dto: CreateItemDto, userId: string) {
|
||||||
|
await this.restaurantService.verifyOwnership(userId, dto.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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return MenuItemDto.fromEntity(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllForRestaurant(restaurantId: string) {
|
||||||
|
const items = await this.prisma.menuItem.findMany({
|
||||||
|
where: { restaurantId },
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return items.map(MenuItemDto.fromEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
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({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name: dto.name,
|
||||||
|
description: dto.description,
|
||||||
|
price: dto.price,
|
||||||
|
image: dto.image,
|
||||||
|
categoryId: dto.categoryId ?? undefined,
|
||||||
|
restaurantId: dto.restaurantId ?? undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return MenuItemDto.fromEntity(updatedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 } });
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create(AppModule, {
|
||||||
|
bodyParser: false,
|
||||||
|
});
|
||||||
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
|
}
|
||||||
|
bootstrap();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PrismaService } from './prisma.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [PrismaService],
|
||||||
|
exports: [PrismaService],
|
||||||
|
})
|
||||||
|
export class PrismaModule {}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PrismaService
|
||||||
|
extends PrismaClient
|
||||||
|
implements OnModuleInit, OnModuleDestroy
|
||||||
|
{
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.$connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleDestroy() {
|
||||||
|
await this.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateRestaurantDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
logo?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsUUID()
|
||||||
|
template?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { CategoryDto } from "src/category/dto/category.dto";
|
||||||
|
|
||||||
|
export class RestaurantDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
logo?: string;
|
||||||
|
categories?: CategoryDto[];
|
||||||
|
createdAt?: Date;
|
||||||
|
|
||||||
|
static fromEntity(entity: any): RestaurantDto {
|
||||||
|
return {
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name,
|
||||||
|
slug: entity.slug,
|
||||||
|
description: entity.description ?? undefined,
|
||||||
|
logo: entity.logo ?? undefined,
|
||||||
|
categories: entity.categories?.map(CategoryDto.fromEntity) ?? [],
|
||||||
|
createdAt: entity.createdAt ? new Date(entity.createdAt) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateRestaurantDto } from './create-restaurant.dto';
|
||||||
|
|
||||||
|
export class UpdateRestaurantDto extends PartialType(CreateRestaurantDto) {}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Request,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { RestaurantService } from './restaurant.service';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@Controller('restaurants')
|
||||||
|
export class RestaurantController {
|
||||||
|
constructor(private readonly service: RestaurantService) {}
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
@Post()
|
||||||
|
async create(
|
||||||
|
@Body() dto: CreateRestaurantDto,
|
||||||
|
@Session() session: UserSession,
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async findAll() {
|
||||||
|
return await this.service.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(@Param('id') id: string) {
|
||||||
|
return await this.service.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
@Put(':id')
|
||||||
|
async update(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() dto: UpdateRestaurantDto,
|
||||||
|
@Session() session: UserSession,
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public menu by slug
|
||||||
|
@Get('/public/:slug')
|
||||||
|
async publicBySlug(@Param('slug') slug: string) {
|
||||||
|
return await this.service.findBySlug(slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { RestaurantService } from './restaurant.service';
|
||||||
|
import { RestaurantController } from './restaurant.controller';
|
||||||
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
providers: [RestaurantService],
|
||||||
|
controllers: [RestaurantController],
|
||||||
|
exports: [RestaurantService],
|
||||||
|
})
|
||||||
|
export class RestaurantModule {}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
ForbiddenException,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
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()
|
||||||
|
export class RestaurantService {
|
||||||
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async verifyOwnership(userId: string, restaurantId: string) {
|
||||||
|
const restaurant = await this.prisma.restaurant.findFirst({
|
||||||
|
where: { id: restaurantId, ownerId: userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!restaurant) {
|
||||||
|
throw new ForbiddenException('You do not own this restaurant');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(ownerId: string, dto: CreateRestaurantDto) {
|
||||||
|
const restaurant = await this.prisma.restaurant.create({
|
||||||
|
data: {
|
||||||
|
name: dto.name,
|
||||||
|
slug: dto.slug,
|
||||||
|
description: dto.description,
|
||||||
|
logo: dto.logo,
|
||||||
|
ownerId,
|
||||||
|
template: dto.template ?? null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return RestaurantDto.fromEntity(restaurant);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByOwner(ownerId: string) {
|
||||||
|
const restaurants = await this.prisma.restaurant.findMany({
|
||||||
|
where: { ownerId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return restaurants.map(RestaurantDto.fromEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll() {
|
||||||
|
const restaurants = await this.prisma.restaurant.findMany();
|
||||||
|
|
||||||
|
return restaurants.map(RestaurantDto.fromEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: string) {
|
||||||
|
const restaurant = await this.prisma.restaurant.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
if (!restaurant) throw new NotFoundException('Restaurant not found');
|
||||||
|
return RestaurantDto.fromEntity(restaurant);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find by slug with nested categories + items + template
|
||||||
|
async findBySlug(slug: string) {
|
||||||
|
const restaurant = await this.prisma.restaurant.findUnique({
|
||||||
|
where: { slug },
|
||||||
|
include: {
|
||||||
|
categories: {
|
||||||
|
include: { items: true },
|
||||||
|
orderBy: { name: 'asc' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.verifyOwnership(userId, id);
|
||||||
|
|
||||||
|
return this.prisma.restaurant.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name: dto.name,
|
||||||
|
slug: dto.slug,
|
||||||
|
description: dto.description,
|
||||||
|
logo: dto.logo,
|
||||||
|
template: dto.template ?? undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 } });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Controller, Get, Param, Delete, UseGuards } from '@nestjs/common';
|
||||||
|
import { AuthGuard, Session } from '@thallesp/nestjs-better-auth';
|
||||||
|
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
|
@Controller('users')
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
export class UserController {
|
||||||
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async getAllUsers() {
|
||||||
|
return this.userService.getAllUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async getUserById(@Param('id') id: string) {
|
||||||
|
return this.userService.getUserById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async deleteUser(@Param('id') id: string) {
|
||||||
|
return this.userService.deleteUser(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/sessions')
|
||||||
|
async getSessionsByUser(@Param('id') id: string) {
|
||||||
|
return this.userService.getSessionsByUserId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id/sessions/:sessionId')
|
||||||
|
async removeSession(
|
||||||
|
@Param('id') userId: string,
|
||||||
|
@Param('sessionId') sessionId: string,
|
||||||
|
) {
|
||||||
|
return this.userService.removeSession(userId, sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id/sessions')
|
||||||
|
async removeAllSessions(@Param('id') id: string) {
|
||||||
|
return this.userService.removeAllSessions(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('session')
|
||||||
|
async getSession(@Session() session: UserSession) {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { UserController } from './user.controller';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [UserController],
|
||||||
|
providers: [UserService],
|
||||||
|
})
|
||||||
|
export class UserModule {}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async getAllUsers() {
|
||||||
|
return this.prisma.user.findMany({
|
||||||
|
include: { accounts: true, sessions: true, restaurants: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserById(id: string) {
|
||||||
|
const user = await this.prisma.user.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: { accounts: true, sessions: true, restaurants: true },
|
||||||
|
});
|
||||||
|
if (!user) throw new NotFoundException('User not found');
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(id: string) {
|
||||||
|
// Delete related sessions, accounts, and restaurants first (cascade-like behavior)
|
||||||
|
await this.prisma.session.deleteMany({ where: { userId: id } });
|
||||||
|
await this.prisma.account.deleteMany({ where: { userId: id } });
|
||||||
|
await this.prisma.restaurant.deleteMany({ where: { ownerId: id } });
|
||||||
|
return this.prisma.user.delete({ where: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSessionsByUserId(id: string) {
|
||||||
|
const sessions = await this.prisma.session.findMany({
|
||||||
|
where: { userId: id },
|
||||||
|
});
|
||||||
|
if (sessions.length === 0)
|
||||||
|
throw new NotFoundException('No sessions found for this user');
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeSession(userId: string, sessionId: string) {
|
||||||
|
const session = await this.prisma.session.findFirst({
|
||||||
|
where: { id: sessionId, userId },
|
||||||
|
});
|
||||||
|
if (!session) throw new NotFoundException('Session not found');
|
||||||
|
return this.prisma.session.delete({ where: { id: sessionId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeAllSessions(userId: string) {
|
||||||
|
await this.prisma.session.deleteMany({ where: { userId } });
|
||||||
|
return { message: 'All user sessions removed' };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { App } from 'supertest/types';
|
||||||
|
import { AppModule } from './../src/app.module';
|
||||||
|
|
||||||
|
describe('AppController (e2e)', () => {
|
||||||
|
let app: INestApplication<App>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/ (GET)', () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get('/')
|
||||||
|
.expect(200)
|
||||||
|
.expect('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"moduleFileExtensions": ["js", "json", "ts"],
|
||||||
|
"rootDir": ".",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testRegex": ".e2e-spec.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "nodenext",
|
||||||
|
"moduleResolution": "nodenext",
|
||||||
|
"resolvePackageJsonExports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "ES2023",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"incremental": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"strictBindCallApply": false,
|
||||||
|
"noFallthroughCasesInSwitch": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user