init project starter from nest.js
This commit is contained in:
		
							parent
							
								
									db23df0fd1
								
							
						
					
					
						commit
						57872f9f1c
					
				
							
								
								
									
										58
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
							
								
								
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
  "singleQuote": true,
 | 
			
		||||
  "trailingComma": "all"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
{
 | 
			
		||||
    "postman.settings.dotenv-detection-notification-visibility": false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								eslint.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								eslint.config.mjs
									
									
									
									
									
										Normal file
									
								
							@ -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" }],
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										8
									
								
								nest-cli.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								nest-cli.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "https://json.schemastore.org/nest-cli",
 | 
			
		||||
  "collection": "@nestjs/schematics",
 | 
			
		||||
  "sourceRoot": "src",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "deleteOutDir": true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11196
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										11196
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										86
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "hadirapp-backend",
 | 
			
		||||
  "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/config": "^4.0.2",
 | 
			
		||||
    "@nestjs/core": "^11.0.1",
 | 
			
		||||
    "@nestjs/jwt": "^11.0.1",
 | 
			
		||||
    "@nestjs/passport": "^11.0.5",
 | 
			
		||||
    "@nestjs/platform-express": "^11.1.8",
 | 
			
		||||
    "@nestjs/swagger": "^11.2.1",
 | 
			
		||||
    "@prisma/client": "^6.18.0",
 | 
			
		||||
    "bcrypt": "^6.0.0",
 | 
			
		||||
    "class-transformer": "^0.5.1",
 | 
			
		||||
    "class-validator": "^0.14.2",
 | 
			
		||||
    "multer": "^2.0.2",
 | 
			
		||||
    "passport": "^0.7.0",
 | 
			
		||||
    "passport-jwt": "^4.0.1",
 | 
			
		||||
    "reflect-metadata": "^0.2.2",
 | 
			
		||||
    "swagger-ui-express": "^5.0.1",
 | 
			
		||||
    "uuid": "^13.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "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",
 | 
			
		||||
    "@types/uuid": "^10.0.0",
 | 
			
		||||
    "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",
 | 
			
		||||
    "rxjs": "^7.8.2",
 | 
			
		||||
    "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.9.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"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								prisma.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								prisma.config.ts
									
									
									
									
									
										Normal file
									
								
							@ -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: env("DATABASE_URL"),
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										256
									
								
								prisma/schema.prisma
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								prisma/schema.prisma
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,256 @@
 | 
			
		||||
generator client {
 | 
			
		||||
  provider = "prisma-client-js"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
datasource db {
 | 
			
		||||
  provider = "mysql"
 | 
			
		||||
  url      = env("DATABASE_URL")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model admins {
 | 
			
		||||
  id        String   @id
 | 
			
		||||
  userId    String   @unique
 | 
			
		||||
  name      String
 | 
			
		||||
  phone     String?
 | 
			
		||||
  createdAt DateTime @default(now())
 | 
			
		||||
  updatedAt DateTime
 | 
			
		||||
  users     users    @relation(fields: [userId], references: [id], onDelete: Cascade)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model attendance_sessions {
 | 
			
		||||
  id          String        @id
 | 
			
		||||
  scheduleId  String
 | 
			
		||||
  date        DateTime      @db.Date
 | 
			
		||||
  startTime   DateTime
 | 
			
		||||
  endTime     DateTime
 | 
			
		||||
  qrCode      String?       @unique
 | 
			
		||||
  qrExpiredAt DateTime?
 | 
			
		||||
  topic       String?
 | 
			
		||||
  notes       String?       @db.Text
 | 
			
		||||
  isActive    Boolean       @default(true)
 | 
			
		||||
  createdAt   DateTime      @default(now())
 | 
			
		||||
  updatedAt   DateTime
 | 
			
		||||
  schedules   schedules     @relation(fields: [scheduleId], references: [id], onDelete: Cascade)
 | 
			
		||||
  attendances attendances[]
 | 
			
		||||
 | 
			
		||||
  @@unique([scheduleId, date])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model attendances {
 | 
			
		||||
  id                  String              @id
 | 
			
		||||
  sessionId           String
 | 
			
		||||
  studentId           String
 | 
			
		||||
  status              attendances_status  @default(PRESENT)
 | 
			
		||||
  checkInTime         DateTime            @default(now())
 | 
			
		||||
  ipAddress           String?
 | 
			
		||||
  latitude            Float?
 | 
			
		||||
  longitude           Float?
 | 
			
		||||
  deviceInfo          String?             @db.Text
 | 
			
		||||
  notes               String?             @db.Text
 | 
			
		||||
  createdAt           DateTime            @default(now())
 | 
			
		||||
  updatedAt           DateTime
 | 
			
		||||
  attendance_sessions attendance_sessions @relation(fields: [sessionId], references: [id], onDelete: Cascade)
 | 
			
		||||
  students            students            @relation(fields: [studentId], references: [id], onDelete: Cascade)
 | 
			
		||||
 | 
			
		||||
  @@unique([sessionId, studentId])
 | 
			
		||||
  @@index([studentId], map: "attendances_studentId_fkey")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model audit_logs {
 | 
			
		||||
  id        String   @id
 | 
			
		||||
  userId    String
 | 
			
		||||
  action    String
 | 
			
		||||
  entity    String
 | 
			
		||||
  entityId  String
 | 
			
		||||
  oldValue  String?  @db.Text
 | 
			
		||||
  newValue  String?  @db.Text
 | 
			
		||||
  ipAddress String?
 | 
			
		||||
  userAgent String?  @db.Text
 | 
			
		||||
  createdAt DateTime @default(now())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model classes {
 | 
			
		||||
  id        String      @id
 | 
			
		||||
  name      String      @unique
 | 
			
		||||
  grade     String
 | 
			
		||||
  major     String?
 | 
			
		||||
  capacity  Int         @default(40)
 | 
			
		||||
  createdAt DateTime    @default(now())
 | 
			
		||||
  updatedAt DateTime
 | 
			
		||||
  schedules schedules[]
 | 
			
		||||
  students  students[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model courses {
 | 
			
		||||
  id          String      @id
 | 
			
		||||
  code        String      @unique
 | 
			
		||||
  name        String
 | 
			
		||||
  description String?     @db.Text
 | 
			
		||||
  teacherId   String
 | 
			
		||||
  credits     Int         @default(2)
 | 
			
		||||
  createdAt   DateTime    @default(now())
 | 
			
		||||
  updatedAt   DateTime
 | 
			
		||||
  teachers    teachers    @relation(fields: [teacherId], references: [id], onDelete: Cascade)
 | 
			
		||||
  schedules   schedules[]
 | 
			
		||||
 | 
			
		||||
  @@index([teacherId], map: "courses_teacherId_fkey")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model leave_requests {
 | 
			
		||||
  id          String                @id
 | 
			
		||||
  studentId   String
 | 
			
		||||
  startDate   DateTime              @db.Date
 | 
			
		||||
  endDate     DateTime              @db.Date
 | 
			
		||||
  type        leave_requests_type
 | 
			
		||||
  reason      String                @db.Text
 | 
			
		||||
  attachment  String?
 | 
			
		||||
  status      leave_requests_status @default(PENDING)
 | 
			
		||||
  reviewedBy  String?
 | 
			
		||||
  reviewedAt  DateTime?
 | 
			
		||||
  reviewNotes String?               @db.Text
 | 
			
		||||
  createdAt   DateTime              @default(now())
 | 
			
		||||
  updatedAt   DateTime
 | 
			
		||||
  students    students              @relation(fields: [studentId], references: [id], onDelete: Cascade)
 | 
			
		||||
 | 
			
		||||
  @@index([studentId], map: "leave_requests_studentId_fkey")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model notifications {
 | 
			
		||||
  id        String             @id
 | 
			
		||||
  userId    String
 | 
			
		||||
  type      notifications_type
 | 
			
		||||
  title     String
 | 
			
		||||
  message   String             @db.Text
 | 
			
		||||
  isRead    Boolean            @default(false)
 | 
			
		||||
  createdAt DateTime           @default(now())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model schedules {
 | 
			
		||||
  id                  String                @id
 | 
			
		||||
  courseId            String
 | 
			
		||||
  classId             String
 | 
			
		||||
  teacherId           String
 | 
			
		||||
  dayOfWeek           schedules_dayOfWeek
 | 
			
		||||
  startTime           String
 | 
			
		||||
  endTime             String
 | 
			
		||||
  room                String?
 | 
			
		||||
  wifiNetworkId       String?
 | 
			
		||||
  isActive            Boolean               @default(true)
 | 
			
		||||
  createdAt           DateTime              @default(now())
 | 
			
		||||
  updatedAt           DateTime
 | 
			
		||||
  attendance_sessions attendance_sessions[]
 | 
			
		||||
  classes             classes               @relation(fields: [classId], references: [id], onDelete: Cascade)
 | 
			
		||||
  courses             courses               @relation(fields: [courseId], references: [id], onDelete: Cascade)
 | 
			
		||||
  teachers            teachers              @relation(fields: [teacherId], references: [id], onDelete: Cascade)
 | 
			
		||||
  wifi_networks       wifi_networks?        @relation(fields: [wifiNetworkId], references: [id])
 | 
			
		||||
 | 
			
		||||
  @@index([classId], map: "schedules_classId_fkey")
 | 
			
		||||
  @@index([courseId], map: "schedules_courseId_fkey")
 | 
			
		||||
  @@index([teacherId], map: "schedules_teacherId_fkey")
 | 
			
		||||
  @@index([wifiNetworkId], map: "schedules_wifiNetworkId_fkey")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model students {
 | 
			
		||||
  id             String           @id
 | 
			
		||||
  userId         String           @unique
 | 
			
		||||
  nis            String           @unique
 | 
			
		||||
  name           String
 | 
			
		||||
  phone          String?
 | 
			
		||||
  address        String?          @db.Text
 | 
			
		||||
  photo          String?
 | 
			
		||||
  classId        String?
 | 
			
		||||
  parentPhone    String?
 | 
			
		||||
  parentEmail    String?
 | 
			
		||||
  createdAt      DateTime         @default(now())
 | 
			
		||||
  updatedAt      DateTime
 | 
			
		||||
  attendances    attendances[]
 | 
			
		||||
  leave_requests leave_requests[]
 | 
			
		||||
  classes        classes?         @relation(fields: [classId], references: [id])
 | 
			
		||||
  users          users            @relation(fields: [userId], references: [id], onDelete: Cascade)
 | 
			
		||||
 | 
			
		||||
  @@index([classId], map: "students_classId_fkey")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model teachers {
 | 
			
		||||
  id        String      @id
 | 
			
		||||
  userId    String      @unique
 | 
			
		||||
  nip       String      @unique
 | 
			
		||||
  name      String
 | 
			
		||||
  phone     String?
 | 
			
		||||
  createdAt DateTime    @default(now())
 | 
			
		||||
  updatedAt DateTime
 | 
			
		||||
  courses   courses[]
 | 
			
		||||
  schedules schedules[]
 | 
			
		||||
  users     users       @relation(fields: [userId], references: [id], onDelete: Cascade)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model users {
 | 
			
		||||
  id        String     @id
 | 
			
		||||
  email     String     @unique
 | 
			
		||||
  password  String
 | 
			
		||||
  role      users_role @default(STUDENT)
 | 
			
		||||
  isActive  Boolean    @default(true)
 | 
			
		||||
  createdAt DateTime   @default(now())
 | 
			
		||||
  updatedAt DateTime
 | 
			
		||||
  admins    admins?
 | 
			
		||||
  students  students?
 | 
			
		||||
  teachers  teachers?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model wifi_networks {
 | 
			
		||||
  id          String      @id
 | 
			
		||||
  ssid        String
 | 
			
		||||
  description String?
 | 
			
		||||
  ipRange     String
 | 
			
		||||
  latitude    Float?
 | 
			
		||||
  longitude   Float?
 | 
			
		||||
  radius      Int         @default(50)
 | 
			
		||||
  isActive    Boolean     @default(true)
 | 
			
		||||
  createdAt   DateTime    @default(now())
 | 
			
		||||
  updatedAt   DateTime
 | 
			
		||||
  schedules   schedules[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum notifications_type {
 | 
			
		||||
  ATTENDANCE_REMINDER
 | 
			
		||||
  ABSENCE_ALERT
 | 
			
		||||
  LEAVE_APPROVED
 | 
			
		||||
  LEAVE_REJECTED
 | 
			
		||||
  SYSTEM
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum attendances_status {
 | 
			
		||||
  PRESENT
 | 
			
		||||
  LATE
 | 
			
		||||
  EXCUSED
 | 
			
		||||
  SICK
 | 
			
		||||
  ABSENT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum users_role {
 | 
			
		||||
  ADMIN
 | 
			
		||||
  STUDENT
 | 
			
		||||
  TEACHER
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum leave_requests_type {
 | 
			
		||||
  SICK
 | 
			
		||||
  EXCUSED
 | 
			
		||||
  OTHER
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum schedules_dayOfWeek {
 | 
			
		||||
  MONDAY
 | 
			
		||||
  TUESDAY
 | 
			
		||||
  WEDNESDAY
 | 
			
		||||
  THURSDAY
 | 
			
		||||
  FRIDAY
 | 
			
		||||
  SATURDAY
 | 
			
		||||
  SUNDAY
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum leave_requests_status {
 | 
			
		||||
  PENDING
 | 
			
		||||
  APPROVED
 | 
			
		||||
  REJECTED
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								src/app.controller.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/app.controller.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { AppController } from './app.controller';
 | 
			
		||||
import { AppService } from './app.service';
 | 
			
		||||
 | 
			
		||||
describe('AppController', () => {
 | 
			
		||||
  let appController: AppController;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    const app: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      controllers: [AppController],
 | 
			
		||||
      providers: [AppService],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    appController = app.get<AppController>(AppController);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('root', () => {
 | 
			
		||||
    it('should return "Hello World!"', () => {
 | 
			
		||||
      expect(appController.getRoot()).toBe('{ message: \'HadirApp Backend is running 🚀\' }');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										9
									
								
								src/app.controller.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/app.controller.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
import { Controller, Get } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
@Controller()
 | 
			
		||||
export class AppController {
 | 
			
		||||
  @Get()
 | 
			
		||||
  getRoot() {
 | 
			
		||||
    return { message: 'HadirApp Backend is running 🚀' };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								src/app.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/app.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { AppController } from './app.controller';
 | 
			
		||||
import { AuthModule } from './modules/auth/auth.module';
 | 
			
		||||
import { UsersModule } from './modules/users/users.module';
 | 
			
		||||
import { PrismaModule } from './modules/prisma/prisma.module';
 | 
			
		||||
import { ConfigModule } from '@nestjs/config';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    ConfigModule.forRoot({ isGlobal: true }),
 | 
			
		||||
    PrismaModule,
 | 
			
		||||
    AuthModule,
 | 
			
		||||
    UsersModule,
 | 
			
		||||
  ],
 | 
			
		||||
  controllers: [AppController],
 | 
			
		||||
})
 | 
			
		||||
export class AppModule {}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/app.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/app.service.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class AppService {
 | 
			
		||||
  getHello(): string {
 | 
			
		||||
    return 'Hello World!';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								src/common/decorators/roles.decorator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/common/decorators/roles.decorator.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
import { SetMetadata } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
export const ROLES_KEY = 'roles';
 | 
			
		||||
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
 | 
			
		||||
							
								
								
									
										5
									
								
								src/common/guards/jwt-auth.guard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/common/guards/jwt-auth.guard.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { AuthGuard } from '@nestjs/passport';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/common/guards/roles.guard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/common/guards/roles.guard.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
 | 
			
		||||
import { Reflector } from '@nestjs/core';
 | 
			
		||||
import { ROLES_KEY } from '../decorators/roles.decorator';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class RolesGuard implements CanActivate {
 | 
			
		||||
  constructor(private reflector: Reflector) {}
 | 
			
		||||
 | 
			
		||||
  canActivate(context: ExecutionContext): boolean {
 | 
			
		||||
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
 | 
			
		||||
      context.getHandler(),
 | 
			
		||||
      context.getClass(),
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    if (!requiredRoles) return true;
 | 
			
		||||
 | 
			
		||||
    const { user } = context.switchToHttp().getRequest();
 | 
			
		||||
    return requiredRoles.includes(user.role);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
import { NestFactory } from '@nestjs/core';
 | 
			
		||||
import { AppModule } from './app.module';
 | 
			
		||||
import { ValidationPipe } from '@nestjs/common';
 | 
			
		||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
 | 
			
		||||
 | 
			
		||||
async function bootstrap() {
 | 
			
		||||
  const app = await NestFactory.create(AppModule);
 | 
			
		||||
  app.setGlobalPrefix('api');
 | 
			
		||||
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
 | 
			
		||||
 | 
			
		||||
  const config = new DocumentBuilder()
 | 
			
		||||
    .setTitle('HadirApp API')
 | 
			
		||||
    .setDescription('API documentation for attendance system')
 | 
			
		||||
    .setVersion('1.0')
 | 
			
		||||
    .addBearerAuth()
 | 
			
		||||
    .build();
 | 
			
		||||
  const document = SwaggerModule.createDocument(app, config);
 | 
			
		||||
  SwaggerModule.setup('docs', app, document);
 | 
			
		||||
 | 
			
		||||
  await app.listen(process.env.PORT || 3000);
 | 
			
		||||
}
 | 
			
		||||
bootstrap();
 | 
			
		||||
							
								
								
									
										18
									
								
								src/modules/auth/auth.controller.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/modules/auth/auth.controller.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { AuthController } from './auth.controller';
 | 
			
		||||
 | 
			
		||||
describe('AuthController', () => {
 | 
			
		||||
  let controller: AuthController;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      controllers: [AuthController],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    controller = module.get<AuthController>(AuthController);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(controller).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										44
									
								
								src/modules/auth/auth.controller.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/modules/auth/auth.controller.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
import { Body, Controller, Post } from '@nestjs/common';
 | 
			
		||||
import { ApiTags, ApiOperation, ApiBody, ApiResponse, ApiProperty } from '@nestjs/swagger';
 | 
			
		||||
import { AuthService } from './auth.service';
 | 
			
		||||
 | 
			
		||||
class RegisterDto {
 | 
			
		||||
  @ApiProperty({ example: 'user@example.com', description: 'User email' })
 | 
			
		||||
  email: string;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: 'strongPassword123', description: 'Plain text password' })
 | 
			
		||||
  password: string;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: 'STUDENT', description: 'Role: ADMIN | STUDENT | TEACHER', required: false })
 | 
			
		||||
  role?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LoginDto {
 | 
			
		||||
  @ApiProperty({ example: 'user@example.com' })
 | 
			
		||||
  email: string;
 | 
			
		||||
 | 
			
		||||
  @ApiProperty({ example: 'strongPassword123' })
 | 
			
		||||
  password: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ApiTags('auth')
 | 
			
		||||
@Controller('auth')
 | 
			
		||||
export class AuthController {
 | 
			
		||||
  constructor(private authService: AuthService) {}
 | 
			
		||||
 | 
			
		||||
  @Post('register')
 | 
			
		||||
  @ApiOperation({ summary: 'Register a new user' })
 | 
			
		||||
  @ApiBody({ type: RegisterDto })
 | 
			
		||||
  @ApiResponse({ status: 201, description: 'User registered successfully' })
 | 
			
		||||
  async register(@Body() body: RegisterDto) {
 | 
			
		||||
    return this.authService.register(body.email, body.password, body.role || 'STUDENT');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Post('login')
 | 
			
		||||
  @ApiOperation({ summary: 'Authenticate user and return JWT' })
 | 
			
		||||
  @ApiBody({ type: LoginDto })
 | 
			
		||||
  @ApiResponse({ status: 200, description: 'Login successful, returns access_token' })
 | 
			
		||||
  async login(@Body() body: LoginDto) {
 | 
			
		||||
    return this.authService.login(body.email, body.password);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								src/modules/auth/auth.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/modules/auth/auth.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { JwtModule } from '@nestjs/jwt';
 | 
			
		||||
import { PassportModule } from '@nestjs/passport';
 | 
			
		||||
import { AuthService } from './auth.service';
 | 
			
		||||
import { AuthController } from './auth.controller';
 | 
			
		||||
import { UsersModule } from '../users/users.module';
 | 
			
		||||
import { PrismaModule } from '../prisma/prisma.module';
 | 
			
		||||
import { JwtStrategy } from './strategies/jwt.strategy';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    UsersModule,
 | 
			
		||||
    PrismaModule,
 | 
			
		||||
    PassportModule,
 | 
			
		||||
    JwtModule.register({
 | 
			
		||||
      secret: process.env.JWT_SECRET || 'supersecretkey',
 | 
			
		||||
      signOptions: { expiresIn: '1d' },
 | 
			
		||||
    }),
 | 
			
		||||
  ],
 | 
			
		||||
  controllers: [AuthController],
 | 
			
		||||
  providers: [AuthService, JwtStrategy],
 | 
			
		||||
})
 | 
			
		||||
export class AuthModule {}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/modules/auth/auth.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/modules/auth/auth.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { AuthService } from './auth.service';
 | 
			
		||||
 | 
			
		||||
describe('AuthService', () => {
 | 
			
		||||
  let service: AuthService;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [AuthService],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    service = module.get<AuthService>(AuthService);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(service).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										55
									
								
								src/modules/auth/auth.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/modules/auth/auth.service.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
			
		||||
import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common';
 | 
			
		||||
import { PrismaService } from '../prisma/prisma.service';
 | 
			
		||||
import { Prisma, users_role } from '@prisma/client';
 | 
			
		||||
import { JwtService } from '@nestjs/jwt';
 | 
			
		||||
import * as bcrypt from 'bcrypt';
 | 
			
		||||
import { v4 as uuidv4 } from 'uuid';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class AuthService {
 | 
			
		||||
  constructor(private prisma: PrismaService, private jwt: JwtService) {}
 | 
			
		||||
 | 
			
		||||
  async register(email: string, password: string, role?: string) {
 | 
			
		||||
    // validate inputs to avoid passing undefined to bcrypt/prisma
 | 
			
		||||
    if (!email || !password) {
 | 
			
		||||
      throw new BadRequestException('Email and password are required');
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof password !== 'string' || password.length < 6) {
 | 
			
		||||
      throw new BadRequestException('Password must be a string with at least 6 characters');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // pastikan role sesuai enum di database (uppercase)
 | 
			
		||||
    const allowedRoles = ['ADMIN', 'STUDENT', 'TEACHER'];
 | 
			
		||||
    const normalizedRole = (role || 'STUDENT').toUpperCase();
 | 
			
		||||
 | 
			
		||||
    if (!allowedRoles.includes(normalizedRole)) {
 | 
			
		||||
      throw new BadRequestException(`Invalid role. Allowed: ${allowedRoles.join(', ')}`);
 | 
			
		||||
    }
 | 
			
		||||
    const hashed = await bcrypt.hash(password, 10);
 | 
			
		||||
    const user = await this.prisma.users.create({
 | 
			
		||||
      data: { id: uuidv4(), email, password: hashed, role: normalizedRole as users_role, updatedAt: new Date() },
 | 
			
		||||
    });
 | 
			
		||||
    return { message: 'User registered', user };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async login(email: string, password: string) {
 | 
			
		||||
    // validate inputs to avoid calling Prisma with undefined values
 | 
			
		||||
    if (!email || !password) {
 | 
			
		||||
      throw new BadRequestException('Email and password are required');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const user = await this.prisma.users.findUnique({ where: { email } });
 | 
			
		||||
    if (!user) throw new UnauthorizedException('User not found');
 | 
			
		||||
 | 
			
		||||
    const valid = await bcrypt.compare(password, user.password);
 | 
			
		||||
    if (!valid) throw new UnauthorizedException('Invalid credentials');
 | 
			
		||||
 | 
			
		||||
    const token = await this.jwt.signAsync({
 | 
			
		||||
      sub: user.id,
 | 
			
		||||
      email: user.email,
 | 
			
		||||
      role: user.role,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return { message: 'Login success', access_token: token };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/modules/auth/strategies/jwt.strategy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/modules/auth/strategies/jwt.strategy.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { PassportStrategy } from '@nestjs/passport';
 | 
			
		||||
import { ExtractJwt, Strategy } from 'passport-jwt';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class JwtStrategy extends PassportStrategy(Strategy) {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super({
 | 
			
		||||
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
 | 
			
		||||
      ignoreExpiration: false,
 | 
			
		||||
      secretOrKey: process.env.JWT_SECRET || 'supersecretkey',
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async validate(payload: any) {
 | 
			
		||||
    return { userId: payload.sub, email: payload.email, role: payload.role };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/modules/prisma/prisma.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/modules/prisma/prisma.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
import { Global, Module } from '@nestjs/common';
 | 
			
		||||
import { PrismaService } from './prisma.service';
 | 
			
		||||
 | 
			
		||||
@Global()
 | 
			
		||||
@Module({
 | 
			
		||||
  providers: [PrismaService],
 | 
			
		||||
  exports: [PrismaService],
 | 
			
		||||
})
 | 
			
		||||
export class PrismaModule {}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/modules/prisma/prisma.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/modules/prisma/prisma.service.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/modules/users/users.controller.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/modules/users/users.controller.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { UsersController } from './users.controller';
 | 
			
		||||
 | 
			
		||||
describe('UsersController', () => {
 | 
			
		||||
  let controller: UsersController;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      controllers: [UsersController],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    controller = module.get<UsersController>(UsersController);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(controller).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										23
									
								
								src/modules/users/users.controller.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/modules/users/users.controller.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
 | 
			
		||||
import { UsersService } from './users.service';
 | 
			
		||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
 | 
			
		||||
import { RolesGuard } from '../../common/guards/roles.guard';
 | 
			
		||||
import { Roles } from '../../common/decorators/roles.decorator';
 | 
			
		||||
 | 
			
		||||
@Controller('users')
 | 
			
		||||
@UseGuards(JwtAuthGuard, RolesGuard)
 | 
			
		||||
export class UsersController {
 | 
			
		||||
  constructor(private usersService: UsersService) {}
 | 
			
		||||
 | 
			
		||||
  @Get()
 | 
			
		||||
  @Roles('ADMIN')
 | 
			
		||||
  async getAll() {
 | 
			
		||||
    return this.usersService.findAll();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Get(':id')
 | 
			
		||||
  @Roles('ADMIN', 'TEACHER')
 | 
			
		||||
  async getOne(@Param('id') id: string) {
 | 
			
		||||
    return this.usersService.findOne(id);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/modules/users/users.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/modules/users/users.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { UsersService } from './users.service';
 | 
			
		||||
import { UsersController } from './users.controller';
 | 
			
		||||
import { PrismaModule } from '../prisma/prisma.module';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [PrismaModule],
 | 
			
		||||
  providers: [UsersService],
 | 
			
		||||
  controllers: [UsersController],
 | 
			
		||||
  exports: [UsersService],
 | 
			
		||||
})
 | 
			
		||||
export class UsersModule {}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/modules/users/users.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/modules/users/users.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { UsersService } from './users.service';
 | 
			
		||||
 | 
			
		||||
describe('UsersService', () => {
 | 
			
		||||
  let service: UsersService;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [UsersService],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    service = module.get<UsersService>(UsersService);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(service).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										15
									
								
								src/modules/users/users.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/modules/users/users.service.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { PrismaService } from '../prisma/prisma.service';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class UsersService {
 | 
			
		||||
  constructor(private prisma: PrismaService) {}
 | 
			
		||||
 | 
			
		||||
  async findAll() {
 | 
			
		||||
    return this.prisma.users.findMany();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async findOne(id: string) {
 | 
			
		||||
    return this.prisma.users.findUnique({ where: { id } });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								test/app.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								test/app.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -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!');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										9
									
								
								test/jest-e2e.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								test/jest-e2e.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
  "moduleFileExtensions": ["js", "json", "ts"],
 | 
			
		||||
  "rootDir": ".",
 | 
			
		||||
  "testEnvironment": "node",
 | 
			
		||||
  "testRegex": ".e2e-spec.ts$",
 | 
			
		||||
  "transform": {
 | 
			
		||||
    "^.+\\.(t|j)s$": "ts-jest"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								tsconfig.build.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tsconfig.build.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "./tsconfig.json",
 | 
			
		||||
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -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": "./src",
 | 
			
		||||
    "incremental": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "strictNullChecks": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true,
 | 
			
		||||
    "noImplicitAny": false,
 | 
			
		||||
    "strictBindCallApply": false,
 | 
			
		||||
    "noFallthroughCasesInSwitch": false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user