diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 4dfb072..fa6191a 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -3,18 +3,14 @@ import { JwtModule } from '@nestjs/jwt'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CustomerModule } from '~/customer/customer.module'; import { AuthController } from './controllers'; -import { Device, User, UserNotificationSettings } from './entities'; +import { Device, User } from './entities'; import { DeviceRepository, UserRepository } from './repositories'; import { AuthService, DeviceService } from './services'; import { UserService } from './services/user.service'; import { AccessTokenStrategy } from './strategies'; @Module({ - imports: [ - TypeOrmModule.forFeature([User, UserNotificationSettings, Device]), - JwtModule.register({}), - forwardRef(() => CustomerModule), - ], + imports: [TypeOrmModule.forFeature([User, Device]), JwtModule.register({}), forwardRef(() => CustomerModule)], providers: [AuthService, UserRepository, UserService, DeviceService, DeviceRepository, AccessTokenStrategy], controllers: [AuthController], exports: [UserService], diff --git a/src/auth/entities/index.ts b/src/auth/entities/index.ts index 423ba61..e9dde3b 100644 --- a/src/auth/entities/index.ts +++ b/src/auth/entities/index.ts @@ -1,3 +1,2 @@ export * from './device.entity'; -export * from './user-notification-settings.entity'; export * from './user.entity'; diff --git a/src/auth/entities/user.entity.ts b/src/auth/entities/user.entity.ts index a85d966..7b4a44d 100644 --- a/src/auth/entities/user.entity.ts +++ b/src/auth/entities/user.entity.ts @@ -3,7 +3,6 @@ import { Column, CreateDateColumn, Entity, - JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn, @@ -11,10 +10,8 @@ import { } from 'typeorm'; import { Otp } from '~/common/modules/otp/entities'; import { Customer } from '~/customer/entities/customer.entity'; -import { Document } from '~/document/entities'; import { Roles } from '../enums'; import { Device } from './device.entity'; -import { UserNotificationSettings } from './user-notification-settings.entity'; @Entity('users') export class User extends BaseEntity { @@ -48,22 +45,9 @@ export class User extends BaseEntity { @Column('text', { nullable: true, array: true, name: 'roles' }) roles!: Roles[]; - @Column('varchar', { name: 'profile_picture_id', nullable: true }) - profilePictureId!: string; - - @OneToOne(() => Document, (document) => document.user, { cascade: true, nullable: true }) - @JoinColumn({ name: 'profile_picture_id' }) - profilePicture!: Document; - @OneToMany(() => Otp, (otp) => otp.user) otp!: Otp[]; - @OneToOne(() => UserNotificationSettings, (notificationSettings) => notificationSettings.user, { - cascade: true, - eager: true, - }) - notificationSettings!: UserNotificationSettings; - @OneToOne(() => Customer, (customer) => customer.user, { cascade: true, eager: true }) customer!: Customer; diff --git a/src/auth/repositories/user.repository.ts b/src/auth/repositories/user.repository.ts index a01e5ec..eeaa281 100644 --- a/src/auth/repositories/user.repository.ts +++ b/src/auth/repositories/user.repository.ts @@ -1,8 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FindOptionsWhere, Repository } from 'typeorm'; -import { UpdateNotificationsSettingsRequestDto } from '~/customer/dtos/request'; -import { User, UserNotificationSettings } from '../entities'; +import { User } from '../entities'; @Injectable() export class UserRepository { @@ -14,7 +13,6 @@ export class UserRepository { phoneNumber: data.phoneNumber, countryCode: data.countryCode, roles: data.roles, - notificationSettings: UserNotificationSettings.create(), }), ); } @@ -23,11 +21,6 @@ export class UserRepository { return this.userRepository.findOne({ where }); } - updateNotificationSettings(user: User, body: UpdateNotificationsSettingsRequestDto) { - user.notificationSettings = UserNotificationSettings.create({ ...user.notificationSettings, ...body }); - return this.userRepository.save(user); - } - update(userId: string, data: Partial) { return this.userRepository.update(userId, data); } @@ -35,7 +28,6 @@ export class UserRepository { createUser(data: Partial) { const user = this.userRepository.create({ ...data, - notificationSettings: UserNotificationSettings.create(), }); return this.userRepository.save(user); diff --git a/src/auth/services/user.service.ts b/src/auth/services/user.service.ts index 47579e2..37942d3 100644 --- a/src/auth/services/user.service.ts +++ b/src/auth/services/user.service.ts @@ -1,6 +1,6 @@ import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common'; import { FindOptionsWhere } from 'typeorm'; -import { UpdateNotificationsSettingsRequestDto } from '~/customer/dtos/request'; +import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity'; import { CustomerService } from '~/customer/services'; import { Guardian } from '~/guardian/entities/guradian.entity'; import { CreateUnverifiedUserRequestDto } from '../dtos/request'; @@ -15,15 +15,6 @@ export class UserService { @Inject(forwardRef(() => CustomerService)) private readonly customerService: CustomerService, ) {} - async updateNotificationSettings(userId: string, body: UpdateNotificationsSettingsRequestDto) { - const user = await this.findUserOrThrow({ id: userId }); - - const notificationSettings = (await this.userRepository.updateNotificationSettings(user, body)) - .notificationSettings; - - return notificationSettings; - } - findUser(where: FindOptionsWhere | FindOptionsWhere[]) { return this.userRepository.findOne(where); } @@ -74,6 +65,7 @@ export class UserService { await this.customerService.createCustomer( { guardian: Guardian.create({ id: user.id }), + notificationSettings: new CustomerNotificationSettings(), }, user, ); diff --git a/src/customer/controllers/customer.controller.ts b/src/customer/controllers/customer.controller.ts index 3c48c4c..83aad3c 100644 --- a/src/customer/controllers/customer.controller.ts +++ b/src/customer/controllers/customer.controller.ts @@ -1,9 +1,9 @@ -import { Body, Controller, Patch, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Patch, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { Roles } from '~/auth/enums'; import { IJwtPayload } from '~/auth/interfaces'; -import { AllowedRoles, AuthenticatedUser } from '~/common/decorators'; -import { AccessTokenGuard, RolesGuard } from '~/common/guards'; +import { AuthenticatedUser } from '~/common/decorators'; +import { AccessTokenGuard } from '~/common/guards'; +import { ApiDataResponse } from '~/core/decorators'; import { ResponseFactory } from '~/core/utils'; import { UpdateCustomerRequestDto, UpdateNotificationsSettingsRequestDto } from '../dtos/request'; import { CustomerResponseDto, NotificationSettingsResponseDto } from '../dtos/response'; @@ -15,9 +15,18 @@ import { CustomerService } from '../services'; export class CustomerController { constructor(private readonly customerService: CustomerService) {} + @Get('/profile') + @UseGuards(AccessTokenGuard) + @ApiDataResponse(CustomerResponseDto) + async getCustomerProfile(@AuthenticatedUser() { sub }: IJwtPayload) { + const customer = await this.customerService.findCustomerById(sub); + + return ResponseFactory.data(new CustomerResponseDto(customer)); + } + @Patch('') - @UseGuards(RolesGuard) - @AllowedRoles(Roles.GUARDIAN) + @UseGuards(AccessTokenGuard) + @ApiDataResponse(CustomerResponseDto) async updateCustomer(@AuthenticatedUser() { sub }: IJwtPayload, @Body() body: UpdateCustomerRequestDto) { const customer = await this.customerService.updateCustomer(sub, body); @@ -26,6 +35,7 @@ export class CustomerController { @Patch('settings/notifications') @UseGuards(AccessTokenGuard) + @ApiDataResponse(NotificationSettingsResponseDto) async updateNotificationSettings( @AuthenticatedUser() { sub }: IJwtPayload, @Body() body: UpdateNotificationsSettingsRequestDto, diff --git a/src/customer/customer.module.ts b/src/customer/customer.module.ts index 7ce58d0..979ff9c 100644 --- a/src/customer/customer.module.ts +++ b/src/customer/customer.module.ts @@ -3,11 +3,12 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthModule } from '~/auth/auth.module'; import { CustomerController } from './controllers'; import { Customer } from './entities'; +import { CustomerNotificationSettings } from './entities/customer-notification-settings.entity'; import { CustomerRepository } from './repositories/customer.repository'; import { CustomerService } from './services'; @Module({ - imports: [TypeOrmModule.forFeature([Customer]), forwardRef(() => AuthModule)], + imports: [TypeOrmModule.forFeature([Customer, CustomerNotificationSettings]), forwardRef(() => AuthModule)], controllers: [CustomerController], providers: [CustomerService, CustomerRepository], exports: [CustomerService], diff --git a/src/customer/dtos/request/update-customer.request.dto.ts b/src/customer/dtos/request/update-customer.request.dto.ts index c81f8a7..a835722 100644 --- a/src/customer/dtos/request/update-customer.request.dto.ts +++ b/src/customer/dtos/request/update-customer.request.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsDateString, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsDateString, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { i18nValidationMessage as i18n } from 'nestjs-i18n'; import { IsAbove18 } from '~/core/decorators/validations'; export class UpdateCustomerRequestDto { @@ -26,4 +26,8 @@ export class UpdateCustomerRequestDto { @IsAbove18({ message: i18n('validation.IsAbove18', { path: 'general', property: 'customer.dateOfBirth' }) }) @IsOptional() dateOfBirth!: Date; + + @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000' }) + @IsUUID('4', { message: i18n('validation.IsUUID', { path: 'general', property: 'customer.profilePictureId' }) }) + profilePictureId!: string; } diff --git a/src/customer/dtos/response/customer-response.dto.ts b/src/customer/dtos/response/customer-response.dto.ts index 4c2ef30..61fbb22 100644 --- a/src/customer/dtos/response/customer-response.dto.ts +++ b/src/customer/dtos/response/customer-response.dto.ts @@ -1,5 +1,7 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Customer } from '~/customer/entities'; +import { DocumentMetaResponseDto } from '~/document/dtos/response'; +import { NotificationSettingsResponseDto } from './notification-settings.response.dto'; export class CustomerResponseDto { @ApiProperty() @@ -50,6 +52,12 @@ export class CustomerResponseDto { @ApiProperty() isGuardian!: boolean; + @ApiProperty() + notificationSettings!: NotificationSettingsResponseDto; + + @ApiPropertyOptional({ type: DocumentMetaResponseDto }) + profilePicture!: DocumentMetaResponseDto | null; + constructor(customer: Customer) { this.id = customer.id; this.customerStatus = customer.customerStatus; @@ -67,5 +75,7 @@ export class CustomerResponseDto { this.gender = customer.gender; this.isJunior = customer.isJunior; this.isGuardian = customer.isGuardian; + this.notificationSettings = new NotificationSettingsResponseDto(customer.notificationSettings); + this.profilePicture = customer.profilePicture ? new DocumentMetaResponseDto(customer.profilePicture) : null; } } diff --git a/src/customer/dtos/response/notification-settings.response.dto.ts b/src/customer/dtos/response/notification-settings.response.dto.ts index f7a5e6f..cddd18f 100644 --- a/src/customer/dtos/response/notification-settings.response.dto.ts +++ b/src/customer/dtos/response/notification-settings.response.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { UserNotificationSettings } from '~/auth/entities'; +import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity'; export class NotificationSettingsResponseDto { @ApiProperty() @@ -11,7 +11,7 @@ export class NotificationSettingsResponseDto { @ApiProperty() isSmsEnabled!: boolean; - constructor(notificationSettings: UserNotificationSettings) { + constructor(notificationSettings: CustomerNotificationSettings) { this.isEmailEnabled = notificationSettings.isEmailEnabled; this.isPushEnabled = notificationSettings.isPushEnabled; this.isSmsEnabled = notificationSettings.isSmsEnabled; diff --git a/src/auth/entities/user-notification-settings.entity.ts b/src/customer/entities/customer-notification-settings.entity.ts similarity index 67% rename from src/auth/entities/user-notification-settings.entity.ts rename to src/customer/entities/customer-notification-settings.entity.ts index c740d30..173734f 100644 --- a/src/auth/entities/user-notification-settings.entity.ts +++ b/src/customer/entities/customer-notification-settings.entity.ts @@ -8,10 +8,10 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { User } from './user.entity'; +import { Customer } from '~/customer/entities'; -@Entity('user_notification_settings') -export class UserNotificationSettings extends BaseEntity { +@Entity('cutsomer_notification_settings') +export class CustomerNotificationSettings extends BaseEntity { @PrimaryGeneratedColumn('uuid') id!: string; @@ -24,9 +24,9 @@ export class UserNotificationSettings extends BaseEntity { @Column({ name: 'is_sms_enabled', default: false }) isSmsEnabled!: boolean; - @OneToOne(() => User, (user) => user.notificationSettings, { onDelete: 'CASCADE' }) - @JoinColumn({ name: 'user_id' }) - user!: User; + @OneToOne(() => Customer, (customer) => customer.notificationSettings, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'customer_id' }) + customer!: Customer; @CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' }) createdAt!: Date; diff --git a/src/customer/entities/customer.entity.ts b/src/customer/entities/customer.entity.ts index 8cd62ad..1405d4c 100644 --- a/src/customer/entities/customer.entity.ts +++ b/src/customer/entities/customer.entity.ts @@ -9,10 +9,12 @@ import { UpdateDateColumn, } from 'typeorm'; import { User } from '~/auth/entities'; +import { Document } from '~/document/entities'; import { Guardian } from '~/guardian/entities/guradian.entity'; import { Junior } from '~/junior/entities'; +import { CustomerNotificationSettings } from './customer-notification-settings.entity'; -@Entity() +@Entity('customers') export class Customer extends BaseEntity { @PrimaryColumn('uuid') id!: string; @@ -65,6 +67,19 @@ export class Customer extends BaseEntity { @Column('varchar', { name: 'user_id' }) userId!: string; + @Column('varchar', { name: 'profile_picture_id', nullable: true }) + profilePictureId!: string; + + @OneToOne(() => CustomerNotificationSettings, (notificationSettings) => notificationSettings.customer, { + cascade: true, + eager: true, + }) + notificationSettings!: CustomerNotificationSettings; + + @OneToOne(() => Document, (document) => document.customerPicture, { cascade: true, nullable: true }) + @JoinColumn({ name: 'profile_picture_id' }) + profilePicture!: Document; + @OneToOne(() => User, (user) => user.customer, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'user_id' }) user!: User; diff --git a/src/customer/repositories/customer.repository.ts b/src/customer/repositories/customer.repository.ts index 4bac158..a18a90c 100644 --- a/src/customer/repositories/customer.repository.ts +++ b/src/customer/repositories/customer.repository.ts @@ -3,19 +3,20 @@ import { InjectRepository } from '@nestjs/typeorm'; import { FindOptionsWhere, Repository } from 'typeorm'; import { User } from '~/auth/entities'; import { Roles } from '~/auth/enums'; -import { UpdateCustomerRequestDto } from '../dtos/request'; +import { UpdateNotificationsSettingsRequestDto } from '../dtos/request'; import { Customer } from '../entities'; +import { CustomerNotificationSettings } from '../entities/customer-notification-settings.entity'; @Injectable() export class CustomerRepository { constructor(@InjectRepository(Customer) private readonly customerRepository: Repository) {} - updateCustomer(id: string, data: UpdateCustomerRequestDto) { + updateCustomer(id: string, data: Partial) { return this.customerRepository.update(id, data); } findOne(where: FindOptionsWhere) { - return this.customerRepository.findOne({ where }); + return this.customerRepository.findOne({ where, relations: ['profilePicture'] }); } createCustomer(customerData: Partial, user: User) { @@ -29,4 +30,12 @@ export class CustomerRepository { }), ); } + + updateNotificationSettings(customer: Customer, body: UpdateNotificationsSettingsRequestDto) { + customer.notificationSettings = CustomerNotificationSettings.create({ + ...customer.notificationSettings, + ...body, + }); + return this.customerRepository.save(customer); + } } diff --git a/src/customer/services/customer.service.ts b/src/customer/services/customer.service.ts index f7ca3ff..7be024b 100644 --- a/src/customer/services/customer.service.ts +++ b/src/customer/services/customer.service.ts @@ -1,18 +1,20 @@ -import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { User } from '~/auth/entities'; -import { UserService } from '~/auth/services/user.service'; +import { OciService } from '~/document/services'; import { UpdateCustomerRequestDto, UpdateNotificationsSettingsRequestDto } from '../dtos/request'; import { Customer } from '../entities'; import { CustomerRepository } from '../repositories/customer.repository'; @Injectable() export class CustomerService { - constructor( - @Inject(forwardRef(() => UserService)) private readonly userService: UserService, - private readonly customerRepository: CustomerRepository, - ) {} - updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto) { - return this.userService.updateNotificationSettings(userId, data); + constructor(private readonly customerRepository: CustomerRepository, private readonly ociService: OciService) {} + async updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto) { + const customer = await this.findCustomerById(userId); + + const notificationSettings = (await this.customerRepository.updateNotificationSettings(customer, data)) + .notificationSettings; + + return notificationSettings; } async updateCustomer(userId: string, data: UpdateCustomerRequestDto): Promise { @@ -26,9 +28,14 @@ export class CustomerService { async findCustomerById(id: string) { const customer = await this.customerRepository.findOne({ id }); + if (!customer) { throw new BadRequestException('CUSTOMER.NOT_FOUND'); } + + if (customer.profilePicture) { + customer.profilePicture.url = await this.ociService.generatePreSignedUrl(customer.profilePicture); + } return customer; } } diff --git a/src/db/migrations/1732434281561-create-document-entity.ts b/src/db/migrations/1732434281561-create-document-entity.ts index 46126dd..7cb57af 100644 --- a/src/db/migrations/1732434281561-create-document-entity.ts +++ b/src/db/migrations/1732434281561-create-document-entity.ts @@ -9,7 +9,7 @@ export class CreateDocumentEntity1732434281561 implements MigrationInterface { "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "extension" character varying(255) NOT NULL, - "documentType" character varying(255) NOT NULL, + "document_type" character varying(255) NOT NULL, "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_ac51aa5181ee2036f5ca482857c" PRIMARY KEY ("id"))`, diff --git a/src/db/migrations/1733206728721-create-user-entity.ts b/src/db/migrations/1733206728721-create-user-entity.ts index be4c9a6..8e0a92c 100644 --- a/src/db/migrations/1733206728721-create-user-entity.ts +++ b/src/db/migrations/1733206728721-create-user-entity.ts @@ -16,16 +16,10 @@ export class CreateUserEntity1733206728721 implements MigrationInterface { "apple_id" character varying(255), "is_profile_completed" boolean NOT NULL DEFAULT false, "roles" text array, - "profile_picture_id" uuid, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - CONSTRAINT "REL_02ec15de199e79a0c46869895f" UNIQUE ("profile_picture_id"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`, ); - - await queryRunner.query( - `ALTER TABLE "users" ADD CONSTRAINT "FK_02ec15de199e79a0c46869895f4" FOREIGN KEY ("profile_picture_id") REFERENCES "documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, - ); } public async down(queryRunner: QueryRunner): Promise { diff --git a/src/db/migrations/1733231692252-create-notification-settings-table.ts b/src/db/migrations/1733231692252-create-notification-settings-table.ts deleted file mode 100644 index a53d572..0000000 --- a/src/db/migrations/1733231692252-create-notification-settings-table.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class CreateNotificationSettingsTable1733231692252 implements MigrationInterface { - name = 'CreateNotificationSettingsTable1733231692252'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "user_notification_settings" - ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), - "is_email_enabled" boolean NOT NULL DEFAULT false, - "is_push_enabled" boolean NOT NULL DEFAULT false, - "is_sms_enabled" boolean NOT NULL DEFAULT false, - "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - "user_id" uuid, CONSTRAINT "REL_52182ffd0f785e8256f8fcb4fd" UNIQUE ("user_id"), - CONSTRAINT "PK_a195de67d093e096152f387afbd" PRIMARY KEY ("id"))`, - ); - await queryRunner.query( - `ALTER TABLE "user_notification_settings" ADD CONSTRAINT "FK_52182ffd0f785e8256f8fcb4fd6" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `ALTER TABLE "user_notification_settings" DROP CONSTRAINT "FK_52182ffd0f785e8256f8fcb4fd6"`, - ); - await queryRunner.query(`DROP TABLE "user_notification_settings"`); - } -} diff --git a/src/db/migrations/1733298524771-create-customer-entity.ts b/src/db/migrations/1733298524771-create-customer-entity.ts index 588cc7e..d1bd3fb 100644 --- a/src/db/migrations/1733298524771-create-customer-entity.ts +++ b/src/db/migrations/1733298524771-create-customer-entity.ts @@ -5,7 +5,7 @@ export class CreateCustomerEntity1733298524771 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `CREATE TABLE "customer" + `CREATE TABLE "customers" ("id" uuid NOT NULL, "customer_status" character varying(255) NOT NULL DEFAULT 'PENDING', "rejection_reason" text, @@ -22,19 +22,25 @@ export class CreateCustomerEntity1733298524771 implements MigrationInterface { "gender" character varying(255), "is_junior" boolean NOT NULL DEFAULT false, "is_guardian" boolean NOT NULL DEFAULT false, + "profile_picture_id" uuid, "user_id" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - CONSTRAINT "REL_5d1f609371a285123294fddcf3" UNIQUE ("user_id"), + CONSTRAINT "REL_5d1f609371a285123294fddcf3" UNIQUE ("user_id"), + CONSTRAINT "REL_02ec15de199e79a0c46869895f" UNIQUE ("profile_picture_id"), CONSTRAINT "PK_a7a13f4cacb744524e44dfdad32" PRIMARY KEY ("id"))`, ); await queryRunner.query( - `ALTER TABLE "customer" ADD CONSTRAINT "FK_5d1f609371a285123294fddcf3a" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + `ALTER TABLE "customers" ADD CONSTRAINT "FK_e7574892da11dd01de5cfc46499" FOREIGN KEY ("profile_picture_id") REFERENCES "documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "customers" ADD CONSTRAINT "FK_11d81cd7be87b6f8865b0cf7661" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, ); } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "customer" DROP CONSTRAINT "FK_5d1f609371a285123294fddcf3a"`); - await queryRunner.query(`DROP TABLE "customer"`); + await queryRunner.query(`ALTER TABLE "customers" DROP CONSTRAINT "FK_11d81cd7be87b6f8865b0cf7661"`); + await queryRunner.query(`ALTER TABLE "customers" DROP CONSTRAINT "FK_e7574892da11dd01de5cfc46499"`); + await queryRunner.query(`DROP TABLE "customers"`); } } diff --git a/src/db/migrations/1733731507261-create-junior-entity.ts b/src/db/migrations/1733731507261-create-junior-entity.ts index 4467c60..b774c54 100644 --- a/src/db/migrations/1733731507261-create-junior-entity.ts +++ b/src/db/migrations/1733731507261-create-junior-entity.ts @@ -24,7 +24,7 @@ export class CreateJuniorEntity1733731507261 implements MigrationInterface { `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`, + `ALTER TABLE "juniors" ADD CONSTRAINT "FK_dfbf64ede1ff823a489902448a2" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, ); } diff --git a/src/db/migrations/1733732021622-create-guardian-entity.ts b/src/db/migrations/1733732021622-create-guardian-entity.ts index ab3d334..ac198a8 100644 --- a/src/db/migrations/1733732021622-create-guardian-entity.ts +++ b/src/db/migrations/1733732021622-create-guardian-entity.ts @@ -17,7 +17,7 @@ export class CreateGuardianEntity1733732021622 implements MigrationInterface { `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`, + `ALTER TABLE "guardians" ADD CONSTRAINT "FK_6c46a1b6af00e6457cb1b70f7e7" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, ); } diff --git a/src/db/migrations/1733993920226-create-customer-notifications-settings-table.ts b/src/db/migrations/1733993920226-create-customer-notifications-settings-table.ts new file mode 100644 index 0000000..69406cf --- /dev/null +++ b/src/db/migrations/1733993920226-create-customer-notifications-settings-table.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateCustomerNotificationsSettingsTable1733993920226 implements MigrationInterface { + name = 'CreateCustomerNotificationsSettingsTable1733993920226'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "cutsomer_notification_settings" + ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "is_email_enabled" boolean NOT NULL DEFAULT false, + "is_push_enabled" boolean NOT NULL DEFAULT false, + "is_sms_enabled" boolean NOT NULL DEFAULT false, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + "customer_id" uuid, CONSTRAINT "REL_32f2b707407298a9eecd6cc7ea" UNIQUE ("customer_id"), + CONSTRAINT "PK_ea94fb22410c89ae6b37d63b0e3" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "cutsomer_notification_settings" ADD CONSTRAINT "FK_32f2b707407298a9eecd6cc7ea6" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "cutsomer_notification_settings" DROP CONSTRAINT "FK_32f2b707407298a9eecd6cc7ea6"`, + ); + await queryRunner.query(`DROP TABLE "cutsomer_notification_settings"`); + } +} diff --git a/src/db/migrations/index.ts b/src/db/migrations/index.ts index b250ac9..3fe36f1 100644 --- a/src/db/migrations/index.ts +++ b/src/db/migrations/index.ts @@ -1,7 +1,6 @@ export * from './1732434281561-create-document-entity'; export * from './1733206728721-create-user-entity'; export * from './1733209041336-create-otp-entity'; -export * from './1733231692252-create-notification-settings-table'; export * from './1733298524771-create-customer-entity'; export * from './1733314952318-create-device-entity'; export * from './1733731507261-create-junior-entity'; @@ -10,3 +9,4 @@ export * from './1733748083604-create-theme-entity'; export * from './1733750228289-seed-default-avatar'; export * from './1733904556416-create-task-entities'; export * from './1733990253208-seeds-default-tasks-logo'; +export * from './1733993920226-create-customer-notifications-settings-table'; diff --git a/src/document/dtos/response/document-meta.response.dto.ts b/src/document/dtos/response/document-meta.response.dto.ts index f2fdeaa..2a0eb8d 100644 --- a/src/document/dtos/response/document-meta.response.dto.ts +++ b/src/document/dtos/response/document-meta.response.dto.ts @@ -18,19 +18,11 @@ export class DocumentMetaResponseDto { @ApiProperty({ type: String }) url!: string | null; - @ApiProperty() - createdAt!: Date; - - @ApiProperty() - updatedAt!: Date; - constructor(document: Document) { this.id = document.id; this.name = document.name; this.extension = document.extension; this.documentType = document.documentType; this.url = document.url || null; - this.createdAt = document.createdAt; - this.updatedAt = document.updatedAt; } } diff --git a/src/document/entities/document.entity.ts b/src/document/entities/document.entity.ts index 9f55d76..1f0d4e9 100644 --- a/src/document/entities/document.entity.ts +++ b/src/document/entities/document.entity.ts @@ -1,5 +1,6 @@ import { Column, Entity, OneToMany, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { User } from '~/auth/entities'; +import { Customer } from '~/customer/entities'; import { Junior, Theme } from '~/junior/entities'; import { Task } from '~/task/entities'; import { TaskSubmission } from '~/task/entities/task-submissions.entity'; @@ -16,16 +17,16 @@ export class Document { @Column({ type: 'varchar', length: 255 }) extension!: string; - @Column({ type: 'varchar', length: 255 }) + @Column({ type: 'varchar', length: 255, name: 'document_type' }) documentType!: DocumentType; - @OneToOne(() => User, (user) => user.profilePicture, { onDelete: 'CASCADE' }) - user?: User; + @OneToOne(() => Customer, (customer) => customer.profilePicture, { onDelete: 'SET NULL' }) + customerPicture?: Customer; - @OneToOne(() => Junior, (junior) => junior.civilIdFront, { onDelete: 'CASCADE' }) + @OneToOne(() => Junior, (junior) => junior.civilIdFront, { onDelete: 'SET NULL' }) juniorCivilIdFront?: User; - @OneToOne(() => Junior, (junior) => junior.civilIdBack, { onDelete: 'CASCADE' }) + @OneToOne(() => Junior, (junior) => junior.civilIdBack, { onDelete: 'SET NULL' }) juniorCivilIdBack?: User; @OneToMany(() => Theme, (theme) => theme.avatar) diff --git a/src/document/services/oci.service.ts b/src/document/services/oci.service.ts index 2b54cd5..4880a88 100644 --- a/src/document/services/oci.service.ts +++ b/src/document/services/oci.service.ts @@ -70,6 +70,7 @@ export class OciService { return null; } const cachedUrl = await this.cacheService.get(document.id); + if (cachedUrl) { return cachedUrl; } @@ -82,7 +83,7 @@ export class OciService { this.logger.debug(`Generating pre-signed url for object ${objectName} in bucket ${bucketName}`); const res = await this.ociClient.createPreauthenticatedRequest({ namespaceName: this.namespace, - bucketName, + bucketName: 'asd', createPreauthenticatedRequestDetails: { name: objectName, accessType: CreatePreauthenticatedRequestDetails.AccessType.AnyObjectRead, @@ -95,7 +96,7 @@ export class OciService { this.cacheService.set(document.id, res.preauthenticatedRequest.fullPath + objectName, '1h'); return res.preauthenticatedRequest.fullPath + objectName; } catch (error) { - this.logger.error('Error generating pre-signed url', JSON.stringify(error)); + this.logger.error(`Error generating pre-signed url: ${error}`); return document.name; } } diff --git a/src/junior/dtos/response/junior.response.dto.ts b/src/junior/dtos/response/junior.response.dto.ts index bd77875..98f67db 100644 --- a/src/junior/dtos/response/junior.response.dto.ts +++ b/src/junior/dtos/response/junior.response.dto.ts @@ -20,8 +20,8 @@ export class JuniorResponseDto { this.id = junior.id; this.fullName = `${junior.customer.firstName} ${junior.customer.lastName}`; this.relationship = junior.relationship; - this.profilePicture = junior.customer.user.profilePicture - ? new DocumentMetaResponseDto(junior.customer.user.profilePicture) + this.profilePicture = junior.customer.profilePicture + ? new DocumentMetaResponseDto(junior.customer.profilePicture) : null; } } diff --git a/src/junior/services/junior.service.ts b/src/junior/services/junior.service.ts index a8788a8..e4bf3e2 100644 --- a/src/junior/services/junior.service.ts +++ b/src/junior/services/junior.service.ts @@ -3,6 +3,7 @@ import { Transactional } from 'typeorm-transactional'; import { Roles } from '~/auth/enums'; import { UserService } from '~/auth/services'; import { PageOptionsRequestDto } from '~/core/dtos'; +import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity'; import { CustomerService } from '~/customer/services'; import { CreateJuniorRequestDto, SetThemeRequestDto } from '../dtos/request'; import { Junior } from '../entities'; @@ -43,6 +44,7 @@ export class JuniorService { civilIdFrontId: body.civilIdFrontId, civilIdBackId: body.civilIdBackId, }), + notificationSettings: new CustomerNotificationSettings(), }, user, ); diff --git a/src/task/services/task.service.ts b/src/task/services/task.service.ts index cf97689..d2caa1c 100644 --- a/src/task/services/task.service.ts +++ b/src/task/services/task.service.ts @@ -80,7 +80,7 @@ export class TaskService { const [imageUrl, submissionUrl, profilePictureUrl] = await Promise.all([ this.ociService.generatePreSignedUrl(task.image), this.ociService.generatePreSignedUrl(task.submission?.proofOfCompletion), - this.ociService.generatePreSignedUrl(task.assignedTo.customer.user.profilePicture), + this.ociService.generatePreSignedUrl(task.assignedTo.customer.profilePicture), ]); task.image.url = imageUrl; @@ -89,8 +89,8 @@ export class TaskService { task.submission.proofOfCompletion.url = submissionUrl; } - if (task.assignedTo.customer.user.profilePicture) { - task.assignedTo.customer.user.profilePicture.url = profilePictureUrl; + if (task.assignedTo.customer.profilePicture) { + task.assignedTo.customer.profilePicture.url = profilePictureUrl; } }), );