diff --git a/src/app.module.ts b/src/app.module.ts index 8fd7828..c860a17 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,7 +14,6 @@ import { UploaderModule } from './modules/uploader/uploader.module'; import { AdminModule } from './modules/admin/admin.module'; // import { CacheService } from './modules/utils/cache.service'; import { ThrottlerModule } from '@nestjs/throttler'; -import { SubmitionModule } from './modules/submition/submition.module'; @Module({ imports: [ @@ -42,7 +41,6 @@ import { SubmitionModule } from './modules/submition/submition.module'; limit: 5, // max requests per window }, ]), - SubmitionModule, ], controllers: [], // providers: [CacheService], diff --git a/src/common/entities/base.entity.ts b/src/common/entities/base.entity.ts index 8d76f08..8d5b529 100755 --- a/src/common/entities/base.entity.ts +++ b/src/common/entities/base.entity.ts @@ -1,5 +1,7 @@ -import { PrimaryKey, Property, sql, OptionalProps } from '@mikro-orm/core'; +import { PrimaryKey, Property, sql, Filter, OptionalProps } from '@mikro-orm/core'; +import { ulid } from 'ulid'; +@Filter({ name: 'notDeleted', cond: { deletedAt: null }, default: true }) export abstract class BaseEntity { [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'deletedAt'; diff --git a/src/modules/admin/admin.module.ts b/src/modules/admin/admin.module.ts index 5c24441..5a245df 100644 --- a/src/modules/admin/admin.module.ts +++ b/src/modules/admin/admin.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { AdminService } from './admin.service'; import { MikroOrmModule } from '@mikro-orm/nestjs'; -import { Admin } from './entities/user.entity'; +import { Admin } from './entities/admin.entity'; import { JwtModule } from '@nestjs/jwt'; @Module({ diff --git a/src/modules/admin/admin.service.ts b/src/modules/admin/admin.service.ts index 3c83480..56280c4 100644 --- a/src/modules/admin/admin.service.ts +++ b/src/modules/admin/admin.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@mikro-orm/nestjs'; import { EntityRepository } from '@mikro-orm/core'; -import { Admin } from './entities/user.entity'; +import { Admin } from './entities/admin.entity'; import { EntityManager } from '@mikro-orm/postgresql'; @Injectable() @@ -16,13 +16,13 @@ export class AdminService { return this.adminRepository.findOne({ phone }); } - async findById(id: number): Promise { + async findById(id: string): Promise { return this.adminRepository.findOne({ id }); } - async create(phone: string): Promise { - const user = this.adminRepository.create({ phone }); - await this.em.persistAndFlush(user); - return user; - } + // async create(phone: string): Promise { + // const user = this.adminRepository.create({ phone }); + // await this.em.persistAndFlush(user); + // return user; + // } } diff --git a/src/modules/admin/entities/admin.entity.ts b/src/modules/admin/entities/admin.entity.ts new file mode 100644 index 0000000..734b07d --- /dev/null +++ b/src/modules/admin/entities/admin.entity.ts @@ -0,0 +1,14 @@ +import { Entity, Property } from '@mikro-orm/core'; +import { BaseEntity } from 'src/common/entities/base.entity'; + +@Entity({ tableName: 'admins' }) +export class Admin extends BaseEntity { + @Property({ nullable: true }) + firstName?: string; + + @Property({ nullable: true }) + lastName?: string; + + @Property({ unique: true }) + phone!: string; +} diff --git a/src/modules/admin/entities/role.entity.ts b/src/modules/admin/entities/role.entity.ts deleted file mode 100644 index 39204d8..0000000 --- a/src/modules/admin/entities/role.entity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Collection, Entity, Enum, OneToMany } from '@mikro-orm/core'; - -import { User } from '../../users/entities/user.entity'; -import { BaseEntity } from '../../../common/entities/base.entity'; -import { RoleEnum } from '../enums/role.enum'; - -@Entity() -export class Role extends BaseEntity { - @Enum({ items: () => RoleEnum, nativeEnumName: 'role_enum', default: RoleEnum.USER }) - name!: RoleEnum; - - @OneToMany(() => User, user => user.role) - users = new Collection(this); -} diff --git a/src/modules/admin/entities/user.entity.ts b/src/modules/admin/entities/user.entity.ts deleted file mode 100644 index c06b4b9..0000000 --- a/src/modules/admin/entities/user.entity.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Entity, PrimaryKey, Property, OptionalProps } from '@mikro-orm/core'; - -@Entity({ tableName: 'admns' }) -export class Admin { - [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt'; - - @PrimaryKey() - id!: number; - - @Property({ nullable: true }) - firstName?: string; - - @Property({ nullable: true }) - lastName?: string; - - @Property({ unique: true }) - phone!: string; - - @Property({ defaultRaw: 'now()', columnType: 'timestamptz' }) - createdAt: Date = new Date(); - - @Property({ onUpdate: () => new Date(), defaultRaw: 'now()', columnType: 'timestamptz' }) - updatedAt: Date = new Date(); -} diff --git a/src/modules/auth/interfaces/IToken-payload.ts b/src/modules/auth/interfaces/IToken-payload.ts index fe727ab..02cd599 100755 --- a/src/modules/auth/interfaces/IToken-payload.ts +++ b/src/modules/auth/interfaces/IToken-payload.ts @@ -1,9 +1,5 @@ -import { RoleEnum } from "../../users/enums/role.enum"; - export interface ILocalTokenPayload { id: string; - role: RoleEnum; - wildduckUserId: string; } export interface IConsoleTokenPayload { diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts index 5dc3758..2567bbb 100644 --- a/src/modules/auth/services/auth.service.ts +++ b/src/modules/auth/services/auth.service.ts @@ -55,7 +55,7 @@ export class AuthService { const user = await this.userService.findOrCreateByPhone(phone); - const accessToken = await this.issueToken(user.id.toString(), AuthEntityType.USER); + const accessToken = await this.issueToken(user.id, AuthEntityType.USER); return { success: true, message: 'User registered successfully!', accessToken }; } diff --git a/src/modules/auth/services/tokens.service.ts b/src/modules/auth/services/tokens.service.ts index 1360476..10913d1 100755 --- a/src/modules/auth/services/tokens.service.ts +++ b/src/modules/auth/services/tokens.service.ts @@ -24,7 +24,6 @@ export class TokensService { return this.generateAccessAndRefreshToken( { id: user.id, - role: user.role.name, }, em, ); @@ -96,21 +95,21 @@ export class TokensService { RefreshToken, { token: oldRefreshToken }, { - populate: ['user', 'user.role'], + populate: ['user'], }, ); if (!token) throw new UnauthorizedException(AuthMessage.INVALID_REFRESH_TOKEN); if (dayjs(token.expiresAt).isBefore(dayjs())) { - await entityManager.remove(token); + entityManager.remove(token); await entityManager.flush(); await entityManager.commit(); throw new UnauthorizedException(AuthMessage.REFRESH_TOKEN_EXPIRED); } // Remove the old token - await entityManager.remove(token); + entityManager.remove(token); // Generate new tokens within the same transaction const tokens = await this.generateTokens(token.user, entityManager); @@ -148,7 +147,7 @@ export class TokensService { } } - private async generateRefreshToken() { + private generateRefreshToken() { return randomBytes(32).toString('hex'); } } diff --git a/src/modules/submition/dto/create-submission.dto.ts b/src/modules/submition/dto/create-submission.dto.ts deleted file mode 100644 index 11bbd12..0000000 --- a/src/modules/submition/dto/create-submission.dto.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { IsString, IsNotEmpty, IsDate, IsOptional } from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; - -export class CreateSubmissioncDto { - @ApiPropertyOptional({ example: 'صداو سیما', description: 'دستگاه همکار : مثلا صدا و سیما' }) - @IsOptional() - @IsString() - cooperator: string; - - @ApiPropertyOptional({ example: 'ملی', description: 'ملی|استانی|' }) - @IsOptional() - @IsString() - cooperationLevel: string; - - @ApiProperty({ - example: '2025-11-03T12:30:00.000Z', - description: 'The date and time of the submission (ISO 8601 format).', - }) - @IsNotEmpty({ message: 'Submission Date is required.' }) - @IsDate({ message: 'Submission Date must be a valid ISO 8601 date.' }) - @Type(() => Date) - submissionDate: Date; - - @ApiProperty({ example: 'مقاله', description: ' ' }) - @IsNotEmpty() - @IsString() - submissionType: string; -} diff --git a/src/modules/submition/dto/find-submitions.dto.ts b/src/modules/submition/dto/find-submitions.dto.ts deleted file mode 100644 index ed3e545..0000000 --- a/src/modules/submition/dto/find-submitions.dto.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { IsOptional, IsString, IsNumber, Min, IsEnum, IsIn } from 'class-validator'; -import { Type } from 'class-transformer'; -import { ApiPropertyOptional } from '@nestjs/swagger'; -import { Submition, SubmitionStatus } from '../entities/submition.entity'; - -const sortOrderOptions = ['asc', 'desc'] as const; -type SortOrder = (typeof sortOrderOptions)[number]; - -export class FindSubmitionsDto { - /** - * The page number to retrieve. - * @default 1 - */ - @ApiPropertyOptional({ - description: 'The page number to retrieve.', - type: Number, - default: 1, - minimum: 1, - }) - @IsOptional() - @IsNumber() - @Min(1) - @Type(() => Number) - page?: number = 1; - - /** - * The number of items per page. - * @default 10 - */ - @ApiPropertyOptional({ - description: 'The number of items per page.', - type: Number, - default: 10, - minimum: 1, - }) - @IsOptional() - @IsNumber() - @Min(1) - @Type(() => Number) - limit?: number = 10; - - /** - * A general search term to filter by. - * Searches firstName, lastName, phone, and nationalCode. - */ - @ApiPropertyOptional({ - description: 'A general search term (firstName, lastName, phone, nationalCode).', - type: String, - example: 'John Doe', - }) - @IsOptional() - @IsString() - search?: string; - - /** - * Filter by a specific user status. - */ - @ApiPropertyOptional({ - description: 'Filter by a specific user status.', - enum: SubmitionStatus, // Use the actual enum - example: SubmitionStatus.finished, - }) - @IsOptional() - @IsEnum(SubmitionStatus) - status?: SubmitionStatus; - - @ApiPropertyOptional({ - description: 'Filter by a cooperationLevel.', - example: 'ملی', - }) - @IsOptional() - @IsString() - cooperationLevel?: string; - - @ApiPropertyOptional({ - description: 'Filter by a submissionType.', - example: 'مقاله', - }) - @IsOptional() - @IsString() - submissionType?: string; - - /** - * The field to sort the results by. - * @default "user" - */ - @ApiPropertyOptional({ - description: 'The field to sort the results by.', - type: String, - default: 'user', - example: 'user', - }) - @IsOptional() - @IsString() - orderBy?: keyof Submition = 'user'; - - /** - * The direction to sort the results. - * @default "desc" - */ - @ApiPropertyOptional({ - description: 'The direction to sort the results.', - enum: sortOrderOptions, // Use the constant array - default: 'desc', - }) - @IsOptional() - @IsIn(sortOrderOptions) - order?: SortOrder = 'desc'; -} diff --git a/src/modules/submition/dto/update-docs.dto.ts b/src/modules/submition/dto/update-docs.dto.ts deleted file mode 100644 index 49abc77..0000000 --- a/src/modules/submition/dto/update-docs.dto.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - IsString, - IsNotEmpty, - IsArray, - ArrayMinSize, - Validate, - ValidatorConstraint, - ValidatorConstraintInterface, -} from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -@ValidatorConstraint({ name: 'MaxWordCount', async: false }) -export class MaxWordCount implements ValidatorConstraintInterface { - validate(text: string) { - if (typeof text !== 'string') return false; - const wordCount = text.trim().split(/\s+/).filter(Boolean).length; - return wordCount <= 10000; - } - - defaultMessage() { - return 'Submission description must not exceed 1000 words.'; - } -} - -export class UpdateDocsDto { - @ApiProperty({ - example: 'توضیحاتی در مورد مدارک ارسالی و هدف از ارسال.', - description: 'Detailed description or notes regarding the documents being submitted (maximum 1000 words).', - }) - @IsNotEmpty({ message: 'Submission description is required.' }) - @IsString({ message: 'Submission description must be a string.' }) - @Validate(MaxWordCount) - submissionDesc: string; - - @ApiProperty({ - example: ['https://example.com/doc1.pdf', 'https://example.com/doc2.jpg'], - description: 'An array of URLs pointing to the submitted documents.', - type: [String], - }) - @IsNotEmpty({ message: 'Document URLs are required.' }) - @IsArray({ message: 'Document URLs must be an array.' }) - @ArrayMinSize(1, { message: 'At least one document URL is required.' }) - @IsString({ each: true, message: 'Each document URL must be a valid string.' }) - docsUrl: string[]; -} diff --git a/src/modules/submition/dto/update-submission.dto.ts b/src/modules/submition/dto/update-submission.dto.ts deleted file mode 100644 index 6eec325..0000000 --- a/src/modules/submition/dto/update-submission.dto.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { IsString, IsNotEmpty, IsDate, IsOptional } from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; - -export class UpdateSubmissioncDto { - @ApiPropertyOptional({ example: 'صداو سیما', description: 'دستگاه همکار : مثلا صدا و سیما' }) - @IsOptional() - @IsString() - cooperator: string; - - @ApiPropertyOptional({ example: 'ملی', description: 'ملی|استانی|' }) - @IsOptional() - @IsString() - cooperationLevel: string; - - @ApiProperty({ - example: '2025-11-03T12:30:00.000Z', - description: 'The date and time of the submission (ISO 8601 format).', - }) - @IsNotEmpty({ message: 'Submission Date is required.' }) - @IsDate({ message: 'Submission Date must be a valid ISO 8601 date.' }) - @Type(() => Date) - submissionDate: Date; - - @ApiProperty({ example: 'مقاله', description: ' ' }) - @IsNotEmpty() - @IsString() - submissionType: string; -} diff --git a/src/modules/submition/entities/submition.entity.ts b/src/modules/submition/entities/submition.entity.ts deleted file mode 100644 index c3e2e27..0000000 --- a/src/modules/submition/entities/submition.entity.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Entity, Enum, PrimaryKey, Property, OptionalProps, ManyToOne } from '@mikro-orm/core'; -import { User } from '../../users/entities/user.entity'; - -export enum SubmitionStatus { - finished = 'تکمیل', - pending = 'ناقص', -} - -@Entity({ tableName: 'submition' }) -export class Submition { - [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt'; - - @ManyToOne(() => User) - user!: User; - - @PrimaryKey() - id!: number; - - @Enum({ items: () => SubmitionStatus, nullable: true, default: SubmitionStatus.pending }) - status?: SubmitionStatus; - - @Property({ nullable: true }) - submissionType?: string; - - @Property({ nullable: true }) - cooperator?: string; - - @Property({ nullable: true }) - cooperationLevel?: string; - - @Property({ nullable: true, columnType: 'text' }) - submissionDesc?: string; - - @Property({ type: 'text[]', nullable: true }) - docsUrl?: string[]; - - @Property({ columnType: 'timestamptz', nullable: true }) - submissionDate?: Date; - - @Property({ defaultRaw: 'now()', columnType: 'timestamptz' }) - createdAt: Date = new Date(); - - @Property({ onUpdate: () => new Date(), defaultRaw: 'now()', columnType: 'timestamptz' }) - updatedAt: Date = new Date(); - - @Property({ nullable: true, columnType: 'timestamptz' }) - deletedAt?: Date; -} diff --git a/src/modules/submition/submition.controller.ts b/src/modules/submition/submition.controller.ts deleted file mode 100644 index b0da0d9..0000000 --- a/src/modules/submition/submition.controller.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { - Controller, - Get, - Patch, - Body, - Req, - UseGuards, - Query, - ValidationPipe, - Post, - Param, - ParseIntPipe, - Delete, -} from '@nestjs/common'; -import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody, ApiParam } from '@nestjs/swagger'; -import { AuthGuard, type AuthRequest } from 'src/modules/auth/guards/auth.guard'; -import { SubmitionService } from './submition.service'; -import { UpdateSubmissioncDto } from './dto/update-submission.dto'; -import { UpdateDocsDto } from './dto/update-docs.dto'; -import { AdminAuthGuard } from 'src/modules/auth/guards/auth.admin.guard'; -import { FindSubmitionsDto } from './dto/find-submitions.dto'; -import { CreateSubmissioncDto } from './dto/create-submission.dto'; -import { SubmitionStatus } from './entities/submition.entity'; - -@ApiTags('submition') -@Controller('submition') -export class SubmitionController { - constructor(private readonly submitionService: SubmitionService) {} - - @UseGuards(AuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'Get User submissions' }) - @Get('/') - async getUserSbmitions(@Req() req: AuthRequest) { - const userId = req.user.sub; - const submitions = await this.submitionService.findByUserId(+userId); - return { - message: `User submissions `, - submitions, - }; - } - - @UseGuards(AuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'Get single submission' }) - @ApiParam({ name: 'id', description: 'The ID of the submission ', type: Number }) - @Get(':id') - async getSbmition(@Req() req: AuthRequest, @Param('id', ParseIntPipe) id: number) { - const userId = req.user.sub; - const submition = await this.submitionService.findById(id, +userId); - return { - message: `Success `, - submition, - }; - } - - @UseGuards(AuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'Create submission :first step of creation' }) - @ApiBody({ type: CreateSubmissioncDto }) - @Post('/') - async createSubmition(@Req() req: AuthRequest, @Body() dto: CreateSubmissioncDto) { - const userId = req.user.sub; - const submition = await this.submitionService.create(+userId, dto); - return { - message: `Created submission `, - userId, - submition, - }; - } - - @UseGuards(AuthGuard) - @ApiBearerAuth() - @ApiParam({ name: 'id', description: 'The ID of the submission to update', type: Number }) - @ApiOperation({ summary: 'Update the submission : first step of update' }) - @ApiBody({ type: UpdateSubmissioncDto }) - @Patch(':id') - async updateUserSubmissionType( - @Req() req: AuthRequest, - @Param('id', ParseIntPipe) id: number, - @Body() dto: UpdateSubmissioncDto, - ) { - const userId = req.user.sub; - const submition = await this.submitionService.updateSubmition(id, +userId, dto); - return { - message: `PATCH request: Updating submission type for user done`, - userId, - submition, - }; - } - - @UseGuards(AuthGuard) - @ApiBearerAuth() - @ApiParam({ name: 'id', description: 'The ID of the submission to update', type: Number }) - @ApiOperation({ summary: 'Update the user document/file reference' }) - @ApiBody({ type: UpdateDocsDto }) - @Patch(':id/doc') - async updateUserDoc(@Req() req: AuthRequest, @Param('id', ParseIntPipe) id: number, @Body() dto: UpdateDocsDto) { - const userId = req.user.sub; - const submition = await this.submitionService.updateSubmition(id, +userId, { - ...dto, - status: SubmitionStatus.finished, - }); - return { - message: `PATCH request: Updating doc field for user ${userId} `, - userId, - submition, - }; - } - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'submissions' }) - @Get('/admin/submissions') - async findAll( - @Query(new ValidationPipe({ transform: true, whitelist: true })) - query: FindSubmitionsDto, - ) { - return this.submitionService.findAll(query); - } - - @UseGuards(AuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'Delete submission' }) - @ApiParam({ name: 'id', description: 'The ID of the submission ', type: Number }) - @Delete(':id') - async deleteSbmition(@Req() req: AuthRequest, @Param('id', ParseIntPipe) id: number) { - const userId = req.user.sub; - const submition = await this.submitionService.deleteSubmition(id, +userId); - return { - message: `submition deleted successfully `, - submition, - }; - } -} diff --git a/src/modules/submition/submition.module.ts b/src/modules/submition/submition.module.ts deleted file mode 100644 index 1d8bbcf..0000000 --- a/src/modules/submition/submition.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SubmitionService } from './submition.service'; -import { SubmitionController } from './submition.controller'; -import { MikroOrmModule } from '@mikro-orm/nestjs'; -import { JwtModule } from '@nestjs/jwt'; -import { Submition } from './entities/submition.entity'; -// import { AuthModule } from '../auth/auth.module'; - -@Module({ - providers: [SubmitionService], - controllers: [SubmitionController], - imports: [MikroOrmModule.forFeature([Submition]), JwtModule], - exports: [SubmitionService], -}) -export class SubmitionModule {} diff --git a/src/modules/submition/submition.service.ts b/src/modules/submition/submition.service.ts deleted file mode 100644 index aff8ead..0000000 --- a/src/modules/submition/submition.service.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@mikro-orm/nestjs'; -import { EntityRepository, FilterQuery } from '@mikro-orm/core'; -import { SubmitionStatus, Submition } from './entities/submition.entity'; -import { EntityManager } from '@mikro-orm/postgresql'; -import { UpdateSubmissioncDto } from './dto/update-submission.dto'; -import { UpdateDocsDto } from './dto/update-docs.dto'; -import { FindSubmitionsDto } from './dto/find-submitions.dto'; -import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; -import { CreateSubmissioncDto } from './dto/create-submission.dto'; - -@Injectable() -export class SubmitionService { - constructor( - @InjectRepository(Submition) - private readonly submitionRepository: EntityRepository, - private readonly em: EntityManager, - ) {} - - async updateSubmition( - submitionId: number, - userId: number, - dto: UpdateSubmissioncDto | (UpdateDocsDto & { status: SubmitionStatus }), - ): Promise { - const sub = await this.em.findOneOrFail(Submition, submitionId, { populate: ['user'] }).catch(() => { - throw new NotFoundException(`submition with ID ${submitionId} not found.`); - }); - - if (sub.user.id !== userId) { - throw new ForbiddenException(`User is not the owner of submition ID ${submitionId}.`); - } - - this.em.assign(sub, dto); - - await this.em.flush(); - - return sub; - } - - async findById(submitionId: number, userId: number): Promise { - return this.submitionRepository.findOne({ id: submitionId, user: { id: userId }, deletedAt: null }); - } - - async findByUserId(userId: number): Promise { - return this.submitionRepository.find({ user: userId, deletedAt: null }); - } - - async create(userId: number, dto: CreateSubmissioncDto): Promise { - const submition = this.submitionRepository.create({ ...dto, user: userId }); - await this.em.persistAndFlush(submition); - return submition; - } - - async findAll(dto: FindSubmitionsDto): Promise> { - const { - page = 1, - limit = 10, - search, - status, - cooperationLevel, - submissionType, - orderBy = 'id', - order = 'desc', - } = dto; - - // 1. Calculate pagination - const offset = (page - 1) * limit; - - // 2. Build the 'where' filter query - const where: FilterQuery = { - deletedAt: null, - }; - - // 3. Add exact-match filters (if provided) - if (status) { - where.status = status; - } - - if (submissionType) { - where.submissionType = submissionType; - } - - if (cooperationLevel) { - where.cooperationLevel = cooperationLevel; - } - - // 4. Add 'search' logic (case-insensitive) - if (search) { - const searchPattern = `%${search}%`; - where.$or = [ - { submissionDesc: { $ilike: searchPattern } }, - { cooperator: { $ilike: searchPattern } }, - { user: { firstName: { $ilike: searchPattern } } }, - { user: { lastName: { $ilike: searchPattern } } }, - { user: { nationalCode: { $ilike: searchPattern } } }, - { user: { personalCode: { $ilike: searchPattern } } }, - { user: { phone: { $ilike: searchPattern } } }, - ]; - } - - // 5. Execute the query using findAndCount - const [sumitions, total] = await this.submitionRepository.findAndCount(where, { - limit, - offset, - orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' }, - populate: ['user'], - }); - - // 6. Calculate total pages - const totalPages = Math.ceil(total / limit); - - // 7. Return the paginated result - return { - data: sumitions, - meta: { - total, - page, - limit, - totalPages, - }, - }; - } - - async deleteSubmition(submitionId: number, userId: number): Promise { - const submission = await this.em.findOneOrFail(Submition, submitionId, { populate: ['user'] }).catch(() => { - throw new NotFoundException(`Submition with ID ${submitionId} not found.`); - }); - - // Ownership Check (same as before) - if (submission.user.id !== userId) { - throw new ForbiddenException(`User with ID ${userId} is not authorized to delete submition ID ${submitionId}.`); - } - - // Execute the Soft Delete: Update the timestamp - submission.deletedAt = new Date(); - await this.em.flush(); - - return submission; - } -} diff --git a/src/modules/users/entities/refresh-token.entity.ts b/src/modules/users/entities/refresh-token.entity.ts index f7231d2..68272ad 100644 --- a/src/modules/users/entities/refresh-token.entity.ts +++ b/src/modules/users/entities/refresh-token.entity.ts @@ -1,17 +1,16 @@ -import { Entity, ManyToOne, Opt, Property } from "@mikro-orm/core"; - -import { User } from "./user.entity"; -import { BaseEntity } from "../../../common/entities/base.entity"; +import { Entity, ManyToOne, Opt, Property } from '@mikro-orm/core'; +import { User } from './user.entity'; +import { BaseEntity } from 'src/common/entities/base.entity'; @Entity() export class RefreshToken extends BaseEntity { - @Property({ type: "varchar", length: 255 }) + @Property({ type: 'varchar', length: 255 }) token!: string; - @ManyToOne(() => User, { deleteRule: "cascade" }) + @ManyToOne(() => User, { deleteRule: 'cascade' }) user!: User; - @Property({ type: "timestamptz" }) + @Property({ type: 'timestamptz' }) expiresAt!: Date; @Property({ default: false }) diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index ea50518..bf671b9 100644 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -1,19 +1,6 @@ -import { Entity, Enum, Property, Filter, BaseEntity } from '@mikro-orm/core'; +import { Entity, Property, BaseEntity, OneToMany, Collection } from '@mikro-orm/core'; +import { RefreshToken } from './refresh-token.entity'; -export enum EducationLevel { - diploma = 'دیپلم', - associate = 'کاردانی', - bachelor = 'کارشناسی', - master = 'کارشناسی ارشد', - phd = 'دکتری', -} - -export enum UserStatus { - finished = 'finished', - pending = 'pending', -} - -@Filter({ name: 'notDeleted', cond: { deletedAt: null }, default: true }) @Entity({ tableName: 'users' }) export class User extends BaseEntity { @Property({ nullable: true }) @@ -25,10 +12,9 @@ export class User extends BaseEntity { @Property({ unique: true }) phone!: string; - @Enum({ - items: () => UserStatus, - nullable: true, - default: UserStatus.pending, - }) - status?: UserStatus; + @Property({ default: true }) + isActive: boolean = true; + + @OneToMany(() => RefreshToken, token => token.user) + refreshTokens = new Collection(this); } diff --git a/src/modules/users/user.service.ts b/src/modules/users/user.service.ts index 0ff9493..1313df9 100644 --- a/src/modules/users/user.service.ts +++ b/src/modules/users/user.service.ts @@ -1,7 +1,7 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@mikro-orm/nestjs'; import { EntityRepository, FilterQuery } from '@mikro-orm/core'; -import { User, UserStatus } from './entities/user.entity'; +import { User } from './entities/user.entity'; import { EntityManager } from '@mikro-orm/postgresql'; import { UpdateUserDto } from './dto/update-user.dto'; import { FindUsersDto } from './dto/find-user.dto'; @@ -28,23 +28,23 @@ export class UserService { return user; } - async updateUser(userId: number, dto: UpdateUserDto): Promise { - const user = await this.em.findOneOrFail(User, userId, {}).catch(() => { - throw new NotFoundException(`User with ID ${userId} not found.`); - }); + // async updateUser(userId: string, dto: UpdateUserDto): Promise { + // const user = await this.em.findOneOrFail(User, userId, {}).catch(() => { + // throw new NotFoundException(`User with ID ${userId} not found.`); + // }); - this.em.assign(user, { ...dto, status: UserStatus.finished }); + // this.em.assign(user, { ...dto }); - await this.em.flush(); + // await this.em.flush(); - return user; - } + // return user; + // } async findByPhone(phone: string): Promise { return this.userRepository.findOne({ phone }); } - async findById(id: number): Promise { + async findById(id: string): Promise { return this.userRepository.findOne({ id }); } @@ -55,7 +55,7 @@ export class UserService { } async findAll(dto: FindUsersDto): Promise> { - const { page = 1, limit = 10, search, education, orderBy = 'createdAt', order = 'desc' } = dto; + const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc' } = dto; // 1. Calculate pagination const offset = (page - 1) * limit; @@ -63,10 +63,6 @@ export class UserService { // 2. Build the 'where' filter query const where: FilterQuery = {}; - if (education) { - where.education = education; - } - // 4. Add 'search' logic (case-insensitive) if (search) { const searchPattern = `%${search}%`; @@ -75,8 +71,6 @@ export class UserService { { firstName: { $ilike: searchPattern } }, { lastName: { $ilike: searchPattern } }, { phone: { $ilike: searchPattern } }, - { nationalCode: { $ilike: searchPattern } }, - { personalCode: { $ilike: searchPattern } }, ]; }