Compare commits
8 Commits
efd468873d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ac4e34436a | |||
| ce0b98783c | |||
| 2cfda9ca45 | |||
| e462606416 | |||
| 823d43fc75 | |||
| 366e2e2a92 | |||
| eb8d87bd48 | |||
| 19e648a299 |
@@ -20,7 +20,9 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keyv/redis": "^5.1.6",
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/cache-manager": "^3.1.3",
|
||||
"@nestjs/common": "^11.1.28",
|
||||
"@nestjs/config": "^4.0.4",
|
||||
"@nestjs/core": "^11.1.28",
|
||||
@@ -31,9 +33,11 @@
|
||||
"@nestjs/typeorm": "^11.0.3",
|
||||
"axios": "^1.18.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"cache-manager": "^7.2.9",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.15.1",
|
||||
"joi": "^18.2.3",
|
||||
"keyv": "^5.6.0",
|
||||
"ms": "^2.1.3",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
|
||||
Generated
+108
@@ -8,9 +8,15 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@keyv/redis':
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6(keyv@5.6.0)
|
||||
'@nestjs/axios':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.18.1)(rxjs@7.8.2)
|
||||
'@nestjs/cache-manager':
|
||||
specifier: ^3.1.3
|
||||
version: 3.1.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(cache-manager@7.2.9)(keyv@5.6.0)(rxjs@7.8.2)
|
||||
'@nestjs/common':
|
||||
specifier: ^11.1.28
|
||||
version: 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
@@ -41,6 +47,9 @@ importers:
|
||||
bcrypt:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
cache-manager:
|
||||
specifier: ^7.2.9
|
||||
version: 7.2.9
|
||||
class-transformer:
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1
|
||||
@@ -50,6 +59,9 @@ importers:
|
||||
joi:
|
||||
specifier: ^18.2.3
|
||||
version: 18.2.3
|
||||
keyv:
|
||||
specifier: ^5.6.0
|
||||
version: 5.6.0
|
||||
ms:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
@@ -352,6 +364,9 @@ packages:
|
||||
'@borewit/text-codec@0.2.2':
|
||||
resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==}
|
||||
|
||||
'@cacheable/utils@2.5.0':
|
||||
resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==}
|
||||
|
||||
'@colors/colors@1.5.0':
|
||||
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
||||
engines: {node: '>=0.1.90'}
|
||||
@@ -706,6 +721,15 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.9':
|
||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||
|
||||
'@keyv/redis@5.1.6':
|
||||
resolution: {integrity: sha512-eKvW6pspvVaU5dxigaIDZr635/Uw6urTXL3gNbY9WTR8d3QigZQT+r8gxYSEOsw4+1cCBsC4s7T2ptR0WC9LfQ==}
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
keyv: ^5.6.0
|
||||
|
||||
'@keyv/serialize@1.1.1':
|
||||
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
||||
|
||||
'@lukeed/csprng@1.1.0':
|
||||
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -726,6 +750,15 @@ packages:
|
||||
axios: ^1.3.1
|
||||
rxjs: ^7.0.0
|
||||
|
||||
'@nestjs/cache-manager@3.1.3':
|
||||
resolution: {integrity: sha512-HMtiOfHz75NZX7mJn1VnZGLSachVI04TnUc5wvEogIaKwk5BDQHgtP5htxreizjv7oxKalJbuTyxtiF6bE+bgQ==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0
|
||||
'@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0
|
||||
cache-manager: '>=6'
|
||||
keyv: '>=5'
|
||||
rxjs: ^7.8.1
|
||||
|
||||
'@nestjs/cli@11.0.24':
|
||||
resolution: {integrity: sha512-aIHxQLSYtXShifA3zwWIeznEsZnNa3Iz2QRykFj+sl9IcbERBHr5nH87FRgywM+He3NxoF5WazHfR8FsmVeWxw==}
|
||||
engines: {node: '>= 20.11'}
|
||||
@@ -869,6 +902,18 @@ packages:
|
||||
resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@redis/client@5.12.1':
|
||||
resolution: {integrity: sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==}
|
||||
engines: {node: '>= 18.19.0'}
|
||||
peerDependencies:
|
||||
'@node-rs/xxhash': ^1.1.0
|
||||
'@opentelemetry/api': '>=1 <2'
|
||||
peerDependenciesMeta:
|
||||
'@node-rs/xxhash':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
|
||||
'@scarf/scarf@1.4.0':
|
||||
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
|
||||
|
||||
@@ -1467,6 +1512,9 @@ packages:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
cache-manager@7.2.9:
|
||||
resolution: {integrity: sha512-d4vceEyYe95gPxEyQchlEOH9vJlkNRW8G6gzFzzMTxJK9PahYMhC9chrEqgZN0HulROjgw3IzmWVNk7Q7ytiGw==}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1550,6 +1598,10 @@ packages:
|
||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
cluster-key-slot@1.1.2:
|
||||
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
co@4.6.0:
|
||||
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||
@@ -2064,10 +2116,17 @@ packages:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hashery@1.5.1:
|
||||
resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
hasown@2.0.4:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hookified@1.15.1:
|
||||
resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
@@ -2381,6 +2440,9 @@ packages:
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
keyv@5.6.0:
|
||||
resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==}
|
||||
|
||||
leven@3.1.0:
|
||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3690,6 +3752,11 @@ snapshots:
|
||||
|
||||
'@borewit/text-codec@0.2.2': {}
|
||||
|
||||
'@cacheable/utils@2.5.0':
|
||||
dependencies:
|
||||
hashery: 1.5.1
|
||||
keyv: 5.6.0
|
||||
|
||||
'@colors/colors@1.5.0':
|
||||
optional: true
|
||||
|
||||
@@ -4157,6 +4224,18 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@keyv/redis@5.1.6(keyv@5.6.0)':
|
||||
dependencies:
|
||||
'@redis/client': 5.12.1
|
||||
cluster-key-slot: 1.1.2
|
||||
hookified: 1.15.1
|
||||
keyv: 5.6.0
|
||||
transitivePeerDependencies:
|
||||
- '@node-rs/xxhash'
|
||||
- '@opentelemetry/api'
|
||||
|
||||
'@keyv/serialize@1.1.1': {}
|
||||
|
||||
'@lukeed/csprng@1.1.0': {}
|
||||
|
||||
'@microsoft/tsdoc@0.16.0': {}
|
||||
@@ -4174,6 +4253,14 @@ snapshots:
|
||||
axios: 1.18.1
|
||||
rxjs: 7.8.2
|
||||
|
||||
'@nestjs/cache-manager@3.1.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(cache-manager@7.2.9)(keyv@5.6.0)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
cache-manager: 7.2.9
|
||||
keyv: 5.6.0
|
||||
rxjs: 7.8.2
|
||||
|
||||
'@nestjs/cli@11.0.24(@types/node@22.20.1)(prettier@3.9.5)':
|
||||
dependencies:
|
||||
'@angular-devkit/core': 19.2.27(chokidar@4.0.3)
|
||||
@@ -4332,6 +4419,10 @@ snapshots:
|
||||
|
||||
'@pkgr/core@0.3.6': {}
|
||||
|
||||
'@redis/client@5.12.1':
|
||||
dependencies:
|
||||
cluster-key-slot: 1.1.2
|
||||
|
||||
'@scarf/scarf@1.4.0': {}
|
||||
|
||||
'@sinclair/typebox@0.34.52': {}
|
||||
@@ -5000,6 +5091,11 @@ snapshots:
|
||||
|
||||
bytes@3.1.2: {}
|
||||
|
||||
cache-manager@7.2.9:
|
||||
dependencies:
|
||||
'@cacheable/utils': 2.5.0
|
||||
keyv: 5.6.0
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -5073,6 +5169,8 @@ snapshots:
|
||||
|
||||
clone@1.0.4: {}
|
||||
|
||||
cluster-key-slot@1.1.2: {}
|
||||
|
||||
co@4.6.0: {}
|
||||
|
||||
collect-v8-coverage@1.0.3: {}
|
||||
@@ -5592,10 +5690,16 @@ snapshots:
|
||||
dependencies:
|
||||
has-symbols: 1.1.0
|
||||
|
||||
hashery@1.5.1:
|
||||
dependencies:
|
||||
hookified: 1.15.1
|
||||
|
||||
hasown@2.0.4:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hookified@1.15.1: {}
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
http-errors@2.0.1:
|
||||
@@ -6100,6 +6204,10 @@ snapshots:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
|
||||
keyv@5.6.0:
|
||||
dependencies:
|
||||
'@keyv/serialize': 1.1.1
|
||||
|
||||
leven@3.1.0: {}
|
||||
|
||||
levn@0.4.1:
|
||||
|
||||
+26
-6
@@ -1,23 +1,43 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import Joi from 'joi';
|
||||
import { validationSchema } from './config/config.validation';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { UsersModule } from './modules/users/users.module';
|
||||
import { databaseConfig } from './config/typeorm.config';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
import { OrganModule } from './modules/organ/organ.module';
|
||||
import { ComplaintModule } from './modules/complaint/complaint.module';
|
||||
import { AttachmentModule } from './modules/attachment/attachment.module';
|
||||
import { ReplyModule } from './modules/reply/reply.module';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import KeyvRedis from '@keyv/redis';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
validationSchema
|
||||
validationSchema,
|
||||
}),
|
||||
CacheModule.registerAsync({
|
||||
isGlobal: true,
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
stores: [new KeyvRedis(config.getOrThrow('REDIS_URL'))],
|
||||
}),
|
||||
}),
|
||||
TypeOrmModule.forRootAsync(databaseConfig()),
|
||||
HttpModule.register({global: true, timeout: 10000, headers: {"Content-Type": "application/json"}}),
|
||||
HttpModule.register({
|
||||
global: true,
|
||||
timeout: 10000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}),
|
||||
UsersModule,
|
||||
AuthModule
|
||||
AuthModule,
|
||||
OrganModule,
|
||||
ComplaintModule,
|
||||
AttachmentModule,
|
||||
ReplyModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Matches } from 'class-validator';
|
||||
|
||||
export class SendOtpDto {
|
||||
@ApiProperty({
|
||||
example: '09123456789',
|
||||
})
|
||||
@Matches(/^09\d{9}$/, {
|
||||
message: 'Phone number must be a valid Iranian mobile number.',
|
||||
})
|
||||
phoneNumber!: string;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, Matches, Length } from 'class-validator';
|
||||
|
||||
export class VerifyOtpDto {
|
||||
@ApiProperty({
|
||||
example: '09123456789',
|
||||
})
|
||||
@Matches(/^09\d{9}$/)
|
||||
phoneNumber: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '123456',
|
||||
})
|
||||
@IsString()
|
||||
@Length(6, 6)
|
||||
code: string;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { applyDecorators, UseGuards } from "@nestjs/common";
|
||||
import { ApiBearerAuth } from "@nestjs/swagger";
|
||||
import { JwtAuthGuard } from "src/auth/guards/jwt-auth.guard";
|
||||
import { JwtAuthGuard } from "src/modules/auth/guards/jwt-auth.guard";
|
||||
|
||||
export function AuthGuard() {
|
||||
return applyDecorators(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { IRequest } from 'src/auth/interfaces/IRequest';
|
||||
import { ITokenPayload } from 'src/auth/interfaces/IToken-payload';
|
||||
import { IRequest } from 'src/modules/auth/interfaces/IRequest';
|
||||
import { ITokenPayload } from 'src/modules/auth/interfaces/IToken-payload';
|
||||
|
||||
export const UserDec = createParamDecorator(
|
||||
(data: keyof ITokenPayload | undefined, ctx: ExecutionContext) => {
|
||||
|
||||
@@ -18,4 +18,20 @@ export enum UserRoleMessages {
|
||||
|
||||
export enum AuthMessages {
|
||||
TOKEN_EXPIRED_OR_INVALID = 'توکن منسوخ شده یا نامعتبر است!',
|
||||
}
|
||||
|
||||
export enum OrganMessages {
|
||||
ORGAN_NOT_FOUND = 'ارگان مورد نظر پیدا نشد!'
|
||||
}
|
||||
|
||||
export enum ComplaintMessages {
|
||||
COMPLAINT_NOT_FOUND = 'شکایت مورد نظر پیدا نشد!'
|
||||
}
|
||||
|
||||
export enum AttachmentMessages {
|
||||
ATTACHMENT_NOT_FOUND = 'ضمیمه مورد نظر پیدا نشد!'
|
||||
}
|
||||
|
||||
export enum ReplyMessages {
|
||||
REPLY_NOT_FOUND = 'پاسخ مورد نظر پیدا نشد!'
|
||||
}
|
||||
@@ -21,5 +21,11 @@ export const validationSchema = Joi.object({
|
||||
JWT_ACCESS_SECRET: Joi.string().required(),
|
||||
JWT_REFRESH_SECRET: Joi.string().required(),
|
||||
JWT_ACCESS_EXPIRES: Joi.string().required(),
|
||||
JWT_REFRESH_EXPIRES: Joi.string().required()
|
||||
JWT_REFRESH_EXPIRES: Joi.string().required(),
|
||||
|
||||
// CACHE Config
|
||||
CACHE_TTL : Joi.number().required(),
|
||||
|
||||
// Redis Config
|
||||
REDIS_URL : Joi.string().required()
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FactoryProvider } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
import { SMS_CONFIG } from "src/utils/constants";
|
||||
import { SMS_CONFIG } from "src/modules/utils/constants";
|
||||
|
||||
export const smsConfigsProvider: FactoryProvider<ISmsConfigs> = {
|
||||
provide: SMS_CONFIG,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { AttachmentService } from './attachment.service';
|
||||
import { CreateAttachmentDto } from './dto/create-attachment.dto';
|
||||
import { UpdateAttachmentDto } from './dto/update-attachment.dto';
|
||||
|
||||
@Controller('attachment')
|
||||
export class AttachmentController {
|
||||
constructor(private readonly attachmentService: AttachmentService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createAttachmentDto: CreateAttachmentDto) {
|
||||
return this.attachmentService.create(createAttachmentDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.attachmentService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.attachmentService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateAttachmentDto: UpdateAttachmentDto) {
|
||||
return this.attachmentService.update(id, updateAttachmentDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.attachmentService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AttachmentService } from './attachment.service';
|
||||
import { AttachmentController } from './attachment.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Attachment } from './entities/attachment.entity';
|
||||
import { AttachmentRepository } from './repositories/attachment.repository';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Attachment])],
|
||||
controllers: [AttachmentController],
|
||||
providers: [AttachmentService, AttachmentRepository],
|
||||
})
|
||||
export class AttachmentModule {}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CreateAttachmentDto } from './dto/create-attachment.dto';
|
||||
import { UpdateAttachmentDto } from './dto/update-attachment.dto';
|
||||
import { Attachment } from './entities/attachment.entity';
|
||||
import { AttachmentRepository } from './repositories/attachment.repository';
|
||||
import { AttachmentMessages } from 'src/common/enums/messages.enum';
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentService {
|
||||
constructor(private readonly attachmentRepository: AttachmentRepository) {}
|
||||
|
||||
async create(createAttachmentDto: CreateAttachmentDto): Promise<Attachment> {
|
||||
const attachment = this.attachmentRepository.create(createAttachmentDto);
|
||||
await this.attachmentRepository.save(attachment);
|
||||
return attachment;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Attachment[]> {
|
||||
return await this.attachmentRepository.find();
|
||||
}
|
||||
|
||||
async findOneOrFail(id: string): Promise<Attachment> {
|
||||
const attachment = await this.attachmentRepository.findOne({where: {id}});
|
||||
if(!attachment) throw new NotFoundException(AttachmentMessages.ATTACHMENT_NOT_FOUND);
|
||||
return attachment;
|
||||
}
|
||||
|
||||
async update(id: string, updateAttachmentDto: UpdateAttachmentDto): Promise<Attachment> {
|
||||
const attachment = await this.findOneOrFail(id);
|
||||
Object.assign(attachment, updateAttachmentDto);
|
||||
return await this.attachmentRepository.save(attachment);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<Attachment> {
|
||||
const attachment = await this.findOneOrFail(id);
|
||||
await this.attachmentRepository.remove(attachment);
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEnum, IsNotEmpty, IsString } from "class-validator";
|
||||
import { AttachmentType } from "../enums/attachment-type.enum";
|
||||
|
||||
export class CreateAttachmentDto {
|
||||
@ApiProperty({
|
||||
example: 'فایل شکایت',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: AttachmentType.IMAGE,
|
||||
})
|
||||
@IsEnum(AttachmentType)
|
||||
@IsNotEmpty()
|
||||
type: AttachmentType;
|
||||
|
||||
@ApiProperty({
|
||||
example: '/images/file.pdf'
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
filepath: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateAttachmentDto } from './create-attachment.dto';
|
||||
|
||||
export class UpdateAttachmentDto extends PartialType(CreateAttachmentDto) {}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { BaseEntity } from "src/common/entities/base.entity";
|
||||
import { Column, Entity, OneToOne } from "typeorm";
|
||||
import { AttachmentType } from "../enums/attachment-type.enum";
|
||||
import { ComplaintAttachment } from "src/modules/complaint/entities/complaint-attachment.entity";
|
||||
import { ReplyAttachment } from "src/modules/reply/entities/reply-attachment.entity";
|
||||
|
||||
@Entity('attachments')
|
||||
export class Attachment extends BaseEntity{
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 50
|
||||
})
|
||||
title: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: AttachmentType,
|
||||
})
|
||||
type: AttachmentType;
|
||||
|
||||
@Column({
|
||||
type: 'text'
|
||||
})
|
||||
filepath: string;
|
||||
|
||||
@OneToOne(() => ComplaintAttachment, (ca) => ca.attachment)
|
||||
complaint: ComplaintAttachment;
|
||||
|
||||
@OneToOne(() => ReplyAttachment, (ra) => ra.attachment)
|
||||
reply: ReplyAttachment;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum AttachmentType {
|
||||
FILE = "file",
|
||||
LINK = "link",
|
||||
IMAGE = "image",
|
||||
VIDEO = "video",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Repository } from "typeorm";
|
||||
import { Attachment } from "../entities/attachment.entity";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentRepository extends Repository<Attachment> {
|
||||
constructor(@InjectRepository(Attachment) attachmentRepository: Repository<Attachment>) {
|
||||
super(attachmentRepository.target, attachmentRepository.manager, attachmentRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsEnum, IsOptional, Matches } from 'class-validator';
|
||||
import { RoleType } from 'src/modules/users/enums/user-role.enum';
|
||||
|
||||
export class SendOtpDto {
|
||||
@ApiProperty({
|
||||
example: '09123456789',
|
||||
})
|
||||
@Matches(/^09\d{9}$/, {
|
||||
message: 'Phone number must be a valid Iranian mobile number.',
|
||||
})
|
||||
phoneNumber!: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'normal_user',
|
||||
enum: RoleType,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsEnum(RoleType)
|
||||
role?: RoleType;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsString, Matches, Length, IsEnum, IsOptional } from 'class-validator';
|
||||
import { RoleType } from 'src/modules/users/enums/user-role.enum';
|
||||
|
||||
export class VerifyOtpDto {
|
||||
@ApiProperty({
|
||||
example: '09123456789',
|
||||
})
|
||||
@Matches(/^09\d{9}$/)
|
||||
phoneNumber: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '123456',
|
||||
})
|
||||
@IsString()
|
||||
@Length(6, 6)
|
||||
code: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'normal_user',
|
||||
enum: RoleType,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsEnum(RoleType)
|
||||
role?: RoleType;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export class AuthController {
|
||||
@ApiResponse({ status: 200, description: 'Sent Otp Successfully!' })
|
||||
@ApiResponse({ status: 400, description: 'Bad Request from User!' })
|
||||
async sendOtp(@Body() dto: SendOtpDto) {
|
||||
return this.authService.sendOTP(dto.phoneNumber);
|
||||
return this.authService.sendOTP(dto);
|
||||
}
|
||||
|
||||
@Post('verify-otp')
|
||||
@@ -2,16 +2,14 @@ import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { UtilsModule } from 'src/utils/utils.module';
|
||||
import { UtilsModule } from '../utils/utils.module';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { OTP } from './entities/otp.entity';
|
||||
import { OtpRepository } from './repositories/otp.repository';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { UserRole } from 'src/users/entities/user-role.entity';
|
||||
import { UsersModule } from 'src/modules/users/users.module';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
import { OtpService } from './otp.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -22,7 +20,7 @@ import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
UsersModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, OtpRepository, JwtStrategy],
|
||||
exports: [JwtModule, PassportModule]
|
||||
providers: [AuthService, OtpService, OtpRepository, JwtStrategy],
|
||||
exports: [JwtModule, PassportModule],
|
||||
})
|
||||
export class AuthModule {}
|
||||
@@ -1,17 +1,21 @@
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import type { ISmsConfigs } from 'src/config/sms.config';
|
||||
import { SMS_CONFIG } from 'src/utils/constants';
|
||||
import { SmsService } from 'src/utils/providers/sms.service';
|
||||
import { SMS_CONFIG } from '../utils/constants';
|
||||
import { SmsService } from '../utils/providers/sms.service';
|
||||
import { OtpRepository } from './repositories/otp.repository';
|
||||
import { OtpStatus } from './enums/otp-status.enum';
|
||||
import { VerifyOtpDto } from './DTO/verify-otp.dto';
|
||||
import { OTPMessages, UserMessages } from 'src/common/enums/messages.enum';
|
||||
import { LoginCredentialDto } from './DTO/login-credential.dto';
|
||||
import { UsersService } from 'src/users/users.service';
|
||||
import { UsersService } from 'src/modules/users/users.service';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import ms from 'ms';
|
||||
import { OtpService } from './otp.service';
|
||||
import { RoleType } from '../users/enums/user-role.enum';
|
||||
import { SendOtpDto } from './DTO/send-otp.dto';
|
||||
import { string } from 'joi';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@@ -19,17 +23,20 @@ export class AuthService {
|
||||
@Inject(SMS_CONFIG) private smsConfig: ISmsConfigs,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly OtpRepository: OtpRepository,
|
||||
private readonly userService: UsersService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly otpService: OtpService,
|
||||
) {}
|
||||
|
||||
async sendOTP(phone: string) {
|
||||
const otp = this.generateOtp();
|
||||
async sendOTP(dto: SendOtpDto) {
|
||||
const otp = await this.otpService.generate({
|
||||
phoneNumber: dto.phoneNumber,
|
||||
role: dto.role,
|
||||
});
|
||||
|
||||
try {
|
||||
await this.smsService.sendSms({
|
||||
Mobile: phone,
|
||||
Mobile: dto.phoneNumber,
|
||||
TemplateId: this.smsConfig.SMS_PATTERN_OTP,
|
||||
Parameters: [
|
||||
{
|
||||
@@ -39,21 +46,6 @@ export class AuthService {
|
||||
],
|
||||
});
|
||||
|
||||
await this.OtpRepository.update(
|
||||
{ phoneNumber: phone, status: OtpStatus.PENDING },
|
||||
{ status: OtpStatus.EXPIRED },
|
||||
);
|
||||
|
||||
await this.OtpRepository.save(
|
||||
this.OtpRepository.create({
|
||||
phoneNumber: phone,
|
||||
code: otp,
|
||||
expirationDate: new Date(Date.now() + 2 * 60 * 1000),
|
||||
status: OtpStatus.PENDING,
|
||||
numberOfTries: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
return { otp };
|
||||
} catch (error) {
|
||||
if (this.configService.get('NODE_ENV') !== 'production') {
|
||||
@@ -65,48 +57,11 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async verifyOTP(dto: VerifyOtpDto) {
|
||||
const otp = await this.OtpRepository.findOne({
|
||||
where: { phoneNumber: dto.phoneNumber, status: OtpStatus.PENDING },
|
||||
order: {
|
||||
createdAt: 'DESC',
|
||||
},
|
||||
});
|
||||
|
||||
if (!otp) {
|
||||
throw new BadRequestException(OTPMessages.OTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (otp.expirationDate < new Date()) {
|
||||
otp.status = OtpStatus.EXPIRED;
|
||||
|
||||
await this.OtpRepository.save(otp);
|
||||
|
||||
throw new BadRequestException(OTPMessages.OTP_EXPIRED);
|
||||
}
|
||||
|
||||
if (otp.code !== dto.code) {
|
||||
otp.numberOfTries++;
|
||||
|
||||
if (otp.numberOfTries >= 3) {
|
||||
otp.status = OtpStatus.EXPIRED;
|
||||
|
||||
await this.OtpRepository.save(otp);
|
||||
|
||||
throw new BadRequestException(OTPMessages.OTP_TOO_MANY_TRIES);
|
||||
}
|
||||
|
||||
await this.OtpRepository.save(otp);
|
||||
|
||||
throw new BadRequestException(OTPMessages.OTP_INVALID);
|
||||
}
|
||||
|
||||
otp.status = OtpStatus.VERIFIED;
|
||||
|
||||
await this.OtpRepository.save(otp);
|
||||
}
|
||||
|
||||
private generateOtp(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
await this.otpService.verify(
|
||||
{ phoneNumber: dto.phoneNumber, role: dto.role },
|
||||
dto.code,
|
||||
);
|
||||
if (dto.role) console.log('VERIFIED OTP FOR ROLE: ', dto.role);
|
||||
}
|
||||
|
||||
async checkLoginCredentials(credential: LoginCredentialDto): Promise<void> {
|
||||
@@ -120,7 +75,7 @@ export class AuthService {
|
||||
throw new BadRequestException(UserMessages.USER_PASSWORD_INVALID);
|
||||
}
|
||||
|
||||
await this.sendOTP(phoneNumber);
|
||||
await this.sendOTP({ phoneNumber } as SendOtpDto);
|
||||
}
|
||||
|
||||
async verifyOtpAndLogin(dto: VerifyOtpDto) {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { RoleType } from "src/users/enums/user-role.enum";
|
||||
import { RoleType } from "src/modules/users/enums/user-role.enum";
|
||||
|
||||
export interface ITokenPayload {
|
||||
id: string;
|
||||
@@ -0,0 +1,99 @@
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import type { Cache } from 'cache-manager';
|
||||
import { metadata } from 'reflect-metadata/no-conflict';
|
||||
import { OTPMessages } from 'src/common/enums/messages.enum';
|
||||
|
||||
interface OtpCache<T = any> {
|
||||
code: string;
|
||||
tries: number;
|
||||
metadata: T; // holds role, etc...
|
||||
}
|
||||
|
||||
type OtpParams<T extends Record<string, any> = {}> = T & {
|
||||
phoneNumber: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class OtpService {
|
||||
private readonly TTL: number;
|
||||
|
||||
private readonly keyTemplate: (keyof any)[] = ['role']; // could add more later if necessary
|
||||
|
||||
constructor(
|
||||
@Inject(CACHE_MANAGER)
|
||||
private readonly cache: Cache,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
this.TTL = this.configService.getOrThrow<number>('CACHE_TTL');
|
||||
}
|
||||
|
||||
private buildKey(phoneNumber: string, metadata: Record<string, any>): string {
|
||||
const prefixParts = this.keyTemplate
|
||||
.map((field) => metadata[field as string])
|
||||
.filter((value) => value !== undefined && value !== null);
|
||||
|
||||
if (prefixParts.length > 0) {
|
||||
return `otp:${prefixParts.join(':')}:${phoneNumber}`;
|
||||
}
|
||||
return `otp:${phoneNumber}`;
|
||||
}
|
||||
|
||||
async generate<T extends Record<string, any>>(
|
||||
params: OtpParams<T>,
|
||||
): Promise<string> {
|
||||
const { phoneNumber, ...metadata } = params;
|
||||
const key = this.buildKey(phoneNumber, metadata);
|
||||
const code = Math.floor(100_000 + Math.random() * 900_000).toString();
|
||||
|
||||
const value: OtpCache<T> = {
|
||||
code,
|
||||
tries: 0,
|
||||
metadata: metadata as unknown as T,
|
||||
};
|
||||
|
||||
await this.cache.set(key, value, this.TTL);
|
||||
return code;
|
||||
}
|
||||
|
||||
async verify<T extends Record<string, any>>(
|
||||
params: OtpParams<T>,
|
||||
code: string,
|
||||
): Promise<boolean> {
|
||||
const { phoneNumber, ...metadata } = params;
|
||||
const key = this.buildKey(phoneNumber, metadata);
|
||||
|
||||
const otp = await this.cache.get<OtpCache<T>>(key);
|
||||
|
||||
if (!otp) throw new BadRequestException(OTPMessages.OTP_EXPIRED);
|
||||
|
||||
if (otp.tries >= 5) {
|
||||
await this.cache.del(key);
|
||||
throw new BadRequestException(OTPMessages.OTP_TOO_MANY_TRIES);
|
||||
}
|
||||
|
||||
if (otp.code !== code) {
|
||||
otp.tries++;
|
||||
await this.cache.set(key, otp, this.TTL);
|
||||
throw new BadRequestException(OTPMessages.OTP_INVALID);
|
||||
}
|
||||
|
||||
await this.cache.del(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
async delete<T extends Record<string, any>>(
|
||||
params: OtpParams<T>,
|
||||
): Promise<void> {
|
||||
const { phoneNumber, ...metadata } = params;
|
||||
await this.cache.del(this.buildKey(phoneNumber, metadata));
|
||||
}
|
||||
|
||||
async exists<T extends Record<string, any>>(
|
||||
params: OtpParams<T>,
|
||||
): Promise<boolean> {
|
||||
const { phoneNumber, ...metadata } = params;
|
||||
return !!(await this.cache.get(this.buildKey(phoneNumber, metadata)));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { RoleType } from 'src/users/enums/user-role.enum';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy, ExtractJwt } from 'passport-jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { ComplaintService } from './complaint.service';
|
||||
import { CreateComplaintDto } from './dto/create-complaint.dto';
|
||||
import { UpdateComplaintDto } from './dto/update-complaint.dto';
|
||||
|
||||
@Controller('complaint')
|
||||
export class ComplaintController {
|
||||
constructor(private readonly complaintService: ComplaintService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createComplaintDto: CreateComplaintDto) {
|
||||
return this.complaintService.create(createComplaintDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.complaintService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.complaintService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateComplaintDto: UpdateComplaintDto) {
|
||||
return this.complaintService.update(id, updateComplaintDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.complaintService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ComplaintService } from './complaint.service';
|
||||
import { ComplaintController } from './complaint.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Complaint } from './entities/complaint.entity';
|
||||
import { ComplaintRepository } from './repositories/complaint.repository';
|
||||
import { ComplaintAttachment } from './entities/complaint-attachment.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Complaint, ComplaintAttachment])],
|
||||
controllers: [ComplaintController],
|
||||
providers: [ComplaintService, ComplaintRepository],
|
||||
})
|
||||
export class ComplaintModule {}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CreateComplaintDto } from './dto/create-complaint.dto';
|
||||
import { UpdateComplaintDto } from './dto/update-complaint.dto';
|
||||
import { ComplaintRepository } from './repositories/complaint.repository';
|
||||
import { Complaint } from './entities/complaint.entity';
|
||||
import { ComplaintMessages } from 'src/common/enums/messages.enum';
|
||||
|
||||
@Injectable()
|
||||
export class ComplaintService {
|
||||
constructor(private readonly complaintRepository: ComplaintRepository) {}
|
||||
|
||||
async create(createComplaintDto: CreateComplaintDto): Promise<Complaint> {
|
||||
const complaint = this.complaintRepository.create(createComplaintDto);
|
||||
await this.complaintRepository.save(complaint);
|
||||
return complaint;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Complaint[]> {
|
||||
return this.complaintRepository.find();
|
||||
}
|
||||
|
||||
async findOneOrFail(id: string): Promise<Complaint> {
|
||||
const complaint = await this.complaintRepository.findOne({ where: { id } });
|
||||
if (!complaint)
|
||||
throw new NotFoundException(ComplaintMessages.COMPLAINT_NOT_FOUND);
|
||||
|
||||
return complaint;
|
||||
}
|
||||
|
||||
async update(id: string, updateComplaintDto: UpdateComplaintDto): Promise<Complaint> {
|
||||
const complaint = await this.findOneOrFail(id);
|
||||
Object.assign(complaint, updateComplaintDto);
|
||||
return this.complaintRepository.save(complaint);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<Complaint> {
|
||||
const complaint = await this.findOneOrFail(id);
|
||||
await this.complaintRepository.remove(complaint);
|
||||
return complaint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEnum, IsNotEmpty, IsString, IsUUID } from "class-validator";
|
||||
import { ComplaintStatus } from "../enums/complaint.enum";
|
||||
|
||||
export class CreateComplaintDto {
|
||||
@ApiProperty({
|
||||
example: 'شکایت من',
|
||||
type: 'string'
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
message: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'شکایت من',
|
||||
type: 'string'
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
subject: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: ComplaintStatus.WAITING_FOR_RESPONSE,
|
||||
enum: ComplaintStatus
|
||||
})
|
||||
@IsEnum(ComplaintStatus)
|
||||
status: ComplaintStatus;
|
||||
|
||||
@ApiProperty({example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', description: 'Id of the User that made the complaint'})
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
userId: string;
|
||||
|
||||
@ApiProperty({example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', description: 'Id of the Orgain the complaint is about.'})
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
organId: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateComplaintDto } from './create-complaint.dto';
|
||||
|
||||
export class UpdateComplaintDto extends PartialType(CreateComplaintDto) {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { BaseEntity } from "src/common/entities/base.entity";
|
||||
import { Entity, JoinColumn, ManyToOne, OneToOne, Unique } from "typeorm";
|
||||
import { Complaint } from "./complaint.entity";
|
||||
import { Attachment } from "src/modules/attachment/entities/attachment.entity";
|
||||
|
||||
@Entity('complaint_attachments')
|
||||
@Unique(['attachment'])
|
||||
export class ComplaintAttachment extends BaseEntity {
|
||||
@ManyToOne(() => Complaint, (complaint) => complaint.attachments)
|
||||
complaint: Complaint;
|
||||
|
||||
@OneToOne(() => Attachment, (a) => a.complaint, {
|
||||
cascade: true,
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
attachment: Attachment;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne } from 'typeorm';
|
||||
import { ComplaintStatus } from '../enums/complaint.enum';
|
||||
import { User } from 'src/modules/users/entities/user.entity';
|
||||
import { Organ } from 'src/modules/organ/entities/organ.entity';
|
||||
import { BaseEntity } from 'src/common/entities/base.entity';
|
||||
import { ComplaintAttachment } from './complaint-attachment.entity';
|
||||
import { Reply } from 'src/modules/reply/entities/reply.entity';
|
||||
|
||||
@Entity('complaints')
|
||||
export class Complaint extends BaseEntity {
|
||||
@Column({
|
||||
type: 'text',
|
||||
})
|
||||
message: string;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
})
|
||||
subject: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ComplaintStatus,
|
||||
default: ComplaintStatus.WAITING_FOR_RESPONSE,
|
||||
})
|
||||
status: ComplaintStatus;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.complaints, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({
|
||||
name: 'userId',
|
||||
})
|
||||
complainant: User;
|
||||
|
||||
@ManyToOne(() => Organ, (organ) => organ.complaints, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn({
|
||||
name: 'organId'
|
||||
})
|
||||
organ: Organ;
|
||||
|
||||
@OneToMany(() => ComplaintAttachment, (ca) => ca.complaint)
|
||||
attachments: ComplaintAttachment[];
|
||||
|
||||
@OneToMany(() => Reply, (reply) => reply.complaint)
|
||||
messages: Reply[];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum ComplaintStatus {
|
||||
WAITING_FOR_RESPONSE = 'waiting_for_response',
|
||||
ANSWERED = 'answered'
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Complaint } from '../entities/complaint.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class ComplaintRepository extends Repository<Complaint> {
|
||||
constructor(
|
||||
@InjectRepository(Complaint) complaintRepository: ComplaintRepository,
|
||||
) {
|
||||
super(
|
||||
complaintRepository.target,
|
||||
complaintRepository.manager,
|
||||
complaintRepository.queryRunner,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsString } from "class-validator";
|
||||
|
||||
export class CreateOrganDto {
|
||||
@ApiProperty({example: 'استانداری', type: 'string'})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty({example: '/images/logos/xyz.jpg', type: 'string'})
|
||||
@IsString()
|
||||
logo: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateOrganDto } from './create-organ.dto';
|
||||
|
||||
export class UpdateOrganDto extends PartialType(CreateOrganDto) {}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { BaseEntity } from 'src/common/entities/base.entity';
|
||||
import { Complaint } from 'src/modules/complaint/entities/complaint.entity';
|
||||
import { Column, Entity, OneToMany } from 'typeorm';
|
||||
|
||||
@Entity('organs')
|
||||
export class Organ extends BaseEntity {
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
})
|
||||
name: string;
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
})
|
||||
logo: string;
|
||||
|
||||
@OneToMany(() => Complaint, (complaint) => complaint.organ)
|
||||
complaints: Complaint[];
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { OrganService } from './organ.service';
|
||||
import { CreateOrganDto } from './dto/create-organ.dto';
|
||||
import { UpdateOrganDto } from './dto/update-organ.dto';
|
||||
|
||||
@Controller('organ')
|
||||
export class OrganController {
|
||||
constructor(private readonly organService: OrganService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createOrganDto: CreateOrganDto) {
|
||||
return this.organService.create(createOrganDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.organService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.organService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateOrganDto: UpdateOrganDto) {
|
||||
return this.organService.update(id, updateOrganDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.organService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { OrganService } from './organ.service';
|
||||
import { OrganController } from './organ.controller';
|
||||
import { OrganRepository } from './repositories/organ.repository';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Organ } from './entities/organ.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Organ])
|
||||
],
|
||||
controllers: [OrganController],
|
||||
providers: [OrganService, OrganRepository],
|
||||
})
|
||||
export class OrganModule {}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CreateOrganDto } from './dto/create-organ.dto';
|
||||
import { UpdateOrganDto } from './dto/update-organ.dto';
|
||||
import { OrganRepository } from './repositories/organ.repository';
|
||||
import { OrganMessages } from 'src/common/enums/messages.enum';
|
||||
import { Organ } from './entities/organ.entity';
|
||||
|
||||
@Injectable()
|
||||
export class OrganService {
|
||||
constructor(private readonly organRepository: OrganRepository) {}
|
||||
|
||||
async create(createOrganDto: CreateOrganDto) {
|
||||
const organ = this.organRepository.create(createOrganDto);
|
||||
await this.organRepository.save(organ);
|
||||
}
|
||||
|
||||
async findAll(): Promise<Organ[]> {
|
||||
return await this.organRepository.find();
|
||||
}
|
||||
|
||||
async findOneOrFail(id: string): Promise<Organ> {
|
||||
const organ = await this.organRepository.findOne({where: {id}});
|
||||
if(!organ) throw new NotFoundException(OrganMessages.ORGAN_NOT_FOUND);
|
||||
|
||||
return organ;
|
||||
}
|
||||
|
||||
async update(id: string, updateOrganDto: UpdateOrganDto) {
|
||||
const organ = await this.findOneOrFail(id);
|
||||
|
||||
Object.assign(organ, updateOrganDto);
|
||||
return await this.organRepository.save(organ);
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
const organ = await this.findOneOrFail(id);
|
||||
await this.organRepository.remove(organ);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Repository } from "typeorm";
|
||||
import { Organ } from "../entities/organ.entity";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OrganRepository extends Repository<Organ> {
|
||||
constructor(@InjectRepository(Organ) organRepository: Repository<Organ>) {
|
||||
super(organRepository.target, organRepository.manager, organRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsString, IsUUID } from "class-validator";
|
||||
|
||||
export class CreateReplyDto {
|
||||
@ApiProperty({
|
||||
description: 'text content of the reply'
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
content: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Id of the sender user'
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
senderId: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Id of the receiver user'
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
receiverId: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Id of the Complaint'
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
complaintId: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateReplyDto } from './create-reply.dto';
|
||||
|
||||
export class UpdateReplyDto extends PartialType(CreateReplyDto) {}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { BaseEntity } from "src/common/entities/base.entity";
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from "typeorm";
|
||||
import { Reply } from "./reply.entity";
|
||||
import { Attachment } from "src/modules/attachment/entities/attachment.entity";
|
||||
|
||||
@Entity('reply_attachments')
|
||||
export class ReplyAttachment extends BaseEntity {
|
||||
@ManyToOne(() => Reply, (rp) => rp.attachments, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
reply: Reply;
|
||||
|
||||
@OneToOne(() => Attachment, (attch) => attch.reply, {
|
||||
cascade: true,
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
attachment: Attachment;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Attachment } from "src/modules/attachment/entities/attachment.entity";
|
||||
import { BaseEntity } from "src/common/entities/base.entity";
|
||||
import { Complaint } from "src/modules/complaint/entities/complaint.entity";
|
||||
import { User } from "src/modules/users/entities/user.entity";
|
||||
import { Column, Entity, ManyToOne, OneToMany } from "typeorm";
|
||||
import { ReplyAttachment } from "./reply-attachment.entity";
|
||||
|
||||
@Entity('replies')
|
||||
export class Reply extends BaseEntity {
|
||||
@Column({
|
||||
type: 'text'
|
||||
})
|
||||
content: string; // the text or message written.
|
||||
|
||||
@ManyToOne(() => User)
|
||||
sender: User;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
receiver: User;
|
||||
|
||||
@ManyToOne(() => Complaint, (complaint) => complaint.messages)
|
||||
complaint: Complaint;
|
||||
|
||||
@OneToMany(() => ReplyAttachment, (ra) => ra.reply)
|
||||
attachments: ReplyAttachment;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { ReplyService } from './reply.service';
|
||||
import { CreateReplyDto } from './dto/create-reply.dto';
|
||||
import { UpdateReplyDto } from './dto/update-reply.dto';
|
||||
|
||||
@Controller('reply')
|
||||
export class ReplyController {
|
||||
constructor(private readonly replyService: ReplyService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createReplyDto: CreateReplyDto) {
|
||||
return this.replyService.create(createReplyDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.replyService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.replyService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateReplyDto: UpdateReplyDto) {
|
||||
return this.replyService.update(id, updateReplyDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.replyService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ReplyService } from './reply.service';
|
||||
import { ReplyController } from './reply.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Reply } from './entities/reply.entity';
|
||||
import { ReplyAttachment } from './entities/reply-attachment.entity';
|
||||
import { ReplyRepository } from './repositories/reply.repository';
|
||||
import { ReplyAttachmentRepository } from './repositories/reply-attachment.repository';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Reply, ReplyAttachment])],
|
||||
controllers: [ReplyController],
|
||||
providers: [ReplyService, ReplyRepository, ReplyAttachmentRepository],
|
||||
})
|
||||
export class ReplyModule {}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CreateReplyDto } from './dto/create-reply.dto';
|
||||
import { UpdateReplyDto } from './dto/update-reply.dto';
|
||||
import { ReplyRepository } from './repositories/reply.repository';
|
||||
// import { ReplyAttachmentRepository } from './repositories/reply-attachment.repository';
|
||||
import { Reply } from './entities/reply.entity';
|
||||
import { ReplyMessages } from 'src/common/enums/messages.enum';
|
||||
|
||||
@Injectable()
|
||||
export class ReplyService {
|
||||
constructor(
|
||||
private readonly replyRepository: ReplyRepository,
|
||||
// private readonly replyAttachmentRepository: ReplyAttachmentRepository,
|
||||
) {}
|
||||
|
||||
async create(createReplyDto: CreateReplyDto): Promise<Reply> {
|
||||
const reply = this.replyRepository.create(createReplyDto);
|
||||
await this.replyRepository.save(reply);
|
||||
return reply;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Reply[]> {
|
||||
return await this.replyRepository.find();
|
||||
}
|
||||
|
||||
async findOneOrFail(id: string): Promise<Reply> {
|
||||
const reply = await this.replyRepository.findOne({where: {id}});
|
||||
if(!reply) throw new NotFoundException(ReplyMessages.REPLY_NOT_FOUND);
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
async update(id: string, updateReplyDto: UpdateReplyDto): Promise<Reply> {
|
||||
const reply = await this.findOneOrFail(id);
|
||||
Object.assign(reply, updateReplyDto);
|
||||
return reply;
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<Reply> {
|
||||
const reply = await this.findOneOrFail(id);
|
||||
await this.replyRepository.remove(reply);
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { Repository } from "typeorm";
|
||||
import { ReplyAttachment } from "../entities/reply-attachment.entity";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
|
||||
@Injectable()
|
||||
export class ReplyAttachmentRepository extends Repository<ReplyAttachment> {
|
||||
constructor(@InjectRepository(ReplyAttachment) replyAttachmentRepository: Repository<ReplyAttachment>) {
|
||||
super(replyAttachmentRepository.target, replyAttachmentRepository.manager, replyAttachmentRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Repository } from "typeorm";
|
||||
import { Reply } from "../entities/reply.entity";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class ReplyRepository extends Repository<Reply> {
|
||||
constructor(@InjectRepository(Reply) replyRepository: Repository<Reply>) {
|
||||
super(replyRepository.target, replyRepository.manager, replyRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Entity, Column, ManyToMany, JoinTable, OneToMany } from 'typeorm';
|
||||
import { BaseEntity } from 'src/common/entities/base.entity';
|
||||
import { Exclude } from 'class-transformer';
|
||||
import { UserRole } from './user-role.entity';
|
||||
import { Complaint } from 'src/modules/complaint/entities/complaint.entity';
|
||||
|
||||
@Entity('users')
|
||||
export class User extends BaseEntity {
|
||||
@@ -33,4 +34,9 @@ export class User extends BaseEntity {
|
||||
cascade: true
|
||||
})
|
||||
roles: UserRole[];
|
||||
|
||||
@OneToMany(() => Complaint, (complaint) => complaint.complainant, {
|
||||
cascade: true
|
||||
})
|
||||
complaints: Complaint[];
|
||||
}
|
||||
Reference in New Issue
Block a user