update
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
|
||||
import { Controller, Post, Body, HttpCode, HttpStatus, Get } from '@nestjs/common';
|
||||
import { AdminService } from './admin.service';
|
||||
import { CreateAdminDto } from './dto/create-admin.dto';
|
||||
import { ApiTags, ApiOperation, ApiBody, ApiResponse } from '@nestjs/swagger';
|
||||
@@ -8,6 +8,14 @@ import { ApiTags, ApiOperation, ApiBody, ApiResponse } from '@nestjs/swagger';
|
||||
export class AdminController {
|
||||
constructor(private readonly adminService: AdminService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'admin' })
|
||||
@ApiResponse({ status: 201, description: 'Admins' })
|
||||
async getAll() {
|
||||
const admin = await this.adminService.findAll();
|
||||
return admin;
|
||||
}
|
||||
|
||||
@Post()
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: 'Create a new admin' })
|
||||
|
||||
@@ -21,6 +21,9 @@ export class AdminService {
|
||||
async findById(id: string): Promise<Admin | null> {
|
||||
return this.adminRepository.findOne({ id });
|
||||
}
|
||||
async findAll(): Promise<Admin[]> {
|
||||
return this.adminRepository.findAll();
|
||||
}
|
||||
|
||||
async create(data: { phone: string; firstName?: string; lastName?: string; roleId: string; restId: string }) {
|
||||
const { phone, firstName, lastName, roleId, restId } = data;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString, IsNumber } from 'class-validator';
|
||||
import { IsOptional, IsString, IsNumber, Min } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class FindRolesDto {
|
||||
@ApiProperty({ description: 'Search by role name', required: false })
|
||||
@@ -14,11 +15,15 @@ export class FindRolesDto {
|
||||
|
||||
@ApiProperty({ description: 'Page number', required: false, default: 1 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
page?: number;
|
||||
|
||||
@ApiProperty({ description: 'Items per page', required: false, default: 20 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Entity, ManyToOne, OneToOne, Property, Unique } from '@mikro-orm/core';
|
||||
import { Entity, ManyToOne, OneToOne, Property } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
|
||||
import { Role } from './role.entity';
|
||||
|
||||
@Entity({ tableName: 'admins' })
|
||||
// Add the Unique constraint for the combination of 'phone' and 'restaurant'
|
||||
@Unique({ properties: ['phone', 'restaurant'] })
|
||||
// @Unique({ properties: ['phone', 'restaurant'] })
|
||||
export class Admin extends BaseEntity {
|
||||
@Property({ nullable: true })
|
||||
firstName?: string;
|
||||
|
||||
@@ -9,6 +9,10 @@ export class AdminRepository extends EntityRepository<Admin> {
|
||||
}
|
||||
|
||||
async findByPhoneAndrestaurantSlug(phone: string, slug: string): Promise<Admin | null> {
|
||||
return this.findOne({ phone, restaurant: { slug } }, { populate: ['restaurant', 'role', 'role.permissions'] });
|
||||
return this.em.findOne(
|
||||
Admin,
|
||||
{ phone, restaurant: { slug } },
|
||||
{ populate: ['restaurant', 'role', 'role.permissions'] },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class AuthService {
|
||||
async requestOtpAdmin(dto: RequestOtpDto) {
|
||||
const { phone, slug } = dto;
|
||||
|
||||
const admin = await this.adminRepository.find({ phone });
|
||||
const admin = await this.adminRepository.findByPhoneAndrestaurantSlug(phone, slug);
|
||||
if (!admin) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@@ -45,9 +45,9 @@ export class AuthService {
|
||||
|
||||
await this.cacheService.set(`otp-admin:${slug}:${phone}`, code, 160);
|
||||
|
||||
await this.smsService.sendOtp(phone, code);
|
||||
// await this.smsService.sendOtp(phone, code);
|
||||
|
||||
return true;
|
||||
return code;
|
||||
}
|
||||
|
||||
async verifyOtp(phone: string, slug: string, code: string) {
|
||||
@@ -65,14 +65,16 @@ export class AuthService {
|
||||
throw new BadRequestException(RestMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
const tokens = await this.tokensService.generateTokens(user, rest);
|
||||
const tokens = await this.tokensService.generateTokens(user.id, rest);
|
||||
|
||||
return { tokens, user };
|
||||
}
|
||||
|
||||
async verifyOtpAdmin(phone: string, slug: string, code: string) {
|
||||
const cachedCode = await this.cacheService.get(`otp-admin:${slug}:${phone}`);
|
||||
|
||||
if (!cachedCode) throw new BadRequestException('OTP expired or not found');
|
||||
|
||||
if (cachedCode !== code) throw new BadRequestException('Invalid OTP');
|
||||
|
||||
await this.cacheService.del(`otp-admin:${slug}:${phone}`);
|
||||
|
||||
@@ -26,10 +26,10 @@ export class TokensService {
|
||||
private readonly cacheService: CacheService,
|
||||
) {}
|
||||
|
||||
async generateTokens(user: User, rest: Restaurant, em?: EntityManager) {
|
||||
async generateTokens(ownerId: string, rest: Restaurant, em?: EntityManager) {
|
||||
return this.generateAccessAndRefreshToken(
|
||||
{
|
||||
userId: user.id,
|
||||
userId: ownerId,
|
||||
restId: rest.id,
|
||||
},
|
||||
em,
|
||||
@@ -76,7 +76,7 @@ export class TokensService {
|
||||
const accessToken = await this.jwtService.signAsync(tokenPayload, { expiresIn: `${accessExpire}m` });
|
||||
const refreshToken = this.generateRefreshToken();
|
||||
|
||||
await this.storeRefreshToken(payload.adminId, payload.restId, refreshToken, em);
|
||||
await this.storeRefreshTokenAdmin(payload.adminId, payload.restId, refreshToken, em);
|
||||
return {
|
||||
accessToken: { token: accessToken, expire: dayjs().add(accessExpire, 'minute').valueOf() },
|
||||
refreshToken: { token: refreshToken, expire: dayjs().add(refreshExpire, 'day').valueOf() },
|
||||
@@ -105,7 +105,52 @@ export class TokensService {
|
||||
const token = entityManager.create(RefreshToken, {
|
||||
token: refreshToken,
|
||||
restaurant,
|
||||
user,
|
||||
ownerId: userId,
|
||||
expiresAt,
|
||||
});
|
||||
|
||||
// Use persist instead of persistAndFlush when in transaction
|
||||
if (isExternalTransaction) {
|
||||
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 storeRefreshTokenAdmin(adminId: string, restId: 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 admin = await entityManager.findOne(Admin, { id: adminId });
|
||||
if (!admin) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||
|
||||
const restaurant = await entityManager.findOne(Restaurant, { id: restId });
|
||||
if (!restaurant) throw new BadRequestException(UserMessage.Rest_NOT_FOUND);
|
||||
|
||||
const token = entityManager.create(RefreshToken, {
|
||||
token: refreshToken,
|
||||
restaurant,
|
||||
ownerId: adminId,
|
||||
expiresAt,
|
||||
});
|
||||
|
||||
@@ -138,7 +183,7 @@ export class TokensService {
|
||||
RefreshToken,
|
||||
{ token: oldRefreshToken },
|
||||
{
|
||||
populate: ['user', 'restaurant'],
|
||||
populate: ['restaurant'],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -158,7 +203,7 @@ export class TokensService {
|
||||
entityManager.remove(token);
|
||||
|
||||
// Generate new tokens within the same transaction
|
||||
const tokens = await this.generateTokens(token.user, token.restaurant, entityManager);
|
||||
const tokens = await this.generateTokens(token.ownerId, token.restaurant, entityManager);
|
||||
|
||||
// Commit the transaction
|
||||
await entityManager.flush();
|
||||
@@ -180,7 +225,7 @@ export class TokensService {
|
||||
try {
|
||||
await entityManager.begin();
|
||||
|
||||
await entityManager.nativeDelete(RefreshToken, { user: { id: userId } });
|
||||
await entityManager.nativeDelete(RefreshToken, { ownerId: userId });
|
||||
this.logger.log(`Successfully deleted all refresh tokens for user ${userId}`);
|
||||
|
||||
await entityManager.commit();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Entity, ManyToOne, Opt, Property } from '@mikro-orm/core';
|
||||
|
||||
import { User } from './user.entity';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
|
||||
|
||||
@@ -9,8 +8,8 @@ export class RefreshToken extends BaseEntity {
|
||||
@Property({ type: 'varchar', length: 255 })
|
||||
token!: string;
|
||||
|
||||
@ManyToOne(() => User, { deleteRule: 'cascade' })
|
||||
user!: User;
|
||||
@Property()
|
||||
ownerId!: string;
|
||||
|
||||
@ManyToOne(() => Restaurant, { deleteRule: 'cascade' })
|
||||
restaurant!: Restaurant;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Entity, Property, OneToMany, Collection, OneToOne } from '@mikro-orm/core';
|
||||
import { RefreshToken } from './refresh-token.entity';
|
||||
import { Entity, Property, OneToOne } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
|
||||
@@ -8,9 +7,6 @@ export class User extends BaseEntity {
|
||||
@OneToOne(() => Restaurant, { owner: true })
|
||||
restaurant!: Restaurant;
|
||||
|
||||
@OneToMany(() => RefreshToken, token => token.user)
|
||||
refreshTokens = new Collection<RefreshToken>(this);
|
||||
|
||||
@Property()
|
||||
firstName!: string;
|
||||
|
||||
|
||||
@@ -5,11 +5,12 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { User } from './entities/user.entity';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { UserRepository } from './repositories/user.repository';
|
||||
import { RefreshToken } from './entities/refresh-token.entity';
|
||||
|
||||
@Module({
|
||||
providers: [UserService, UserRepository],
|
||||
controllers: [AdminUserController],
|
||||
imports: [MikroOrmModule.forFeature([User]), JwtModule],
|
||||
imports: [MikroOrmModule.forFeature([User, RefreshToken]), JwtModule],
|
||||
exports: [UserService, UserRepository],
|
||||
})
|
||||
export class UserModule {}
|
||||
|
||||
Reference in New Issue
Block a user