diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..295a108 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +DATABASE_URL=postgresql://user:password@localhost:5432/crowmate +JWT_SECRET=your_secret_here +JWT_EXPIRY=7d +PORT=3000 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65867a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +*.js.map +coverage/ diff --git a/nest-cli.json b/nest-cli.json new file mode 100644 index 0000000..f9aa683 --- /dev/null +++ b/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..01168bc --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "nest-backend", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "nest build", + "start": "node dist/main", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "test": "vitest run", + "test:watch": "vitest", + "test:cov": "vitest run --coverage" + }, + "dependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/config": "^4.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/jwt": "^11.0.0", + "@nestjs/passport": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/swagger": "^11.0.0", + "@prisma/client": "^6.0.0", + "bcrypt": "^5.1.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^11.0.0", + "@nestjs/testing": "^11.0.0", + "@swc/core": "^1.10.0", + "@types/bcrypt": "^5.0.2", + "@types/express": "^5.0.0", + "@types/node": "^22.0.0", + "@types/passport-jwt": "^4.0.1", + "@vitest/coverage-v8": "^3.0.0", + "prisma": "^6.0.0", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.0", + "unplugin-swc": "^1.5.0", + "vitest": "^3.0.0" + } +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..a6ca777 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,24 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id @default(cuid()) + email String @unique + username String @unique + password String + role Role @default(MEMBER) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +enum Role { + ADMIN + DEV + MEMBER +} diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..6b7ce13 --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { PrismaModule } from './prisma/prisma.module'; + +@Module({ + imports: [ + ConfigModule.forRoot({ isGlobal: true }), + PrismaModule, + ], +}) +export class AppModule {} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..736c650 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,25 @@ +import { NestFactory } from '@nestjs/core'; +import { ValidationPipe } from '@nestjs/common'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); + app.enableCors(); + + const config = new DocumentBuilder() + .setTitle('CrowMate API') + .setDescription('REST API for CrowMate Web Platform') + .setVersion('1.0') + .addBearerAuth() + .build(); + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api/docs', app, document); + + const port = process.env.PORT ?? 3000; + await app.listen(port); + console.log(`Application running on port ${port}`); +} +bootstrap(); diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts new file mode 100644 index 0000000..7207426 --- /dev/null +++ b/src/prisma/prisma.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +@Global() +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts new file mode 100644 index 0000000..359f950 --- /dev/null +++ b/src/prisma/prisma.service.ts @@ -0,0 +1,9 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit { + async onModuleInit() { + await this.$connect(); + } +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..64f86c6 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c5555e2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..648f679 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,14 @@ +import swc from 'unplugin-swc'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + root: './', + }, + plugins: [ + swc.vite({ + module: { type: 'es6' }, + }), + ], +});