/me
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Body, Controller, Post } from '@nestjs/common';
|
import { Body, Controller, Get, Post } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { AllowWithoutSubscription } from '../auth/decorators/allow-without-subscription.decorator';
|
import { AllowWithoutSubscription } from '../auth/decorators/allow-without-subscription.decorator';
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +18,7 @@ import { Public } from './decorators/public.decorator';
|
|||||||
import { AuthTokensResponseDto } from './dto/auth-tokens-response.dto';
|
import { AuthTokensResponseDto } from './dto/auth-tokens-response.dto';
|
||||||
import { CheckSalonMobileDto } from './dto/check-salon-mobile.dto';
|
import { CheckSalonMobileDto } from './dto/check-salon-mobile.dto';
|
||||||
import { CheckSalonMobileResponseDto } from './dto/check-salon-mobile-response.dto';
|
import { CheckSalonMobileResponseDto } from './dto/check-salon-mobile-response.dto';
|
||||||
|
import { MeResponseDto } from './dto/me-response.dto';
|
||||||
import { RefreshTokenDto } from './dto/refresh-token.dto';
|
import { RefreshTokenDto } from './dto/refresh-token.dto';
|
||||||
import { RequestOtpDto } from './dto/request-otp.dto';
|
import { RequestOtpDto } from './dto/request-otp.dto';
|
||||||
import { RequestOtpResponseDto } from './dto/request-otp-response.dto';
|
import { RequestOtpResponseDto } from './dto/request-otp-response.dto';
|
||||||
@@ -89,6 +90,14 @@ export class AuthController {
|
|||||||
return this.authService.refresh(dto.refreshToken);
|
return this.authService.refresh(dto.refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AllowWithoutSubscription()
|
||||||
|
@ApiProtectedEndpoint('Get current authenticated user profile')
|
||||||
|
@ApiStandardOkResponse(MeResponseDto)
|
||||||
|
@Get('me')
|
||||||
|
getMe(@CurrentUser() user: AuthUser) {
|
||||||
|
return this.authService.getMe(user);
|
||||||
|
}
|
||||||
|
|
||||||
@AllowWithoutSubscription()
|
@AllowWithoutSubscription()
|
||||||
@ApiProtectedEndpoint('Logout and invalidate refresh token')
|
@ApiProtectedEndpoint('Logout and invalidate refresh token')
|
||||||
@ApiStandardEmptyResponse()
|
@ApiStandardEmptyResponse()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { AuthorizationModule } from '../authorization/authorization.module';
|
|||||||
import { Customer } from '../customers/entities/customer.entity';
|
import { Customer } from '../customers/entities/customer.entity';
|
||||||
import { Salon } from '../salons/entities/salon.entity';
|
import { Salon } from '../salons/entities/salon.entity';
|
||||||
import { SmsModule } from '../sms/sms.module';
|
import { SmsModule } from '../sms/sms.module';
|
||||||
|
import { SubscriptionsModule } from '../subscriptions/subscriptions.module';
|
||||||
import { UsersModule } from '../users/users.module';
|
import { UsersModule } from '../users/users.module';
|
||||||
import { AuthController } from './auth.controller';
|
import { AuthController } from './auth.controller';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
@@ -33,6 +34,7 @@ import { JwtStrategy } from './strategies/jwt.strategy';
|
|||||||
}),
|
}),
|
||||||
UsersModule,
|
UsersModule,
|
||||||
SmsModule,
|
SmsModule,
|
||||||
|
SubscriptionsModule,
|
||||||
AuthorizationModule,
|
AuthorizationModule,
|
||||||
],
|
],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { Repository, IsNull } from 'typeorm';
|
|||||||
import { Customer } from '../customers/entities/customer.entity';
|
import { Customer } from '../customers/entities/customer.entity';
|
||||||
import { Salon } from '../salons/entities/salon.entity';
|
import { Salon } from '../salons/entities/salon.entity';
|
||||||
import { SmsService } from '../sms/sms.service';
|
import { SmsService } from '../sms/sms.service';
|
||||||
|
import { SalonSubscription } from '../subscriptions/entities/salon-subscription.entity';
|
||||||
|
import { SalonSubscriptionsService } from '../subscriptions/salon-subscriptions.service';
|
||||||
import { UserRole } from '../users/enums/user-role.enum';
|
import { UserRole } from '../users/enums/user-role.enum';
|
||||||
import { User } from '../users/entities/user.entity';
|
import { User } from '../users/entities/user.entity';
|
||||||
import { UsersService } from '../users/users.service';
|
import { UsersService } from '../users/users.service';
|
||||||
@@ -22,6 +24,7 @@ import { VerifyOtpDto } from './dto/verify-otp.dto';
|
|||||||
import { VerifySalonOtpDto } from './dto/verify-salon-otp.dto';
|
import { VerifySalonOtpDto } from './dto/verify-salon-otp.dto';
|
||||||
import { Otp } from './entities/otp.entity';
|
import { Otp } from './entities/otp.entity';
|
||||||
import { RefreshToken } from './entities/refresh-token.entity';
|
import { RefreshToken } from './entities/refresh-token.entity';
|
||||||
|
import { AuthUser } from './interfaces/auth-user.interface';
|
||||||
import { JwtPayload } from './interfaces/jwt-payload.interface';
|
import { JwtPayload } from './interfaces/jwt-payload.interface';
|
||||||
|
|
||||||
export interface AuthTokensResponse {
|
export interface AuthTokensResponse {
|
||||||
@@ -40,6 +43,12 @@ export interface SalonAuthResponse extends AuthTokensResponse {
|
|||||||
salon?: Salon;
|
salon?: Salon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MeResponse {
|
||||||
|
user: AuthTokensResponse['user'];
|
||||||
|
salons?: Salon[];
|
||||||
|
activeSubscription?: SalonSubscription | null;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private readonly logger = new Logger(AuthService.name);
|
private readonly logger = new Logger(AuthService.name);
|
||||||
@@ -60,6 +69,7 @@ export class AuthService {
|
|||||||
@InjectRepository(Salon)
|
@InjectRepository(Salon)
|
||||||
private readonly salonsRepository: Repository<Salon>,
|
private readonly salonsRepository: Repository<Salon>,
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
|
private readonly salonSubscriptionsService: SalonSubscriptionsService,
|
||||||
private readonly smsService: SmsService,
|
private readonly smsService: SmsService,
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
@@ -336,6 +346,33 @@ export class AuthService {
|
|||||||
throw new UnauthorizedException('Invalid refresh token');
|
throw new UnauthorizedException('Invalid refresh token');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMe(authUser: AuthUser): Promise<MeResponse> {
|
||||||
|
const user = await this.usersService.findOne(authUser.id);
|
||||||
|
|
||||||
|
const response: MeResponse = {
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
mobile: user.mobile,
|
||||||
|
role: user.role,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user.role === UserRole.SALON) {
|
||||||
|
response.salons = await this.salonsRepository.find({
|
||||||
|
where: { ownerId: user.id },
|
||||||
|
order: { createdAt: 'ASC' },
|
||||||
|
});
|
||||||
|
response.activeSubscription =
|
||||||
|
await this.salonSubscriptionsService.findActiveSubscriptionForOwner(
|
||||||
|
user.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private async resolveUserForLogin(dto: VerifyOtpDto): Promise<User> {
|
private async resolveUserForLogin(dto: VerifyOtpDto): Promise<User> {
|
||||||
const existingUser = await this.usersService.findByMobile(dto.mobile);
|
const existingUser = await this.usersService.findByMobile(dto.mobile);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Salon } from '../../salons/entities/salon.entity';
|
||||||
|
import { SalonSubscription } from '../../subscriptions/entities/salon-subscription.entity';
|
||||||
|
import { UserRole } from '../../users/enums/user-role.enum';
|
||||||
|
|
||||||
|
class MeUserResponseDto {
|
||||||
|
@ApiProperty({ format: 'uuid' })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
mobile: string;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: UserRole })
|
||||||
|
role: UserRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MeResponseDto {
|
||||||
|
@ApiProperty({ type: MeUserResponseDto })
|
||||||
|
user: MeUserResponseDto;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
type: Salon,
|
||||||
|
isArray: true,
|
||||||
|
description: 'Present for salon owners',
|
||||||
|
})
|
||||||
|
salons?: Salon[];
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
type: SalonSubscription,
|
||||||
|
nullable: true,
|
||||||
|
description: 'Active subscription for salon owners, null if none',
|
||||||
|
})
|
||||||
|
activeSubscription?: SalonSubscription | null;
|
||||||
|
}
|
||||||
@@ -154,25 +154,34 @@ export class SalonSubscriptionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async hasActiveSubscriptionForOwner(ownerId: string): Promise<boolean> {
|
async hasActiveSubscriptionForOwner(ownerId: string): Promise<boolean> {
|
||||||
|
const subscription = await this.findActiveSubscriptionForOwner(ownerId);
|
||||||
|
return subscription !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findActiveSubscriptionForOwner(
|
||||||
|
ownerId: string,
|
||||||
|
): Promise<SalonSubscription | null> {
|
||||||
const subscriptions = await this.subscriptionsRepository
|
const subscriptions = await this.subscriptionsRepository
|
||||||
.createQueryBuilder('subscription')
|
.createQueryBuilder('subscription')
|
||||||
.innerJoin('subscription.salon', 'salon')
|
.innerJoinAndSelect('subscription.salon', 'salon')
|
||||||
|
.innerJoinAndSelect('subscription.plan', 'plan')
|
||||||
.where('salon.ownerId = :ownerId', { ownerId })
|
.where('salon.ownerId = :ownerId', { ownerId })
|
||||||
.andWhere('subscription.status = :status', {
|
.andWhere('subscription.status = :status', {
|
||||||
status: SalonSubscriptionStatus.ACTIVE,
|
status: SalonSubscriptionStatus.ACTIVE,
|
||||||
})
|
})
|
||||||
|
.orderBy('subscription.createdAt', 'DESC')
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
for (const subscription of subscriptions) {
|
for (const subscription of subscriptions) {
|
||||||
if (subscription.endDate > now) {
|
if (subscription.endDate > now) {
|
||||||
return true;
|
return subscription;
|
||||||
}
|
}
|
||||||
subscription.status = SalonSubscriptionStatus.EXPIRED;
|
subscription.status = SalonSubscriptionStatus.EXPIRED;
|
||||||
await this.subscriptionsRepository.save(subscription);
|
await this.subscriptionsRepository.save(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async consumeSms(salonId: string, count = 1): Promise<void> {
|
async consumeSms(salonId: string, count = 1): Promise<void> {
|
||||||
|
|||||||
Reference in New Issue
Block a user