user
This commit is contained in:
+2
-2
@@ -2,7 +2,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { Module } from '@nestjs/common';
|
||||
import dataBaseConfig from './config/mikro-orm.config';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { UserModule } from './modules/user/user.module';
|
||||
import { UserModule } from './modules/users/user.module';
|
||||
// import { CacheModule } from '@nestjs/cache-manager';
|
||||
// import { cacheConfig } from './config/cache.config';
|
||||
import { UtilsModule } from './modules/utils/utils.module';
|
||||
@@ -42,7 +42,7 @@ import { SubmitionModule } from './modules/submition/submition.module';
|
||||
limit: 5, // max requests per window
|
||||
},
|
||||
]),
|
||||
SubmitionModule
|
||||
SubmitionModule,
|
||||
],
|
||||
controllers: [],
|
||||
// providers: [CacheService],
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
import { PrimaryKey, Property, sql, OptionalProps } from '@mikro-orm/core';
|
||||
|
||||
export abstract class BaseEntity {
|
||||
[OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'deletedAt';
|
||||
|
||||
@PrimaryKey({ type: 'string', columnType: 'char(26)' })
|
||||
id: string = ulid();
|
||||
|
||||
@Property({ default: sql.now(), type: 'timestamptz' })
|
||||
createdAt: Date = new Date();
|
||||
|
||||
@Property({
|
||||
onUpdate: () => new Date(),
|
||||
defaultRaw: 'now()',
|
||||
columnType: 'timestamptz',
|
||||
})
|
||||
updatedAt: Date = new Date();
|
||||
|
||||
@Property({ nullable: true })
|
||||
deletedAt?: Date;
|
||||
}
|
||||
+499
-499
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
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<User>(this);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Controller, Post, Body } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { RequestOtpDto } from './dto/request-otp.dto';
|
||||
import { ApiTags, ApiOperation, ApiBody, ApiResponse } from '@nestjs/swagger';
|
||||
import { VerifyOtpDto } from './dto/verify-otp.dto copy';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { UtilsModule } from '../utils/utils.module';
|
||||
import { UserModule } from '../user/user.module';
|
||||
import { UserModule } from '../users/user.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { AdminModule } from '../admin/admin.module';
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { RoleEnum } from "../../users/enums/role.enum";
|
||||
|
||||
export interface ILocalTokenPayload {
|
||||
id: string;
|
||||
role: RoleEnum;
|
||||
wildduckUserId: string;
|
||||
}
|
||||
|
||||
export interface IConsoleTokenPayload {
|
||||
id: string;
|
||||
permissions?: string[];
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
export interface ITokenPayload extends ILocalTokenPayload, IConsoleTokenPayload {}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Injectable, BadRequestException, UnauthorizedException, NotFoundException } from '@nestjs/common';
|
||||
import { RequestOtpDto } from './dto/request-otp.dto';
|
||||
import { CacheService } from '../utils/cache.service';
|
||||
import { SmsService } from '../utils/sms.service';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { RequestOtpDto } from '../dto/request-otp.dto';
|
||||
import { CacheService } from '../../utils/cache.service';
|
||||
import { SmsService } from '../../utils/sms.service';
|
||||
import { UserService } from '../../users/user.service';
|
||||
import { randomInt } from 'crypto';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { AdminService } from '../admin/admin.service';
|
||||
import { AuthEntityType } from 'src/common/guards/auth.guard';
|
||||
import { AdminService } from '../../admin/admin.service';
|
||||
import { AuthEntityType } from 'src/modules/auth/guards/auth.guard';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { BadRequestException, Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { AuthMessage, UserMessage } from '../../../common/enums/message.enum';
|
||||
import { RefreshToken } from '../../users/entities/refresh-token.entity';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { ITokenPayload } from '../interfaces/IToken-payload';
|
||||
|
||||
@Injectable()
|
||||
export class TokensService {
|
||||
private readonly logger = new Logger(TokensService.name);
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly em: EntityManager,
|
||||
) {}
|
||||
|
||||
async generateTokens(user: User, em?: EntityManager) {
|
||||
return this.generateAccessAndRefreshToken(
|
||||
{
|
||||
id: user.id,
|
||||
role: user.role.name,
|
||||
},
|
||||
em,
|
||||
);
|
||||
}
|
||||
|
||||
private async generateAccessAndRefreshToken(payload: ITokenPayload, em?: EntityManager) {
|
||||
const accessExpire = this.configService.getOrThrow<number>('ACCESS_TOKEN_EXPIRE');
|
||||
const refreshExpire = this.configService.getOrThrow<number>('REFRESH_TOKEN_EXPIRE');
|
||||
|
||||
const accessToken = await this.jwtService.signAsync(payload, { expiresIn: `${accessExpire}m` });
|
||||
const refreshToken = await this.generateRefreshToken();
|
||||
|
||||
await this.storeRefreshToken(payload.id, refreshToken, em);
|
||||
|
||||
return {
|
||||
accessToken: { token: accessToken, expire: dayjs().add(accessExpire, 'minute').valueOf() },
|
||||
refreshToken: { token: refreshToken, expire: dayjs().add(refreshExpire, 'day').valueOf() },
|
||||
};
|
||||
}
|
||||
|
||||
async storeRefreshToken(userId: string, refreshToken: string, em?: EntityManager) {
|
||||
const entityManager = em || this.em.fork();
|
||||
const isExternalTransaction = !!em && em.isInTransaction();
|
||||
|
||||
try {
|
||||
// Only start transaction if no external EntityManager with transaction is provided
|
||||
if (!isExternalTransaction && !entityManager.isInTransaction()) {
|
||||
await entityManager.begin();
|
||||
}
|
||||
|
||||
const refreshExpire = this.configService.getOrThrow<number>('REFRESH_TOKEN_EXPIRE');
|
||||
const expiresAt = dayjs().add(refreshExpire, 'day').toDate();
|
||||
|
||||
const user = await entityManager.findOne(User, { id: userId });
|
||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||
|
||||
const token = entityManager.create(RefreshToken, {
|
||||
token: refreshToken,
|
||||
user,
|
||||
expiresAt,
|
||||
});
|
||||
|
||||
// Use persist instead of persistAndFlush when in transaction
|
||||
if (isExternalTransaction) {
|
||||
await entityManager.persist(token);
|
||||
} else {
|
||||
await entityManager.persistAndFlush(token);
|
||||
if (entityManager.isInTransaction()) {
|
||||
await entityManager.commit();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
// Only rollback if we started the transaction
|
||||
if (!isExternalTransaction && entityManager.isInTransaction()) {
|
||||
await entityManager.rollback();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async refreshToken(oldRefreshToken: string) {
|
||||
const entityManager = this.em.fork();
|
||||
|
||||
try {
|
||||
await entityManager.begin();
|
||||
|
||||
const token = await entityManager.findOne(
|
||||
RefreshToken,
|
||||
{ token: oldRefreshToken },
|
||||
{
|
||||
populate: ['user', 'user.role'],
|
||||
},
|
||||
);
|
||||
|
||||
if (!token) throw new UnauthorizedException(AuthMessage.INVALID_REFRESH_TOKEN);
|
||||
|
||||
if (dayjs(token.expiresAt).isBefore(dayjs())) {
|
||||
await entityManager.remove(token);
|
||||
await entityManager.flush();
|
||||
await entityManager.commit();
|
||||
throw new UnauthorizedException(AuthMessage.REFRESH_TOKEN_EXPIRED);
|
||||
}
|
||||
|
||||
// Remove the old token
|
||||
await entityManager.remove(token);
|
||||
|
||||
// Generate new tokens within the same transaction
|
||||
const tokens = await this.generateTokens(token.user, entityManager);
|
||||
|
||||
// Commit the transaction
|
||||
await entityManager.flush();
|
||||
await entityManager.commit();
|
||||
|
||||
return tokens;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
if (entityManager.isInTransaction()) {
|
||||
await entityManager.rollback();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async invalidateRefreshToken(userId: string) {
|
||||
const entityManager = this.em.fork();
|
||||
|
||||
try {
|
||||
await entityManager.begin();
|
||||
|
||||
await entityManager.nativeDelete(RefreshToken, { user: { id: userId } });
|
||||
this.logger.log(`Successfully deleted all refresh tokens for user ${userId}`);
|
||||
|
||||
await entityManager.commit();
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
if (entityManager.isInTransaction()) {
|
||||
await entityManager.rollback();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async generateRefreshToken() {
|
||||
return randomBytes(32).toString('hex');
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Entity, Enum, PrimaryKey, Property, OptionalProps, ManyToOne } from '@mikro-orm/core';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
|
||||
export enum SubmitionStatus {
|
||||
finished = 'تکمیل',
|
||||
|
||||
@@ -13,11 +13,11 @@ import {
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody, ApiParam } from '@nestjs/swagger';
|
||||
import { AuthGuard, type AuthRequest } from 'src/common/guards/auth.guard';
|
||||
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/common/guards/auth.admin.guard';
|
||||
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';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ApiBody, ApiConsumes, ApiOperation, ApiBearerAuth } from '@nestjs/swagg
|
||||
|
||||
import { UploadMultipleFileDto, UploadSingleFileDto } from './DTO/upload-file.dto';
|
||||
import { UploaderService } from './providers/uploader.service';
|
||||
import { AuthGuard } from 'src/common/guards/auth.guard';
|
||||
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
|
||||
|
||||
@Controller('uploader')
|
||||
export class UploaderController {
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { Entity, Enum, PrimaryKey, Property, OptionalProps, OneToMany, Collection } from '@mikro-orm/core';
|
||||
import { Submition } from '../../submition/entities/submition.entity';
|
||||
|
||||
export enum EducationLevel {
|
||||
diploma = 'دیپلم',
|
||||
associate = 'کاردانی',
|
||||
bachelor = 'کارشناسی',
|
||||
master = 'کارشناسی ارشد',
|
||||
phd = 'دکتری',
|
||||
}
|
||||
|
||||
export enum UserStatus {
|
||||
finished = 'finished',
|
||||
pending = 'pending',
|
||||
}
|
||||
|
||||
@Entity({ tableName: 'users' })
|
||||
export class User {
|
||||
[OptionalProps]?: 'id' | 'createdAt' | 'updatedAt';
|
||||
|
||||
@OneToMany(() => Submition, submition => submition.user)
|
||||
submissions = new Collection<Submition>(this);
|
||||
|
||||
@PrimaryKey()
|
||||
id!: number;
|
||||
|
||||
@Property({ nullable: true })
|
||||
firstName?: string;
|
||||
|
||||
@Property({ nullable: true })
|
||||
lastName?: string;
|
||||
|
||||
@Property({ nullable: true })
|
||||
fatherName?: string;
|
||||
|
||||
@Property({ unique: true })
|
||||
phone!: string;
|
||||
|
||||
@Property({ nullable: true })
|
||||
isMale?: boolean;
|
||||
|
||||
@Property({ unique: true, nullable: true })
|
||||
nationalCode?: string;
|
||||
|
||||
@Property({ nullable: true })
|
||||
personalCode?: string;
|
||||
|
||||
@Property({ nullable: true })
|
||||
hireType?: string;
|
||||
|
||||
@Property({ nullable: true })
|
||||
workLocation?: string;
|
||||
|
||||
@Property({ default: 0, nullable: true })
|
||||
workExperienceYear?: number;
|
||||
|
||||
@Enum({ items: () => EducationLevel, nullable: true })
|
||||
education?: EducationLevel;
|
||||
|
||||
@Enum({ items: () => UserStatus, nullable: true, default: UserStatus.pending })
|
||||
status?: UserStatus;
|
||||
|
||||
@Property({ defaultRaw: 'now()', columnType: 'timestamptz' })
|
||||
createdAt: Date = new Date();
|
||||
|
||||
@Property({ onUpdate: () => new Date(), defaultRaw: 'now()', columnType: 'timestamptz' })
|
||||
updatedAt: Date = new Date();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Entity, ManyToOne, Opt, Property } from "@mikro-orm/core";
|
||||
|
||||
import { User } from "./user.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
|
||||
@Entity()
|
||||
export class RefreshToken extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 255 })
|
||||
token!: string;
|
||||
|
||||
@ManyToOne(() => User, { deleteRule: "cascade" })
|
||||
user!: User;
|
||||
|
||||
@Property({ type: "timestamptz" })
|
||||
expiresAt!: Date;
|
||||
|
||||
@Property({ default: false })
|
||||
isRevoked: boolean & Opt;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Entity, Enum, Property, Filter, BaseEntity } from '@mikro-orm/core';
|
||||
|
||||
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 })
|
||||
firstName?: string;
|
||||
|
||||
@Property({ nullable: true })
|
||||
lastName?: string;
|
||||
|
||||
@Property({ unique: true })
|
||||
phone!: string;
|
||||
|
||||
@Enum({
|
||||
items: () => UserStatus,
|
||||
nullable: true,
|
||||
default: UserStatus.pending,
|
||||
})
|
||||
status?: UserStatus;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Controller, Get, Patch, Body, Req, UseGuards, Query, ValidationPipe } from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody } from '@nestjs/swagger';
|
||||
import { AuthGuard, type AuthRequest } from 'src/common/guards/auth.guard';
|
||||
import { AuthGuard, type AuthRequest } from 'src/modules/auth/guards/auth.guard';
|
||||
import { UserService } from './user.service';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { FindUsersDto } from './dto/find-user.dto';
|
||||
import { AdminAuthGuard } from 'src/common/guards/auth.admin.guard';
|
||||
import { AdminAuthGuard } from 'src/modules/auth/guards/auth.admin.guard';
|
||||
|
||||
@ApiTags('User')
|
||||
@Controller('user')
|
||||
Reference in New Issue
Block a user