From 6da4b5114c4a0745ff73df569c3ea45f57df33b0 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Sat, 25 Jan 2025 16:52:54 +0330 Subject: [PATCH] chore: add danak services and admin login --- database/connection.ts | 25 +++++++ database/seeder.runner.ts | 19 ++++++ database/seeders/admin.seeder.ts | 33 +++++++++ database/seeders/role.seeder.ts | 20 +++++- docker-compose.yaml | 2 +- package.json | 9 ++- pnpm-lock.yaml | 3 + src/app.module.ts | 2 + src/common/enums/message.enum.ts | 13 ++++ src/modules/auth/auth.controller.ts | 16 +++++ src/modules/auth/providers/auth.service.ts | 67 ++++++++++++++++--- .../danak-services/DTO/create-category.dto.ts | 23 +++++++ .../danak-services.controller.ts | 18 +++++ .../danak-services/danak-services.module.ts | 19 ++++++ .../entities/danak-service-category.entity.ts | 26 +++++++ .../entities/danak-service-image.entity.ts | 13 ++++ .../entities/danak-service.entity.ts | 52 ++++++++++++++ .../enums/service-status.enum.ts | 4 ++ .../enums/services-language.enum.ts | 5 ++ .../providers/danak-services.service.ts | 24 +++++++ .../danak-services-category.repository.ts | 12 ++++ .../danak-services-image.repository.ts | 12 ++++ .../repositories/danak-services.repository.ts | 12 ++++ .../tickets/entities/ticket-message.entity.ts | 21 ++++++ src/modules/tickets/entities/ticket.entity.ts | 9 ++- .../{ => providers}/tickets.service.ts | 0 .../tickets-category.repository.ts | 11 +++ .../tickets-message.repository.ts | 11 +++ .../repositories/tickets.repository.ts | 11 +++ src/modules/tickets/tickets.controller.ts | 8 ++- src/modules/tickets/tickets.module.ts | 11 ++- src/modules/users/entities/user.entity.ts | 7 +- src/modules/users/providers/users.service.ts | 19 +++--- .../users.repository.ts | 0 src/modules/users/users.module.ts | 2 +- 35 files changed, 505 insertions(+), 34 deletions(-) create mode 100644 database/seeder.runner.ts create mode 100644 database/seeders/admin.seeder.ts create mode 100644 src/modules/danak-services/DTO/create-category.dto.ts create mode 100644 src/modules/danak-services/danak-services.controller.ts create mode 100644 src/modules/danak-services/danak-services.module.ts create mode 100644 src/modules/danak-services/entities/danak-service-category.entity.ts create mode 100644 src/modules/danak-services/entities/danak-service-image.entity.ts create mode 100644 src/modules/danak-services/entities/danak-service.entity.ts create mode 100644 src/modules/danak-services/enums/service-status.enum.ts create mode 100644 src/modules/danak-services/enums/services-language.enum.ts create mode 100644 src/modules/danak-services/providers/danak-services.service.ts create mode 100644 src/modules/danak-services/repositories/danak-services-category.repository.ts create mode 100644 src/modules/danak-services/repositories/danak-services-image.repository.ts create mode 100644 src/modules/danak-services/repositories/danak-services.repository.ts create mode 100644 src/modules/tickets/entities/ticket-message.entity.ts rename src/modules/tickets/{ => providers}/tickets.service.ts (100%) create mode 100644 src/modules/tickets/repositories/tickets-category.repository.ts create mode 100644 src/modules/tickets/repositories/tickets-message.repository.ts create mode 100644 src/modules/tickets/repositories/tickets.repository.ts rename src/modules/users/{providers => repositories}/users.repository.ts (100%) diff --git a/database/connection.ts b/database/connection.ts index e69de29..2122a03 100644 --- a/database/connection.ts +++ b/database/connection.ts @@ -0,0 +1,25 @@ +import { join } from "path"; + +import { config } from "dotenv"; +import { DataSource } from "typeorm"; +config(); + +const { DB_HOST, DB_NAME, DB_PASS, DB_PORT, DB_USER, NODE_ENV } = process.env; +const IS_PROD = NODE_ENV === "production"; +const PATH = IS_PROD ? "dist" : "src"; + +export const connectionSource = new DataSource({ + type: "postgres", + host: DB_HOST as string, + port: parseInt(DB_PORT as string), + username: DB_USER, + password: DB_PASS, + database: DB_NAME, + logging: true, + entities: [join(process.cwd(), PATH, "modules/**/*.entity{.ts,.js}")], + migrations: [join(process.cwd(), "database/migrations/*{.ts,.js}")], + synchronize: false, + migrationsTableName: "typeorm_migrations", + migrationsRun: false, +}); +console.log(join(process.cwd(), PATH, "modules/**/*.entity{.ts,.js}")); diff --git a/database/seeder.runner.ts b/database/seeder.runner.ts new file mode 100644 index 0000000..22a533e --- /dev/null +++ b/database/seeder.runner.ts @@ -0,0 +1,19 @@ +import { Logger } from "@nestjs/common"; + +import { connectionSource } from "./connection"; +import { seedAdmin } from "./seeders/admin.seeder"; +import { seedRole } from "./seeders/role.seeder"; + +const logger = new Logger("Seeder"); + +export const runSeeder = async () => { + await connectionSource.initialize(); + logger.log("start seeding database"); + await seedRole(connectionSource, logger); + await seedAdmin(connectionSource, logger); + + logger.log("seeding completed"); + process.exit(0); +}; + +runSeeder(); diff --git a/database/seeders/admin.seeder.ts b/database/seeders/admin.seeder.ts new file mode 100644 index 0000000..d5f0e8a --- /dev/null +++ b/database/seeders/admin.seeder.ts @@ -0,0 +1,33 @@ +import { Logger } from "@nestjs/common"; +import { DataSource, DeepPartial } from "typeorm"; + +import { Role } from "../../src/modules/users/entities/role.entity"; +import { User } from "../../src/modules/users/entities/user.entity"; +import { RoleEnum } from "../../src/modules/users/enums/role.enum"; + +const defaultAdmin: DeepPartial = { + email: "admin-dsc@gmail.com", + phone: "09922320740", + userName: "admin-default", + password: "admin123", + firstName: "DSC", + lastName: "Admin", + nationalCode: "1234567890", +}; + +export const seedAdmin = async (dataSource: DataSource, logger: Logger) => { + try { + const roleRepo = dataSource.getRepository(Role); + const userRepo = dataSource.getRepository(User); + + const adminRole = await roleRepo.findOneBy({ name: RoleEnum.ADMIN }); + if (!adminRole) throw new Error("Role not found"); + + const admin = userRepo.create({ ...defaultAdmin, role: adminRole }); + await userRepo.save(admin); + logger.log("admin created successfully"); + } catch (error) { + logger.error("Error in seeding admin", error); + process.exit(1); + } +}; diff --git a/database/seeders/role.seeder.ts b/database/seeders/role.seeder.ts index ef886a3..2e86b4e 100644 --- a/database/seeders/role.seeder.ts +++ b/database/seeders/role.seeder.ts @@ -1,3 +1,21 @@ +import { Logger } from "@nestjs/common"; +import { DataSource } from "typeorm"; + +import { Role } from "../../src/modules/users/entities/role.entity"; import { RoleEnum } from "../../src/modules/users/enums/role.enum"; -export const roles = [{ name: RoleEnum.SUPER_ADMIN }, { name: RoleEnum.ADMIN }, { name: RoleEnum.VISITOR }, { name: RoleEnum.USER }]; +const roles = [{ name: RoleEnum.SUPER_ADMIN }, { name: RoleEnum.ADMIN }, { name: RoleEnum.VISITOR }, { name: RoleEnum.USER }]; + +export const seedRole = async (dataSource: DataSource, logger: Logger) => { + try { + const roleRepo = dataSource.getRepository(Role); + for (const role of roles) { + const newRole = roleRepo.create(role); + await roleRepo.save(newRole); + } + logger.log("Role seeded successfully"); + } catch (error) { + logger.error("Error in seeding role", error); + process.exit(1); + } +}; diff --git a/docker-compose.yaml b/docker-compose.yaml index f978f53..f027090 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,7 +6,7 @@ services: restart: always container_name: postgres_dsc ports: - - 54320:5432 + - ${DB_PORT}:5432 # networks: # - app_network volumes: diff --git a/package.json b/package.json index 8add0e6..fdfd24c 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "start": "nest start", + "start:nest": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "NODE_ENV=production node --trace-warnings dist/main", + "start": "NODE_ENV=production node --trace-warnings dist/main", "lint:fix": "eslint . --ext .ts --fix", "test": "jest", "test:watch": "jest --watch", @@ -19,12 +19,14 @@ "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", "typeorm": "node -r ts-node/register ./node_modules/typeorm/cli", + "schema:drop": "npx typeorm-ts-node-commonjs schema:drop -d ./database/connection.ts", "migration:run": "npx typeorm-ts-node-commonjs migration:run -d ./database/connection.ts", "migration:generate": "npx typeorm-ts-node-commonjs migration:generate -d ./database/connection.ts ./database/migrations/$npm_config_name", "migration:create": "npx typeorm-ts-node-commonjs migration:create ./database/migrations/$npm_config_name", "migration:revert": "npx typeorm-ts-node-commonjs migration:revert -d ./database/connection.ts", "migration:show": "npx typeorm-ts-node-commonjs migration:show -d ./database/connection.ts ", - "prepare": "husky" + "seed:ts": "npx ts-node -r tsconfig-paths/register ./database/seeder.runner.ts", + "prepare": "husky || true" }, "dependencies": { "@fastify/static": "^7.0.4", @@ -48,6 +50,7 @@ "cache-manager": "^6.3.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "dotenv": "^16.4.7", "fastify": "^5.2.1", "handlebars": "^4.7.8", "nodemailer": "^6.9.16", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a1f86f..d6efe76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: class-validator: specifier: ^0.14.1 version: 0.14.1 + dotenv: + specifier: ^16.4.7 + version: 16.4.7 fastify: specifier: ^5.2.1 version: 5.2.1 diff --git a/src/app.module.ts b/src/app.module.ts index 8ee0b4c..19f2f08 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,6 +9,7 @@ import { rateLimitConfig } from "./configs/rateLimit.config"; import { databaseConfigs } from "./configs/typeorm.config"; import { HTTPLogger } from "./core/middlewares/logger.middleware"; import { AuthModule } from "./modules/auth/auth.module"; +import { DanakServicesModule } from "./modules/danak-services/danak-services.module"; import { TicketsModule } from "./modules/tickets/tickets.module"; import { UsersModule } from "./modules/users/users.module"; @@ -21,6 +22,7 @@ import { UsersModule } from "./modules/users/users.module"; UsersModule, TicketsModule, AuthModule, + DanakServicesModule, ], controllers: [], providers: [], diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index a270854..2a503e4 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -49,6 +49,7 @@ export const enum AuthMessage { NATIONAL_NOT_EMPTY = "کد ملی نمی‌تواند خالی باشد", OTP_ALREADY_SENT = "کد یکبار مصرف قبلا ارسال شده است", TOO_MANY_REQUESTS = "تعداد درخواست های شما بیش از حد مجاز است", + NOT_ADMIN = "شما دسترسی به بخش ادمین ندارید", } export const enum UserMessage { @@ -66,4 +67,16 @@ export const enum CommonMessage { THIS_FILED_IS_REQUIRED = "این فیلد الزامی است", VALID_FOR_CHOOSE = "معتبر برای انتخاب", UPDATE_SUCCESS = "با موفقیت به روز رسانی شد", + CREATED = "با موفقیت ایجاد شد", +} + +export const enum CategoryMessage { + TITLE_REQUIRED = "نام دسته بندی مورد نیاز است", + TITLE_STRING = "نام دسته بندی باید یک رشته باشد", + TITLE_LENGTH = "نام دسته بندی باید بین ۳ تا ۵۰ کاراکتر باشد", + ICON_REQUIRED = "آیکون دسته بندی مورد نیاز است", + ICON_SHOULD_BE_URL = "آیکون باید یک آدرس یو آر ال باشد", + PARENT_ID_REQUIRED = "شناسه والد دسته بندی مورد نیاز است", + PARENT_ID_SHOULD_BE_UUID = "شناسه والد باید یک یو یو آی دی باشد", + TITLE_EXIST = "دسته بندی با این نام قبلا ثبت شده است", } diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index b4ff5f6..217d6cb 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -57,6 +57,22 @@ export class AuthController { return this.authService.loginWithPassword(loginPasswordDto); } + //***************************** */ + @ApiOperation({ summary: "login with password ==> admin" }) + @HttpCode(HttpStatus.OK) + @Post("login/password/admin") + adminLoginWithPassword(@Body() loginPasswordDto: LoginPasswordDTO) { + return this.authService.adminLoginWithPassword(loginPasswordDto); + } + + @ApiOperation({ summary: "verify otp for login" }) + @HttpCode(HttpStatus.OK) + @Post("otp/verify/admin") + adminVerifyOtp(@Body() verifyOtpDto: VerifyOtpDto) { + return this.authService.adminVerifyLoginOtp(verifyOtpDto); + } + //***************************** */ + // @Post("forgot-password") // async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto) { // return this.authService.forgotPassword(forgotPasswordDto); diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index fce6c9e..3257195 100644 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -2,6 +2,7 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common"; import { TokensService } from "./tokens.service"; import { AuthMessage, UserMessage } from "../../../common/enums/message.enum"; +import { RoleEnum } from "../../users/enums/role.enum"; import { UsersService } from "../../users/providers/users.service"; import { OTPService } from "../../utils/providers/otp.service"; import { PasswordService } from "../../utils/providers/password.service"; @@ -64,11 +65,7 @@ export class AuthService { async loginWithPassword(loginDto: LoginPasswordDTO) { const { email, password } = loginDto; - const user = await this.usersService.findOneWithEmail(email); - if (!user) throw new BadRequestException(AuthMessage.INVALID_PASSWORD); - - const passCompare = await this.passwordService.comparePassword(password, user.password); - if (!passCompare) throw new BadRequestException(AuthMessage.INVALID_PASSWORD); + const user = await this.checkUserLoginCredentialWithEmail(email, password); const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); @@ -79,6 +76,21 @@ export class AuthService { } //****************** */ + async adminLoginWithPassword(loginDto: LoginPasswordDTO) { + const { email, password } = loginDto; + + const user = await this.checkUserLoginCredentialWithEmail(email, password); + + if (user.role.name !== RoleEnum.ADMIN) throw new BadRequestException(AuthMessage.NOT_ADMIN); + const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); + + return { + message: AuthMessage.PASSWORD_LOGIN_SUCCESS, + ...tokens, + }; + } + + //****************** */ async checkUserExist(checkUserExistDto: CheckUserExistDto) { const user = await this.usersService.findOneWithEmail(checkUserExistDto.email); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); @@ -86,7 +98,6 @@ export class AuthService { } //****************** */ - async requestLoginOtp(requestOtpDto: RequestOtpDto) { const { phone } = requestOtpDto; const user = await this.usersService.findOneWithPhone(phone); @@ -114,13 +125,22 @@ export class AuthService { async verifyLoginOtp(verifyOtpDto: VerifyOtpDto) { const { code, phone } = verifyOtpDto; - const isValid = await this.otpService.verifyOtp(phone, code, "LOGIN"); - if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); + const user = await this.checkUserLoginCredentialWithPhone(phone, code); - await this.otpService.delOtpFormCache(phone, "LOGIN"); + const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); + return { + message: AuthMessage.LOGIN_SUCCESS, + ...tokens, + }; + } - const user = await this.usersService.findOneWithPhone(phone); - if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + //****************** */ + async adminVerifyLoginOtp(verifyOtpDto: VerifyOtpDto) { + const { code, phone } = verifyOtpDto; + + const user = await this.checkUserLoginCredentialWithPhone(phone, code); + + if (user.role.name !== RoleEnum.ADMIN) throw new BadRequestException(AuthMessage.NOT_ADMIN); const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); return { @@ -129,4 +149,29 @@ export class AuthService { }; } //****************** */ + //****************** */ + + private async checkUserLoginCredentialWithEmail(email: string, password: string) { + const user = await this.usersService.findOneWithEmail(email); + if (!user) throw new BadRequestException(AuthMessage.INVALID_PASSWORD); + + const passCompare = await this.passwordService.comparePassword(password, user.password); + if (!passCompare) throw new BadRequestException(AuthMessage.INVALID_PASSWORD); + + return user; + } + //****************** */ + //****************** */ + + private async checkUserLoginCredentialWithPhone(phone: string, otpCode: string) { + const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN"); + if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); + + await this.otpService.delOtpFormCache(phone, "LOGIN"); + + const user = await this.usersService.findOneWithPhone(phone); + if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + + return user; + } } diff --git a/src/modules/danak-services/DTO/create-category.dto.ts b/src/modules/danak-services/DTO/create-category.dto.ts new file mode 100644 index 0000000..45ebf43 --- /dev/null +++ b/src/modules/danak-services/DTO/create-category.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length } from "class-validator"; + +import { CategoryMessage } from "../../../common/enums/message.enum"; + +export class CreateCategoryDto { + @IsNotEmpty({ message: CategoryMessage.TITLE_REQUIRED }) + @IsString({ message: CategoryMessage.TITLE_STRING }) + @Length(3, 50, { message: CategoryMessage.TITLE_LENGTH }) + @ApiProperty({ description: "Category title", example: "business" }) + title: string; + + @IsNotEmpty({ message: CategoryMessage.ICON_REQUIRED }) + @IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: CategoryMessage.ICON_SHOULD_BE_URL }) + @ApiProperty({ description: "Category icon", example: "https://example.com/icon.png" }) + icon: string; + + @IsOptional() + @IsNotEmpty({ message: CategoryMessage.PARENT_ID_REQUIRED }) + @IsUUID("4", { message: CategoryMessage.PARENT_ID_SHOULD_BE_UUID }) + @ApiPropertyOptional({ description: "Parent category id", example: "8b1e8b1e-8b1e-8b1e-8b1e-8b1e8b1e8b1e" }) + parentId?: string; +} diff --git a/src/modules/danak-services/danak-services.controller.ts b/src/modules/danak-services/danak-services.controller.ts new file mode 100644 index 0000000..0f9e773 --- /dev/null +++ b/src/modules/danak-services/danak-services.controller.ts @@ -0,0 +1,18 @@ +import { Body, Controller, Post } from "@nestjs/common"; +import { ApiOperation, ApiTags } from "@nestjs/swagger"; + +import { CreateCategoryDto } from "./DTO/create-category.dto"; +import { DanakServicesService } from "./providers/danak-services.service"; +import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; + +@Controller("services") +@ApiTags("Danak-Services") +export class DanakServicesController { + constructor(private readonly danakServicesService: DanakServicesService) {} + @AuthGuards() + @ApiOperation({ summary: "Create a new service category" }) + @Post("category") + createCategory(@Body() createDto: CreateCategoryDto) { + return this.danakServicesService.createCategory(createDto); + } +} diff --git a/src/modules/danak-services/danak-services.module.ts b/src/modules/danak-services/danak-services.module.ts new file mode 100644 index 0000000..2477b50 --- /dev/null +++ b/src/modules/danak-services/danak-services.module.ts @@ -0,0 +1,19 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; + +import { DanakServicesController } from "./danak-services.controller"; +import { DanakServiceCategory } from "./entities/danak-service-category.entity"; +import { DanakServiceImage } from "./entities/danak-service-image.entity"; +import { DanakService } from "./entities/danak-service.entity"; +import { DanakServicesService } from "./providers/danak-services.service"; +import { DanakServicesCategoryRepository } from "./repositories/danak-services-category.repository"; +import { DanakServicesImageRepository } from "./repositories/danak-services-image.repository"; +import { DanakServicesRepository } from "./repositories/danak-services.repository"; + +@Module({ + imports: [TypeOrmModule.forFeature([DanakService, DanakServiceImage, DanakServiceCategory])], + providers: [DanakServicesService, DanakServicesRepository, DanakServicesCategoryRepository, DanakServicesImageRepository], + controllers: [DanakServicesController], + exports: [DanakServicesService, TypeOrmModule], +}) +export class DanakServicesModule {} diff --git a/src/modules/danak-services/entities/danak-service-category.entity.ts b/src/modules/danak-services/entities/danak-service-category.entity.ts new file mode 100644 index 0000000..61637e8 --- /dev/null +++ b/src/modules/danak-services/entities/danak-service-category.entity.ts @@ -0,0 +1,26 @@ +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from "typeorm"; + +import { DanakService } from "./danak-service.entity"; +import { BaseEntity } from "../../../common/entities/base.entity"; + +@Entity() +export class DanakServiceCategory extends BaseEntity { + @Column({ type: "varchar", length: 100, nullable: false, unique: true }) + title: string; + + @Column({ type: "varchar", length: 150, nullable: false }) + icon: string; + + @ManyToOne(() => DanakServiceCategory, (category) => category.children) + @JoinColumn({ name: "parentId" }) + parent: DanakServiceCategory; + + @OneToMany(() => DanakServiceCategory, (category) => category.parent) + children: DanakServiceCategory[]; + + @Column({ nullable: true }) + parentId: string; + + @OneToMany(() => DanakService, (danakService) => danakService.category) + danakService: DanakService[]; +} diff --git a/src/modules/danak-services/entities/danak-service-image.entity.ts b/src/modules/danak-services/entities/danak-service-image.entity.ts new file mode 100644 index 0000000..4279401 --- /dev/null +++ b/src/modules/danak-services/entities/danak-service-image.entity.ts @@ -0,0 +1,13 @@ +import { Column, Entity, ManyToOne } from "typeorm"; + +import { DanakService } from "./danak-service.entity"; +import { BaseEntity } from "../../../common/entities/base.entity"; + +@Entity() +export class DanakServiceImage extends BaseEntity { + @Column({ type: "varchar", length: 255, nullable: false }) + imageUrl: string; + + @ManyToOne(() => DanakService, (danakService) => danakService.images, { nullable: false, onDelete: "CASCADE" }) + danakService: DanakService; +} diff --git a/src/modules/danak-services/entities/danak-service.entity.ts b/src/modules/danak-services/entities/danak-service.entity.ts new file mode 100644 index 0000000..beb6562 --- /dev/null +++ b/src/modules/danak-services/entities/danak-service.entity.ts @@ -0,0 +1,52 @@ +import { Column, Entity, ManyToOne, OneToMany } from "typeorm"; + +import { DanakServiceCategory } from "./danak-service-category.entity"; +import { DanakServiceImage } from "./danak-service-image.entity"; +import { BaseEntity } from "../../../common/entities/base.entity"; +import { ServiceStatus } from "../enums/service-status.enum"; +import { ServicesLanguage } from "../enums/services-language.enum"; + +@Entity() +export class DanakService extends BaseEntity { + @Column({ type: "varchar", length: 255, nullable: false }) + name: string; + + @Column({ type: "text", nullable: false }) + description: string; + + @Column({ type: "varchar", length: 255, nullable: false }) + author: string; + + @Column({ type: "timestamp", nullable: false, default: () => "CURRENT_TIMESTAMP" }) + createDate: Date; + + @Column({ type: "int", nullable: false, default: 0 }) + userCount: number; + + @Column({ type: "enum", enum: ServicesLanguage, nullable: false }) + serviceLanguage: ServicesLanguage; + + @Column({ type: "varchar", length: 150, nullable: false }) + softwareLanguage: string; + + @Column({ type: "text", nullable: false }) + metaDescription: string; + + @Column({ type: "enum", enum: ServiceStatus, nullable: false, default: ServiceStatus.IN_ACTIVE }) + status: ServiceStatus; + + @Column({ type: "varchar", length: 255, nullable: false }) + link: string; + + @Column({ type: "varchar", length: 255, nullable: false }) + icon: string; + + @OneToMany(() => DanakServiceImage, (danakServiceImage) => danakServiceImage.danakService, { eager: true }) + images: DanakServiceImage[]; + + @ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakService, { + nullable: false, + onDelete: "RESTRICT", + }) + category: DanakServiceCategory; +} diff --git a/src/modules/danak-services/enums/service-status.enum.ts b/src/modules/danak-services/enums/service-status.enum.ts new file mode 100644 index 0000000..5ce8823 --- /dev/null +++ b/src/modules/danak-services/enums/service-status.enum.ts @@ -0,0 +1,4 @@ +export enum ServiceStatus { + ACTIVE = "ACTIVE", + IN_ACTIVE = "IN_ACTIVE", +} diff --git a/src/modules/danak-services/enums/services-language.enum.ts b/src/modules/danak-services/enums/services-language.enum.ts new file mode 100644 index 0000000..f11496f --- /dev/null +++ b/src/modules/danak-services/enums/services-language.enum.ts @@ -0,0 +1,5 @@ +export enum ServicesLanguage { + ENGLISH = "en", + GERMAN = "de", + PERSIAN = "fa", +} diff --git a/src/modules/danak-services/providers/danak-services.service.ts b/src/modules/danak-services/providers/danak-services.service.ts new file mode 100644 index 0000000..06710a7 --- /dev/null +++ b/src/modules/danak-services/providers/danak-services.service.ts @@ -0,0 +1,24 @@ +import { BadRequestException, Injectable, Logger } from "@nestjs/common"; + +import { CategoryMessage, CommonMessage } from "../../../common/enums/message.enum"; +import { CreateCategoryDto } from "../DTO/create-category.dto"; +import { DanakServicesCategoryRepository } from "../repositories/danak-services-category.repository"; + +@Injectable() +export class DanakServicesService { + private readonly logger = new Logger(DanakServicesService.name); + constructor(private readonly danakServicesCategoryRepository: DanakServicesCategoryRepository) {} + /********** */ + async createCategory(createDto: CreateCategoryDto) { + this.logger.log(`Creating a new category with title: ${createDto.title}`); + const existCategory = await this.danakServicesCategoryRepository.findOneBy({ title: createDto.title }); + if (existCategory) throw new BadRequestException(CategoryMessage.TITLE_EXIST); + const category = this.danakServicesCategoryRepository.create(createDto); + await this.danakServicesCategoryRepository.save(category); + return { + message: CommonMessage.CREATED, + category, + }; + } + /********** */ +} diff --git a/src/modules/danak-services/repositories/danak-services-category.repository.ts b/src/modules/danak-services/repositories/danak-services-category.repository.ts new file mode 100644 index 0000000..712b1c1 --- /dev/null +++ b/src/modules/danak-services/repositories/danak-services-category.repository.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { DanakServiceCategory } from "../entities/danak-service-category.entity"; + +@Injectable() +export class DanakServicesCategoryRepository extends Repository { + constructor(@InjectRepository(DanakServiceCategory) danakServicesCategoryRepository: Repository) { + super(danakServicesCategoryRepository.target, danakServicesCategoryRepository.manager, danakServicesCategoryRepository.queryRunner); + } +} diff --git a/src/modules/danak-services/repositories/danak-services-image.repository.ts b/src/modules/danak-services/repositories/danak-services-image.repository.ts new file mode 100644 index 0000000..fd7fef5 --- /dev/null +++ b/src/modules/danak-services/repositories/danak-services-image.repository.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { DanakServiceImage } from "../entities/danak-service-image.entity"; + +@Injectable() +export class DanakServicesImageRepository extends Repository { + constructor(@InjectRepository(DanakServiceImage) danakServicesImageRepository: Repository) { + super(danakServicesImageRepository.target, danakServicesImageRepository.manager, danakServicesImageRepository.queryRunner); + } +} diff --git a/src/modules/danak-services/repositories/danak-services.repository.ts b/src/modules/danak-services/repositories/danak-services.repository.ts new file mode 100644 index 0000000..d64bcaf --- /dev/null +++ b/src/modules/danak-services/repositories/danak-services.repository.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { DanakService } from "../entities/danak-service.entity"; + +@Injectable() +export class DanakServicesRepository extends Repository { + constructor(@InjectRepository(DanakService) danakServicesRepository: Repository) { + super(danakServicesRepository.target, danakServicesRepository.manager, danakServicesRepository.queryRunner); + } +} diff --git a/src/modules/tickets/entities/ticket-message.entity.ts b/src/modules/tickets/entities/ticket-message.entity.ts new file mode 100644 index 0000000..29cf2ce --- /dev/null +++ b/src/modules/tickets/entities/ticket-message.entity.ts @@ -0,0 +1,21 @@ +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; +import { BaseEntity } from "../../../common/entities/base.entity"; +import { Ticket } from "./ticket.entity"; +import { User } from "../../users/entities/user.entity"; + +@Entity() +export class TicketMessage extends BaseEntity { + @Column({ type: "text", nullable: false }) + content: string; + + @Column({ type: "varchar", length: 150, nullable: true, default: null }) + attachmentUrl: string; + + @JoinColumn() + @ManyToOne(() => Ticket, (ticket) => ticket.messages, { onDelete: "CASCADE", nullable: false }) + ticket: Ticket; + + @ManyToOne(() => User, (user) => user.ticketMessage, { onDelete: "SET NULL", nullable: true }) + @JoinColumn() + author: User; +} diff --git a/src/modules/tickets/entities/ticket.entity.ts b/src/modules/tickets/entities/ticket.entity.ts index b85e1dd..8d6761f 100644 --- a/src/modules/tickets/entities/ticket.entity.ts +++ b/src/modules/tickets/entities/ticket.entity.ts @@ -1,13 +1,17 @@ -import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from "typeorm"; import { TicketCategory } from "./ticket-category.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { User } from "../../users/entities/user.entity"; import { TicketPriority } from "../enums/ticket-priority.enum"; import { TicketStatus } from "../enums/ticket-status.enum"; +import { TicketMessage } from "./ticket-message.entity"; @Entity() export class Ticket extends BaseEntity { + @Column({ type: "int", generated: "identity", insert: false }) + numericId: number; + @Column({ type: "varchar", length: 150, nullable: false }) title: string; @@ -33,4 +37,7 @@ export class Ticket extends BaseEntity { @JoinColumn() @ManyToOne(() => TicketCategory, (category) => category.tickets, { onDelete: "RESTRICT", nullable: false }) category: TicketCategory; + + @OneToMany(() => TicketMessage, (message) => message.ticket) + messages: TicketMessage[]; } diff --git a/src/modules/tickets/tickets.service.ts b/src/modules/tickets/providers/tickets.service.ts similarity index 100% rename from src/modules/tickets/tickets.service.ts rename to src/modules/tickets/providers/tickets.service.ts diff --git a/src/modules/tickets/repositories/tickets-category.repository.ts b/src/modules/tickets/repositories/tickets-category.repository.ts new file mode 100644 index 0000000..50397eb --- /dev/null +++ b/src/modules/tickets/repositories/tickets-category.repository.ts @@ -0,0 +1,11 @@ +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; +import { TicketCategory } from "../entities/ticket-category.entity"; +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class TicketCategoryRepository extends Repository { + constructor(@InjectRepository(TicketCategory) ticketCategoryRepository: Repository) { + super(ticketCategoryRepository.target, ticketCategoryRepository.manager, ticketCategoryRepository.queryRunner); + } +} diff --git a/src/modules/tickets/repositories/tickets-message.repository.ts b/src/modules/tickets/repositories/tickets-message.repository.ts new file mode 100644 index 0000000..366d0a1 --- /dev/null +++ b/src/modules/tickets/repositories/tickets-message.repository.ts @@ -0,0 +1,11 @@ +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; +import { TicketMessage } from "../entities/ticket-message.entity"; +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class TicketMessagesRepository extends Repository { + constructor(@InjectRepository(TicketMessage) ticketMessagesRepository: Repository) { + super(ticketMessagesRepository.target, ticketMessagesRepository.manager, ticketMessagesRepository.queryRunner); + } +} diff --git a/src/modules/tickets/repositories/tickets.repository.ts b/src/modules/tickets/repositories/tickets.repository.ts new file mode 100644 index 0000000..52d7c3b --- /dev/null +++ b/src/modules/tickets/repositories/tickets.repository.ts @@ -0,0 +1,11 @@ +import { Repository } from "typeorm"; +import { Ticket } from "../entities/ticket.entity"; +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; + +@Injectable() +export class TicketsRepository extends Repository { + constructor(@InjectRepository(Ticket) ticketsRepository: Repository) { + super(ticketsRepository.target, ticketsRepository.manager, ticketsRepository.queryRunner); + } +} diff --git a/src/modules/tickets/tickets.controller.ts b/src/modules/tickets/tickets.controller.ts index 68c1d9b..25ffc64 100644 --- a/src/modules/tickets/tickets.controller.ts +++ b/src/modules/tickets/tickets.controller.ts @@ -1,6 +1,10 @@ import { Controller } from "@nestjs/common"; import { ApiTags } from "@nestjs/swagger"; +// import { TicketsService } from "./providers/tickets.service"; + @Controller("tickets") -@ApiTags("users") -export class TicketsController {} +@ApiTags("Tickets") +export class TicketsController { + // constructor(private readonly ticketsService: TicketsService) {} +} diff --git a/src/modules/tickets/tickets.module.ts b/src/modules/tickets/tickets.module.ts index 5629da4..b3bcdb4 100644 --- a/src/modules/tickets/tickets.module.ts +++ b/src/modules/tickets/tickets.module.ts @@ -4,11 +4,16 @@ import { TypeOrmModule } from "@nestjs/typeorm"; import { TicketCategory } from "./entities/ticket-category.entity"; import { Ticket } from "./entities/ticket.entity"; import { TicketsController } from "./tickets.controller"; -import { TicketsService } from "./tickets.service"; +import { TicketsService } from "./providers/tickets.service"; +import { TicketMessage } from "./entities/ticket-message.entity"; +import { TicketsRepository } from "./repositories/tickets.repository"; +import { TicketCategoryRepository } from "./repositories/tickets-category.repository"; +import { TicketMessagesRepository } from "./repositories/tickets-message.repository"; @Module({ - imports: [TypeOrmModule.forFeature([Ticket, TicketCategory])], - providers: [TicketsService], + imports: [TypeOrmModule.forFeature([Ticket, TicketCategory, TicketMessage])], + providers: [TicketsService, TicketsRepository, TicketCategoryRepository, TicketMessagesRepository], controllers: [TicketsController], + exports: [TypeOrmModule, TicketsService, TicketsRepository, TicketCategoryRepository, TicketMessagesRepository], }) export class TicketsModule {} diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index 0739ab9..53be104 100644 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -1,7 +1,8 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from "typeorm"; +import { Column, Entity, ManyToOne, OneToMany } from "typeorm"; import { Role } from "./role.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; +import { TicketMessage } from "../../tickets/entities/ticket-message.entity"; import { Ticket } from "../../tickets/entities/ticket.entity"; @Entity() @@ -30,10 +31,12 @@ export class User extends BaseEntity { @Column({ type: "varchar", length: 100, unique: true, nullable: false }) nationalCode: string; - @JoinColumn() @ManyToOne(() => Role, { eager: true, onDelete: "RESTRICT", nullable: false }) role: Role; @OneToMany(() => Ticket, (ticket) => ticket.user) tickets: Ticket[]; + + @OneToMany(() => TicketMessage, (ticketMessage) => ticketMessage.author) + ticketMessage: TicketMessage[]; } diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index d776889..d6a3bae 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -3,8 +3,8 @@ import slugify from "slugify"; import { Not } from "typeorm"; import { RoleRepository } from "./roles.repository"; -import { UserRepository } from "./users.repository"; -import { roles } from "../../../../database/seeders/role.seeder"; +import { UserRepository } from "../repositories/users.repository"; +// import { roles } from "../../../../database/seeders/role.seeder"; import { CommonMessage, UserMessage } from "../../../common/enums/message.enum"; import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; import { CheckValidityDTO } from "../DTO/check-validity.dto"; @@ -38,6 +38,7 @@ export class UsersService { /************************************************************ */ async getMe(userId: string) { + this.logger.debug("sss"); const user = await this.userRepository.findOne({ where: { id: userId }, select: this.userSelect }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return { user }; @@ -105,11 +106,11 @@ export class UsersService { } ///****************************************************** */ - async roleSeeder() { - const countRole = await this.roleRepository.count(); - if (countRole > 0) return; - const createdRoles = await this.roleRepository.insert(roles); - this.logger.log({ createdRoles }); - return createdRoles; - } + // async roleSeeder() { + // const countRole = await this.roleRepository.count(); + // if (countRole > 0) return; + // const createdRoles = await this.roleRepository.insert(roles); + // this.logger.log({ createdRoles }); + // return createdRoles; + // } } diff --git a/src/modules/users/providers/users.repository.ts b/src/modules/users/repositories/users.repository.ts similarity index 100% rename from src/modules/users/providers/users.repository.ts rename to src/modules/users/repositories/users.repository.ts diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index e4f2d2f..e23b216 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -4,8 +4,8 @@ import { TypeOrmModule } from "@nestjs/typeorm"; import { Role } from "./entities/role.entity"; import { User } from "./entities/user.entity"; import { RoleRepository } from "./providers/roles.repository"; -import { UserRepository } from "./providers/users.repository"; import { UsersService } from "./providers/users.service"; +import { UserRepository } from "./repositories/users.repository"; import { UsersController } from "./users.controller"; @Module({