mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-07-10 15:17:44 +00:00
feat: create junior
This commit is contained in:
88
package-lock.json
generated
88
package-lock.json
generated
@ -45,9 +45,10 @@
|
|||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"pino-http": "^10.3.0",
|
"pino-http": "^10.3.0",
|
||||||
"pino-pretty": "^13.0.0",
|
"pino-pretty": "^13.0.0",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"typeorm": "^0.3.20"
|
"typeorm": "^0.3.20",
|
||||||
|
"typeorm-transactional": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@golevelup/ts-jest": "^0.6.0",
|
"@golevelup/ts-jest": "^0.6.0",
|
||||||
@ -2743,6 +2744,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cls-hooked": {
|
||||||
|
"version": "4.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cls-hooked/-/cls-hooked-4.3.9.tgz",
|
||||||
|
"integrity": "sha512-CMtHMz6Q/dkfcHarq9nioXH8BDPP+v5xvd+N90lBQ2bdmu06UvnLDqxTKoOJzz4SzIwb/x9i4UXGAAcnUDuIvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/connect": {
|
"node_modules/@types/connect": {
|
||||||
"version": "3.4.38",
|
"version": "3.4.38",
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||||
@ -3966,6 +3976,18 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/async-hook-jl": {
|
||||||
|
"version": "1.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz",
|
||||||
|
"integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"stack-chain": "^1.3.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^4.7 || >=6.9 || >=7.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@ -5011,6 +5033,29 @@
|
|||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cls-hooked": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"async-hook-jl": "^1.7.6",
|
||||||
|
"emitter-listener": "^1.0.1",
|
||||||
|
"semver": "^5.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^4.7 || >=6.9 || >=7.3 || >=8.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cls-hooked/node_modules/semver": {
|
||||||
|
"version": "5.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||||
|
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cluster-key-slot": {
|
"node_modules/cluster-key-slot": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||||
@ -5796,6 +5841,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/emitter-listener": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"shimmer": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/emittery": {
|
"node_modules/emittery": {
|
||||||
"version": "0.13.1",
|
"version": "0.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
|
||||||
@ -14467,6 +14521,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shimmer": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||||
@ -14648,6 +14708,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stack-chain": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/stack-utils": {
|
"node_modules/stack-utils": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||||
@ -15601,6 +15667,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typeorm-transactional": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typeorm-transactional/-/typeorm-transactional-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-53/CwnXpOIJnWU3oVCNbhHB95FwciKSGbY+m/Hw4e2dBM2c4toiOHwf4pmk83Ne7guznmDgVr/5IUfbp+JTPCg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/cls-hooked": "^4.3.3",
|
||||||
|
"cls-hooked": "^4.2.2",
|
||||||
|
"semver": "^7.5.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"reflect-metadata": ">= 0.1.12",
|
||||||
|
"typeorm": ">= 0.2.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typeorm/node_modules/buffer": {
|
"node_modules/typeorm/node_modules/buffer": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
@ -62,9 +62,10 @@
|
|||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"pino-http": "^10.3.0",
|
"pino-http": "^10.3.0",
|
||||||
"pino-pretty": "^13.0.0",
|
"pino-pretty": "^13.0.0",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"typeorm": "^0.3.20"
|
"typeorm": "^0.3.20",
|
||||||
|
"typeorm-transactional": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@golevelup/ts-jest": "^0.6.0",
|
"@golevelup/ts-jest": "^0.6.0",
|
||||||
|
@ -4,6 +4,8 @@ import { APP_FILTER, APP_PIPE } from '@nestjs/core';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { I18nMiddleware, I18nModule } from 'nestjs-i18n';
|
import { I18nMiddleware, I18nModule } from 'nestjs-i18n';
|
||||||
import { LoggerModule } from 'nestjs-pino';
|
import { LoggerModule } from 'nestjs-pino';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { addTransactionalDataSource } from 'typeorm-transactional';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
import { OtpModule } from './common/modules/otp/otp.module';
|
import { OtpModule } from './common/modules/otp/otp.module';
|
||||||
import { AllExceptionsFilter, buildI18nValidationExceptionFilter } from './core/filters';
|
import { AllExceptionsFilter, buildI18nValidationExceptionFilter } from './core/filters';
|
||||||
@ -13,7 +15,9 @@ import { buildValidationPipe } from './core/pipes';
|
|||||||
import { CustomerModule } from './customer/customer.module';
|
import { CustomerModule } from './customer/customer.module';
|
||||||
import { migrations } from './db';
|
import { migrations } from './db';
|
||||||
import { DocumentModule } from './document/document.module';
|
import { DocumentModule } from './document/document.module';
|
||||||
|
import { GuardianModule } from './guardian/guardian.module';
|
||||||
import { HealthModule } from './health/health.module';
|
import { HealthModule } from './health/health.module';
|
||||||
|
import { JuniorModule } from './junior/junior.module';
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [],
|
controllers: [],
|
||||||
imports: [
|
imports: [
|
||||||
@ -21,7 +25,18 @@ import { HealthModule } from './health/health.module';
|
|||||||
TypeOrmModule.forRootAsync({
|
TypeOrmModule.forRootAsync({
|
||||||
imports: [],
|
imports: [],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: (config: ConfigService) => buildTypeormOptions(config, migrations),
|
useFactory: (config: ConfigService) => {
|
||||||
|
return buildTypeormOptions(config, migrations);
|
||||||
|
},
|
||||||
|
/* eslint-disable require-await */
|
||||||
|
async dataSourceFactory(options) {
|
||||||
|
if (!options) {
|
||||||
|
throw new Error('Invalid options passed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return addTransactionalDataSource(new DataSource(options));
|
||||||
|
},
|
||||||
|
/* eslint-enable require-await */
|
||||||
}),
|
}),
|
||||||
LoggerModule.forRootAsync({
|
LoggerModule.forRootAsync({
|
||||||
useFactory: (config: ConfigService) => buildLoggerOptions(config),
|
useFactory: (config: ConfigService) => buildLoggerOptions(config),
|
||||||
@ -31,9 +46,11 @@ import { HealthModule } from './health/health.module';
|
|||||||
// App modules
|
// App modules
|
||||||
AuthModule,
|
AuthModule,
|
||||||
CustomerModule,
|
CustomerModule,
|
||||||
|
JuniorModule,
|
||||||
|
GuardianModule,
|
||||||
|
OtpModule,
|
||||||
DocumentModule,
|
DocumentModule,
|
||||||
HealthModule,
|
HealthModule,
|
||||||
OtpModule,
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
// Global Pipes
|
// Global Pipes
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { CustomerModule } from '~/customer/customer.module';
|
||||||
import { AuthController } from './controllers';
|
import { AuthController } from './controllers';
|
||||||
import { Device, User, UserNotificationSettings } from './entities';
|
import { Device, User, UserNotificationSettings } from './entities';
|
||||||
import { DeviceRepository, UserRepository } from './repositories';
|
import { DeviceRepository, UserRepository } from './repositories';
|
||||||
@ -9,7 +10,11 @@ import { UserService } from './services/user.service';
|
|||||||
import { AccessTokenStrategy } from './strategies';
|
import { AccessTokenStrategy } from './strategies';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([User, UserNotificationSettings, Device]), JwtModule.register({})],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([User, UserNotificationSettings, Device]),
|
||||||
|
JwtModule.register({}),
|
||||||
|
forwardRef(() => CustomerModule),
|
||||||
|
],
|
||||||
providers: [AuthService, UserRepository, UserService, DeviceService, DeviceRepository, AccessTokenStrategy],
|
providers: [AuthService, UserRepository, UserService, DeviceService, DeviceRepository, AccessTokenStrategy],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
exports: [UserService],
|
exports: [UserService],
|
||||||
|
@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { FindOptionsWhere, Repository } from 'typeorm';
|
import { FindOptionsWhere, Repository } from 'typeorm';
|
||||||
import { UpdateNotificationsSettingsRequestDto } from '~/customer/dtos/request';
|
import { UpdateNotificationsSettingsRequestDto } from '~/customer/dtos/request';
|
||||||
import { Customer } from '~/customer/entities';
|
|
||||||
import { User, UserNotificationSettings } from '../entities';
|
import { User, UserNotificationSettings } from '../entities';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -20,7 +19,7 @@ export class UserRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
findOne(where: FindOptionsWhere<User>) {
|
findOne(where: FindOptionsWhere<User> | FindOptionsWhere<User>[]) {
|
||||||
return this.userRepository.findOne({ where });
|
return this.userRepository.findOne({ where });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,13 +28,16 @@ export class UserRepository {
|
|||||||
return this.userRepository.save(user);
|
return this.userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyUserAndCreateCustomer(user: User) {
|
|
||||||
user.customer = Customer.create({ ...user.customer, id: user.id, isGuardian: true });
|
|
||||||
|
|
||||||
return this.userRepository.save(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(userId: string, data: Partial<User>) {
|
update(userId: string, data: Partial<User>) {
|
||||||
return this.userRepository.update(userId, data);
|
return this.userRepository.update(userId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createUser(data: Partial<User>) {
|
||||||
|
const user = this.userRepository.create({
|
||||||
|
...data,
|
||||||
|
notificationSettings: UserNotificationSettings.create(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.userRepository.save(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
import { UpdateNotificationsSettingsRequestDto } from '~/customer/dtos/request';
|
import { UpdateNotificationsSettingsRequestDto } from '~/customer/dtos/request';
|
||||||
|
import { CustomerService } from '~/customer/services';
|
||||||
|
import { Guardian } from '~/guardian/entities/guradian.entity';
|
||||||
import { CreateUnverifiedUserRequestDto } from '../dtos/request';
|
import { CreateUnverifiedUserRequestDto } from '../dtos/request';
|
||||||
import { User } from '../entities';
|
import { User } from '../entities';
|
||||||
import { Roles } from '../enums';
|
import { Roles } from '../enums';
|
||||||
@ -8,7 +10,10 @@ import { UserRepository } from '../repositories';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
constructor(private readonly userRepository: UserRepository) {}
|
constructor(
|
||||||
|
private readonly userRepository: UserRepository,
|
||||||
|
@Inject(forwardRef(() => CustomerService)) private readonly customerService: CustomerService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async updateNotificationSettings(userId: string, body: UpdateNotificationsSettingsRequestDto) {
|
async updateNotificationSettings(userId: string, body: UpdateNotificationsSettingsRequestDto) {
|
||||||
const user = await this.findUserOrThrow({ id: userId });
|
const user = await this.findUserOrThrow({ id: userId });
|
||||||
@ -19,7 +24,7 @@ export class UserService {
|
|||||||
return notificationSettings;
|
return notificationSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
findUser(where: FindOptionsWhere<User>) {
|
findUser(where: FindOptionsWhere<User> | FindOptionsWhere<User>[]) {
|
||||||
return this.userRepository.findOne(where);
|
return this.userRepository.findOne(where);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,12 +49,19 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (user && user.roles.includes(Roles.JUNIOR)) {
|
if (user && user.roles.includes(Roles.JUNIOR)) {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createUser(data: Partial<User>) {
|
||||||
|
const user = await this.userRepository.createUser(data);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
setEmail(userId: string, email: string) {
|
setEmail(userId: string, email: string) {
|
||||||
return this.userRepository.update(userId, { email });
|
return this.userRepository.update(userId, { email });
|
||||||
}
|
}
|
||||||
@ -58,7 +70,14 @@ export class UserService {
|
|||||||
return this.userRepository.update(userId, { password: passcode, salt, isProfileCompleted: true });
|
return this.userRepository.update(userId, { password: passcode, salt, isProfileCompleted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyUserAndCreateCustomer(user: User) {
|
async verifyUserAndCreateCustomer(user: User) {
|
||||||
return this.userRepository.verifyUserAndCreateCustomer(user);
|
await this.customerService.createCustomer(
|
||||||
|
{
|
||||||
|
guardian: Guardian.create({ id: user.id }),
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.findUserOrThrow({ id: user.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AuthModule } from '~/auth/auth.module';
|
import { AuthModule } from '~/auth/auth.module';
|
||||||
import { CustomerController } from './controllers';
|
import { CustomerController } from './controllers';
|
||||||
@ -7,8 +7,9 @@ import { CustomerRepository } from './repositories/customer.repository';
|
|||||||
import { CustomerService } from './services';
|
import { CustomerService } from './services';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Customer]), AuthModule],
|
imports: [TypeOrmModule.forFeature([Customer]), forwardRef(() => AuthModule)],
|
||||||
controllers: [CustomerController],
|
controllers: [CustomerController],
|
||||||
providers: [CustomerService, CustomerRepository],
|
providers: [CustomerService, CustomerRepository],
|
||||||
|
exports: [CustomerService],
|
||||||
})
|
})
|
||||||
export class CustomerModule {}
|
export class CustomerModule {}
|
||||||
|
@ -9,6 +9,8 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { User } from '~/auth/entities';
|
import { User } from '~/auth/entities';
|
||||||
|
import { Guardian } from '~/guardian/entities/guradian.entity';
|
||||||
|
import { Junior } from '~/junior/entities';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Customer extends BaseEntity {
|
export class Customer extends BaseEntity {
|
||||||
@ -67,6 +69,12 @@ export class Customer extends BaseEntity {
|
|||||||
@JoinColumn({ name: 'user_id' })
|
@JoinColumn({ name: 'user_id' })
|
||||||
user!: User;
|
user!: User;
|
||||||
|
|
||||||
|
@OneToOne(() => Junior, (junior) => junior.customer, { cascade: true })
|
||||||
|
junior!: Junior;
|
||||||
|
|
||||||
|
@OneToOne(() => Guardian, (guardian) => guardian.customer, { cascade: true })
|
||||||
|
guardian!: Guardian;
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' })
|
@CreateDateColumn({ type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { FindOptionsWhere, Repository } from 'typeorm';
|
import { FindOptionsWhere, Repository } from 'typeorm';
|
||||||
|
import { User } from '~/auth/entities';
|
||||||
|
import { Roles } from '~/auth/enums';
|
||||||
import { UpdateCustomerRequestDto } from '../dtos/request';
|
import { UpdateCustomerRequestDto } from '../dtos/request';
|
||||||
import { Customer } from '../entities';
|
import { Customer } from '../entities';
|
||||||
|
|
||||||
@ -15,4 +17,16 @@ export class CustomerRepository {
|
|||||||
findOne(where: FindOptionsWhere<Customer>) {
|
findOne(where: FindOptionsWhere<Customer>) {
|
||||||
return this.customerRepository.findOne({ where });
|
return this.customerRepository.findOne({ where });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createCustomer(customerData: Partial<Customer>, user: User) {
|
||||||
|
return this.customerRepository.save(
|
||||||
|
this.customerRepository.create({
|
||||||
|
...customerData,
|
||||||
|
id: user.id,
|
||||||
|
user,
|
||||||
|
isGuardian: user.roles.includes(Roles.GUARDIAN),
|
||||||
|
isJunior: user.roles.includes(Roles.JUNIOR),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { User } from '~/auth/entities';
|
||||||
import { UserService } from '~/auth/services/user.service';
|
import { UserService } from '~/auth/services/user.service';
|
||||||
import { UpdateCustomerRequestDto, UpdateNotificationsSettingsRequestDto } from '../dtos/request';
|
import { UpdateCustomerRequestDto, UpdateNotificationsSettingsRequestDto } from '../dtos/request';
|
||||||
import { Customer } from '../entities';
|
import { Customer } from '../entities';
|
||||||
@ -6,7 +7,10 @@ import { CustomerRepository } from '../repositories/customer.repository';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CustomerService {
|
export class CustomerService {
|
||||||
constructor(private readonly userService: UserService, private readonly customerRepository: CustomerRepository) {}
|
constructor(
|
||||||
|
@Inject(forwardRef(() => UserService)) private readonly userService: UserService,
|
||||||
|
private readonly customerRepository: CustomerRepository,
|
||||||
|
) {}
|
||||||
updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto) {
|
updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto) {
|
||||||
return this.userService.updateNotificationSettings(userId, data);
|
return this.userService.updateNotificationSettings(userId, data);
|
||||||
}
|
}
|
||||||
@ -16,6 +20,10 @@ export class CustomerService {
|
|||||||
return this.findCustomerById(userId);
|
return this.findCustomerById(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createCustomer(customerData: Partial<Customer>, user: User) {
|
||||||
|
return this.customerRepository.createCustomer(customerData, user);
|
||||||
|
}
|
||||||
|
|
||||||
async findCustomerById(id: string) {
|
async findCustomerById(id: string) {
|
||||||
const customer = await this.customerRepository.findOne({ id });
|
const customer = await this.customerRepository.findOne({ id });
|
||||||
if (!customer) {
|
if (!customer) {
|
||||||
|
37
src/db/migrations/1733731507261-create-junior-entity.ts
Normal file
37
src/db/migrations/1733731507261-create-junior-entity.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateJuniorEntity1733731507261 implements MigrationInterface {
|
||||||
|
name = 'CreateJuniorEntity1733731507261';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "juniors"
|
||||||
|
("id" uuid NOT NULL,
|
||||||
|
"relationship" character varying(255) NOT NULL,
|
||||||
|
"civil_id_front_id" uuid NOT NULL,
|
||||||
|
"civil_id_back_id" uuid NOT NULL,
|
||||||
|
"customer_id" uuid NOT NULL,
|
||||||
|
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT "REL_6a72e1a5758643737cc563b96c" UNIQUE ("civil_id_front_id"),
|
||||||
|
CONSTRAINT "REL_4662c4433223c01fe69fc1382f" UNIQUE ("civil_id_back_id"),
|
||||||
|
CONSTRAINT "REL_dfbf64ede1ff823a489902448a" UNIQUE ("customer_id"), CONSTRAINT "PK_2d273092322c1f8bf26296fa608" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "juniors" ADD CONSTRAINT "FK_6a72e1a5758643737cc563b96c7" FOREIGN KEY ("civil_id_front_id") REFERENCES "documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "juniors" ADD CONSTRAINT "FK_4662c4433223c01fe69fc1382f5" FOREIGN KEY ("civil_id_back_id") REFERENCES "documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "juniors" ADD CONSTRAINT "FK_dfbf64ede1ff823a489902448a2" FOREIGN KEY ("customer_id") REFERENCES "customer"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "juniors" DROP CONSTRAINT "FK_dfbf64ede1ff823a489902448a2"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "juniors" DROP CONSTRAINT "FK_4662c4433223c01fe69fc1382f5"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "juniors" DROP CONSTRAINT "FK_6a72e1a5758643737cc563b96c7"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "juniors"`);
|
||||||
|
}
|
||||||
|
}
|
30
src/db/migrations/1733732021622-create-guardian-entity.ts
Normal file
30
src/db/migrations/1733732021622-create-guardian-entity.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateGuardianEntity1733732021622 implements MigrationInterface {
|
||||||
|
name = 'CreateGuardianEntity1733732021622';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "guardians"
|
||||||
|
("id" uuid NOT NULL,
|
||||||
|
"customer_id" uuid NOT NULL,
|
||||||
|
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT "REL_6c46a1b6af00e6457cb1b70f7e" UNIQUE ("customer_id"), CONSTRAINT "PK_3dcf02f3dc96a2c017106f280be" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(`ALTER TABLE "juniors" ADD "guardian_id" uuid NOT NULL`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "juniors" ADD CONSTRAINT "FK_0b11aa56264184690e2220da4a0" FOREIGN KEY ("guardian_id") REFERENCES "guardians"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "guardians" ADD CONSTRAINT "FK_6c46a1b6af00e6457cb1b70f7e7" FOREIGN KEY ("customer_id") REFERENCES "customer"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "guardians" DROP CONSTRAINT "FK_6c46a1b6af00e6457cb1b70f7e7"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "juniors" DROP CONSTRAINT "FK_0b11aa56264184690e2220da4a0"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "juniors" DROP COLUMN "guardian_id"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "guardians"`);
|
||||||
|
}
|
||||||
|
}
|
@ -4,3 +4,5 @@ export * from './1733209041336-create-otp-entity';
|
|||||||
export * from './1733231692252-create-notification-settings-table';
|
export * from './1733231692252-create-notification-settings-table';
|
||||||
export * from './1733298524771-create-customer-entity';
|
export * from './1733298524771-create-customer-entity';
|
||||||
export * from './1733314952318-create-device-entity';
|
export * from './1733314952318-create-device-entity';
|
||||||
|
export * from './1733731507261-create-junior-entity';
|
||||||
|
export * from './1733732021622-create-guardian-entity';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Column, Entity, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
import { Column, Entity, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||||
import { User } from '~/auth/entities';
|
import { User } from '~/auth/entities';
|
||||||
|
import { Junior } from '~/junior/entities';
|
||||||
import { DocumentType } from '../enums';
|
import { DocumentType } from '../enums';
|
||||||
|
|
||||||
@Entity('documents')
|
@Entity('documents')
|
||||||
@ -19,6 +20,12 @@ export class Document {
|
|||||||
@OneToOne(() => User, (user) => user.profilePicture, { onDelete: 'CASCADE' })
|
@OneToOne(() => User, (user) => user.profilePicture, { onDelete: 'CASCADE' })
|
||||||
user!: User;
|
user!: User;
|
||||||
|
|
||||||
|
@OneToOne(() => Junior, (junior) => junior.civilIdFront, { onDelete: 'CASCADE' })
|
||||||
|
juniorCivilIdFront!: User;
|
||||||
|
|
||||||
|
@OneToOne(() => Junior, (junior) => junior.civilIdBack, { onDelete: 'CASCADE' })
|
||||||
|
juniorCivilIdBack!: User;
|
||||||
|
|
||||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
|
||||||
|
35
src/guardian/entities/guradian.entity.ts
Normal file
35
src/guardian/entities/guradian.entity.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
OneToMany,
|
||||||
|
OneToOne,
|
||||||
|
PrimaryColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Customer } from '~/customer/entities';
|
||||||
|
import { Junior } from '~/junior/entities';
|
||||||
|
|
||||||
|
@Entity('guardians')
|
||||||
|
export class Guardian extends BaseEntity {
|
||||||
|
@PrimaryColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Column('uuid', { name: 'customer_id' })
|
||||||
|
customerId!: string;
|
||||||
|
|
||||||
|
@OneToOne(() => Customer, (customer) => customer.guardian, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'customer_id' })
|
||||||
|
customer!: Customer;
|
||||||
|
|
||||||
|
@OneToMany(() => Junior, (junior) => junior.guardian)
|
||||||
|
juniors!: Junior[];
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
updatedAt!: Date;
|
||||||
|
}
|
8
src/guardian/guardian.module.ts
Normal file
8
src/guardian/guardian.module.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Guardian } from './entities/guradian.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Guardian])],
|
||||||
|
})
|
||||||
|
export class GuardianModule {}
|
1
src/junior/controllers/index.ts
Normal file
1
src/junior/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './junior.controller';
|
56
src/junior/controllers/junior.controller.ts
Normal file
56
src/junior/controllers/junior.controller.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { IJwtPayload } from '~/auth/interfaces';
|
||||||
|
import { AuthenticatedUser } from '~/common/decorators';
|
||||||
|
import { AccessTokenGuard } from '~/common/guards';
|
||||||
|
import { ApiDataPageResponse, ApiDataResponse } from '~/core/decorators';
|
||||||
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
|
import { CustomParseUUIDPipe } from '~/core/pipes';
|
||||||
|
import { ResponseFactory } from '~/core/utils';
|
||||||
|
import { CreateJuniorRequestDto } from '../dtos/request';
|
||||||
|
import { JuniorResponseDto } from '../dtos/response';
|
||||||
|
import { JuniorService } from '../services';
|
||||||
|
|
||||||
|
@Controller('juniors')
|
||||||
|
@ApiTags('Juniors')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
export class JuniorController {
|
||||||
|
constructor(private readonly juniorService: JuniorService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@UseGuards(AccessTokenGuard)
|
||||||
|
@ApiDataResponse(JuniorResponseDto)
|
||||||
|
async createJunior(@Body() body: CreateJuniorRequestDto, @AuthenticatedUser() user: IJwtPayload) {
|
||||||
|
const junior = await this.juniorService.createJuniors(body, user.sub);
|
||||||
|
|
||||||
|
return ResponseFactory.data(new JuniorResponseDto(junior));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@UseGuards(AccessTokenGuard)
|
||||||
|
@ApiDataPageResponse(JuniorResponseDto)
|
||||||
|
async findJuniors(@AuthenticatedUser() user: IJwtPayload, @Query() pageOptions: PageOptionsRequestDto) {
|
||||||
|
const [juniors, count] = await this.juniorService.findJuniorsByGuardianId(user.sub, pageOptions);
|
||||||
|
|
||||||
|
return ResponseFactory.dataPage(
|
||||||
|
juniors.map((juniors) => new JuniorResponseDto(juniors)),
|
||||||
|
{
|
||||||
|
page: pageOptions.page,
|
||||||
|
size: pageOptions.size,
|
||||||
|
itemCount: count,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':juniorId')
|
||||||
|
@UseGuards(AccessTokenGuard)
|
||||||
|
@ApiDataResponse(JuniorResponseDto)
|
||||||
|
async findJuniorById(
|
||||||
|
@AuthenticatedUser() user: IJwtPayload,
|
||||||
|
@Param('juniorId', CustomParseUUIDPipe) juniorId: string,
|
||||||
|
) {
|
||||||
|
const junior = await this.juniorService.findJuniorById(juniorId, user.sub);
|
||||||
|
|
||||||
|
return ResponseFactory.data(new JuniorResponseDto(junior));
|
||||||
|
}
|
||||||
|
}
|
37
src/junior/dtos/request/create-junior-user.request.dto.ts
Normal file
37
src/junior/dtos/request/create-junior-user.request.dto.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsDateString, IsEmail, IsNotEmpty, IsString, Matches } from 'class-validator';
|
||||||
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
import { COUNTRY_CODE_REGEX } from '~/auth/constants';
|
||||||
|
import { IsValidPhoneNumber } from '~/core/decorators/validations';
|
||||||
|
|
||||||
|
export class CreateJuniorUserRequestDto {
|
||||||
|
@ApiProperty({ example: '+962' })
|
||||||
|
@Matches(COUNTRY_CODE_REGEX, {
|
||||||
|
message: i18n('validation.Matches', { path: 'general', property: 'auth.countryCode' }),
|
||||||
|
})
|
||||||
|
countryCode: string = '+966';
|
||||||
|
|
||||||
|
@ApiProperty({ example: '787259134' })
|
||||||
|
@IsValidPhoneNumber({
|
||||||
|
message: i18n('validation.IsValidPhoneNumber', { path: 'general', property: 'auth.phoneNumber' }),
|
||||||
|
})
|
||||||
|
phoneNumber!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'John' })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.firstName' }) })
|
||||||
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.firstName' }) })
|
||||||
|
firstName!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Doe' })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.lastName' }) })
|
||||||
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.lastName' }) })
|
||||||
|
lastName!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '2020-01-01' })
|
||||||
|
@IsDateString({}, { message: i18n('validation.IsDateString', { path: 'general', property: 'customer.dateOfBirth' }) })
|
||||||
|
dateOfBirth!: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'test@test.com' })
|
||||||
|
@IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) })
|
||||||
|
email!: string;
|
||||||
|
}
|
18
src/junior/dtos/request/create-junior.request.dto.ts
Normal file
18
src/junior/dtos/request/create-junior.request.dto.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEnum, IsUUID } from 'class-validator';
|
||||||
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
import { Relationship } from '~/junior/enums';
|
||||||
|
import { CreateJuniorUserRequestDto } from './create-junior-user.request.dto';
|
||||||
|
export class CreateJuniorRequestDto extends CreateJuniorUserRequestDto {
|
||||||
|
@ApiProperty({ example: Relationship.PARENT })
|
||||||
|
@IsEnum(Relationship, { message: i18n('validation.IsEnum', { path: 'general', property: 'junior.relationship' }) })
|
||||||
|
relationship!: Relationship;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'bf342-3f3f-3f3f-3f3f' })
|
||||||
|
@IsUUID('4', { message: i18n('validation.IsUUID', { path: 'general', property: 'junior.civilIdFrontId' }) })
|
||||||
|
civilIdFrontId!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'bf342-3f3f-3f3f-3f3f' })
|
||||||
|
@IsUUID('4', { message: i18n('validation.IsUUID', { path: 'general', property: 'junior.civilIdBackId' }) })
|
||||||
|
civilIdBackId!: string;
|
||||||
|
}
|
2
src/junior/dtos/request/index.ts
Normal file
2
src/junior/dtos/request/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './create-junior-user.request.dto';
|
||||||
|
export * from './create-junior.request.dto';
|
1
src/junior/dtos/response/index.ts
Normal file
1
src/junior/dtos/response/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './junior.response.dto';
|
24
src/junior/dtos/response/junior.response.dto.ts
Normal file
24
src/junior/dtos/response/junior.response.dto.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Junior } from '~/junior/entities';
|
||||||
|
import { Relationship } from '~/junior/enums';
|
||||||
|
|
||||||
|
export class JuniorResponseDto {
|
||||||
|
@ApiProperty({ example: 'id' })
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'fullName' })
|
||||||
|
fullName!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'relationship' })
|
||||||
|
relationship!: Relationship;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'profilePictureId' })
|
||||||
|
profilePictureId: string | null;
|
||||||
|
|
||||||
|
constructor(junior: Junior) {
|
||||||
|
this.id = junior.id;
|
||||||
|
this.fullName = `${junior.customer.firstName} ${junior.customer.lastName}`;
|
||||||
|
this.relationship = junior.relationship;
|
||||||
|
this.profilePictureId = junior.customer.user.profilePictureId;
|
||||||
|
}
|
||||||
|
}
|
1
src/junior/entities/index.ts
Normal file
1
src/junior/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './junior.entity';
|
58
src/junior/entities/junior.entity.ts
Normal file
58
src/junior/entities/junior.entity.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToOne,
|
||||||
|
PrimaryColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Customer } from '~/customer/entities';
|
||||||
|
import { Document } from '~/document/entities';
|
||||||
|
import { Guardian } from '~/guardian/entities/guradian.entity';
|
||||||
|
import { Relationship } from '../enums';
|
||||||
|
|
||||||
|
@Entity('juniors')
|
||||||
|
export class Junior extends BaseEntity {
|
||||||
|
@PrimaryColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Column('varchar', { length: 255 })
|
||||||
|
relationship!: Relationship;
|
||||||
|
|
||||||
|
@Column('uuid', { name: 'civil_id_front_id' })
|
||||||
|
civilIdFrontId!: string;
|
||||||
|
|
||||||
|
@Column('uuid', { name: 'civil_id_back_id' })
|
||||||
|
civilIdBackId!: string;
|
||||||
|
|
||||||
|
@Column('uuid', { name: 'customer_id' })
|
||||||
|
customerId!: string;
|
||||||
|
|
||||||
|
@Column('uuid', { name: 'guardian_id' })
|
||||||
|
guardianId!: string;
|
||||||
|
|
||||||
|
@OneToOne(() => Document, (document) => document.juniorCivilIdFront)
|
||||||
|
@JoinColumn({ name: 'civil_id_front_id' })
|
||||||
|
civilIdFront!: Document;
|
||||||
|
|
||||||
|
@OneToOne(() => Document, (document) => document.juniorCivilIdBack)
|
||||||
|
@JoinColumn({ name: 'civil_id_back_id' })
|
||||||
|
civilIdBack!: Document;
|
||||||
|
|
||||||
|
@OneToOne(() => Customer, (customer) => customer.junior, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'customer_id' })
|
||||||
|
customer!: Customer;
|
||||||
|
|
||||||
|
@ManyToOne(() => Guardian, (guardian) => guardian.juniors)
|
||||||
|
@JoinColumn({ name: 'guardian_id' })
|
||||||
|
guardian!: Guardian;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
updatedAt!: Date;
|
||||||
|
}
|
1
src/junior/enums/index.ts
Normal file
1
src/junior/enums/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './relationship.enum';
|
4
src/junior/enums/relationship.enum.ts
Normal file
4
src/junior/enums/relationship.enum.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum Relationship {
|
||||||
|
PARENT = 'PARENT',
|
||||||
|
GUARDIAN = 'GUARDIAN',
|
||||||
|
}
|
15
src/junior/junior.module.ts
Normal file
15
src/junior/junior.module.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { AuthModule } from '~/auth/auth.module';
|
||||||
|
import { CustomerModule } from '~/customer/customer.module';
|
||||||
|
import { JuniorController } from './controllers';
|
||||||
|
import { Junior } from './entities';
|
||||||
|
import { JuniorRepository } from './repositories';
|
||||||
|
import { JuniorService } from './services';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [JuniorController],
|
||||||
|
providers: [JuniorService, JuniorRepository],
|
||||||
|
imports: [TypeOrmModule.forFeature([Junior]), AuthModule, CustomerModule],
|
||||||
|
})
|
||||||
|
export class JuniorModule {}
|
1
src/junior/repositories/index.ts
Normal file
1
src/junior/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './junior.repository';
|
26
src/junior/repositories/junior.repository.ts
Normal file
26
src/junior/repositories/junior.repository.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
|
import { Junior } from '../entities/junior.entity';
|
||||||
|
const FIRST_PAGE = 1;
|
||||||
|
@Injectable()
|
||||||
|
export class JuniorRepository {
|
||||||
|
constructor(@InjectRepository(Junior) private juniorRepository: Repository<Junior>) {}
|
||||||
|
|
||||||
|
findJuniorsByGuardianId(guardianId: string, pageOptions: PageOptionsRequestDto) {
|
||||||
|
return this.juniorRepository.findAndCount({
|
||||||
|
where: { guardianId },
|
||||||
|
relations: ['customer', 'customer.user'],
|
||||||
|
skip: (pageOptions.page - FIRST_PAGE) * pageOptions.size,
|
||||||
|
take: pageOptions.size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findJuniorById(juniorId: string, guardianId: string) {
|
||||||
|
return this.juniorRepository.findOne({
|
||||||
|
where: { id: juniorId, guardianId },
|
||||||
|
relations: ['customer', 'customer.user'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
1
src/junior/services/index.ts
Normal file
1
src/junior/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './junior.service';
|
65
src/junior/services/junior.service.ts
Normal file
65
src/junior/services/junior.service.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { Transactional } from 'typeorm-transactional';
|
||||||
|
import { Roles } from '~/auth/enums';
|
||||||
|
import { UserService } from '~/auth/services';
|
||||||
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
|
import { CustomerService } from '~/customer/services';
|
||||||
|
import { CreateJuniorRequestDto } from '../dtos/request';
|
||||||
|
import { Junior } from '../entities';
|
||||||
|
import { JuniorRepository } from '../repositories';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JuniorService {
|
||||||
|
constructor(
|
||||||
|
private readonly juniorRepository: JuniorRepository,
|
||||||
|
private readonly userService: UserService,
|
||||||
|
private readonly customerService: CustomerService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Transactional()
|
||||||
|
async createJuniors(body: CreateJuniorRequestDto, guardianId: string) {
|
||||||
|
const existingUser = await this.userService.findUser([{ email: body.email }, { phoneNumber: body.phoneNumber }]);
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
throw new BadRequestException('USER.ALREADY_EXISTS');
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await this.userService.createUser({
|
||||||
|
email: body.email,
|
||||||
|
countryCode: body.countryCode,
|
||||||
|
phoneNumber: body.phoneNumber,
|
||||||
|
roles: [Roles.JUNIOR],
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.customerService.createCustomer(
|
||||||
|
{
|
||||||
|
firstName: body.firstName,
|
||||||
|
lastName: body.lastName,
|
||||||
|
dateOfBirth: body.dateOfBirth,
|
||||||
|
junior: Junior.create({
|
||||||
|
id: user.id,
|
||||||
|
guardianId,
|
||||||
|
relationship: body.relationship,
|
||||||
|
civilIdFrontId: body.civilIdFrontId,
|
||||||
|
civilIdBackId: body.civilIdBackId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.findJuniorById(user.id, guardianId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findJuniorById(juniorId: string, guardianId: string) {
|
||||||
|
const junior = await this.juniorRepository.findJuniorById(juniorId, guardianId);
|
||||||
|
|
||||||
|
if (!junior) {
|
||||||
|
throw new BadRequestException('JUNIOR.NOT_FOUND');
|
||||||
|
}
|
||||||
|
return junior;
|
||||||
|
}
|
||||||
|
|
||||||
|
findJuniorsByGuardianId(guardianId: string, pageOptions: PageOptionsRequestDto) {
|
||||||
|
return this.juniorRepository.findJuniorsByGuardianId(guardianId, pageOptions);
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,20 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
import { Logger } from 'nestjs-pino';
|
import { Logger } from 'nestjs-pino';
|
||||||
|
import { initializeTransactionalContext } from 'typeorm-transactional';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
const DEFAULT_PORT = 3000;
|
const DEFAULT_PORT = 3000;
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
initializeTransactionalContext();
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
app.useLogger(app.get(Logger));
|
app.useLogger(app.get(Logger));
|
||||||
|
app.enableCors({
|
||||||
|
origin: '*',
|
||||||
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
|
preflightContinue: false,
|
||||||
|
optionsSuccessStatus: 204,
|
||||||
|
});
|
||||||
const config = app.get(ConfigService);
|
const config = app.get(ConfigService);
|
||||||
const swaggerDocument = await createSwagger(app);
|
const swaggerDocument = await createSwagger(app);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './src/app.module';
|
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
|
import { initializeTransactionalContext } from 'typeorm-transactional';
|
||||||
|
import { AppModule } from './src/app.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getting data source through NestJS app helps in getting entities dynamically with "autoLoadEntities" NestJS feature
|
* Getting data source through NestJS app helps in getting entities dynamically with "autoLoadEntities" NestJS feature
|
||||||
@ -8,6 +9,7 @@ import { DataSource } from 'typeorm';
|
|||||||
*/
|
*/
|
||||||
async function getTypeOrmDataSource() {
|
async function getTypeOrmDataSource() {
|
||||||
process.env.MIGRATIONS_RUN = 'false';
|
process.env.MIGRATIONS_RUN = 'false';
|
||||||
|
initializeTransactionalContext();
|
||||||
|
|
||||||
const app = await NestFactory.createApplicationContext(AppModule);
|
const app = await NestFactory.createApplicationContext(AppModule);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user