mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-26 06:09:41 +00:00
feat: add loggers to all services
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
import { PageOptionsRequestDto } from '~/core/dtos';
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
import { OciService } from '~/document/services';
|
import { OciService } from '~/document/services';
|
||||||
@ -10,6 +10,7 @@ import { AllowancesService } from './allowances.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AllowanceChangeRequestsService {
|
export class AllowanceChangeRequestsService {
|
||||||
|
private readonly logger = new Logger(AllowanceChangeRequestsService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly allowanceChangeRequestsRepository: AllowanceChangeRequestsRepository,
|
private readonly allowanceChangeRequestsRepository: AllowanceChangeRequestsRepository,
|
||||||
private readonly ociService: OciService,
|
private readonly ociService: OciService,
|
||||||
@ -17,9 +18,11 @@ export class AllowanceChangeRequestsService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createAllowanceChangeRequest(juniorId: string, body: CreateAllowanceChangeRequestDto) {
|
async createAllowanceChangeRequest(juniorId: string, body: CreateAllowanceChangeRequestDto) {
|
||||||
|
this.logger.log(`Creating allowance change request for junior ${juniorId}`);
|
||||||
const allowance = await this.allowanceService.validateAllowanceForJunior(juniorId, body.allowanceId);
|
const allowance = await this.allowanceService.validateAllowanceForJunior(juniorId, body.allowanceId);
|
||||||
|
|
||||||
if (allowance.amount === body.amount) {
|
if (allowance.amount === body.amount) {
|
||||||
|
this.logger.error(`Amount is the same as the current allowance amount`);
|
||||||
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.SAME_AMOUNT');
|
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.SAME_AMOUNT');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +33,7 @@ export class AllowanceChangeRequestsService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (requestWithTheSameAmount) {
|
if (requestWithTheSameAmount) {
|
||||||
|
this.logger.error(`There is a pending request with the same amount`);
|
||||||
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.SAME_AMOUNT_PENDING');
|
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.SAME_AMOUNT_PENDING');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,16 +41,20 @@ export class AllowanceChangeRequestsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findAllowanceChangeRequestBy(where: FindOptionsWhere<AllowanceChangeRequest>) {
|
findAllowanceChangeRequestBy(where: FindOptionsWhere<AllowanceChangeRequest>) {
|
||||||
|
this.logger.log(`Finding allowance change request by ${JSON.stringify(where)}`);
|
||||||
return this.allowanceChangeRequestsRepository.findAllowanceChangeRequestBy(where);
|
return this.allowanceChangeRequestsRepository.findAllowanceChangeRequestBy(where);
|
||||||
}
|
}
|
||||||
|
|
||||||
async approveAllowanceChangeRequest(guardianId: string, requestId: string) {
|
async approveAllowanceChangeRequest(guardianId: string, requestId: string) {
|
||||||
|
this.logger.log(`Approving allowance change request ${requestId} by guardian ${guardianId}`);
|
||||||
const request = await this.findAllowanceChangeRequestBy({ id: requestId, allowance: { guardianId } });
|
const request = await this.findAllowanceChangeRequestBy({ id: requestId, allowance: { guardianId } });
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
|
this.logger.error(`Allowance change request ${requestId} not found for guardian ${guardianId}`);
|
||||||
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.NOT_FOUND');
|
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.NOT_FOUND');
|
||||||
}
|
}
|
||||||
if (request.status === AllowanceChangeRequestStatus.APPROVED) {
|
if (request.status === AllowanceChangeRequestStatus.APPROVED) {
|
||||||
|
this.logger.error(`Allowance change request ${requestId} already approved`);
|
||||||
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.ALREADY_APPROVED');
|
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.ALREADY_APPROVED');
|
||||||
}
|
}
|
||||||
return this.allowanceChangeRequestsRepository.updateAllowanceChangeRequestStatus(
|
return this.allowanceChangeRequestsRepository.updateAllowanceChangeRequestStatus(
|
||||||
@ -56,12 +64,15 @@ export class AllowanceChangeRequestsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async rejectAllowanceChangeRequest(guardianId: string, requestId: string) {
|
async rejectAllowanceChangeRequest(guardianId: string, requestId: string) {
|
||||||
|
this.logger.log(`Rejecting allowance change request ${requestId} by guardian ${guardianId}`);
|
||||||
const request = await this.findAllowanceChangeRequestBy({ id: requestId, allowance: { guardianId } });
|
const request = await this.findAllowanceChangeRequestBy({ id: requestId, allowance: { guardianId } });
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
|
this.logger.error(`Allowance change request ${requestId} not found for guardian ${guardianId}`);
|
||||||
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.NOT_FOUND');
|
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.NOT_FOUND');
|
||||||
}
|
}
|
||||||
if (request.status === AllowanceChangeRequestStatus.REJECTED) {
|
if (request.status === AllowanceChangeRequestStatus.REJECTED) {
|
||||||
|
this.logger.error(`Allowance change request ${requestId} already rejected`);
|
||||||
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.ALREADY_REJECTED');
|
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.ALREADY_REJECTED');
|
||||||
}
|
}
|
||||||
return this.allowanceChangeRequestsRepository.updateAllowanceChangeRequestStatus(
|
return this.allowanceChangeRequestsRepository.updateAllowanceChangeRequestStatus(
|
||||||
@ -74,6 +85,7 @@ export class AllowanceChangeRequestsService {
|
|||||||
guardianId: string,
|
guardianId: string,
|
||||||
query: PageOptionsRequestDto,
|
query: PageOptionsRequestDto,
|
||||||
): Promise<[AllowanceChangeRequest[], number]> {
|
): Promise<[AllowanceChangeRequest[], number]> {
|
||||||
|
this.logger.log(`Finding allowance change requests for guardian ${guardianId}`);
|
||||||
const [requests, itemCount] = await this.allowanceChangeRequestsRepository.findAllowanceChangeRequests(
|
const [requests, itemCount] = await this.allowanceChangeRequestsRepository.findAllowanceChangeRequests(
|
||||||
guardianId,
|
guardianId,
|
||||||
query,
|
query,
|
||||||
@ -81,10 +93,12 @@ export class AllowanceChangeRequestsService {
|
|||||||
|
|
||||||
await this.prepareAllowanceChangeRequestsImages(requests);
|
await this.prepareAllowanceChangeRequestsImages(requests);
|
||||||
|
|
||||||
|
this.logger.log(`Returning allowance change requests for guardian ${guardianId}`);
|
||||||
return [requests, itemCount];
|
return [requests, itemCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllowanceChangeRequestById(guardianId: string, requestId: string) {
|
async findAllowanceChangeRequestById(guardianId: string, requestId: string) {
|
||||||
|
this.logger.log(`Finding allowance change request ${requestId} for guardian ${guardianId}`);
|
||||||
const request = await this.allowanceChangeRequestsRepository.findAllowanceChangeRequestBy(
|
const request = await this.allowanceChangeRequestsRepository.findAllowanceChangeRequestBy(
|
||||||
{
|
{
|
||||||
id: requestId,
|
id: requestId,
|
||||||
@ -94,15 +108,18 @@ export class AllowanceChangeRequestsService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
|
this.logger.error(`Allowance change request ${requestId} not found for guardian ${guardianId}`);
|
||||||
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.NOT_FOUND');
|
throw new BadRequestException('ALLOWANCE_CHANGE_REQUEST.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prepareAllowanceChangeRequestsImages([request]);
|
await this.prepareAllowanceChangeRequestsImages([request]);
|
||||||
|
|
||||||
|
this.logger.log(`Allowance change request ${requestId} found successfully`);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private prepareAllowanceChangeRequestsImages(requests: AllowanceChangeRequest[]) {
|
private prepareAllowanceChangeRequestsImages(requests: AllowanceChangeRequest[]) {
|
||||||
|
this.logger.log(`Preparing allowance change requests images`);
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
requests.map(async (request) => {
|
requests.map(async (request) => {
|
||||||
const profilePicture = request.allowance.junior.customer.profilePicture;
|
const profilePicture = request.allowance.junior.customer.profilePicture;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { PageOptionsRequestDto } from '~/core/dtos';
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
import { OciService } from '~/document/services';
|
import { OciService } from '~/document/services';
|
||||||
@ -9,6 +9,7 @@ import { AllowancesRepository } from '../repositories';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AllowancesService {
|
export class AllowancesService {
|
||||||
|
private readonly logger = new Logger(AllowancesService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly allowancesRepository: AllowancesRepository,
|
private readonly allowancesRepository: AllowancesRepository,
|
||||||
private readonly juniorService: JuniorService,
|
private readonly juniorService: JuniorService,
|
||||||
@ -16,56 +17,72 @@ export class AllowancesService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createAllowance(guardianId: string, body: CreateAllowanceRequestDto) {
|
async createAllowance(guardianId: string, body: CreateAllowanceRequestDto) {
|
||||||
|
this.logger.log(`Creating allowance for junior ${body.juniorId} by guardian ${guardianId}`);
|
||||||
if (moment(body.startDate).isBefore(moment().startOf('day'))) {
|
if (moment(body.startDate).isBefore(moment().startOf('day'))) {
|
||||||
|
this.logger.error(`Start date ${body.startDate} is before today`);
|
||||||
throw new BadRequestException('ALLOWANCE.START_DATE_BEFORE_TODAY');
|
throw new BadRequestException('ALLOWANCE.START_DATE_BEFORE_TODAY');
|
||||||
}
|
}
|
||||||
if (moment(body.startDate).isAfter(body.endDate)) {
|
if (moment(body.startDate).isAfter(body.endDate)) {
|
||||||
|
this.logger.error(`Start date ${body.startDate} is after end date ${body.endDate}`);
|
||||||
throw new BadRequestException('ALLOWANCE.START_DATE_AFTER_END_DATE');
|
throw new BadRequestException('ALLOWANCE.START_DATE_AFTER_END_DATE');
|
||||||
}
|
}
|
||||||
|
|
||||||
const doesJuniorBelongToGuardian = await this.juniorService.doesJuniorBelongToGuardian(guardianId, body.juniorId);
|
const doesJuniorBelongToGuardian = await this.juniorService.doesJuniorBelongToGuardian(guardianId, body.juniorId);
|
||||||
|
|
||||||
if (!doesJuniorBelongToGuardian) {
|
if (!doesJuniorBelongToGuardian) {
|
||||||
|
this.logger.error(`Junior ${body.juniorId} does not belong to guardian ${guardianId}`);
|
||||||
throw new BadRequestException('JUNIOR.DOES_NOT_BELONG_TO_GUARDIAN');
|
throw new BadRequestException('JUNIOR.DOES_NOT_BELONG_TO_GUARDIAN');
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowance = await this.allowancesRepository.createAllowance(guardianId, body);
|
const allowance = await this.allowancesRepository.createAllowance(guardianId, body);
|
||||||
|
|
||||||
|
this.logger.log(`Allowance ${allowance.id} created successfully`);
|
||||||
return this.findAllowanceById(allowance.id);
|
return this.findAllowanceById(allowance.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllowanceById(allowanceId: string, guardianId?: string) {
|
async findAllowanceById(allowanceId: string, guardianId?: string) {
|
||||||
|
this.logger.log(`Finding allowance ${allowanceId} ${guardianId ? `by guardian ${guardianId}` : ''}`);
|
||||||
const allowance = await this.allowancesRepository.findAllowanceById(allowanceId, guardianId);
|
const allowance = await this.allowancesRepository.findAllowanceById(allowanceId, guardianId);
|
||||||
|
|
||||||
if (!allowance) {
|
if (!allowance) {
|
||||||
|
this.logger.error(`Allowance ${allowanceId} not found ${guardianId ? `for guardian ${guardianId}` : ''}`);
|
||||||
throw new BadRequestException('ALLOWANCE.NOT_FOUND');
|
throw new BadRequestException('ALLOWANCE.NOT_FOUND');
|
||||||
}
|
}
|
||||||
await this.prepareAllowanceDocuments([allowance]);
|
await this.prepareAllowanceDocuments([allowance]);
|
||||||
|
this.logger.log(`Allowance ${allowanceId} found successfully`);
|
||||||
return allowance;
|
return allowance;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllowances(guardianId: string, query: PageOptionsRequestDto): Promise<[Allowance[], number]> {
|
async findAllowances(guardianId: string, query: PageOptionsRequestDto): Promise<[Allowance[], number]> {
|
||||||
|
this.logger.log(`Finding allowances for guardian ${guardianId}`);
|
||||||
const [allowances, itemCount] = await this.allowancesRepository.findAllowances(guardianId, query);
|
const [allowances, itemCount] = await this.allowancesRepository.findAllowances(guardianId, query);
|
||||||
await this.prepareAllowanceDocuments(allowances);
|
await this.prepareAllowanceDocuments(allowances);
|
||||||
|
this.logger.log(`Returning allowances for guardian ${guardianId}`);
|
||||||
return [allowances, itemCount];
|
return [allowances, itemCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAllowance(guardianId: string, allowanceId: string) {
|
async deleteAllowance(guardianId: string, allowanceId: string) {
|
||||||
|
this.logger.log(`Deleting allowance ${allowanceId} for guardian ${guardianId}`);
|
||||||
const { affected } = await this.allowancesRepository.deleteAllowance(guardianId, allowanceId);
|
const { affected } = await this.allowancesRepository.deleteAllowance(guardianId, allowanceId);
|
||||||
|
|
||||||
if (!affected) {
|
if (!affected) {
|
||||||
|
this.logger.error(`Allowance ${allowanceId} not found`);
|
||||||
throw new BadRequestException('ALLOWANCE.NOT_FOUND');
|
throw new BadRequestException('ALLOWANCE.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
this.logger.log(`Allowance ${allowanceId} deleted successfully`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateAllowanceForJunior(juniorId: string, allowanceId: string) {
|
async validateAllowanceForJunior(juniorId: string, allowanceId: string) {
|
||||||
|
this.logger.log(`Validating allowance ${allowanceId} for junior ${juniorId}`);
|
||||||
const allowance = await this.allowancesRepository.findAllowanceById(allowanceId);
|
const allowance = await this.allowancesRepository.findAllowanceById(allowanceId);
|
||||||
|
|
||||||
if (!allowance) {
|
if (!allowance) {
|
||||||
|
this.logger.error(`Allowance ${allowanceId} not found`);
|
||||||
throw new BadRequestException('ALLOWANCE.NOT_FOUND');
|
throw new BadRequestException('ALLOWANCE.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowance.juniorId !== juniorId) {
|
if (allowance.juniorId !== juniorId) {
|
||||||
|
this.logger.error(`Allowance ${allowanceId} does not belong to junior ${juniorId}`);
|
||||||
throw new BadRequestException('ALLOWANCE.DOES_NOT_BELONG_TO_JUNIOR');
|
throw new BadRequestException('ALLOWANCE.DOES_NOT_BELONG_TO_JUNIOR');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +90,7 @@ export class AllowancesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async prepareAllowanceDocuments(allowance: Allowance[]) {
|
private async prepareAllowanceDocuments(allowance: Allowance[]) {
|
||||||
|
this.logger.log(`Preparing document for allowances`);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
allowance.map(async (allowance) => {
|
allowance.map(async (allowance) => {
|
||||||
const profilePicture = allowance.junior.customer.profilePicture;
|
const profilePicture = allowance.junior.customer.profilePicture;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
@ -7,10 +7,9 @@ import { CacheService } from '~/common/modules/cache/services';
|
|||||||
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
||||||
import { OtpService } from '~/common/modules/otp/services';
|
import { OtpService } from '~/common/modules/otp/services';
|
||||||
import { JuniorTokenService } from '~/junior/services';
|
import { JuniorTokenService } from '~/junior/services';
|
||||||
import { User } from '../../user/entities';
|
|
||||||
|
|
||||||
import { DeviceService, UserService } from '~/user/services';
|
import { DeviceService, UserService } from '~/user/services';
|
||||||
import { PASSCODE_REGEX, PASSWORD_REGEX } from '../constants';
|
import { User } from '../../user/entities';
|
||||||
|
import { PASSCODE_REGEX } from '../constants';
|
||||||
import {
|
import {
|
||||||
CreateUnverifiedUserRequestDto,
|
CreateUnverifiedUserRequestDto,
|
||||||
DisableBiometricRequestDto,
|
DisableBiometricRequestDto,
|
||||||
@ -20,9 +19,9 @@ import {
|
|||||||
SendForgetPasswordOtpRequestDto,
|
SendForgetPasswordOtpRequestDto,
|
||||||
SetEmailRequestDto,
|
SetEmailRequestDto,
|
||||||
setJuniorPasswordRequestDto,
|
setJuniorPasswordRequestDto,
|
||||||
|
VerifyUserRequestDto,
|
||||||
} from '../dtos/request';
|
} from '../dtos/request';
|
||||||
import { VerifyUserRequestDto } from '../dtos/request/verify-user.request.dto';
|
import { GrantType } from '../enums';
|
||||||
import { GrantType, Roles } from '../enums';
|
|
||||||
import { IJwtPayload, ILoginResponse } from '../interfaces';
|
import { IJwtPayload, ILoginResponse } from '../interfaces';
|
||||||
import { removePadding, verifySignature } from '../utils';
|
import { removePadding, verifySignature } from '../utils';
|
||||||
|
|
||||||
@ -30,6 +29,7 @@ const ONE_THOUSAND = 1000;
|
|||||||
const SALT_ROUNDS = 10;
|
const SALT_ROUNDS = 10;
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
|
private readonly logger = new Logger(AuthService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly otpService: OtpService,
|
private readonly otpService: OtpService,
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
@ -40,6 +40,7 @@ export class AuthService {
|
|||||||
private readonly cacheService: CacheService,
|
private readonly cacheService: CacheService,
|
||||||
) {}
|
) {}
|
||||||
async sendRegisterOtp({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
async sendRegisterOtp({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
||||||
|
this.logger.log(`Sending OTP to ${countryCode + phoneNumber}`);
|
||||||
const user = await this.userService.findOrCreateUser({ phoneNumber, countryCode });
|
const user = await this.userService.findOrCreateUser({ phoneNumber, countryCode });
|
||||||
|
|
||||||
return this.otpService.generateAndSendOtp({
|
return this.otpService.generateAndSendOtp({
|
||||||
@ -51,9 +52,13 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyUser(verifyUserDto: VerifyUserRequestDto): Promise<[ILoginResponse, User]> {
|
async verifyUser(verifyUserDto: VerifyUserRequestDto): Promise<[ILoginResponse, User]> {
|
||||||
|
this.logger.log(`Verifying user with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber}`);
|
||||||
const user = await this.userService.findUserOrThrow({ phoneNumber: verifyUserDto.phoneNumber });
|
const user = await this.userService.findUserOrThrow({ phoneNumber: verifyUserDto.phoneNumber });
|
||||||
|
|
||||||
if (user.isPasswordSet) {
|
if (user.isPasswordSet) {
|
||||||
|
this.logger.error(
|
||||||
|
`User with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber} already verified`,
|
||||||
|
);
|
||||||
throw new BadRequestException('USERS.PHONE_ALREADY_VERIFIED');
|
throw new BadRequestException('USERS.PHONE_ALREADY_VERIFIED');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,26 +70,34 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!isOtpValid) {
|
if (!isOtpValid) {
|
||||||
|
this.logger.error(
|
||||||
|
`Invalid OTP for user with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber}`,
|
||||||
|
);
|
||||||
throw new BadRequestException('USERS.INVALID_OTP');
|
throw new BadRequestException('USERS.INVALID_OTP');
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedUser = await this.userService.verifyUserAndCreateCustomer(user);
|
const updatedUser = await this.userService.verifyUserAndCreateCustomer(user);
|
||||||
|
|
||||||
const tokens = await this.generateAuthToken(updatedUser);
|
const tokens = await this.generateAuthToken(updatedUser);
|
||||||
|
this.logger.log(
|
||||||
|
`User with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber} verified successfully`,
|
||||||
|
);
|
||||||
return [tokens, updatedUser];
|
return [tokens, updatedUser];
|
||||||
}
|
}
|
||||||
|
|
||||||
async setEmail(userId: string, { email }: SetEmailRequestDto) {
|
async setEmail(userId: string, { email }: SetEmailRequestDto) {
|
||||||
|
this.logger.log(`Setting email for user with id ${userId}`);
|
||||||
const user = await this.userService.findUserOrThrow({ id: userId });
|
const user = await this.userService.findUserOrThrow({ id: userId });
|
||||||
|
|
||||||
if (user.email) {
|
if (user.email) {
|
||||||
|
this.logger.error(`Email already set for user with id ${userId}`);
|
||||||
throw new BadRequestException('USERS.EMAIL_ALREADY_SET');
|
throw new BadRequestException('USERS.EMAIL_ALREADY_SET');
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingUser = await this.userService.findUser({ email });
|
const existingUser = await this.userService.findUser({ email });
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
|
this.logger.error(`Email ${email} already taken`);
|
||||||
throw new BadRequestException('USERS.EMAIL_ALREADY_TAKEN');
|
throw new BadRequestException('USERS.EMAIL_ALREADY_TAKEN');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,21 +105,26 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setPasscode(userId: string, passcode: string) {
|
async setPasscode(userId: string, passcode: string) {
|
||||||
|
this.logger.log(`Setting passcode for user with id ${userId}`);
|
||||||
const user = await this.userService.findUserOrThrow({ id: userId });
|
const user = await this.userService.findUserOrThrow({ id: userId });
|
||||||
|
|
||||||
if (user.password) {
|
if (user.password) {
|
||||||
|
this.logger.error(`Passcode already set for user with id ${userId}`);
|
||||||
throw new BadRequestException('USERS.PASSCODE_ALREADY_SET');
|
throw new BadRequestException('USERS.PASSCODE_ALREADY_SET');
|
||||||
}
|
}
|
||||||
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
||||||
const hashedPasscode = bcrypt.hashSync(passcode, salt);
|
const hashedPasscode = bcrypt.hashSync(passcode, salt);
|
||||||
|
|
||||||
await this.userService.setPasscode(userId, hashedPasscode, salt);
|
await this.userService.setPasscode(userId, hashedPasscode, salt);
|
||||||
|
this.logger.log(`Passcode set successfully for user with id ${userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) {
|
async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) {
|
||||||
|
this.logger.log(`Enabling biometric for user with id ${userId}`);
|
||||||
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
|
this.logger.log(`Device not found, creating new device for user with id ${userId}`);
|
||||||
return this.deviceService.createDevice({
|
return this.deviceService.createDevice({
|
||||||
deviceId,
|
deviceId,
|
||||||
userId,
|
userId,
|
||||||
@ -115,6 +133,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (device.publicKey) {
|
if (device.publicKey) {
|
||||||
|
this.logger.error(`Biometric already enabled for user with id ${userId}`);
|
||||||
throw new BadRequestException('AUTH.BIOMETRIC_ALREADY_ENABLED');
|
throw new BadRequestException('AUTH.BIOMETRIC_ALREADY_ENABLED');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,10 +144,12 @@ export class AuthService {
|
|||||||
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
|
this.logger.error(`Device not found for user with id ${userId} and device id ${deviceId}`);
|
||||||
throw new BadRequestException('AUTH.DEVICE_NOT_FOUND');
|
throw new BadRequestException('AUTH.DEVICE_NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!device.publicKey) {
|
if (!device.publicKey) {
|
||||||
|
this.logger.error(`Biometric already disabled for user with id ${userId}`);
|
||||||
throw new BadRequestException('AUTH.BIOMETRIC_ALREADY_DISABLED');
|
throw new BadRequestException('AUTH.BIOMETRIC_ALREADY_DISABLED');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,9 +157,11 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendForgetPasswordOtp({ email }: SendForgetPasswordOtpRequestDto) {
|
async sendForgetPasswordOtp({ email }: SendForgetPasswordOtpRequestDto) {
|
||||||
|
this.logger.log(`Sending forget password OTP to ${email}`);
|
||||||
const user = await this.userService.findUserOrThrow({ email });
|
const user = await this.userService.findUserOrThrow({ email });
|
||||||
|
|
||||||
if (!user.isProfileCompleted) {
|
if (!user.isProfileCompleted) {
|
||||||
|
this.logger.error(`Profile not completed for user with email ${email}`);
|
||||||
throw new BadRequestException('USERS.PROFILE_NOT_COMPLETED');
|
throw new BadRequestException('USERS.PROFILE_NOT_COMPLETED');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,8 +174,10 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyForgetPasswordOtp({ email, otp, password, confirmPassword }: ForgetPasswordRequestDto) {
|
async verifyForgetPasswordOtp({ email, otp, password, confirmPassword }: ForgetPasswordRequestDto) {
|
||||||
|
this.logger.log(`Verifying forget password OTP for ${email}`);
|
||||||
const user = await this.userService.findUserOrThrow({ email });
|
const user = await this.userService.findUserOrThrow({ email });
|
||||||
if (!user.isProfileCompleted) {
|
if (!user.isProfileCompleted) {
|
||||||
|
this.logger.error(`Profile not completed for user with email ${email}`);
|
||||||
throw new BadRequestException('USERS.PROFILE_NOT_COMPLETED');
|
throw new BadRequestException('USERS.PROFILE_NOT_COMPLETED');
|
||||||
}
|
}
|
||||||
const isOtpValid = await this.otpService.verifyOtp({
|
const isOtpValid = await this.otpService.verifyOtp({
|
||||||
@ -163,6 +188,7 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!isOtpValid) {
|
if (!isOtpValid) {
|
||||||
|
this.logger.error(`Invalid OTP for user with email ${email}`);
|
||||||
throw new BadRequestException('USERS.INVALID_OTP');
|
throw new BadRequestException('USERS.INVALID_OTP');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,81 +197,102 @@ export class AuthService {
|
|||||||
const hashedPassword = bcrypt.hashSync(password, user.salt);
|
const hashedPassword = bcrypt.hashSync(password, user.salt);
|
||||||
|
|
||||||
await this.userService.setPasscode(user.id, hashedPassword, user.salt);
|
await this.userService.setPasscode(user.id, hashedPassword, user.salt);
|
||||||
|
this.logger.log(`Passcode updated successfully for user with email ${email}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
async login(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
||||||
|
this.logger.log(`Logging in user with email ${loginDto.email}`);
|
||||||
const user = await this.userService.findUser({ email: loginDto.email });
|
const user = await this.userService.findUser({ email: loginDto.email });
|
||||||
let tokens;
|
let tokens;
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
this.logger.error(`User with email ${loginDto.email} not found`);
|
||||||
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginDto.grantType === GrantType.PASSWORD) {
|
if (loginDto.grantType === GrantType.PASSWORD) {
|
||||||
|
this.logger.log(`Logging in user with email ${loginDto.email} using password`);
|
||||||
tokens = await this.loginWithPassword(loginDto, user);
|
tokens = await this.loginWithPassword(loginDto, user);
|
||||||
} else {
|
} else {
|
||||||
|
this.logger.log(`Logging in user with email ${loginDto.email} using biometric`);
|
||||||
tokens = await this.loginWithBiometric(loginDto, user, deviceId);
|
tokens = await this.loginWithBiometric(loginDto, user, deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deviceService.updateDevice(deviceId, {
|
await this.deviceService.updateDevice(deviceId, {
|
||||||
lastAccessOn: new Date(),
|
lastAccessOn: new Date(),
|
||||||
fcmToken: loginDto.fcmToken,
|
fcmToken: loginDto.fcmToken,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.logger.log(`User with email ${loginDto.email} logged in successfully`);
|
||||||
|
|
||||||
return [tokens, user];
|
return [tokens, user];
|
||||||
}
|
}
|
||||||
|
|
||||||
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
||||||
|
this.logger.log(`Setting passcode for junior with qrToken ${body.qrToken}`);
|
||||||
const juniorId = await this.juniorTokenService.validateToken(body.qrToken);
|
const juniorId = await this.juniorTokenService.validateToken(body.qrToken);
|
||||||
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
||||||
const hashedPasscode = bcrypt.hashSync(body.passcode, salt);
|
const hashedPasscode = bcrypt.hashSync(body.passcode, salt);
|
||||||
await this.userService.setPasscode(juniorId, hashedPasscode, salt);
|
await this.userService.setPasscode(juniorId, hashedPasscode, salt);
|
||||||
await this.juniorTokenService.invalidateToken(body.qrToken);
|
await this.juniorTokenService.invalidateToken(body.qrToken);
|
||||||
|
this.logger.log(`Passcode set successfully for junior with id ${juniorId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshToken(refreshToken: string): Promise<[ILoginResponse, User]> {
|
async refreshToken(refreshToken: string): Promise<[ILoginResponse, User]> {
|
||||||
|
this.logger.log('Refreshing token');
|
||||||
try {
|
try {
|
||||||
const isValid = await this.jwtService.verifyAsync<IJwtPayload>(refreshToken, {
|
const isValid = await this.jwtService.verifyAsync<IJwtPayload>(refreshToken, {
|
||||||
secret: this.configService.getOrThrow('JWT_REFRESH_TOKEN_SECRET'),
|
secret: this.configService.getOrThrow('JWT_REFRESH_TOKEN_SECRET'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.logger.log(`Refreshing token for user with id ${isValid.sub}`);
|
||||||
|
|
||||||
const user = await this.userService.findUserOrThrow({ id: isValid.sub });
|
const user = await this.userService.findUserOrThrow({ id: isValid.sub });
|
||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
const tokens = await this.generateAuthToken(user);
|
||||||
|
|
||||||
|
this.logger.log(`Token refreshed successfully for user with id ${isValid.sub}`);
|
||||||
|
|
||||||
return [tokens, user];
|
return [tokens, user];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.logger.error('Invalid refresh token');
|
||||||
throw new BadRequestException('AUTH.INVALID_REFRESH_TOKEN');
|
throw new BadRequestException('AUTH.INVALID_REFRESH_TOKEN');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logout(req: Request) {
|
logout(req: Request) {
|
||||||
|
this.logger.log('Logging out');
|
||||||
const accessToken = req.headers.authorization?.split(' ')[1] as string;
|
const accessToken = req.headers.authorization?.split(' ')[1] as string;
|
||||||
const expiryInTtl = this.jwtService.decode(accessToken).exp - Date.now() / ONE_THOUSAND;
|
const expiryInTtl = this.jwtService.decode(accessToken).exp - Date.now() / ONE_THOUSAND;
|
||||||
return this.cacheService.set(accessToken, 'LOGOUT', expiryInTtl);
|
return this.cacheService.set(accessToken, 'LOGOUT', expiryInTtl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loginWithPassword(loginDto: LoginRequestDto, user: User): Promise<ILoginResponse> {
|
private async loginWithPassword(loginDto: LoginRequestDto, user: User): Promise<ILoginResponse> {
|
||||||
|
this.logger.log(`validating password for user with email ${loginDto.email}`);
|
||||||
const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password);
|
const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password);
|
||||||
|
|
||||||
if (!isPasswordValid) {
|
if (!isPasswordValid) {
|
||||||
|
this.logger.error(`Invalid password for user with email ${loginDto.email}`);
|
||||||
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
const tokens = await this.generateAuthToken(user);
|
||||||
|
this.logger.log(`Password validated successfully for user with email ${loginDto.email}`);
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loginWithBiometric(loginDto: LoginRequestDto, user: User, deviceId: string): Promise<ILoginResponse> {
|
private async loginWithBiometric(loginDto: LoginRequestDto, user: User, deviceId: string): Promise<ILoginResponse> {
|
||||||
|
this.logger.log(`validating biometric for user with email ${loginDto.email}`);
|
||||||
const device = await this.deviceService.findUserDeviceById(deviceId, user.id);
|
const device = await this.deviceService.findUserDeviceById(deviceId, user.id);
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
|
this.logger.error(`Device not found for user with email ${loginDto.email} and device id ${deviceId}`);
|
||||||
throw new UnauthorizedException('AUTH.DEVICE_NOT_FOUND');
|
throw new UnauthorizedException('AUTH.DEVICE_NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!device.publicKey) {
|
if (!device.publicKey) {
|
||||||
|
this.logger.error(`Biometric not enabled for user with email ${loginDto.email}`);
|
||||||
throw new UnauthorizedException('AUTH.BIOMETRIC_NOT_ENABLED');
|
throw new UnauthorizedException('AUTH.BIOMETRIC_NOT_ENABLED');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,15 +305,17 @@ export class AuthService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!isValidToken) {
|
if (!isValidToken) {
|
||||||
|
this.logger.error(`Invalid biometric for user with email ${loginDto.email}`);
|
||||||
throw new UnauthorizedException('AUTH.INVALID_BIOMETRIC');
|
throw new UnauthorizedException('AUTH.INVALID_BIOMETRIC');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
const tokens = await this.generateAuthToken(user);
|
||||||
|
this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`);
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateAuthToken(user: User) {
|
private async generateAuthToken(user: User) {
|
||||||
|
this.logger.log(`Generating auth token for user with id ${user.id}`);
|
||||||
const [accessToken, refreshToken] = await Promise.all([
|
const [accessToken, refreshToken] = await Promise.all([
|
||||||
this.jwtService.sign(
|
this.jwtService.sign(
|
||||||
{ sub: user.id, roles: user.roles },
|
{ sub: user.id, roles: user.roles },
|
||||||
@ -284,22 +333,20 @@ export class AuthService {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this.logger.log(`Auth token generated successfully for user with id ${user.id}`);
|
||||||
return { accessToken, refreshToken, expiresAt: new Date(this.jwtService.decode(accessToken).exp * ONE_THOUSAND) };
|
return { accessToken, refreshToken, expiresAt: new Date(this.jwtService.decode(accessToken).exp * ONE_THOUSAND) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private validatePassword(password: string, confirmPassword: string, user: User) {
|
private validatePassword(password: string, confirmPassword: string, user: User) {
|
||||||
|
this.logger.log(`Validating password for user with id ${user.id}`);
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
|
this.logger.error(`Password mismatch for user with id ${user.id}`);
|
||||||
throw new BadRequestException('AUTH.PASSWORD_MISMATCH');
|
throw new BadRequestException('AUTH.PASSWORD_MISMATCH');
|
||||||
}
|
}
|
||||||
|
|
||||||
const roles = user.roles;
|
if (!PASSCODE_REGEX.test(password)) {
|
||||||
|
this.logger.error(`Invalid password for user with id ${user.id}`);
|
||||||
if (roles.includes(Roles.GUARDIAN) && !PASSCODE_REGEX.test(password)) {
|
|
||||||
throw new BadRequestException('AUTH.INVALID_PASSCODE');
|
throw new BadRequestException('AUTH.INVALID_PASSCODE');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roles.includes(Roles.JUNIOR) && !PASSWORD_REGEX.test(password)) {
|
|
||||||
throw new BadRequestException('AUTH.INVALID_PASSWORD');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { Cacheable } from 'cacheable';
|
import { Cacheable } from 'cacheable';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CacheService {
|
export class CacheService {
|
||||||
|
private readonly logger = new Logger(CacheService.name);
|
||||||
constructor(@Inject('CACHE_INSTANCE') private readonly cache: Cacheable) {}
|
constructor(@Inject('CACHE_INSTANCE') private readonly cache: Cacheable) {}
|
||||||
|
|
||||||
get<T>(key: string): Promise<T | undefined> {
|
get<T>(key: string): Promise<T | undefined> {
|
||||||
|
this.logger.log(`Getting value for key ${key}`);
|
||||||
return this.cache.get(key);
|
return this.cache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async set<T>(key: string, value: T, ttl?: number | string): Promise<void> {
|
async set<T>(key: string, value: T, ttl?: number | string): Promise<void> {
|
||||||
|
this.logger.log(`Setting value for key ${key}`);
|
||||||
await this.cache.set(key, value, ttl);
|
await this.cache.set(key, value, ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(key: string): Promise<void> {
|
async delete(key: string): Promise<void> {
|
||||||
|
this.logger.log(`Deleting value for key ${key}`);
|
||||||
await this.cache.delete(key);
|
await this.cache.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { DocumentType } from '~/document/enums';
|
import { DocumentType } from '~/document/enums';
|
||||||
import { DocumentService, OciService } from '~/document/services';
|
import { DocumentService, OciService } from '~/document/services';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LookupService {
|
export class LookupService {
|
||||||
|
private readonly logger = new Logger(LookupService.name);
|
||||||
constructor(private readonly documentService: DocumentService, private readonly ociService: OciService) {}
|
constructor(private readonly documentService: DocumentService, private readonly ociService: OciService) {}
|
||||||
async findDefaultAvatar() {
|
async findDefaultAvatar() {
|
||||||
|
this.logger.log(`Finding default avatar`);
|
||||||
const documents = await this.documentService.findDocuments({ documentType: DocumentType.DEFAULT_AVATAR });
|
const documents = await this.documentService.findDocuments({ documentType: DocumentType.DEFAULT_AVATAR });
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
documents.map(async (document) => {
|
documents.map(async (document) => {
|
||||||
document.url = await this.ociService.generatePreSignedUrl(document);
|
document.url = await this.ociService.generatePreSignedUrl(document);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.logger.log(`Default avatar returned successfully`);
|
||||||
|
|
||||||
return documents;
|
return documents;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findDefaultTasksLogo() {
|
async findDefaultTasksLogo() {
|
||||||
|
this.logger.log(`Finding default tasks logos`);
|
||||||
const documents = await this.documentService.findDocuments({ documentType: DocumentType.DEFAULT_TASKS_LOGO });
|
const documents = await this.documentService.findDocuments({ documentType: DocumentType.DEFAULT_TASKS_LOGO });
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@ -26,6 +30,7 @@ export class LookupService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.logger.log(`Default tasks logos returned successfully`);
|
||||||
return documents;
|
return documents;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import * as admin from 'firebase-admin';
|
import * as admin from 'firebase-admin';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FirebaseService {
|
export class FirebaseService {
|
||||||
|
private readonly logger = new Logger(FirebaseService.name);
|
||||||
constructor(private readonly configService: ConfigService) {
|
constructor(private readonly configService: ConfigService) {
|
||||||
admin.initializeApp({
|
admin.initializeApp({
|
||||||
credential: admin.credential.cert({
|
credential: admin.credential.cert({
|
||||||
@ -14,6 +15,7 @@ export class FirebaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendNotification(tokens: string | string[], title: string, body: string) {
|
sendNotification(tokens: string | string[], title: string, body: string) {
|
||||||
|
this.logger.log(`Sending push notification to ${tokens}`);
|
||||||
const message = {
|
const message = {
|
||||||
notification: {
|
notification: {
|
||||||
title,
|
title,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
|
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
|
||||||
import { PageOptionsRequestDto } from '~/core/dtos';
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
import { DeviceService } from '~/user/services';
|
import { DeviceService } from '~/user/services';
|
||||||
@ -13,6 +13,7 @@ import { TwilioService } from './twilio.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotificationsService {
|
export class NotificationsService {
|
||||||
|
private readonly logger = new Logger(NotificationsService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly firebaseService: FirebaseService,
|
private readonly firebaseService: FirebaseService,
|
||||||
private readonly notificationRepository: NotificationsRepository,
|
private readonly notificationRepository: NotificationsRepository,
|
||||||
@ -22,11 +23,13 @@ export class NotificationsService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async sendPushNotification(userId: string, title: string, body: string) {
|
async sendPushNotification(userId: string, title: string, body: string) {
|
||||||
|
this.logger.log(`Sending push notification to user ${userId}`);
|
||||||
// Get the device tokens for the user
|
// Get the device tokens for the user
|
||||||
|
|
||||||
const tokens = await this.deviceService.getTokens(userId);
|
const tokens = await this.deviceService.getTokens(userId);
|
||||||
|
|
||||||
if (!tokens.length) {
|
if (!tokens.length) {
|
||||||
|
this.logger.log(`No device tokens found for user ${userId} but notification created in the database`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Send the notification
|
// Send the notification
|
||||||
@ -34,27 +37,34 @@ export class NotificationsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendSMS(to: string, body: string) {
|
async sendSMS(to: string, body: string) {
|
||||||
|
this.logger.log(`Sending SMS to ${to}`);
|
||||||
await this.twilioService.sendSMS(to, body);
|
await this.twilioService.sendSMS(to, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNotifications(userId: string, pageOptionsDto: PageOptionsRequestDto) {
|
async getNotifications(userId: string, pageOptionsDto: PageOptionsRequestDto) {
|
||||||
|
this.logger.log(`Getting notifications for user ${userId}`);
|
||||||
const [[notifications, count], unreadCount] = await Promise.all([
|
const [[notifications, count], unreadCount] = await Promise.all([
|
||||||
this.notificationRepository.getNotifications(userId, pageOptionsDto),
|
this.notificationRepository.getNotifications(userId, pageOptionsDto),
|
||||||
this.notificationRepository.getUnreadNotificationsCount(userId),
|
this.notificationRepository.getUnreadNotificationsCount(userId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this.logger.log(`Returning notifications for user ${userId}`);
|
||||||
|
|
||||||
return { notifications, count, unreadCount };
|
return { notifications, count, unreadCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
createNotification(notification: Partial<Notification>) {
|
createNotification(notification: Partial<Notification>) {
|
||||||
|
this.logger.log(`Creating notification for user ${notification.userId}`);
|
||||||
return this.notificationRepository.createNotification(notification);
|
return this.notificationRepository.createNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
markAsRead(userId: string) {
|
markAsRead(userId: string) {
|
||||||
|
this.logger.log(`Marking notifications as read for user ${userId}`);
|
||||||
return this.notificationRepository.markAsRead(userId);
|
return this.notificationRepository.markAsRead(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendOtpNotification(sendOtpRequest: ISendOtp, otp: string) {
|
async sendOtpNotification(sendOtpRequest: ISendOtp, otp: string) {
|
||||||
|
this.logger.log(`Sending OTP to ${sendOtpRequest.recipient}`);
|
||||||
const notification = await this.createNotification({
|
const notification = await this.createNotification({
|
||||||
recipient: sendOtpRequest.recipient,
|
recipient: sendOtpRequest.recipient,
|
||||||
title: OTP_TITLE,
|
title: OTP_TITLE,
|
||||||
@ -63,11 +73,16 @@ export class NotificationsService {
|
|||||||
channel: sendOtpRequest.otpType === OtpType.EMAIL ? NotificationChannel.EMAIL : NotificationChannel.SMS,
|
channel: sendOtpRequest.otpType === OtpType.EMAIL ? NotificationChannel.EMAIL : NotificationChannel.SMS,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.logger.log(`emitting ${EventType.NOTIFICATION_CREATED} event`);
|
||||||
|
|
||||||
return this.eventEmitter.emit(EventType.NOTIFICATION_CREATED, notification);
|
return this.eventEmitter.emit(EventType.NOTIFICATION_CREATED, notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent(EventType.NOTIFICATION_CREATED)
|
@OnEvent(EventType.NOTIFICATION_CREATED)
|
||||||
handleNotificationCreatedEvent(notification: Notification) {
|
handleNotificationCreatedEvent(notification: Notification) {
|
||||||
|
this.logger.log(
|
||||||
|
`Handling ${EventType.NOTIFICATION_CREATED} event for notification ${notification.id} and type ${notification.channel}`,
|
||||||
|
);
|
||||||
switch (notification.channel) {
|
switch (notification.channel) {
|
||||||
case NotificationChannel.SMS:
|
case NotificationChannel.SMS:
|
||||||
return this.sendSMS(notification.recipient!, notification.message);
|
return this.sendSMS(notification.recipient!, notification.message);
|
||||||
|
@ -8,6 +8,7 @@ export class TwilioService {
|
|||||||
constructor(private readonly twilioService: TwilioApiService, private readonly configService: ConfigService) {}
|
constructor(private readonly twilioService: TwilioApiService, private readonly configService: ConfigService) {}
|
||||||
private from = this.configService.getOrThrow('TWILIO_PHONE_NUMBER');
|
private from = this.configService.getOrThrow('TWILIO_PHONE_NUMBER');
|
||||||
sendSMS(to: string, body: string) {
|
sendSMS(to: string, body: string) {
|
||||||
|
this.logger.log(`Sending SMS to ${to}`);
|
||||||
if (this.configService.get('NODE_ENV') === Environment.DEV) {
|
if (this.configService.get('NODE_ENV') === Environment.DEV) {
|
||||||
this.logger.log(`Skipping SMS sending in DEV environment. Message: ${body} to: ${to}`);
|
this.logger.log(`Skipping SMS sending in DEV environment. Message: ${body} to: ${to}`);
|
||||||
return;
|
return;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { NotificationsService } from '../../notification/services/notifications.service';
|
import { NotificationsService } from '../../notification/services/notifications.service';
|
||||||
import { DEFAULT_OTP_DIGIT, DEFAULT_OTP_LENGTH } from '../constants';
|
import { DEFAULT_OTP_DIGIT, DEFAULT_OTP_LENGTH } from '../constants';
|
||||||
@ -9,27 +9,37 @@ import { generateRandomOtp } from '../utils';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OtpService {
|
export class OtpService {
|
||||||
|
private readonly logger = new Logger(OtpService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly otpRepository: OtpRepository,
|
private readonly otpRepository: OtpRepository,
|
||||||
private readonly notificationService: NotificationsService,
|
private readonly notificationService: NotificationsService,
|
||||||
) {}
|
) {}
|
||||||
private useMock = this.configService.get<boolean>('USE_MOCK', false);
|
private useMock = this.configService.get<boolean>('USE_MOCK', false);
|
||||||
async generateAndSendOtp(sendotpRequest: ISendOtp): Promise<string> {
|
async generateAndSendOtp(sendOtpRequest: ISendOtp): Promise<string> {
|
||||||
|
this.logger.log(`Generating OTP for ${sendOtpRequest.recipient}`);
|
||||||
const otp = this.useMock ? DEFAULT_OTP_DIGIT.repeat(DEFAULT_OTP_LENGTH) : generateRandomOtp(DEFAULT_OTP_LENGTH);
|
const otp = this.useMock ? DEFAULT_OTP_DIGIT.repeat(DEFAULT_OTP_LENGTH) : generateRandomOtp(DEFAULT_OTP_LENGTH);
|
||||||
|
|
||||||
await this.otpRepository.createOtp({ ...sendotpRequest, value: otp });
|
await this.otpRepository.createOtp({ ...sendOtpRequest, value: otp });
|
||||||
|
|
||||||
this.sendOtp(sendotpRequest, otp);
|
await this.sendOtp(sendOtpRequest, otp);
|
||||||
|
this.logger.log(`OTP generated and sent successfully to ${sendOtpRequest.recipient}`);
|
||||||
return sendotpRequest.otpType == OtpType.EMAIL
|
return sendOtpRequest.otpType == OtpType.EMAIL
|
||||||
? sendotpRequest.recipient
|
? sendOtpRequest.recipient
|
||||||
: sendotpRequest.recipient?.replace(/.(?=.{4})/g, '*');
|
: sendOtpRequest.recipient?.replace(/.(?=.{4})/g, '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyOtp(verifyOtpRequest: IVerifyOtp) {
|
async verifyOtp(verifyOtpRequest: IVerifyOtp) {
|
||||||
|
this.logger.log(`Verifying OTP for ${verifyOtpRequest.userId}`);
|
||||||
const otp = await this.otpRepository.findOtp(verifyOtpRequest);
|
const otp = await this.otpRepository.findOtp(verifyOtpRequest);
|
||||||
|
|
||||||
|
if (!otp) {
|
||||||
|
this.logger.error(
|
||||||
|
`OTP value ${verifyOtpRequest.value} not found for ${verifyOtpRequest.userId} and ${verifyOtpRequest.otpType}`,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return !!otp;
|
return !!otp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { OciService } from '~/document/services';
|
import { OciService } from '~/document/services';
|
||||||
import { User } from '~/user/entities';
|
import { User } from '~/user/entities';
|
||||||
import { DeviceService } from '~/user/services';
|
import { DeviceService } from '~/user/services';
|
||||||
@ -8,46 +8,56 @@ import { CustomerRepository } from '../repositories/customer.repository';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CustomerService {
|
export class CustomerService {
|
||||||
|
private readonly logger = new Logger(CustomerService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly customerRepository: CustomerRepository,
|
private readonly customerRepository: CustomerRepository,
|
||||||
private readonly ociService: OciService,
|
private readonly ociService: OciService,
|
||||||
private readonly deviceService: DeviceService,
|
private readonly deviceService: DeviceService,
|
||||||
) {}
|
) {}
|
||||||
async updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto, deviceId: string) {
|
async updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto, deviceId: string) {
|
||||||
|
this.logger.log(`Updating notification settings for user ${userId}`);
|
||||||
const customer = await this.findCustomerById(userId);
|
const customer = await this.findCustomerById(userId);
|
||||||
|
|
||||||
const notificationSettings = (await this.customerRepository.updateNotificationSettings(customer, data))
|
const notificationSettings = (await this.customerRepository.updateNotificationSettings(customer, data))
|
||||||
.notificationSettings;
|
.notificationSettings;
|
||||||
|
|
||||||
if (data.isPushEnabled && deviceId) {
|
if (data.isPushEnabled && deviceId) {
|
||||||
|
this.logger.log(`Updating device ${deviceId} with fcmToken`);
|
||||||
await this.deviceService.updateDevice(deviceId, {
|
await this.deviceService.updateDevice(deviceId, {
|
||||||
fcmToken: data.fcmToken,
|
fcmToken: data.fcmToken,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.logger.log(`Notification settings updated for user ${userId}`);
|
||||||
return notificationSettings;
|
return notificationSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCustomer(userId: string, data: UpdateCustomerRequestDto): Promise<Customer> {
|
async updateCustomer(userId: string, data: UpdateCustomerRequestDto): Promise<Customer> {
|
||||||
|
this.logger.log(`Updating customer ${userId}`);
|
||||||
await this.customerRepository.updateCustomer(userId, data);
|
await this.customerRepository.updateCustomer(userId, data);
|
||||||
|
this.logger.log(`Customer ${userId} updated successfully`);
|
||||||
return this.findCustomerById(userId);
|
return this.findCustomerById(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
createCustomer(customerData: Partial<Customer>, user: User) {
|
createCustomer(customerData: Partial<Customer>, user: User) {
|
||||||
|
this.logger.log(`Creating customer for user ${user.id}`);
|
||||||
return this.customerRepository.createCustomer(customerData, user);
|
return this.customerRepository.createCustomer(customerData, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCustomerById(id: string) {
|
async findCustomerById(id: string) {
|
||||||
|
this.logger.log(`Finding customer ${id}`);
|
||||||
const customer = await this.customerRepository.findOne({ id });
|
const customer = await this.customerRepository.findOne({ id });
|
||||||
|
|
||||||
if (!customer) {
|
if (!customer) {
|
||||||
|
this.logger.error(`Customer ${id} not found`);
|
||||||
throw new BadRequestException('CUSTOMER.NOT_FOUND');
|
throw new BadRequestException('CUSTOMER.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customer.profilePicture) {
|
if (customer.profilePicture) {
|
||||||
|
this.logger.log(`Generating pre-signed url for profile picture of customer ${id}`);
|
||||||
customer.profilePicture.url = await this.ociService.generatePreSignedUrl(customer.profilePicture);
|
customer.profilePicture.url = await this.ociService.generatePreSignedUrl(customer.profilePicture);
|
||||||
}
|
}
|
||||||
|
this.logger.log(`Customer ${id} found successfully`);
|
||||||
return customer;
|
return customer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
import { UploadDocumentRequestDto } from '../dtos/request';
|
import { UploadDocumentRequestDto } from '../dtos/request';
|
||||||
import { Document } from '../entities';
|
import { Document } from '../entities';
|
||||||
@ -7,13 +7,16 @@ import { OciService } from './oci.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DocumentService {
|
export class DocumentService {
|
||||||
|
private readonly logger = new Logger(DocumentService.name);
|
||||||
constructor(private readonly ociService: OciService, private readonly documentRepository: DocumentRepository) {}
|
constructor(private readonly ociService: OciService, private readonly documentRepository: DocumentRepository) {}
|
||||||
async createDocument(file: Express.Multer.File, uploadedDocumentRequest: UploadDocumentRequestDto) {
|
async createDocument(file: Express.Multer.File, uploadedDocumentRequest: UploadDocumentRequestDto) {
|
||||||
|
this.logger.log(`creating document for with type ${uploadedDocumentRequest.documentType}`);
|
||||||
const uploadedFile = await this.ociService.uploadFile(file, uploadedDocumentRequest);
|
const uploadedFile = await this.ociService.uploadFile(file, uploadedDocumentRequest);
|
||||||
return this.documentRepository.createDocument(uploadedFile);
|
return this.documentRepository.createDocument(uploadedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
findDocuments(where: FindOptionsWhere<Document>) {
|
findDocuments(where: FindOptionsWhere<Document>) {
|
||||||
|
this.logger.log(`finding documents with where clause ${JSON.stringify(where)}`);
|
||||||
return this.documentRepository.findDocuments(where);
|
return this.documentRepository.findDocuments(where);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ export class OciService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async uploadFile(file: Express.Multer.File, { documentType }: UploadDocumentRequestDto): Promise<UploadResponseDto> {
|
async uploadFile(file: Express.Multer.File, { documentType }: UploadDocumentRequestDto): Promise<UploadResponseDto> {
|
||||||
|
this.logger.log(`Uploading file with type ${documentType}`);
|
||||||
const bucketName = BUCKETS[documentType];
|
const bucketName = BUCKETS[documentType];
|
||||||
const objectName = generateNewFileName(file.originalname);
|
const objectName = generateNewFileName(file.originalname);
|
||||||
|
|
||||||
@ -66,12 +67,16 @@ export class OciService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async generatePreSignedUrl(document?: Document): Promise<string | any> {
|
async generatePreSignedUrl(document?: Document): Promise<string | any> {
|
||||||
|
this.logger.log(`Generating pre-signed url for document ${document?.id}`);
|
||||||
if (!document) {
|
if (!document) {
|
||||||
|
this.logger.error('Document not found, skipping pre-signed url generation');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedUrl = await this.cacheService.get<string>(document.id);
|
const cachedUrl = await this.cacheService.get<string>(document.id);
|
||||||
|
|
||||||
if (cachedUrl) {
|
if (cachedUrl) {
|
||||||
|
this.logger.debug(`Returning cached pre-signed url for document ${document.id}`);
|
||||||
return cachedUrl;
|
return cachedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,8 +97,9 @@ export class OciService {
|
|||||||
},
|
},
|
||||||
retryConfiguration: { terminationStrategy: { shouldTerminate: () => true } },
|
retryConfiguration: { terminationStrategy: { shouldTerminate: () => true } },
|
||||||
});
|
});
|
||||||
|
this.logger.log(`Pre-signed url generated successfully for document ${document.id}`);
|
||||||
this.cacheService.set(document.id, res.preauthenticatedRequest.fullPath + objectName, '1h');
|
await this.cacheService.set(document.id, res.preauthenticatedRequest.fullPath + objectName, '1h');
|
||||||
|
this.logger.log(`Pre-signed url cached for document ${document.id}`);
|
||||||
return res.preauthenticatedRequest.fullPath + objectName;
|
return res.preauthenticatedRequest.fullPath + objectName;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error generating pre-signed url: ${error}`);
|
this.logger.error(`Error generating pre-signed url: ${error}`);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { Roles } from '~/auth/enums';
|
import { Roles } from '~/auth/enums';
|
||||||
import { IJwtPayload } from '~/auth/interfaces';
|
import { IJwtPayload } from '~/auth/interfaces';
|
||||||
import { OciService } from '~/document/services';
|
import { OciService } from '~/document/services';
|
||||||
@ -10,6 +10,7 @@ import { GiftsRepository } from '../repositories';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GiftsService {
|
export class GiftsService {
|
||||||
|
private readonly logger = new Logger(GiftsService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly juniorService: JuniorService,
|
private readonly juniorService: JuniorService,
|
||||||
private readonly giftsRepository: GiftsRepository,
|
private readonly giftsRepository: GiftsRepository,
|
||||||
@ -17,42 +18,51 @@ export class GiftsService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createGift(guardianId: string, body: CreateGiftRequestDto) {
|
async createGift(guardianId: string, body: CreateGiftRequestDto) {
|
||||||
|
this.logger.log(`Creating gift for junior ${body.recipientId} by guardian ${guardianId}`);
|
||||||
const doesJuniorBelongToGuardian = await this.juniorService.doesJuniorBelongToGuardian(
|
const doesJuniorBelongToGuardian = await this.juniorService.doesJuniorBelongToGuardian(
|
||||||
guardianId,
|
guardianId,
|
||||||
body.recipientId,
|
body.recipientId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!doesJuniorBelongToGuardian) {
|
if (!doesJuniorBelongToGuardian) {
|
||||||
|
this.logger.error(`Junior ${body.recipientId} does not belong to guardian ${guardianId}`);
|
||||||
throw new BadRequestException('JUNIOR.DOES_NOT_BELONG_TO_GUARDIAN');
|
throw new BadRequestException('JUNIOR.DOES_NOT_BELONG_TO_GUARDIAN');
|
||||||
}
|
}
|
||||||
|
|
||||||
const gift = await this.giftsRepository.create(guardianId, body);
|
const gift = await this.giftsRepository.create(guardianId, body);
|
||||||
|
this.logger.log(`Gift ${gift.id} created successfully`);
|
||||||
return this.findUserGiftById({ sub: guardianId, roles: [Roles.GUARDIAN] }, gift.id);
|
return this.findUserGiftById({ sub: guardianId, roles: [Roles.GUARDIAN] }, gift.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findUserGiftById(user: IJwtPayload, giftId: string) {
|
async findUserGiftById(user: IJwtPayload, giftId: string) {
|
||||||
|
this.logger.log(`Finding gift ${giftId} for user ${user.sub} with roles ${user.roles}`);
|
||||||
const gift = user.roles.includes(Roles.GUARDIAN)
|
const gift = user.roles.includes(Roles.GUARDIAN)
|
||||||
? await this.giftsRepository.findGuardianGiftById(user.sub, giftId)
|
? await this.giftsRepository.findGuardianGiftById(user.sub, giftId)
|
||||||
: await this.giftsRepository.findJuniorGiftById(user.sub, giftId);
|
: await this.giftsRepository.findJuniorGiftById(user.sub, giftId);
|
||||||
|
|
||||||
if (!gift) {
|
if (!gift) {
|
||||||
|
this.logger.error(`Gift ${giftId} not found`);
|
||||||
throw new BadRequestException('GIFT.NOT_FOUND');
|
throw new BadRequestException('GIFT.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prepareGiftImages([gift]);
|
await this.prepareGiftImages([gift]);
|
||||||
|
|
||||||
|
this.logger.log(`Gift ${giftId} found successfully`);
|
||||||
|
|
||||||
return gift;
|
return gift;
|
||||||
}
|
}
|
||||||
|
|
||||||
async redeemGift(juniorId: string, giftId: string) {
|
async redeemGift(juniorId: string, giftId: string) {
|
||||||
|
this.logger.log(`Redeeming gift ${giftId} for junior ${juniorId}`);
|
||||||
const gift = await this.giftsRepository.findJuniorGiftById(juniorId, giftId);
|
const gift = await this.giftsRepository.findJuniorGiftById(juniorId, giftId);
|
||||||
|
|
||||||
if (!gift) {
|
if (!gift) {
|
||||||
|
this.logger.error(`Gift ${giftId} not found`);
|
||||||
throw new BadRequestException('GIFT.NOT_FOUND');
|
throw new BadRequestException('GIFT.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gift.status === GiftStatus.REDEEMED) {
|
if (gift.status === GiftStatus.REDEEMED) {
|
||||||
|
this.logger.error(`Gift ${giftId} already redeemed`);
|
||||||
throw new BadRequestException('GIFT.ALREADY_REDEEMED');
|
throw new BadRequestException('GIFT.ALREADY_REDEEMED');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,13 +70,16 @@ export class GiftsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async UndoGiftRedemption(juniorId: string, giftId: string) {
|
async UndoGiftRedemption(juniorId: string, giftId: string) {
|
||||||
|
this.logger.log(`Undoing gift redemption for junior ${juniorId} and gift ${giftId}`);
|
||||||
const gift = await this.giftsRepository.findJuniorGiftById(juniorId, giftId);
|
const gift = await this.giftsRepository.findJuniorGiftById(juniorId, giftId);
|
||||||
|
|
||||||
if (!gift) {
|
if (!gift) {
|
||||||
|
this.logger.error(`Gift ${giftId} not found`);
|
||||||
throw new BadRequestException('GIFT.NOT_FOUND');
|
throw new BadRequestException('GIFT.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gift.status === GiftStatus.AVAILABLE) {
|
if (gift.status === GiftStatus.AVAILABLE) {
|
||||||
|
this.logger.error(`Gift ${giftId} is not redeemed yet`);
|
||||||
throw new BadRequestException('GIFT.NOT_REDEEMED');
|
throw new BadRequestException('GIFT.NOT_REDEEMED');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,13 +87,16 @@ export class GiftsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async replyToGift(juniorId: string, giftId: string, body: GiftReplyRequestDto) {
|
async replyToGift(juniorId: string, giftId: string, body: GiftReplyRequestDto) {
|
||||||
|
this.logger.log(`Replying to gift ${giftId} for junior ${juniorId}`);
|
||||||
const gift = await this.giftsRepository.findJuniorGiftById(juniorId, giftId);
|
const gift = await this.giftsRepository.findJuniorGiftById(juniorId, giftId);
|
||||||
|
|
||||||
if (!gift) {
|
if (!gift) {
|
||||||
|
this.logger.error(`Gift ${giftId} not found`);
|
||||||
throw new BadRequestException('GIFT.NOT_FOUND');
|
throw new BadRequestException('GIFT.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gift.reply) {
|
if (gift.reply) {
|
||||||
|
this.logger.error(`Gift ${giftId} already replied`);
|
||||||
throw new BadRequestException('GIFT.ALREADY_REPLIED');
|
throw new BadRequestException('GIFT.ALREADY_REPLIED');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,10 +104,12 @@ export class GiftsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findGifts(user: IJwtPayload, filters: GiftFiltersRequestDto) {
|
findGifts(user: IJwtPayload, filters: GiftFiltersRequestDto) {
|
||||||
|
this.logger.log(`Finding gifts for user ${user.sub} with roles ${user.roles}`);
|
||||||
return this.giftsRepository.findGifts(user, filters);
|
return this.giftsRepository.findGifts(user, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async prepareGiftImages(gifts: Gift[]) {
|
private async prepareGiftImages(gifts: Gift[]) {
|
||||||
|
this.logger.log('Preparing gifts image');
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
gifts.map(async (gift) => {
|
gifts.map(async (gift) => {
|
||||||
gift.image.url = await this.ociService.generatePreSignedUrl(gift.image);
|
gift.image.url = await this.ociService.generatePreSignedUrl(gift.image);
|
||||||
|
@ -1,33 +1,41 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { JuniorTokenRepository } from '../repositories';
|
import { JuniorTokenRepository } from '../repositories';
|
||||||
import { QrcodeService } from './qrcode.service';
|
import { QrcodeService } from './qrcode.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JuniorTokenService {
|
export class JuniorTokenService {
|
||||||
|
private readonly logger = new Logger(JuniorTokenService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly juniorTokenRepository: JuniorTokenRepository,
|
private readonly juniorTokenRepository: JuniorTokenRepository,
|
||||||
private readonly qrCodeService: QrcodeService,
|
private readonly qrCodeService: QrcodeService,
|
||||||
) {}
|
) {}
|
||||||
async generateToken(juniorId: string): Promise<string> {
|
async generateToken(juniorId: string): Promise<string> {
|
||||||
|
this.logger.log(`Generating token for junior ${juniorId}`);
|
||||||
const tokenEntity = await this.juniorTokenRepository.generateToken(juniorId);
|
const tokenEntity = await this.juniorTokenRepository.generateToken(juniorId);
|
||||||
|
this.logger.log(`Token generated successfully for junior ${juniorId}`);
|
||||||
return this.qrCodeService.generateQrCode(tokenEntity.token);
|
return this.qrCodeService.generateQrCode(tokenEntity.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateToken(token: string) {
|
async validateToken(token: string) {
|
||||||
|
this.logger.log(`Validating token ${token}`);
|
||||||
const tokenEntity = await this.juniorTokenRepository.findByToken(token);
|
const tokenEntity = await this.juniorTokenRepository.findByToken(token);
|
||||||
|
|
||||||
if (!tokenEntity) {
|
if (!tokenEntity) {
|
||||||
|
this.logger.error(`Token ${token} not found`);
|
||||||
throw new BadRequestException('TOKEN.INVALID');
|
throw new BadRequestException('TOKEN.INVALID');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokenEntity.expiryDate < new Date()) {
|
if (tokenEntity.expiryDate < new Date()) {
|
||||||
|
this.logger.error(`Token ${token} expired`);
|
||||||
throw new BadRequestException('TOKEN.EXPIRED');
|
throw new BadRequestException('TOKEN.EXPIRED');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Token validated successfully`);
|
||||||
return tokenEntity.juniorId;
|
return tokenEntity.juniorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateToken(token: string) {
|
invalidateToken(token: string) {
|
||||||
|
this.logger.log(`Invalidating token ${token}`);
|
||||||
return this.juniorTokenRepository.invalidateToken(token);
|
return this.juniorTokenRepository.invalidateToken(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { Transactional } from 'typeorm-transactional';
|
import { Transactional } from 'typeorm-transactional';
|
||||||
import { Roles } from '~/auth/enums';
|
import { Roles } from '~/auth/enums';
|
||||||
import { PageOptionsRequestDto } from '~/core/dtos';
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
@ -12,6 +12,7 @@ import { JuniorTokenService } from './junior-token.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JuniorService {
|
export class JuniorService {
|
||||||
|
private readonly logger = new Logger(JuniorService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly juniorRepository: JuniorRepository,
|
private readonly juniorRepository: JuniorRepository,
|
||||||
private readonly juniorTokenService: JuniorTokenService,
|
private readonly juniorTokenService: JuniorTokenService,
|
||||||
@ -21,9 +22,11 @@ export class JuniorService {
|
|||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async createJuniors(body: CreateJuniorRequestDto, guardianId: string) {
|
async createJuniors(body: CreateJuniorRequestDto, guardianId: string) {
|
||||||
|
this.logger.log(`Creating junior for guardian ${guardianId}`);
|
||||||
const existingUser = await this.userService.findUser([{ email: body.email }, { phoneNumber: body.phoneNumber }]);
|
const existingUser = await this.userService.findUser([{ email: body.email }, { phoneNumber: body.phoneNumber }]);
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
|
this.logger.error(`User with email ${body.email} or phone number ${body.phoneNumber} already exists`);
|
||||||
throw new BadRequestException('USER.ALREADY_EXISTS');
|
throw new BadRequestException('USER.ALREADY_EXISTS');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,43 +54,56 @@ export class JuniorService {
|
|||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.logger.log(`Junior ${user.id} created successfully`);
|
||||||
|
|
||||||
return this.juniorTokenService.generateToken(user.id);
|
return this.juniorTokenService.generateToken(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findJuniorById(juniorId: string, withGuardianRelation = false, guardianId?: string) {
|
async findJuniorById(juniorId: string, withGuardianRelation = false, guardianId?: string) {
|
||||||
|
this.logger.log(`Finding junior ${juniorId}`);
|
||||||
const junior = await this.juniorRepository.findJuniorById(juniorId, withGuardianRelation, guardianId);
|
const junior = await this.juniorRepository.findJuniorById(juniorId, withGuardianRelation, guardianId);
|
||||||
|
|
||||||
if (!junior) {
|
if (!junior) {
|
||||||
|
this.logger.error(`Junior ${juniorId} not found`);
|
||||||
throw new BadRequestException('JUNIOR.NOT_FOUND');
|
throw new BadRequestException('JUNIOR.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Junior ${juniorId} found successfully`);
|
||||||
return junior;
|
return junior;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async setTheme(body: SetThemeRequestDto, juniorId: string) {
|
async setTheme(body: SetThemeRequestDto, juniorId: string) {
|
||||||
|
this.logger.log(`Setting theme for junior ${juniorId}`);
|
||||||
const junior = await this.findJuniorById(juniorId);
|
const junior = await this.findJuniorById(juniorId);
|
||||||
if (junior.theme) {
|
if (junior.theme) {
|
||||||
|
this.logger.log(`Removing existing theme for junior ${juniorId}`);
|
||||||
await this.juniorRepository.removeTheme(junior.theme);
|
await this.juniorRepository.removeTheme(junior.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.juniorRepository.setTheme(body, junior);
|
await this.juniorRepository.setTheme(body, junior);
|
||||||
|
this.logger.log(`Theme set for junior ${juniorId}`);
|
||||||
return this.juniorRepository.findThemeForJunior(juniorId);
|
return this.juniorRepository.findThemeForJunior(juniorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
findJuniorsByGuardianId(guardianId: string, pageOptions: PageOptionsRequestDto) {
|
findJuniorsByGuardianId(guardianId: string, pageOptions: PageOptionsRequestDto) {
|
||||||
|
this.logger.log(`Finding juniors for guardian ${guardianId}`);
|
||||||
return this.juniorRepository.findJuniorsByGuardianId(guardianId, pageOptions);
|
return this.juniorRepository.findJuniorsByGuardianId(guardianId, pageOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateToken(token: string) {
|
async validateToken(token: string) {
|
||||||
|
this.logger.log(`Validating token ${token}`);
|
||||||
const juniorId = await this.juniorTokenService.validateToken(token);
|
const juniorId = await this.juniorTokenService.validateToken(token);
|
||||||
|
|
||||||
return this.findJuniorById(juniorId, true);
|
return this.findJuniorById(juniorId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateToken(juniorId: string) {
|
generateToken(juniorId: string) {
|
||||||
|
this.logger.log(`Generating token for junior ${juniorId}`);
|
||||||
return this.juniorTokenService.generateToken(juniorId);
|
return this.juniorTokenService.generateToken(juniorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async doesJuniorBelongToGuardian(guardianId: string, juniorId: string) {
|
async doesJuniorBelongToGuardian(guardianId: string, juniorId: string) {
|
||||||
|
this.logger.log(`Checking if junior ${juniorId} belongs to guardian ${guardianId}`);
|
||||||
const junior = await this.findJuniorById(juniorId, false, guardianId);
|
const junior = await this.findJuniorById(juniorId, false, guardianId);
|
||||||
|
|
||||||
return !!junior;
|
return !!junior;
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import * as qrcode from 'qrcode';
|
import * as qrcode from 'qrcode';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QrcodeService {
|
export class QrcodeService {
|
||||||
|
private readonly logger = new Logger(QrcodeService.name);
|
||||||
generateQrCode(token: string): Promise<string> {
|
generateQrCode(token: string): Promise<string> {
|
||||||
|
this.logger.log(`Generating QR code for token ${token}`);
|
||||||
return qrcode.toDataURL(token);
|
return qrcode.toDataURL(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { OciService } from '~/document/services';
|
import { OciService } from '~/document/services';
|
||||||
import { JuniorService } from '~/junior/services';
|
import { JuniorService } from '~/junior/services';
|
||||||
import { CreateMoneyRequestRequestDto, MoneyRequestsFiltersRequestDto } from '../dtos/request';
|
import { CreateMoneyRequestRequestDto, MoneyRequestsFiltersRequestDto } from '../dtos/request';
|
||||||
@ -8,6 +8,7 @@ import { MoneyRequestsRepository } from '../repositories';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MoneyRequestsService {
|
export class MoneyRequestsService {
|
||||||
|
private readonly logger = new Logger(MoneyRequestsService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly moneyRequestsRepository: MoneyRequestsRepository,
|
private readonly moneyRequestsRepository: MoneyRequestsRepository,
|
||||||
private readonly juniorService: JuniorService,
|
private readonly juniorService: JuniorService,
|
||||||
@ -15,30 +16,36 @@ export class MoneyRequestsService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createMoneyRequest(userId: string, body: CreateMoneyRequestRequestDto) {
|
async createMoneyRequest(userId: string, body: CreateMoneyRequestRequestDto) {
|
||||||
|
this.logger.log(`Creating money request for junior ${userId}`);
|
||||||
if (body.frequency === MoneyRequestFrequency.ONE_TIME) {
|
if (body.frequency === MoneyRequestFrequency.ONE_TIME) {
|
||||||
|
this.logger.log(`Setting end date to null for one time money request`);
|
||||||
delete body.endDate;
|
delete body.endDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.endDate && new Date(body.endDate) < new Date()) {
|
if (body.endDate && new Date(body.endDate) < new Date()) {
|
||||||
|
this.logger.error(`End date ${body.endDate} is in the past`);
|
||||||
throw new BadRequestException('MONEY_REQUEST.END_DATE_IN_THE_PAST');
|
throw new BadRequestException('MONEY_REQUEST.END_DATE_IN_THE_PAST');
|
||||||
}
|
}
|
||||||
|
|
||||||
const junior = await this.juniorService.findJuniorById(userId, true);
|
const junior = await this.juniorService.findJuniorById(userId, true);
|
||||||
|
|
||||||
const moneyRequest = await this.moneyRequestsRepository.createMoneyRequest(junior.id, junior.guardianId, body);
|
const moneyRequest = await this.moneyRequestsRepository.createMoneyRequest(junior.id, junior.guardianId, body);
|
||||||
|
this.logger.log(`Money request ${moneyRequest.id} created successfully`);
|
||||||
return this.findMoneyRequestById(moneyRequest.id);
|
return this.findMoneyRequestById(moneyRequest.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findMoneyRequestById(moneyRequestId: string, reviewerId?: string) {
|
async findMoneyRequestById(moneyRequestId: string, reviewerId?: string) {
|
||||||
|
this.logger.log(`Finding money request ${moneyRequestId} ${reviewerId ? `by reviewer ${reviewerId}` : ''}`);
|
||||||
const moneyRequest = await this.moneyRequestsRepository.findMoneyRequestById(moneyRequestId, reviewerId);
|
const moneyRequest = await this.moneyRequestsRepository.findMoneyRequestById(moneyRequestId, reviewerId);
|
||||||
|
|
||||||
if (!moneyRequest) {
|
if (!moneyRequest) {
|
||||||
|
this.logger.error(`Money request ${moneyRequestId} not found ${reviewerId ? `for reviewer ${reviewerId}` : ''}`);
|
||||||
throw new BadRequestException('MONEY_REQUEST.NOT_FOUND');
|
throw new BadRequestException('MONEY_REQUEST.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prepareMoneyRequestDocument([moneyRequest]);
|
await this.prepareMoneyRequestDocument([moneyRequest]);
|
||||||
|
|
||||||
|
this.logger.log(`Money request ${moneyRequestId} found successfully`);
|
||||||
return moneyRequest;
|
return moneyRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,12 +53,16 @@ export class MoneyRequestsService {
|
|||||||
ReviewerId: string,
|
ReviewerId: string,
|
||||||
filters: MoneyRequestsFiltersRequestDto,
|
filters: MoneyRequestsFiltersRequestDto,
|
||||||
): Promise<[MoneyRequest[], number]> {
|
): Promise<[MoneyRequest[], number]> {
|
||||||
|
this.logger.log(`Finding money requests for reviewer ${ReviewerId}`);
|
||||||
const [moneyRequests, itemCount] = await this.moneyRequestsRepository.findMoneyRequests(ReviewerId, filters);
|
const [moneyRequests, itemCount] = await this.moneyRequestsRepository.findMoneyRequests(ReviewerId, filters);
|
||||||
await this.prepareMoneyRequestDocument(moneyRequests);
|
await this.prepareMoneyRequestDocument(moneyRequests);
|
||||||
|
|
||||||
|
this.logger.log(`Returning money requests for reviewer ${ReviewerId}`);
|
||||||
return [moneyRequests, itemCount];
|
return [moneyRequests, itemCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
async approveMoneyRequest(moneyRequestId: string, reviewerId: string) {
|
async approveMoneyRequest(moneyRequestId: string, reviewerId: string) {
|
||||||
|
this.logger.log(`Approving money request ${moneyRequestId} by reviewer ${reviewerId}`);
|
||||||
await this.validateMoneyRequestForReview(moneyRequestId, reviewerId);
|
await this.validateMoneyRequestForReview(moneyRequestId, reviewerId);
|
||||||
await this.moneyRequestsRepository.updateMoneyRequestStatus(
|
await this.moneyRequestsRepository.updateMoneyRequestStatus(
|
||||||
moneyRequestId,
|
moneyRequestId,
|
||||||
@ -63,6 +74,7 @@ export class MoneyRequestsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async rejectMoneyRequest(moneyRequestId: string, reviewerId: string) {
|
async rejectMoneyRequest(moneyRequestId: string, reviewerId: string) {
|
||||||
|
this.logger.log(`Rejecting money request ${moneyRequestId} by reviewer ${reviewerId}`);
|
||||||
await this.validateMoneyRequestForReview(moneyRequestId, reviewerId);
|
await this.validateMoneyRequestForReview(moneyRequestId, reviewerId);
|
||||||
|
|
||||||
await this.moneyRequestsRepository.updateMoneyRequestStatus(
|
await this.moneyRequestsRepository.updateMoneyRequestStatus(
|
||||||
@ -75,6 +87,7 @@ export class MoneyRequestsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async prepareMoneyRequestDocument(moneyRequests: MoneyRequest[]) {
|
private async prepareMoneyRequestDocument(moneyRequests: MoneyRequest[]) {
|
||||||
|
this.logger.log(`Preparing document for money requests`);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
moneyRequests.map(async (moneyRequest) => {
|
moneyRequests.map(async (moneyRequest) => {
|
||||||
const profilePicture = moneyRequest.requester.customer.profilePicture;
|
const profilePicture = moneyRequest.requester.customer.profilePicture;
|
||||||
@ -87,12 +100,15 @@ export class MoneyRequestsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async validateMoneyRequestForReview(moneyRequestId: string, reviewerId: string) {
|
private async validateMoneyRequestForReview(moneyRequestId: string, reviewerId: string) {
|
||||||
|
this.logger.log(`Validating money request ${moneyRequestId} for reviewer ${reviewerId}`);
|
||||||
const moneyRequest = await this.moneyRequestsRepository.findMoneyRequestById(moneyRequestId, reviewerId);
|
const moneyRequest = await this.moneyRequestsRepository.findMoneyRequestById(moneyRequestId, reviewerId);
|
||||||
|
|
||||||
if (!moneyRequest) {
|
if (!moneyRequest) {
|
||||||
|
this.logger.error(`Money request ${moneyRequestId} not found`);
|
||||||
throw new BadRequestException('MONEY_REQUEST.NOT_FOUND');
|
throw new BadRequestException('MONEY_REQUEST.NOT_FOUND');
|
||||||
}
|
}
|
||||||
if (moneyRequest.status !== MoneyRequestStatus.PENDING) {
|
if (moneyRequest.status !== MoneyRequestStatus.PENDING) {
|
||||||
|
this.logger.error(`Money request ${moneyRequestId} already reviewed`);
|
||||||
throw new BadRequestException('MONEY_REQUEST.ALREADY_REVIEWED');
|
throw new BadRequestException('MONEY_REQUEST.ALREADY_REVIEWED');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { createCategoryRequestDto } from '../dtos/request';
|
import { createCategoryRequestDto } from '../dtos/request';
|
||||||
import { Category } from '../entities';
|
import { Category } from '../entities';
|
||||||
import { CategoryType } from '../enums';
|
import { CategoryType } from '../enums';
|
||||||
@ -6,27 +6,35 @@ import { CategoryRepository } from '../repositories';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CategoryService {
|
export class CategoryService {
|
||||||
|
private readonly logger = new Logger(CategoryService.name);
|
||||||
constructor(private readonly categoryRepository: CategoryRepository) {}
|
constructor(private readonly categoryRepository: CategoryRepository) {}
|
||||||
|
|
||||||
createCustomCategory(juniorId: string, body: createCategoryRequestDto) {
|
createCustomCategory(juniorId: string, body: createCategoryRequestDto) {
|
||||||
|
this.logger.log(`Creating custom category for junior ${juniorId}`);
|
||||||
return this.categoryRepository.createCustomCategory(juniorId, body);
|
return this.categoryRepository.createCustomCategory(juniorId, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCategoryByIds(categoryIds: string[], juniorId: string) {
|
async findCategoryByIds(categoryIds: string[], juniorId: string) {
|
||||||
|
this.logger.log(`Finding categories by ids for junior ${juniorId} with ids ${categoryIds}`);
|
||||||
const categories = await this.categoryRepository.findCategoryById(categoryIds, juniorId);
|
const categories = await this.categoryRepository.findCategoryById(categoryIds, juniorId);
|
||||||
|
|
||||||
if (categories.length !== categoryIds.length) {
|
if (categories.length !== categoryIds.length) {
|
||||||
throw new BadRequestException('CATEGORY.NOT_FOUND');
|
this.logger.error(`Invalid category ids ${categoryIds}`);
|
||||||
|
throw new BadRequestException('CATEGORY.INVALID_CATEGORY_ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Categories found successfully for junior ${juniorId}`);
|
||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCategories(juniorId: string): Promise<{ globalCategories: Category[]; customCategories: Category[] }> {
|
async findCategories(juniorId: string): Promise<{ globalCategories: Category[]; customCategories: Category[] }> {
|
||||||
|
this.logger.log(`Finding categories for junior ${juniorId}`);
|
||||||
const categories = await this.categoryRepository.findCategories(juniorId);
|
const categories = await this.categoryRepository.findCategories(juniorId);
|
||||||
|
|
||||||
const globalCategories = categories.filter((category) => category.type === CategoryType.GLOBAL);
|
const globalCategories = categories.filter((category) => category.type === CategoryType.GLOBAL);
|
||||||
const customCategories = categories.filter((category) => category.type === CategoryType.CUSTOM);
|
const customCategories = categories.filter((category) => category.type === CategoryType.CUSTOM);
|
||||||
|
|
||||||
|
this.logger.log(`Returning categories for junior ${juniorId}`);
|
||||||
return {
|
return {
|
||||||
globalCategories,
|
globalCategories,
|
||||||
customCategories,
|
customCategories,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { PageOptionsRequestDto } from '~/core/dtos';
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
import { OciService } from '~/document/services';
|
import { OciService } from '~/document/services';
|
||||||
@ -9,6 +9,7 @@ import { SavingGoalsRepository } from '../repositories';
|
|||||||
import { CategoryService } from './category.service';
|
import { CategoryService } from './category.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SavingGoalsService {
|
export class SavingGoalsService {
|
||||||
|
private readonly logger = new Logger(SavingGoalsService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly savingGoalsRepository: SavingGoalsRepository,
|
private readonly savingGoalsRepository: SavingGoalsRepository,
|
||||||
private readonly categoryService: CategoryService,
|
private readonly categoryService: CategoryService,
|
||||||
@ -16,7 +17,9 @@ export class SavingGoalsService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createGoal(juniorId: string, body: CreateGoalRequestDto) {
|
async createGoal(juniorId: string, body: CreateGoalRequestDto) {
|
||||||
|
this.logger.log(`Creating goal for junior ${juniorId}`);
|
||||||
if (moment(body.dueDate).isBefore(moment())) {
|
if (moment(body.dueDate).isBefore(moment())) {
|
||||||
|
this.logger.error(`Due date must be in the future`);
|
||||||
throw new BadRequestException('GOAL.DUE_DATE_MUST_BE_IN_THE_FUTURE');
|
throw new BadRequestException('GOAL.DUE_DATE_MUST_BE_IN_THE_FUTURE');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,43 +27,57 @@ export class SavingGoalsService {
|
|||||||
|
|
||||||
const createdGoal = await this.savingGoalsRepository.createGoal(juniorId, body, categories);
|
const createdGoal = await this.savingGoalsRepository.createGoal(juniorId, body, categories);
|
||||||
|
|
||||||
|
this.logger.log(`Goal ${createdGoal.id} created successfully`);
|
||||||
|
|
||||||
return this.findGoalById(juniorId, createdGoal.id);
|
return this.findGoalById(juniorId, createdGoal.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findGoals(juniorId: string, pageOptions: PageOptionsRequestDto): Promise<[SavingGoal[], number]> {
|
async findGoals(juniorId: string, pageOptions: PageOptionsRequestDto): Promise<[SavingGoal[], number]> {
|
||||||
|
this.logger.log(`Finding goals for junior ${juniorId}`);
|
||||||
const [goals, itemCount] = await this.savingGoalsRepository.findGoals(juniorId, pageOptions);
|
const [goals, itemCount] = await this.savingGoalsRepository.findGoals(juniorId, pageOptions);
|
||||||
await this.prepareGoalsImages(goals);
|
await this.prepareGoalsImages(goals);
|
||||||
|
|
||||||
|
this.logger.log(`Returning goals for junior ${juniorId}`);
|
||||||
return [goals, itemCount];
|
return [goals, itemCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
async findGoalById(juniorId: string, goalId: string) {
|
async findGoalById(juniorId: string, goalId: string) {
|
||||||
|
this.logger.log(`Finding goal ${goalId} for junior ${juniorId}`);
|
||||||
const goal = await this.savingGoalsRepository.findGoalById(juniorId, goalId);
|
const goal = await this.savingGoalsRepository.findGoalById(juniorId, goalId);
|
||||||
|
|
||||||
if (!goal) {
|
if (!goal) {
|
||||||
|
this.logger.error(`Goal ${goalId} not found`);
|
||||||
throw new BadRequestException('GOAL.NOT_FOUND');
|
throw new BadRequestException('GOAL.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prepareGoalsImages([goal]);
|
await this.prepareGoalsImages([goal]);
|
||||||
|
|
||||||
|
this.logger.log(`Goal ${goalId} found successfully`);
|
||||||
return goal;
|
return goal;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fundGoal(juniorId: string, goalId: string, body: FundGoalRequestDto) {
|
async fundGoal(juniorId: string, goalId: string, body: FundGoalRequestDto) {
|
||||||
|
this.logger.log(`Funding goal ${goalId} for junior ${juniorId}`);
|
||||||
const goal = await this.savingGoalsRepository.findGoalById(juniorId, goalId);
|
const goal = await this.savingGoalsRepository.findGoalById(juniorId, goalId);
|
||||||
|
|
||||||
if (!goal) {
|
if (!goal) {
|
||||||
|
this.logger.error(`Goal ${goalId} not found`);
|
||||||
throw new BadRequestException('GOAL.NOT_FOUND');
|
throw new BadRequestException('GOAL.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (goal.currentAmount + body.fundAmount > goal.targetAmount) {
|
if (goal.currentAmount + body.fundAmount > goal.targetAmount) {
|
||||||
|
this.logger.error(
|
||||||
|
`Funding amount exceeds total amount , currentAmount: ${goal.currentAmount}, fundAmount: ${body.fundAmount}, targetAmount: ${goal.targetAmount}`,
|
||||||
|
);
|
||||||
throw new BadRequestException('GOAL.FUND_EXCEEDS_TOTAL_AMOUNT');
|
throw new BadRequestException('GOAL.FUND_EXCEEDS_TOTAL_AMOUNT');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.savingGoalsRepository.fundGoal(juniorId, goalId, body.fundAmount);
|
await this.savingGoalsRepository.fundGoal(juniorId, goalId, body.fundAmount);
|
||||||
|
this.logger.log(`Goal ${goalId} funded successfully with amount ${body.fundAmount}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStats(juniorId: string): Promise<IGoalStats> {
|
async getStats(juniorId: string): Promise<IGoalStats> {
|
||||||
|
this.logger.log(`Getting stats for junior ${juniorId}`);
|
||||||
const result = await this.savingGoalsRepository.getStats(juniorId);
|
const result = await this.savingGoalsRepository.getStats(juniorId);
|
||||||
return {
|
return {
|
||||||
totalTarget: result?.totalTarget,
|
totalTarget: result?.totalTarget,
|
||||||
@ -69,6 +86,7 @@ export class SavingGoalsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async prepareGoalsImages(goals: SavingGoal[]) {
|
private async prepareGoalsImages(goals: SavingGoal[]) {
|
||||||
|
this.logger.log(`Preparing images for goals`);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
goals.map(async (goal) => {
|
goals.map(async (goal) => {
|
||||||
if (goal.imageId) {
|
if (goal.imageId) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
import { IJwtPayload } from '~/auth/interfaces';
|
import { IJwtPayload } from '~/auth/interfaces';
|
||||||
@ -10,80 +10,104 @@ import { TaskRepository } from '../repositories';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TaskService {
|
export class TaskService {
|
||||||
|
private readonly logger = new Logger(TaskService.name);
|
||||||
constructor(private readonly taskRepository: TaskRepository, private readonly ociService: OciService) {}
|
constructor(private readonly taskRepository: TaskRepository, private readonly ociService: OciService) {}
|
||||||
async createTask(userId: string, body: CreateTaskRequestDto) {
|
async createTask(userId: string, body: CreateTaskRequestDto) {
|
||||||
|
this.logger.log(`Creating task for user ${userId}`);
|
||||||
if (moment(body.dueDate).isBefore(moment(body.startDate))) {
|
if (moment(body.dueDate).isBefore(moment(body.startDate))) {
|
||||||
|
this.logger.error(`Due date must be after start date`);
|
||||||
throw new BadRequestException('TASK.DUE_DATE_BEFORE_START_DATE');
|
throw new BadRequestException('TASK.DUE_DATE_BEFORE_START_DATE');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moment(body.dueDate).isBefore(moment())) {
|
if (moment(body.dueDate).isBefore(moment())) {
|
||||||
|
this.logger.error(`Due date must be in the future`);
|
||||||
throw new BadRequestException('TASK.DUE_DATE_IN_PAST');
|
throw new BadRequestException('TASK.DUE_DATE_IN_PAST');
|
||||||
}
|
}
|
||||||
const task = await this.taskRepository.createTask(userId, body);
|
const task = await this.taskRepository.createTask(userId, body);
|
||||||
|
|
||||||
|
this.logger.log(`Task ${task.id} created successfully`);
|
||||||
return this.findTask({ id: task.id });
|
return this.findTask({ id: task.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
async findTask(where: FindOptionsWhere<Task>) {
|
async findTask(where: FindOptionsWhere<Task>) {
|
||||||
|
this.logger.log(`Finding task with where ${JSON.stringify(where)}`);
|
||||||
const task = await this.taskRepository.findTask(where);
|
const task = await this.taskRepository.findTask(where);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
|
this.logger.error(`Task not found`);
|
||||||
throw new BadRequestException('TASK.NOT_FOUND');
|
throw new BadRequestException('TASK.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prepareTasksPictures([task]);
|
await this.prepareTasksPictures([task]);
|
||||||
|
|
||||||
|
this.logger.log(`Task found successfully`);
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findTasks(user: IJwtPayload, query: TasksFilterOptions): Promise<[Task[], number]> {
|
async findTasks(user: IJwtPayload, query: TasksFilterOptions): Promise<[Task[], number]> {
|
||||||
|
this.logger.log(`Finding tasks for user ${user.sub} and roles ${user.roles} and filters ${JSON.stringify(query)}`);
|
||||||
const [tasks, count] = await this.taskRepository.findTasks(user, query);
|
const [tasks, count] = await this.taskRepository.findTasks(user, query);
|
||||||
await this.prepareTasksPictures(tasks);
|
await this.prepareTasksPictures(tasks);
|
||||||
|
|
||||||
|
this.logger.log(`Returning tasks for user ${user.sub}`);
|
||||||
return [tasks, count];
|
return [tasks, count];
|
||||||
}
|
}
|
||||||
async submitTask(userId: string, taskId: string, body: TaskSubmissionRequestDto) {
|
async submitTask(userId: string, taskId: string, body: TaskSubmissionRequestDto) {
|
||||||
|
this.logger.log(`Submitting task ${taskId} for user ${userId}`);
|
||||||
const task = await this.findTask({ id: taskId, assignedToId: userId });
|
const task = await this.findTask({ id: taskId, assignedToId: userId });
|
||||||
|
|
||||||
if (task.status == TaskStatus.COMPLETED) {
|
if (task.status == TaskStatus.COMPLETED) {
|
||||||
|
this.logger.error(`Task ${taskId} already completed`);
|
||||||
throw new BadRequestException('TASK.ALREADY_COMPLETED');
|
throw new BadRequestException('TASK.ALREADY_COMPLETED');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.isProofRequired && !body.imageId) {
|
if (task.isProofRequired && !body.imageId) {
|
||||||
|
this.logger.error(`Proof of completion is required for task ${taskId}`);
|
||||||
throw new BadRequestException('TASK.PROOF_REQUIRED');
|
throw new BadRequestException('TASK.PROOF_REQUIRED');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.taskRepository.createSubmission(task, body);
|
await this.taskRepository.createSubmission(task, body);
|
||||||
|
this.logger.log(`Task ${taskId} submitted successfully`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async approveTaskSubmission(userId: string, taskId: string) {
|
async approveTaskSubmission(userId: string, taskId: string) {
|
||||||
|
this.logger.log(`Approving task submission ${taskId} by user ${userId}`);
|
||||||
const task = await this.findTask({ id: taskId, assignedById: userId });
|
const task = await this.findTask({ id: taskId, assignedById: userId });
|
||||||
|
|
||||||
if (!task.submission) {
|
if (!task.submission) {
|
||||||
|
this.logger.error(`No submission found for task ${taskId}`);
|
||||||
throw new BadRequestException('TASK.NO_SUBMISSION');
|
throw new BadRequestException('TASK.NO_SUBMISSION');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.submission.status == SubmissionStatus.APPROVED) {
|
if (task.submission.status == SubmissionStatus.APPROVED) {
|
||||||
|
this.logger.error(`Submission already approved for task ${taskId}`);
|
||||||
throw new BadRequestException('TASK.SUBMISSION_ALREADY_REVIEWED');
|
throw new BadRequestException('TASK.SUBMISSION_ALREADY_REVIEWED');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.taskRepository.approveSubmission(task.submission);
|
await this.taskRepository.approveSubmission(task.submission);
|
||||||
|
this.logger.log(`Task submission ${taskId} approved successfully`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async rejectTaskSubmission(userId: string, taskId: string) {
|
async rejectTaskSubmission(userId: string, taskId: string) {
|
||||||
|
this.logger.log(`Rejecting task submission ${taskId} by user ${userId}`);
|
||||||
const task = await this.findTask({ id: taskId, assignedById: userId });
|
const task = await this.findTask({ id: taskId, assignedById: userId });
|
||||||
|
|
||||||
if (!task.submission) {
|
if (!task.submission) {
|
||||||
|
this.logger.error(`No submission found for task ${taskId}`);
|
||||||
throw new BadRequestException('TASK.NO_SUBMISSION');
|
throw new BadRequestException('TASK.NO_SUBMISSION');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.submission.status == SubmissionStatus.REJECTED) {
|
if (task.submission.status == SubmissionStatus.REJECTED) {
|
||||||
|
this.logger.error(`Submission already rejected for task ${taskId}`);
|
||||||
throw new BadRequestException('TASK.SUBMISSION_ALREADY_REVIEWED');
|
throw new BadRequestException('TASK.SUBMISSION_ALREADY_REVIEWED');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.taskRepository.rejectSubmission(task.submission);
|
await this.taskRepository.rejectSubmission(task.submission);
|
||||||
|
this.logger.log(`Task submission ${taskId} rejected successfully`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepareTasksPictures(tasks: Task[]) {
|
async prepareTasksPictures(tasks: Task[]) {
|
||||||
|
this.logger.log(`Preparing tasks pictures`);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
tasks.map(async (task) => {
|
tasks.map(async (task) => {
|
||||||
const [imageUrl, submissionUrl, profilePictureUrl] = await Promise.all([
|
const [imageUrl, submissionUrl, profilePictureUrl] = await Promise.all([
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { Device } from '../entities';
|
import { Device } from '../entities';
|
||||||
import { DeviceRepository } from '../repositories';
|
import { DeviceRepository } from '../repositories';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeviceService {
|
export class DeviceService {
|
||||||
|
private readonly logger = new Logger(DeviceService.name);
|
||||||
constructor(private readonly deviceRepository: DeviceRepository) {}
|
constructor(private readonly deviceRepository: DeviceRepository) {}
|
||||||
findUserDeviceById(deviceId: string, userId: string) {
|
findUserDeviceById(deviceId: string, userId: string) {
|
||||||
|
this.logger.log(`Finding device with id ${deviceId} for user ${userId}`);
|
||||||
return this.deviceRepository.findUserDeviceById(deviceId, userId);
|
return this.deviceRepository.findUserDeviceById(deviceId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
createDevice(data: Partial<Device>) {
|
createDevice(data: Partial<Device>) {
|
||||||
|
this.logger.log(`Creating device with data ${JSON.stringify(data)}`);
|
||||||
return this.deviceRepository.createDevice(data);
|
return this.deviceRepository.createDevice(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDevice(deviceId: string, data: Partial<Device>) {
|
updateDevice(deviceId: string, data: Partial<Device>) {
|
||||||
|
this.logger.log(`Updating device with id ${deviceId} with data ${JSON.stringify(data)}`);
|
||||||
return this.deviceRepository.updateDevice(deviceId, data);
|
return this.deviceRepository.updateDevice(deviceId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTokens(userId: string): Promise<string[]> {
|
async getTokens(userId: string): Promise<string[]> {
|
||||||
|
this.logger.log(`Getting tokens for user ${userId}`);
|
||||||
const devices = await this.deviceRepository.getTokens(userId);
|
const devices = await this.deviceRepository.getTokens(userId);
|
||||||
|
|
||||||
return devices.map((device) => device.fcmToken!);
|
return devices.map((device) => device.fcmToken!);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity';
|
import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity';
|
||||||
import { CustomerService } from '~/customer/services';
|
import { CustomerService } from '~/customer/services';
|
||||||
@ -10,58 +10,73 @@ import { UserRepository } from '../repositories';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
|
private readonly logger = new Logger(UserService.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly userRepository: UserRepository,
|
private readonly userRepository: UserRepository,
|
||||||
@Inject(forwardRef(() => CustomerService)) private readonly customerService: CustomerService,
|
@Inject(forwardRef(() => CustomerService)) private readonly customerService: CustomerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
findUser(where: FindOptionsWhere<User> | FindOptionsWhere<User>[]) {
|
findUser(where: FindOptionsWhere<User> | FindOptionsWhere<User>[]) {
|
||||||
|
this.logger.log(`finding user with where clause ${JSON.stringify(where)}`);
|
||||||
return this.userRepository.findOne(where);
|
return this.userRepository.findOne(where);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findUserOrThrow(where: FindOptionsWhere<User>) {
|
async findUserOrThrow(where: FindOptionsWhere<User>) {
|
||||||
|
this.logger.log(`Finding user with where clause ${JSON.stringify(where)}`);
|
||||||
const user = await this.findUser(where);
|
const user = await this.findUser(where);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
this.logger.error(`User with not found with where clause ${JSON.stringify(where)}`);
|
||||||
throw new BadRequestException('USERS.NOT_FOUND');
|
throw new BadRequestException('USERS.NOT_FOUND');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.log(`User with where clause ${JSON.stringify(where)} found successfully`);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOrCreateUser({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
async findOrCreateUser({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
|
||||||
|
this.logger.log(`Finding or creating user with phone number ${phoneNumber} and country code ${countryCode}`);
|
||||||
const user = await this.userRepository.findOne({ phoneNumber });
|
const user = await this.userRepository.findOne({ phoneNumber });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
this.logger.log(`User with phone number ${phoneNumber} not found, creating new user`);
|
||||||
return this.userRepository.createUnverifiedUser({ phoneNumber, countryCode, roles: [Roles.GUARDIAN] });
|
return this.userRepository.createUnverifiedUser({ phoneNumber, countryCode, roles: [Roles.GUARDIAN] });
|
||||||
}
|
}
|
||||||
if (user && user.roles.includes(Roles.GUARDIAN) && user.isPasswordSet) {
|
if (user && user.roles.includes(Roles.GUARDIAN) && user.isPasswordSet) {
|
||||||
|
this.logger.error(`User with phone number ${phoneNumber} already exists`);
|
||||||
throw new BadRequestException('USERS.PHONE_NUMBER_ALREADY_EXISTS');
|
throw new BadRequestException('USERS.PHONE_NUMBER_ALREADY_EXISTS');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user && user.roles.includes(Roles.JUNIOR)) {
|
if (user && user.roles.includes(Roles.JUNIOR)) {
|
||||||
|
this.logger.error(`User with phone number ${phoneNumber} is an already registered junior`);
|
||||||
throw new BadRequestException('USERS.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
|
throw new BadRequestException('USERS.JUNIOR_UPGRADE_NOT_SUPPORTED_YET');
|
||||||
//TODO add role Guardian to the existing user and send OTP
|
//TODO add role Guardian to the existing user and send OTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.log(`User with phone number ${phoneNumber} and country code ${countryCode} found successfully`);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUser(data: Partial<User>) {
|
async createUser(data: Partial<User>) {
|
||||||
|
this.logger.log(`Creating user with data ${JSON.stringify(data)}`);
|
||||||
const user = await this.userRepository.createUser(data);
|
const user = await this.userRepository.createUser(data);
|
||||||
|
|
||||||
|
this.logger.log(`User with data ${JSON.stringify(data)} created successfully`);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
setEmail(userId: string, email: string) {
|
setEmail(userId: string, email: string) {
|
||||||
|
this.logger.log(`Setting email ${email} for user ${userId}`);
|
||||||
return this.userRepository.update(userId, { email });
|
return this.userRepository.update(userId, { email });
|
||||||
}
|
}
|
||||||
|
|
||||||
setPasscode(userId: string, passcode: string, salt: string) {
|
setPasscode(userId: string, passcode: string, salt: string) {
|
||||||
|
this.logger.log(`Setting passcode for user ${userId}`);
|
||||||
return this.userRepository.update(userId, { password: passcode, salt, isProfileCompleted: true });
|
return this.userRepository.update(userId, { password: passcode, salt, isProfileCompleted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyUserAndCreateCustomer(user: User) {
|
async verifyUserAndCreateCustomer(user: User) {
|
||||||
|
this.logger.log(`Verifying user ${user.id} and creating customer`);
|
||||||
await this.customerService.createCustomer(
|
await this.customerService.createCustomer(
|
||||||
{
|
{
|
||||||
guardian: Guardian.create({ id: user.id }),
|
guardian: Guardian.create({ id: user.id }),
|
||||||
@ -70,6 +85,7 @@ export class UserService {
|
|||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.logger.log(`User ${user.id} verified and customer created successfully`);
|
||||||
return this.findUserOrThrow({ id: user.id });
|
return this.findUserOrThrow({ id: user.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user