diff --git a/apps/business/Jenkinsfile b/apps/business/Jenkinsfile index 7abb8b69..279045d0 100644 --- a/apps/business/Jenkinsfile +++ b/apps/business/Jenkinsfile @@ -3,13 +3,13 @@ pipeline { agent any environment { ENV_TYPE = "production" - PORT = 3986 + PORT = 4052 NAMESPACE = "yogram-ru" REGISTRY_HOSTNAME = "idogmat" - PROJECT = "business" + PROJECT = "yogram-business" SERVICE="business" REGISTRY = "registry.hub.docker.com" - DEPLOYMENT_NAME = "business-deployment" + DEPLOYMENT_NAME = "yogram-business-deployment" IMAGE_NAME = "${env.BUILD_ID}_${env.ENV_TYPE}_${env.GIT_COMMIT}" DOCKER_BUILD_NAME = "${env.REGISTRY_HOSTNAME}/${env.PROJECT}:${env.IMAGE_NAME}" } diff --git a/apps/business/deployment.yaml b/apps/business/deployment.yaml index 65e66d67..58292cff 100644 --- a/apps/business/deployment.yaml +++ b/apps/business/deployment.yaml @@ -22,63 +22,63 @@ spec: - containerPort: PORT_CONTAINER env: - - name: POSTGRES_TYPE - valueFrom: - secretKeyRef: - name: business-production-config-secret - key: POSTGRES_TYPE - - name: BUSINESS_SERVICE_URL - valueFrom: - secretKeyRef: - name: business-production-config-secret - key: BUSINESS_SERVICE_URL - - name: POSTGRES_URL_SLAVE - valueFrom: - secretKeyRef: - name: business-production-config-secret - key: POSTGRES_URL_SLAVE - - name: PAYPAL_CLIENT_ID - valueFrom: - secretKeyRef: - name: business-production-config-secret - key: PAYPAL_CLIENT_ID - name: PAYPAL_SECRET valueFrom: secretKeyRef: - name: business-production-config-secret + name: yogram-business-production-config-secret key: PAYPAL_SECRET - name: USERS_SERVICE_URL valueFrom: secretKeyRef: - name: business-production-config-secret + name: yogram-business-production-config-secret key: USERS_SERVICE_URL - - name: REDIS_USER - valueFrom: - secretKeyRef: - name: business-production-config-secret - key: REDIS_USER - name: REDIS_PASSWORD valueFrom: secretKeyRef: - name: business-production-config-secret + name: yogram-business-production-config-secret key: REDIS_PASSWORD - name: REDIS_HOST valueFrom: secretKeyRef: - name: business-production-config-secret + name: yogram-business-production-config-secret key: REDIS_HOST - name: REDIS_PORT valueFrom: secretKeyRef: - name: business-production-config-secret + name: yogram-business-production-config-secret key: REDIS_PORT - name: TIME_PERIOD valueFrom: secretKeyRef: - name: business-production-config-secret + name: yogram-business-production-config-secret key: TIME_PERIOD + - name: REDIS_USER + valueFrom: + secretKeyRef: + name: yogram-business-production-config-secret + key: REDIS_USER + - name: BUSINESS_SERVICE_URL + valueFrom: + secretKeyRef: + name: yogram-business-production-config-secret + key: BUSINESS_SERVICE_URL + - name: POSTGRES_TYPE + valueFrom: + secretKeyRef: + name: yogram-business-production-config-secret + key: POSTGRES_TYPE - name: POSTGRES_URL valueFrom: secretKeyRef: - name: business-production-config-secret + name: yogram-business-production-config-secret key: POSTGRES_URL + - name: PROFILE_SETTINGS_PAGE + valueFrom: + secretKeyRef: + name: yogram-business-production-config-secret + key: PROFILE_SETTINGS_PAGE + - name: PAYPAL_CLIENT_ID + valueFrom: + secretKeyRef: + name: yogram-business-production-config-secret + key: PAYPAL_CLIENT_ID diff --git a/apps/business/src/api/business.controller.ts b/apps/business/src/api/business.controller.ts index 0c02d205..4dc844ad 100644 --- a/apps/business/src/api/business.controller.ts +++ b/apps/business/src/api/business.controller.ts @@ -21,6 +21,7 @@ import { Param, Patch, Post, + Res, Sse, } from '@nestjs/common'; import { @@ -38,6 +39,9 @@ import { import { NotificationResponseDto } from 'apps/libs/Business/dto/response/response-notification.dto'; import { ReadNotificationCommand } from '../application/command/read-notification.command'; import { ReadNotificationDto } from '../../../../apps/libs/Business/dto/input/read-notification.dto'; +import { Response } from 'express'; +import { ConfigService } from '@nestjs/config'; +import { CancelSubscriptionCommand } from '../application/command/cancel-subscription.handler'; @Controller() export class BusinessController { @@ -45,6 +49,7 @@ export class BusinessController { constructor( private readonly commandBus: CommandBus, private readonly queryBus: QueryBus, + private readonly configService: ConfigService, ) { this.eventEmitter = new EventEmitter2(); } @@ -57,8 +62,16 @@ export class BusinessController { @Post('business/paypal-proccess') async paypalProcess( @Body('subscriptionId') subscriptionId: string, + @Res() res: Response, ): Promise { - await this.commandBus.execute(new SaveSubscriptionCommand(subscriptionId)); + const subscription = await this.commandBus.execute( + new SaveSubscriptionCommand(subscriptionId), + ); + res.status(200).json(subscription); + // let page = this.configService.get('PROFILE_SETTINGS_PAGE'); + // page = page.replace('replace', subscription.userId); + // console.log('🚀 ~ BusinessController ~ paypalProcess ~ page:', page); + // res.redirect(301, 'https://www.google.com/'); } @HttpCode(200) @@ -113,6 +126,12 @@ export class BusinessController { return await this.commandBus.execute(new ActivateSubscriptionCommand(id)); } + @Patch('business/subscriptions/:id/cancel') + async cancelSubscription(@Param('id') id: string): Promise { + console.log('🚀 ~ BusinessController ~ cancelSubscription ~ id:', id); + return await this.commandBus.execute(new CancelSubscriptionCommand(id)); + } + @Get('business/subscriptions/get/:id') async getCurrentSubscriptions( @Param('id') userId: string, diff --git a/apps/business/src/application/command/cancel-subscription.handler.ts b/apps/business/src/application/command/cancel-subscription.handler.ts new file mode 100644 index 00000000..263444ab --- /dev/null +++ b/apps/business/src/application/command/cancel-subscription.handler.ts @@ -0,0 +1,19 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { BusinessCommandService } from '../../business-command.service'; + +export class CancelSubscriptionCommand { + constructor(public readonly id: string) {} +} + +@CommandHandler(CancelSubscriptionCommand) +export class CancelSubscriptionHandler + implements ICommandHandler +{ + constructor( + private readonly businessCommandService: BusinessCommandService, + ) {} + + async execute({ id }: CancelSubscriptionCommand): Promise { + return await this.businessCommandService.cancelSubscription(id); + } +} diff --git a/apps/business/src/application/command/save-subscribtion.handler.ts b/apps/business/src/application/command/save-subscribtion.handler.ts index cbf4d5c7..63742c54 100644 --- a/apps/business/src/application/command/save-subscribtion.handler.ts +++ b/apps/business/src/application/command/save-subscribtion.handler.ts @@ -32,16 +32,17 @@ export class SaveSubscriptionHandler }; const key = getNotificationKey(subscription, notification); - await this.notificationGateway.saveNotification( - key, - notification, - 2629746000, - ); - await this.notificationGateway.send( - notification, - WebsocketEvents.SubscriptionActive, - 30000, - ); + // todo uncomment notif + // await this.notificationGateway.saveNotification( + // key, + // notification, + // 2629746000, + // ); + // await this.notificationGateway.send( + // notification, + // WebsocketEvents.SubscriptionActive, + // 30000, + // ); return subscription; } } diff --git a/apps/business/src/business-command.service.ts b/apps/business/src/business-command.service.ts index 8563485d..f4c96f29 100644 --- a/apps/business/src/business-command.service.ts +++ b/apps/business/src/business-command.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, + ConflictException, HttpException, Injectable, NotFoundException, @@ -12,10 +13,11 @@ import { IPaymentService } from './payment/interfaces/payment-service.interface' import { SaveSubscriptionDto } from './payment/payment-services/paypal/dto/save-subscription.dto'; import { Subscription } from './infrastructure/entity/subscription.entity'; import { SubscriptionStatus } from './payment/payment-services/paypal/constants/subscription-status.enum'; -import { DataSource, EntityManager } from 'typeorm'; +import { DataSource, EntityManager, Repository } from 'typeorm'; import { BusinessQueryService } from './business-query.service'; import { SubscriptionUpdateDto } from './dto/subscription-update.dto'; import { NotificationsGateway } from '../../../apps/libs/common/notifications/notifications.gateway'; +import { InjectRepository } from '@nestjs/typeorm'; @Injectable() export class BusinessCommandService { @@ -27,6 +29,8 @@ export class BusinessCommandService { private readonly paymentService: IPaymentService, private readonly businessQueryService: BusinessQueryService, private readonly notificationGateway: NotificationsGateway, + @InjectRepository(Subscription) + private readonly subscriptionCommandRepository: Repository, private readonly dataSource: DataSource, ) {} @@ -36,13 +40,19 @@ export class BusinessCommandService { subscribeDto.userId, ); - if (currentSubscriptions.length > 1) { + const currentSubscriptionsWithoutCancelled = currentSubscriptions.filter( + (subscription) => { + return subscription.status !== SubscriptionStatus.Canceled; + }, + ); + + if (currentSubscriptionsWithoutCancelled.length > 1) { throw new BadRequestException( 'BusinessCommandService error: user cant have more than 2 not expired subscriptions simultaniously', ); } - currentSubscriptions?.map((subscription) => { + currentSubscriptionsWithoutCancelled?.map((subscription) => { if ( new Date(subscription.expiresAt) > new Date() && subscription.subscriptionType === subscribeDto.subscriptionType @@ -57,8 +67,13 @@ export class BusinessCommandService { currentSubscriptions.length === 1 ? new Date(currentSubscriptions[0].expiresAt) : new Date(); + console.log( + '🚀 ~ BusinessCommandService ~ subscribe ~ start_date:', + start_date, + ); const response = await this.paymentService.subscribeToPlan( + subscribeDto.userId, subscribeDto.subscriptionType, currentSubscriptions.length === 1 ? start_date.toISOString() : undefined, ); @@ -92,7 +107,12 @@ export class BusinessCommandService { await this.businessQueryService.getCurrentUserSubscriptions( subscription.userId, ); - //todo* the second one subscription should starts from end of the first one (get first expiresAt, get time difference between now and first expiresAt, add this to startAt of the new subscription) + + const currentSubscriptionsWithoutCancelled = + currentSubscriptionsArr.filter((subscription) => { + return subscription.status !== SubscriptionStatus.Canceled; + }); + const price = getSubscriptionPrice(subscription.subscriptionType); const updatePlanDto = { subscriptionType: subscription.subscriptionType, @@ -107,8 +127,8 @@ export class BusinessCommandService { ); //* expires+1 when it second subscription const startDate = new Date( - currentSubscriptionsArr.length === 1 - ? currentSubscriptionsArr[0].expiresAt + currentSubscriptionsWithoutCancelled.length === 1 + ? currentSubscriptionsWithoutCancelled[0].expiresAt : paypalSubscription.start_time, ); @@ -140,11 +160,31 @@ export class BusinessCommandService { subscription.userId, queryRunner.manager, ); - const firstSubscription: Subscription[] = currentSubscriptions.filter( - (subscr) => subscr.id !== subscription.id, + + const currentSubscriptionsWithoutCancelled1 = currentSubscriptions.filter( + (subscription) => { + return subscription.status !== SubscriptionStatus.Canceled; + }, ); - if (currentSubscriptions.length === 2) { + console.log( + '🚀 ~ BusinessCommandService ~ saveSubscription ~ currentSubscriptionsWithoutCancelled1:', + currentSubscriptionsWithoutCancelled1, + ); + // get suspended + const firstSubscription: Subscription[] = + currentSubscriptionsWithoutCancelled1.filter( + (subscr) => subscr.id !== subscription.id, + ); + console.log( + '🚀 ~ BusinessCommandService ~ saveSubscription ~ firstSubscription:', + firstSubscription, + ); + if (currentSubscriptionsWithoutCancelled1.length === 2) { firstSubscription[0].status = SubscriptionStatus.Suspended; + console.log( + '🚀 ~ BusinessCommandService ~ saveSubscription ~ firstSubscription[0]:', + firstSubscription[0], + ); await this.suspendSubscription(firstSubscription[0].subscriptionId); } const subscription1 = await this.paymentService.getSubscription( @@ -177,6 +217,11 @@ export class BusinessCommandService { throw new NotFoundException( 'BusinessCommandService error: subscription does not exist', ); + if (subscription.status === SubscriptionStatus.Canceled) { + throw new ConflictException( + 'BusinessCommandService error: subscription was canceled', + ); + } const currentSubscriptions = await this.businessQueryService.getCurrentUserSubscriptions( subscription.userId, @@ -233,6 +278,68 @@ export class BusinessCommandService { ); } + async cancelSubscription(id: string): Promise { + console.log('🚀 ~ BusinessCommandService ~ cancelSubscription ~ id:', id); + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction('READ COMMITTED'); + try { + const subscription = await this.businessQueryService.getSubscription( + id, + queryRunner.manager, + ); + if (!subscription) + throw new NotFoundException( + 'BusinessCommandService error: subscription does not exist', + ); + const currentSubscriptions = + await this.businessQueryService.getCurrentUserSubscriptions( + subscription.userId, + queryRunner.manager, + ); + const paypalSubscription = await this.paymentService.getSubscription(id); + console.log( + '🚀 ~ BusinessCommandService ~ cancelSubscription ~ paypalSubscription:', + paypalSubscription, + ); + if (!paypalSubscription) + throw new NotFoundException( + 'BusinessCommandService error: paypal subscription does not exist', + ); + await this.paymentService.cancelSubscription(id); + + subscription.status = SubscriptionStatus.Canceled; + console.log( + '🚀 ~ BusinessCommandService ~ cancelSubscription ~ subscription:', + subscription, + ); + await queryRunner.manager.save(subscription); + // await this.subscriptionCommandRepository.save(subscription); + + // todo! problem if second is suspended + // if (currentSubscriptions.length === 2) { + // const anotherSubscription: Subscription[] = currentSubscriptions.filter( + // (subscr) => subscr.id !== subscription.id, + // ); + // if (anotherSubscription[0].status === SubscriptionStatus.Suspended) { + // await this.activateSubscription( + // anotherSubscription[0].subscriptionId, + // ); + // } + // } + await queryRunner.commitTransaction(); + } catch (err) { + console.log('BusinessCommandService cancelSubscription ~ err:', err); + await queryRunner.rollbackTransaction(); + throw new HttpException( + err.response, + err.response.httpStatusCode || err.httpStatusCode, + ); + } finally { + await queryRunner.release(); + } + } + async updateSubscription( id: string, subscriptionUpdateDto: SubscriptionUpdateDto, diff --git a/apps/business/src/business-query.service.ts b/apps/business/src/business-query.service.ts index 7575907a..68268666 100644 --- a/apps/business/src/business-query.service.ts +++ b/apps/business/src/business-query.service.ts @@ -80,7 +80,8 @@ export class BusinessQueryService { if ( subscription.expiresAt && new Date(subscription.expiresAt) > new Date() && - subscription.status !== SubscriptionStatus.Approval_Pending + subscription.status !== SubscriptionStatus.Approval_Pending && + subscription.status !== SubscriptionStatus.Canceled ) { if (subscription.status === SubscriptionStatus.Active) { subscription['nextPayment'] = subscription.expiresAt; diff --git a/apps/business/src/business.module.ts b/apps/business/src/business.module.ts index 1b333285..0ab0b25e 100644 --- a/apps/business/src/business.module.ts +++ b/apps/business/src/business.module.ts @@ -64,6 +64,10 @@ import { BullModule } from '@nestjs/bullmq'; import { NotificationsProducer } from './notifications-producer.service'; import { NotificationConsumer } from './notifications-consumer.service'; import { DatabaseModule } from '../../../apps/libs/common/database/database.module'; +import { + CancelSubscriptionCommand, + CancelSubscriptionHandler, +} from './application/command/cancel-subscription.handler'; const getEnvFilePath = (env: EnvironmentsTypes) => { const defaultEnvFilePath = ['apps/business/src/.env.development']; @@ -117,18 +121,18 @@ export const NOTIFICATION_SCHEDULER = 'NOTIFICATION_SCHEDULER'; // url: configService.get('url'), // migrationsTableName: configService.get('migrationsTableName'), // migrations: [`${__dirname}/../../db/migrations/*{.ts,.js}`], - // autoLoadEntities: configService.get('autoLoadEntities'), - // synchronize: configService.get('synchronize'), - // dropSchema: configService.get('dropSchema'), + // autoLoadEntities: true, + // synchronize: false, + // dropSchema: false, // }, // slaves: [ // { // url: configService.get('urlSlave'), // migrationsTableName: configService.get('migrationsTableName'), // migrations: [`${__dirname}/../../db/migrations/*{.ts,.js}`], - // autoLoadEntities: configService.get('autoLoadEntities'), - // synchronize: configService.get('synchronize'), - // dropSchema: configService.get('dropSchema'), + // autoLoadEntities: true, + // synchronize: false, + // dropSchema: false, // }, // ], // }, @@ -154,6 +158,8 @@ export const NOTIFICATION_SCHEDULER = 'NOTIFICATION_SCHEDULER'; SuspendSubscriptionHandler, ActivateSubscriptionHandler, ActivateSubscriptionCommand, + CancelSubscriptionCommand, + CancelSubscriptionHandler, SubscriptionUpdatedCommand, SubscriptionUpdatedHandler, SubscriptionExpiredCommand, diff --git a/apps/business/src/db/1761610470647-business_dev.ts b/apps/business/src/db/1761610470647-business_dev.ts new file mode 100644 index 00000000..c653d5fa --- /dev/null +++ b/apps/business/src/db/1761610470647-business_dev.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class BusinessDev1761610470647 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + } + + public async down(queryRunner: QueryRunner): Promise { + } + +} diff --git a/apps/business/src/db/1761610624210-business_dev.ts b/apps/business/src/db/1761610624210-business_dev.ts new file mode 100644 index 00000000..5fab5538 --- /dev/null +++ b/apps/business/src/db/1761610624210-business_dev.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class BusinessDev1761610624210 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + } + + public async down(queryRunner: QueryRunner): Promise { + } + +} diff --git a/apps/business/src/db/1761610763416-business_dev.ts b/apps/business/src/db/1761610763416-business_dev.ts new file mode 100644 index 00000000..5dba4d88 --- /dev/null +++ b/apps/business/src/db/1761610763416-business_dev.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class BusinessDev1761610763416 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + } + + public async down(queryRunner: QueryRunner): Promise { + } + +} diff --git a/apps/business/src/db/migrations/1761610631993-business_dev.ts b/apps/business/src/db/migrations/1761610631993-business_dev.ts new file mode 100644 index 00000000..1826c2f4 --- /dev/null +++ b/apps/business/src/db/migrations/1761610631993-business_dev.ts @@ -0,0 +1,86 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class BusinessDev1761610631993 implements MigrationInterface { + name = 'BusinessDev1761610631993' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TYPE "public"."payments_paymenttype_enum" AS ENUM('paypal', 'stripe') + `); + await queryRunner.query(` + CREATE TABLE "payments" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "createdAt" date NOT NULL DEFAULT now(), + "updatedAt" date NOT NULL DEFAULT now(), + "deletedAt" date, + "userId" uuid NOT NULL, + "paymentType" "public"."payments_paymenttype_enum" NOT NULL, + "price" integer NOT NULL, + "subscriptionId" uuid, + CONSTRAINT "PK_197ab7af18c93fbb0c9b28b4a59" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TYPE "public"."subscriptions_status_enum" AS ENUM( + 'ACTIVE', + 'CANCELED', + 'INACTIVE', + 'SUSPENDED', + 'APPROVAL_PENDING' + ) + `); + await queryRunner.query(` + CREATE TYPE "public"."subscriptions_subscriptiontype_enum" AS ENUM('1', '7', '30') + `); + await queryRunner.query(` + CREATE TYPE "public"."subscriptions_paymenttype_enum" AS ENUM('paypal', 'stripe') + `); + await queryRunner.query(` + CREATE TABLE "subscriptions" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "createdAt" date NOT NULL DEFAULT now(), + "updatedAt" date NOT NULL DEFAULT now(), + "deletedAt" date, + "subscriptionId" character varying NOT NULL, + "paymentId" uuid, + "userId" uuid NOT NULL, + "status" "public"."subscriptions_status_enum" NOT NULL, + "subscriptionType" "public"."subscriptions_subscriptiontype_enum", + "paymentType" "public"."subscriptions_paymenttype_enum" NOT NULL, + "startAt" date, + "expiresAt" date, + CONSTRAINT "PK_a87248d73155605cf782be9ee5e" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + ALTER TABLE "payments" + ADD CONSTRAINT "FK_2017d0cbfdbfec6b1b388e6aa08" FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE + SET NULL ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "payments" DROP CONSTRAINT "FK_2017d0cbfdbfec6b1b388e6aa08" + `); + await queryRunner.query(` + DROP TABLE "subscriptions" + `); + await queryRunner.query(` + DROP TYPE "public"."subscriptions_paymenttype_enum" + `); + await queryRunner.query(` + DROP TYPE "public"."subscriptions_subscriptiontype_enum" + `); + await queryRunner.query(` + DROP TYPE "public"."subscriptions_status_enum" + `); + await queryRunner.query(` + DROP TABLE "payments" + `); + await queryRunner.query(` + DROP TYPE "public"."payments_paymenttype_enum" + `); + } + +} diff --git a/apps/business/src/db/migrations/1761610767438-business_dev.ts b/apps/business/src/db/migrations/1761610767438-business_dev.ts new file mode 100644 index 00000000..e049e37d --- /dev/null +++ b/apps/business/src/db/migrations/1761610767438-business_dev.ts @@ -0,0 +1,86 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class BusinessDev1761610767438 implements MigrationInterface { + name = 'BusinessDev1761610767438' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TYPE "public"."payments_paymenttype_enum" AS ENUM('paypal', 'stripe') + `); + await queryRunner.query(` + CREATE TABLE "payments" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "createdAt" date NOT NULL DEFAULT now(), + "updatedAt" date NOT NULL DEFAULT now(), + "deletedAt" date, + "userId" uuid NOT NULL, + "paymentType" "public"."payments_paymenttype_enum" NOT NULL, + "price" integer NOT NULL, + "subscriptionId" uuid, + CONSTRAINT "PK_197ab7af18c93fbb0c9b28b4a59" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TYPE "public"."subscriptions_status_enum" AS ENUM( + 'ACTIVE', + 'CANCELED', + 'INACTIVE', + 'SUSPENDED', + 'APPROVAL_PENDING' + ) + `); + await queryRunner.query(` + CREATE TYPE "public"."subscriptions_subscriptiontype_enum" AS ENUM('1', '7', '30') + `); + await queryRunner.query(` + CREATE TYPE "public"."subscriptions_paymenttype_enum" AS ENUM('paypal', 'stripe') + `); + await queryRunner.query(` + CREATE TABLE "subscriptions" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "createdAt" date NOT NULL DEFAULT now(), + "updatedAt" date NOT NULL DEFAULT now(), + "deletedAt" date, + "subscriptionId" character varying NOT NULL, + "paymentId" uuid, + "userId" uuid NOT NULL, + "status" "public"."subscriptions_status_enum" NOT NULL, + "subscriptionType" "public"."subscriptions_subscriptiontype_enum", + "paymentType" "public"."subscriptions_paymenttype_enum" NOT NULL, + "startAt" date, + "expiresAt" date, + CONSTRAINT "PK_a87248d73155605cf782be9ee5e" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + ALTER TABLE "payments" + ADD CONSTRAINT "FK_2017d0cbfdbfec6b1b388e6aa08" FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE + SET NULL ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "payments" DROP CONSTRAINT "FK_2017d0cbfdbfec6b1b388e6aa08" + `); + await queryRunner.query(` + DROP TABLE "subscriptions" + `); + await queryRunner.query(` + DROP TYPE "public"."subscriptions_paymenttype_enum" + `); + await queryRunner.query(` + DROP TYPE "public"."subscriptions_subscriptiontype_enum" + `); + await queryRunner.query(` + DROP TYPE "public"."subscriptions_status_enum" + `); + await queryRunner.query(` + DROP TABLE "payments" + `); + await queryRunner.query(` + DROP TYPE "public"."payments_paymenttype_enum" + `); + } + +} diff --git a/apps/business/src/decorators/swagger/subscribe-swagger.decorator.ts b/apps/business/src/decorators/swagger/subscribe-swagger.decorator.ts index 2323c756..88ee7a90 100644 --- a/apps/business/src/decorators/swagger/subscribe-swagger.decorator.ts +++ b/apps/business/src/decorators/swagger/subscribe-swagger.decorator.ts @@ -8,6 +8,15 @@ import { import { applyDecorators, HttpStatus } from '@nestjs/common'; import { SubscribeDto } from '../../../../../apps/libs/Business/dto/input/subscribe.dto'; import { PaymentType } from '../../../../../apps/libs/Business/constants/payment-type.enum'; +import { IsString } from 'class-validator'; +import { Expose } from 'class-transformer'; + +@Expose() +class SubscribeResponseDto { + @Expose() + @IsString() + link: string; +} export const SubscribeSwagger = () => applyDecorators( @@ -29,8 +38,9 @@ export const SubscribeSwagger = () => }), ApiBody({ type: SubscribeDto }), ApiResponse({ - status: HttpStatus.OK, - description: 'Success and redirected', + status: HttpStatus.TEMPORARY_REDIRECT, + type: SubscribeResponseDto, + description: 'Need to open this link in browser', }), ApiResponse({ status: HttpStatus.BAD_REQUEST, diff --git a/apps/business/src/infrastructure/repository/query/business-query.repository.ts b/apps/business/src/infrastructure/repository/query/business-query.repository.ts index 2f6b72bd..ced1435f 100644 --- a/apps/business/src/infrastructure/repository/query/business-query.repository.ts +++ b/apps/business/src/infrastructure/repository/query/business-query.repository.ts @@ -65,10 +65,22 @@ export class BusinessQueryRepository take: pagination.limit, order: sort, where: filter, + relations: { + subscription: true, + }, }); const paginatedResponse: PaymentsPaginatedResponseDto = { - items: plainToInstance(ResponsePaymentDto, payments[0]), + items: plainToInstance( + ResponsePaymentDto, + payments[0].map((payment) => { + if (payment.subscription) { + payment['expiresAt'] = payment.subscription.expiresAt; + delete payment.subscription; + } + return payment; + }), + ), totalItems: payments[1], page: pagination.page, limit: pagination.limit, diff --git a/apps/business/src/notifications-consumer.service.ts b/apps/business/src/notifications-consumer.service.ts index 5ecae996..eeb691ea 100644 --- a/apps/business/src/notifications-consumer.service.ts +++ b/apps/business/src/notifications-consumer.service.ts @@ -33,6 +33,7 @@ export class NotificationConsumer extends WorkerHost { delete notificationsObjectsAray.differ; notificationsObjectsAray.shift(); notificationsObjectsAray.map(async (item) => { + console.log('🚀 ~ NotificationConsumer ~ process ~ item:', item); await this.notificationGateway.send( item, WebsocketEvents.DaysToExpires, diff --git a/apps/business/src/payment/interfaces/payment-service.interface.ts b/apps/business/src/payment/interfaces/payment-service.interface.ts index 05ec3d56..769aab4a 100644 --- a/apps/business/src/payment/interfaces/payment-service.interface.ts +++ b/apps/business/src/payment/interfaces/payment-service.interface.ts @@ -2,6 +2,7 @@ import { SubscriptionType } from '../../../../../apps/libs/Business/constants/su export abstract class IPaymentService { abstract subscribeToPlan( + userId: string, subscriptionType: SubscriptionType, startAt?: string, ): Promise; @@ -30,4 +31,6 @@ export abstract class IPaymentService { abstract suspendSubscription(id: string): Promise; abstract activateSubscription(id: string): Promise; + + abstract cancelSubscription(id: string): Promise; } diff --git a/apps/business/src/payment/payment-services/paypal/paypal.service.ts b/apps/business/src/payment/payment-services/paypal/paypal.service.ts index 69800d89..014ab3f5 100644 --- a/apps/business/src/payment/payment-services/paypal/paypal.service.ts +++ b/apps/business/src/payment/payment-services/paypal/paypal.service.ts @@ -14,6 +14,7 @@ import { Client, Environment, LogLevel } from '@paypal/paypal-server-sdk'; import { createBusinessPlan } from './helpers/create-business-plan.helper'; import { SubscriptionStatus } from './constants/subscription-status.enum'; import axios from 'axios'; +import { ConfigService } from '@nestjs/config'; export class PayPalService implements IPaymentService { private client: Client; @@ -21,6 +22,7 @@ export class PayPalService implements IPaymentService { private readonly client_id: string, private readonly client_secret: string, private readonly businessServiceUrl: string, + private readonly configService: ConfigService, ) { this.client = new Client({ clientCredentialsAuthCredentials: { @@ -179,6 +181,7 @@ export class PayPalService implements IPaymentService { } async subscribeToPlan( + userId: string, subscriptionType: SubscriptionType, startAt?: string, ): Promise { @@ -199,12 +202,20 @@ export class PayPalService implements IPaymentService { if (!plan) throw new BadRequestException('Paypal error: plan does not exist'); // console.log('plan', plan); - + // const token = await this.authentication(); const today = new Date(); const nextDay = startAt ? startAt : new Date(today.setDate(today.getDate() + 1)).toISOString(); + + let returnUrl = this.configService.get('PROFILE_SETTINGS_PAGE'); + returnUrl = returnUrl.replace('replace', userId); + const successUrl = [returnUrl, 'success=1'].join('?'); + const cancelUrl = [returnUrl, 'success=0'].join('?'); + console.log('🚀 ~ PayPalService successUrl:', successUrl); + console.log('🚀 ~ PayPalService cancelUrl:', cancelUrl); + const subscribe = { plan_id: plan['id'], quantity: 1, @@ -218,11 +229,13 @@ export class PayPalService implements IPaymentService { payer_selected: 'PAYPAL', payee_preferred: 'IMMEDIATE_PAYMENT_REQUIRED', }, + return_url: successUrl, + cancel_url: cancelUrl, }, }; - //todo! To start a PayPal subscription plan immediately, set the trial_duration to 0 days when creating the subscription plan in the PayPal Developer portal or via the API, - //todo! which effectively bypasses the trial period and initiates the subscription right away. Alternatively, you can set the trial_duration_unit to "month" - //todo! but specify trial_duration as 0, which achieves the same result of starting the subscription immediately without a trial. + // To start a PayPal subscription plan immediately, set the trial_duration to 0 days when creating the subscription plan in the PayPal Developer portal or via the API, + // which effectively bypasses the trial period and initiates the subscription right away. Alternatively, you can set the trial_duration_unit to "month" + // but specify trial_duration as 0, which achieves the same result of starting the subscription immediately without a trial. const response = await axios.post( 'https://api-m.sandbox.paypal.com/v1/billing/subscriptions', @@ -311,6 +324,36 @@ export class PayPalService implements IPaymentService { } } + async cancelSubscription(id: string): Promise { + const token = await this.authentication(); + const status = (await this.getSubscription(id)).status; + console.log('🚀 ~ PayPalService ~ cancelSubscription ~ status:', status); + if (status === 'CANCELLED') + throw new ConflictException( + 'PayPalService error: subscription is canceled already', + ); + + try { + return await axios.post( + `https://api-m.sandbox.paypal.com/v1/billing/subscriptions/${id}/cancel`, + { reason: 'Activate' }, + { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }, + ); + } catch (err) { + console.log( + '🚀 ~ PayPalService ~ activateSubscription ~ err:', + err.response.data.details, + ); + throw new HttpException(err.response.data, err.response.status); + } + } + async getSubscription(id: string): Promise { const token = await this.authentication(); return ( diff --git a/apps/business/src/payment/payment.factory.ts b/apps/business/src/payment/payment.factory.ts index 2e4fb22f..592b6027 100644 --- a/apps/business/src/payment/payment.factory.ts +++ b/apps/business/src/payment/payment.factory.ts @@ -3,6 +3,7 @@ import { PayPalService } from './payment-services/paypal/paypal.service'; import { StripeService } from './payment-services/stripe/stripe.service'; import { PaymentType } from '../../../../apps/libs/Business/constants/payment-type.enum'; import { RequestContext } from 'nestjs-request-context'; +import { ConfigService } from '@nestjs/config'; @Injectable({ scope: Scope.REQUEST }) export class PaymentFactory { @@ -32,7 +33,12 @@ export class PaymentFactory { switch (service) { case PaymentType.PAYPAL: { - return new PayPalService(clientId, secret, businessServiceUrl); + return new PayPalService( + clientId, + secret, + businessServiceUrl, + new ConfigService(), + ); } case PaymentType.STRIPE: { return this.stripeService; diff --git a/apps/business/src/settings/configuration.ts b/apps/business/src/settings/configuration.ts index e16035b1..64cc238e 100644 --- a/apps/business/src/settings/configuration.ts +++ b/apps/business/src/settings/configuration.ts @@ -30,5 +30,6 @@ export const getConfiguration = () => { dropSchema: process.env.DROP_SCHEMA === 'true', JWT_SECRET: process.env.JWT_SECRET, TIME_PERIOD: process.env.TIME_PERIOD, + PROFILE_SETTINGS_PAGE: process.env.PROFILE_SETTINGS_PAGE, }; }; diff --git a/apps/files/Jenkinsfile b/apps/files/Jenkinsfile index e59c4299..4ce0b3e2 100644 --- a/apps/files/Jenkinsfile +++ b/apps/files/Jenkinsfile @@ -3,13 +3,13 @@ pipeline { agent any environment { ENV_TYPE = "production" - PORT = 3930 + PORT = 4051 NAMESPACE = "yogram-ru" REGISTRY_HOSTNAME = "idogmat" - PROJECT = "files-yogram" + PROJECT = "yogram-files" SERVICE="files" REGISTRY = "registry.hub.docker.com" - DEPLOYMENT_NAME = "files-yogram-deployment" + DEPLOYMENT_NAME = "yogram-files-deployment" IMAGE_NAME = "${env.BUILD_ID}_${env.ENV_TYPE}_${env.GIT_COMMIT}" DOCKER_BUILD_NAME = "${env.REGISTRY_HOSTNAME}/${env.PROJECT}:${env.IMAGE_NAME}" } diff --git a/apps/files/deployment.yaml b/apps/files/deployment.yaml index 23e26479..9d81285c 100644 --- a/apps/files/deployment.yaml +++ b/apps/files/deployment.yaml @@ -22,48 +22,43 @@ spec: - containerPort: PORT_CONTAINER env: - - name: AWS_SECRET_KEY - valueFrom: - secretKeyRef: - name: files-yogram-production-config-secret - key: AWS_SECRET_KEY - - name: AWS_REGION - valueFrom: - secretKeyRef: - name: files-yogram-production-config-secret - key: AWS_REGION - - name: RMQ_URL + - name: FILES_SERVICE_AVATAR_UPLOAD_PATH valueFrom: secretKeyRef: - name: files-yogram-production-config-secret - key: RMQ_URL - - name: BUCKET + name: yogram-files-production-config-secret + key: FILES_SERVICE_AVATAR_UPLOAD_PATH + - name: FILES_SERVICE_CHUNKS_DIR valueFrom: secretKeyRef: - name: files-yogram-production-config-secret - key: BUCKET + name: yogram-files-production-config-secret + key: FILES_SERVICE_CHUNKS_DIR - name: UPLOAD_SERVICE_URL_PREFIX valueFrom: secretKeyRef: - name: files-yogram-production-config-secret + name: yogram-files-production-config-secret key: UPLOAD_SERVICE_URL_PREFIX - - name: FILES_SERVICE_CHUNKS_DIR - valueFrom: - secretKeyRef: - name: files-yogram-production-config-secret - key: FILES_SERVICE_CHUNKS_DIR - - name: FILES_SERVICE_AVATAR_UPLOAD_PATH + - name: AWS_REGION valueFrom: secretKeyRef: - name: files-yogram-production-config-secret - key: FILES_SERVICE_AVATAR_UPLOAD_PATH - - name: AWS_ACCOUNT_ID + name: yogram-files-production-config-secret + key: AWS_REGION + - name: AWS_SECRET_KEY valueFrom: secretKeyRef: - name: files-yogram-production-config-secret - key: AWS_ACCOUNT_ID + name: yogram-files-production-config-secret + key: AWS_SECRET_KEY - name: AWS_ACCESS_KEY valueFrom: secretKeyRef: - name: files-yogram-production-config-secret + name: yogram-files-production-config-secret key: AWS_ACCESS_KEY + - name: RMQ_URL + valueFrom: + secretKeyRef: + name: yogram-files-production-config-secret + key: RMQ_URL + - name: BUCKET + valueFrom: + secretKeyRef: + name: yogram-files-production-config-secret + key: BUCKET diff --git a/apps/gate/Jenkinsfile b/apps/gate/Jenkinsfile index ff4cff94..5b35bdc4 100644 --- a/apps/gate/Jenkinsfile +++ b/apps/gate/Jenkinsfile @@ -3,13 +3,13 @@ pipeline { agent any environment { ENV_TYPE = "production" - PORT = 3869 + PORT = 4046 NAMESPACE = "yogram-ru" REGISTRY_HOSTNAME = "idogmat" - PROJECT = "gate-yogram" + PROJECT = "yogram" SERVICE="gate" REGISTRY = "registry.hub.docker.com" - DEPLOYMENT_NAME = "gate-yogram-deployment" + DEPLOYMENT_NAME = "yogram-deployment" IMAGE_NAME = "${env.BUILD_ID}_${env.ENV_TYPE}_${env.GIT_COMMIT}" DOCKER_BUILD_NAME = "${env.REGISTRY_HOSTNAME}/${env.PROJECT}:${env.IMAGE_NAME}" } diff --git a/apps/gate/deployment.yaml b/apps/gate/deployment.yaml index 410047b8..3bdf82b8 100644 --- a/apps/gate/deployment.yaml +++ b/apps/gate/deployment.yaml @@ -22,148 +22,163 @@ spec: - containerPort: PORT_CONTAINER env: - - name: USERS_PROD_SERVICE_URL - valueFrom: - secretKeyRef: - name: gate-yogram-production-config-secret - key: USERS_PROD_SERVICE_URL - name: JWT_SECRET valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: JWT_SECRET - name: VERIFY_TOKEN_EXPIRES valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: VERIFY_TOKEN_EXPIRES - name: ACCESS_TOKEN_EXPIRES valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: ACCESS_TOKEN_EXPIRES - name: REFRESH_TOKEN_EXPIRES valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: REFRESH_TOKEN_EXPIRES - name: RESEND_EMAIL_VERIFY_PAGE valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: RESEND_EMAIL_VERIFY_PAGE - name: SEND_RESTORE_PASSWORD_EMAIL_PAGE valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: SEND_RESTORE_PASSWORD_EMAIL_PAGE - name: RESTORE_PASSWORD_PAGE valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: RESTORE_PASSWORD_PAGE - name: LOGIN_PAGE valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: LOGIN_PAGE - name: RECAPTCHA_HOSTNAME valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: RECAPTCHA_HOSTNAME - name: RECAPTCHA_URL valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: RECAPTCHA_URL - name: RECAPTCHA_SECRET_KEY valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: RECAPTCHA_SECRET_KEY - name: GOOGLE_OAUTH_URI valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: GOOGLE_OAUTH_URI - name: GOOGLE_CLIENT_ID valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: GOOGLE_CLIENT_ID - name: GOOGLE_CLIENT_SECRET valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: GOOGLE_CLIENT_SECRET - name: GOOGLE_REDIRECT_URI valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: GOOGLE_REDIRECT_URI - name: RMQ_URL valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: RMQ_URL - name: FORGOT_PASSWORD_TOKEN_EXPIRES valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: FORGOT_PASSWORD_TOKEN_EXPIRES - name: POSTS_PROD_SERVICE_URL valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: POSTS_PROD_SERVICE_URL - name: REDIS_USER valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: REDIS_USER - name: REDIS_PASSWORD valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: REDIS_PASSWORD - name: REDIS_HOST valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: REDIS_HOST - name: REDIS_PORT valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: REDIS_PORT - name: USERS_SERVICE_URL valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: USERS_SERVICE_URL - name: POSTS_SERVICE_URL valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: POSTS_SERVICE_URL - name: FILES_SERVICE_URL valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: FILES_SERVICE_URL + - name: BUSINESS_SERVICE_URL + valueFrom: + secretKeyRef: + name: yogram-production-config-secret + key: BUSINESS_SERVICE_URL + - name: PORT + valueFrom: + secretKeyRef: + name: yogram-production-config-secret + key: PORT - name: FILES_PROD_SERVICE_URL valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: FILES_PROD_SERVICE_URL - - name: BUSINESS_SERVICE_URL + - name: POSTS_PROD_SERVICE_URL valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret - key: BUSINESS_SERVICE_URL + name: yogram-production-config-secret + key: POSTS_PROD_SERVICE_URL + - name: USERS_PROD_SERVICE_URL + valueFrom: + secretKeyRef: + name: yogram-production-config-secret + key: USERS_PROD_SERVICE_URL + - name: PROFILE_SETTINGS_PAGE + valueFrom: + secretKeyRef: + name: yogram-production-config-secret + key: PROFILE_SETTINGS_PAGE - name: NODE_ENV valueFrom: secretKeyRef: - name: gate-yogram-production-config-secret + name: yogram-production-config-secret key: NODE_ENV diff --git a/apps/gate/src/business/business.controller.ts b/apps/gate/src/business/business.controller.ts index 496ab5f5..0522cc0b 100644 --- a/apps/gate/src/business/business.controller.ts +++ b/apps/gate/src/business/business.controller.ts @@ -42,6 +42,7 @@ import { Request, Response } from 'express'; import axios from 'axios'; import { ReadNotificationDto } from '../../../../apps/libs/Business/dto/input/read-notification.dto'; import { ReadNotificationSwagger } from './decorators/swagger/read-notification.swagger.decorator'; +import { CancelSubscriptionSwagger } from './decorators/swagger/cancel-subscription.swagger'; @Controller('business') export class BusinessController { @@ -117,7 +118,8 @@ export class BusinessController { payment, ); console.log('link:', response.link); - res.status(200).redirect(303, response.link); + // res.status(200).redirect(303, response.link); + return res.status(307).json({ link: response.link }); } @Public() @@ -126,12 +128,23 @@ export class BusinessController { async paypalProcess( @Req() req: Request, @Query('payment') payment: PaymentType, + @Res() res: Response, ): Promise { if (req.body.event_type === PaypalEvents.BillingSubscriptionActivated) { - return await this.businessService.paypalProccess( + const subscription = await this.businessService.paypalProccess( req.body.resource.id, payment, ); + console.log( + '🚀 ~ BusinessController ~ paypalProcess ~ subscription:', + subscription['userId'], + ); + let page = this.configService.get('PROFILE_SETTINGS_PAGE'); + page = page.replace('replace', subscription['userId']); + console.log('🚀 ~ BusinessController ~ paypalProcess ~ page:', page); + // res.redirect(301, page); + // res.sendStatus(200); + return subscription; } } @@ -191,6 +204,15 @@ export class BusinessController { return await this.businessService.activateSubscription(id, payment); } + @CancelSubscriptionSwagger() + @Patch('subscriptions/:id/cancel') + async cancelSubscription( + @Param('id') id: string, + @Query('payment') payment: PaymentType, + ): Promise { + return await this.businessService.cancelSubscription(id, payment); + } + @GetPaymentsSwagger() @Get('payments') async getPayments( diff --git a/apps/gate/src/business/business.module.ts b/apps/gate/src/business/business.module.ts index 52e71c3b..e6f480fe 100644 --- a/apps/gate/src/business/business.module.ts +++ b/apps/gate/src/business/business.module.ts @@ -3,10 +3,22 @@ import { BusinessService } from './business.service'; import { BusinessController } from './business.controller'; import { HttpModule } from '@nestjs/axios'; import { GateService } from '../../../../apps/libs/gateService'; +import { NotificationsGateway } from './notifications.gateway'; +import { JwtModule } from '@nestjs/jwt'; +import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ - imports: [HttpModule], + imports: [ + HttpModule, + JwtModule.registerAsync({ + inject: [ConfigService], + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + secret: configService.get('JWT_SECRET'), + }), + }), + ], controllers: [BusinessController], - providers: [BusinessService, GateService], + providers: [BusinessService, GateService, NotificationsGateway], }) export class BusinessModule {} diff --git a/apps/gate/src/business/business.service.ts b/apps/gate/src/business/business.service.ts index ffdff6f7..26087cb4 100644 --- a/apps/gate/src/business/business.service.ts +++ b/apps/gate/src/business/business.service.ts @@ -126,6 +126,19 @@ export class BusinessService { ); } + async cancelSubscription(id: string, payment: PaymentType): Promise { + const path = [ + [HttpBusinessPath.CancelSubscription.replace(':id', id)].join('/'), + `payment=${payment}`, + ].join('?'); + return await this.gateService.requestHttpServicePatch( + HttpServices.Business, + path, + {}, + {}, + ); + } + async getPayments( payment: PaymentType, pagination: IPagination, diff --git a/apps/gate/src/business/decorators/swagger/activate-subscription-swagger.decorator.ts b/apps/gate/src/business/decorators/swagger/activate-subscription-swagger.decorator.ts index 3db64839..e36c1eef 100644 --- a/apps/gate/src/business/decorators/swagger/activate-subscription-swagger.decorator.ts +++ b/apps/gate/src/business/decorators/swagger/activate-subscription-swagger.decorator.ts @@ -39,7 +39,7 @@ export function ActivateSubscriptionSwagger() { }), ApiResponse({ status: 409, - description: 'PayPalService error: subscription is active already', + description: 'PayPalService error: subscription is active already ', }), ); } diff --git a/apps/gate/src/business/decorators/swagger/cancel-subscription.swagger.ts b/apps/gate/src/business/decorators/swagger/cancel-subscription.swagger.ts new file mode 100644 index 00000000..6509f89b --- /dev/null +++ b/apps/gate/src/business/decorators/swagger/cancel-subscription.swagger.ts @@ -0,0 +1,44 @@ +import { + ApiHeader, + ApiOperation, + ApiParam, + ApiQuery, + ApiResponse, +} from '@nestjs/swagger'; +import { applyDecorators } from '@nestjs/common'; +import { PaymentType } from '../../../../../../apps/libs/Business/constants/payment-type.enum'; + +export function CancelSubscriptionSwagger() { + return applyDecorators( + ApiHeader({ + name: 'Authorization', + description: 'Authorization with bearer token', + }), + ApiParam({ + name: 'id', + type: 'string', + example: 'I-1CWCLXSVTX7R', + }), + ApiQuery({ + name: 'payment', + required: true, + type: 'string', + example: 'payment=paypal', + enum: PaymentType, + }), + ApiOperation({ + summary: 'Cancel subscription.', + }), + ApiResponse({ + status: 200, + }), + ApiResponse({ + status: 404, + description: 'BusinessCommandService error: subscription does not exist', + }), + ApiResponse({ + status: 409, + description: 'PayPalService error: subscription is canceled already', + }), + ); +} diff --git a/apps/gate/src/business/notifications.gateway.ts b/apps/gate/src/business/notifications.gateway.ts new file mode 100644 index 00000000..14d319f5 --- /dev/null +++ b/apps/gate/src/business/notifications.gateway.ts @@ -0,0 +1,46 @@ +import { socketAuthMiddleware } from '../../../../apps/libs/common/notifications/helper/socket-auth.helper'; +import { OnModuleInit } from '@nestjs/common'; +import { Server, Socket } from 'socket.io'; +import { + ConnectedSocket, + MessageBody, + OnGatewayConnection, + SubscribeMessage, + WebSocketGateway, + WebSocketServer, +} from '@nestjs/websockets'; +import { JwtService } from '@nestjs/jwt'; + +@WebSocketGateway(3007, { namespace: 'notifications' }) +export class NotificationsGateway implements OnModuleInit, OnGatewayConnection { + @WebSocketServer() server: Server; + private connectedSockets: string[] = []; + constructor(private readonly jwtService: JwtService) {} + + handleConnection(socket: Socket) { + console.log(`${socket.id} connected`); + //todo* make auth -> connectedSockets.push({socket.id, userId}) -> send to notifications -> add there to array + this.connectedSockets.push(socket.id); + this.server.emit('connectedSocket', { + userId: socket.data.user, + socketId: socket.id, + }); + } + + afterInit(server: Server) { + const authMiddleware = socketAuthMiddleware(this.jwtService); + server.use(authMiddleware); + } + + onModuleInit() { + this.server.on('message_from_gateway', (data: any) => { + console.log('Received from gateway:', data); + }); + } + + @SubscribeMessage('send_to_gateway') + handleMessage(@MessageBody() data: any, @ConnectedSocket() client: Socket) { + console.log('message', data); + client.emit('message_from_gateway', 'Hello from Gateway!'); + } +} diff --git a/apps/gate/src/main.ts b/apps/gate/src/main.ts index cd628351..8657ed8a 100644 --- a/apps/gate/src/main.ts +++ b/apps/gate/src/main.ts @@ -30,10 +30,13 @@ async function bootstrap() { 'Connection', 'User-Agent', 'x-recaptcha-token', + '*', ], exposedHeaders: ['Set-cookie'], origin: [ + 'https://developer.paypal.com', + 'https://www.sandbox.paypal.com', 'https://yogram.ru', 'http://localhost:5173', 'http://localhost:56938', diff --git a/apps/libs/Business/constants/path.constant.ts b/apps/libs/Business/constants/path.constant.ts index 8605829b..a9279245 100644 --- a/apps/libs/Business/constants/path.constant.ts +++ b/apps/libs/Business/constants/path.constant.ts @@ -3,6 +3,7 @@ export const HttpBusinessPath = { CurrentSubscriptions: 'business/subscriptions/get', SuspendSubscription: 'business/subscriptions/:id/suspend', ActivateSubscription: 'business/subscriptions/:id/activate', + CancelSubscription: 'business/subscriptions/:id/cancel', PaypalProcess: 'business/paypal-proccess', SubscriptionsExpired: 'business/subscriptions/expired', SubscriptionsUpdated: 'business/subscriptions/updated', diff --git a/apps/libs/Business/dto/response/response-payment.dto.ts b/apps/libs/Business/dto/response/response-payment.dto.ts index f4519204..63f56e29 100644 --- a/apps/libs/Business/dto/response/response-payment.dto.ts +++ b/apps/libs/Business/dto/response/response-payment.dto.ts @@ -1,3 +1,7 @@ +import { Expose } from 'class-transformer'; import { Payment } from '../../../../../apps/business/src/infrastructure/entity/payment.entity'; -export class ResponsePaymentDto extends Payment {} +export class ResponsePaymentDto extends Payment { + @Expose() + expiresAt: Date; +} diff --git a/apps/libs/common/database/database.module.ts b/apps/libs/common/database/database.module.ts index b6fb6d4c..91a8cec8 100644 --- a/apps/libs/common/database/database.module.ts +++ b/apps/libs/common/database/database.module.ts @@ -36,9 +36,9 @@ export class DatabaseModule { migrationsTableName: configService.get('migrationsTableName'), entities: [`${__dirname}/infrastructure/**/*.entity{.ts,.js}`], migrations: [`${__dirname}/../../db/migrations/*{.ts,.js}`], - autoLoadEntities: configService.get('autoLoadEntities'), - synchronize: configService.get('synchronize'), - dropSchema: configService.get('dropSchema'), + autoLoadEntities: true, + synchronize: false, + dropSchema: false, }, dataSourceFactory: async (options) => { const dataSource = await new DataSource(options).initialize(); diff --git a/apps/libs/common/notifications/helper/socket-auth.helper.ts b/apps/libs/common/notifications/helper/socket-auth.helper.ts index b29cd787..acedc0e5 100644 --- a/apps/libs/common/notifications/helper/socket-auth.helper.ts +++ b/apps/libs/common/notifications/helper/socket-auth.helper.ts @@ -10,9 +10,11 @@ export const socketAuthMiddleware = ( ): SocketMiddleware => { return async (socket: Socket, next) => { try { - const token = socket.handshake.headers?.authorization; + console.log('authorization:', socket.handshake.auth?.authorization); + const token = socket.handshake.auth?.authorization; + console.log('🚀 ~ socketAuthMiddleware ~ token:', token); if (!token) next(new WsException('Socket Unauthorized Exception')); - const payload = jwtService.verify(token.trim()); + const payload = jwtService.verify(token); if (!payload) next(new WsException('Socket Unauthorized Exception')); socket.data.user = payload['id']; next(); diff --git a/apps/libs/common/notifications/interfaces/notification-service.interface.ts b/apps/libs/common/notifications/interfaces/notification-service.interface.ts index ed70c61c..84c884b3 100644 --- a/apps/libs/common/notifications/interfaces/notification-service.interface.ts +++ b/apps/libs/common/notifications/interfaces/notification-service.interface.ts @@ -8,10 +8,7 @@ import { } from '@nestjs/websockets'; import { ExpiresInDuration } from 'apps/business/src/constants/expires-in-duration.enum'; -export interface INotificationsService - extends OnGatewayConnection, - OnGatewayInit, - OnGatewayDisconnect { +export interface INotificationsService { send( notification: INotification, event: WebsocketEvents, diff --git a/apps/libs/common/notifications/notifications.gateway.ts b/apps/libs/common/notifications/notifications.gateway.ts index 78098058..be5e96da 100644 --- a/apps/libs/common/notifications/notifications.gateway.ts +++ b/apps/libs/common/notifications/notifications.gateway.ts @@ -2,33 +2,75 @@ import { NotificationResponseDto } from '../../../../apps/libs/Business/dto/resp import { ExpiresInDuration } from '../../../../apps/business/src/constants/expires-in-duration.enum'; import { WebsocketEvents } from '../../../../apps/business/src/constants/websocket.event.enum'; import { INotificationsService } from './interfaces/notification-service.interface'; -import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; +import { + OnGatewayConnection, + OnGatewayInit, + WebSocketGateway, + WebSocketServer, +} from '@nestjs/websockets'; import { INotification } from './interfaces/notification.interface'; import { socketAuthMiddleware } from './helper/socket-auth.helper'; import { NotificationsService } from './notifications.service'; import { Socket, Server } from 'socket.io'; import { JwtService } from '@nestjs/jwt'; +import { OnModuleInit } from '@nestjs/common'; +import { io, Socket as Socket1 } from 'socket.io-client'; -@WebSocketGateway() -export class NotificationsGateway implements INotificationsService { +export class NotificationsGateway + implements + INotificationsService, + OnModuleInit, + OnGatewayConnection, + OnGatewayInit +{ private connectedClients: Map = new Map(); - @WebSocketServer() - private server: Server; + private socket: Socket1; constructor( private readonly notificationsService: NotificationsService, - private readonly jwtService: JwtService, + // private readonly jwtService: JwtService, ) {} - afterInit(server: any) { - const authMiddleware = socketAuthMiddleware(this.jwtService); - server.use(authMiddleware); + async onModuleInit() { + const jwtService = new JwtService({ global: true }); + const token = await jwtService.signAsync( + { id: 'd25a77e9-1e92-469f-8e01-c325e8220cc9' }, + { secret: 'secret_jwt_1234' }, + ); + // console.log('🚀 ~ NotificationsGateway ~ onModuleInit ~ token:', token); + this.socket = io('http://localhost:3007/notifications', { + auth: { authorization: token }, + }); + this.socket.on('connect', () => { + console.log('Connected to WebSocket Gateway!'); + }); + this.socket.on('message_from_gateway', (data: any) => { + console.log('Received from gateway:', data); + }); + this.socket.on('connectedSocket', (data: any) => { + console.log('connectedSocket', data); + }); + this.socket.emit('send_to_gateway', 'hello'); } - handleDisconnect(socket: Socket) { - this.notificationsService.handleDisconnect(socket); + + sendMessageToGateway(message: string) { + this.socket.emit('send_to_gateway', message); // Emit events to the gateway } + afterInit(server: any) { + // const authMiddleware = socketAuthMiddleware(this.jwtService); + // server.use(authMiddleware); + } + // handleDisconnect(socket: Socket) { + // this.notificationsService.handleDisconnect(socket); + // } + handleConnection(socket: Socket) { - this.notificationsService.handleConnection(socket); + console.log( + '🚀 ~ NotificationsGateway ~ handleConnection ~ socket:', + socket.id, + ); + // this.notificationsService.handleConnection(socket); + this.sendMessageToGateway(`connected ${socket.id}`); } async saveNotification( diff --git a/apps/libs/common/notifications/notifications.service.ts b/apps/libs/common/notifications/notifications.service.ts index ed4de7ee..5b734fd4 100644 --- a/apps/libs/common/notifications/notifications.service.ts +++ b/apps/libs/common/notifications/notifications.service.ts @@ -25,6 +25,11 @@ export class NotificationsService implements INotificationsService { this.redisClient.call(''); } + //todo* add addClient / removeClient + addClient(socket: Socket) {} + + removeClient(socket: Socket) {} + handleDisconnect(socket: Socket) { this.connectedClients.delete(socket.id); diff --git a/apps/mailer/Jenkinsfile b/apps/mailer/Jenkinsfile index e600a5e7..8ebe6fdb 100644 --- a/apps/mailer/Jenkinsfile +++ b/apps/mailer/Jenkinsfile @@ -3,13 +3,13 @@ pipeline { agent any environment { ENV_TYPE = "production" - PORT = 3884 + PORT = 4053 NAMESPACE = "yogram-ru" REGISTRY_HOSTNAME = "idogmat" - PROJECT = "mailer" + PROJECT = "yogram-mailer" SERVICE="mailer" REGISTRY = "registry.hub.docker.com" - DEPLOYMENT_NAME = "mailer-deployment" + DEPLOYMENT_NAME = "yogram-mailer-deployment" IMAGE_NAME = "${env.BUILD_ID}_${env.ENV_TYPE}_${env.GIT_COMMIT}" DOCKER_BUILD_NAME = "${env.REGISTRY_HOSTNAME}/${env.PROJECT}:${env.IMAGE_NAME}" } diff --git a/apps/mailer/deployment.yaml b/apps/mailer/deployment.yaml index 37f6e317..8fb166a5 100644 --- a/apps/mailer/deployment.yaml +++ b/apps/mailer/deployment.yaml @@ -25,35 +25,35 @@ spec: - name: SMTP_USER valueFrom: secretKeyRef: - name: mailer-production-config-secret + name: yogram-mailer-production-config-secret key: SMTP_USER - name: SMTP_HOST valueFrom: secretKeyRef: - name: mailer-production-config-secret + name: yogram-mailer-production-config-secret key: SMTP_HOST - name: SMTP_PASS valueFrom: secretKeyRef: - name: mailer-production-config-secret + name: yogram-mailer-production-config-secret key: SMTP_PASS - name: SMTP_PORT valueFrom: secretKeyRef: - name: mailer-production-config-secret + name: yogram-mailer-production-config-secret key: SMTP_PORT - name: JWT_SECRET valueFrom: secretKeyRef: - name: mailer-production-config-secret + name: yogram-mailer-production-config-secret key: JWT_SECRET - name: VERIFY_TOKEN_EXPIRES valueFrom: secretKeyRef: - name: mailer-production-config-secret + name: yogram-mailer-production-config-secret key: VERIFY_TOKEN_EXPIRES - name: RMQ_URL valueFrom: secretKeyRef: - name: mailer-production-config-secret + name: yogram-mailer-production-config-secret key: RMQ_URL diff --git a/apps/posts/Jenkinsfile b/apps/posts/Jenkinsfile index a4eeaaa6..e4c68b72 100644 --- a/apps/posts/Jenkinsfile +++ b/apps/posts/Jenkinsfile @@ -3,13 +3,13 @@ pipeline { agent any environment { ENV_TYPE = "production" - PORT = 3914 + PORT = 4050 NAMESPACE = "yogram-ru" REGISTRY_HOSTNAME = "idogmat" - PROJECT = "posts-yogram" + PROJECT = "yogram-posts" SERVICE="posts" REGISTRY = "registry.hub.docker.com" - DEPLOYMENT_NAME = "posts-yogram-deployment" + DEPLOYMENT_NAME = "yogram-posts-deployment" IMAGE_NAME = "${env.BUILD_ID}_${env.ENV_TYPE}_${env.GIT_COMMIT}" DOCKER_BUILD_NAME = "${env.REGISTRY_HOSTNAME}/${env.PROJECT}:${env.IMAGE_NAME}" } diff --git a/apps/posts/deployment.yaml b/apps/posts/deployment.yaml index afeb95dc..05b53ed8 100644 --- a/apps/posts/deployment.yaml +++ b/apps/posts/deployment.yaml @@ -16,69 +16,49 @@ spec: project: PROJECT spec: containers: - - name: PROJECT - image: REGISTRY_HOSTNAME/PROJECT:TAG_VERSION - ports: - - containerPort: PORT_CONTAINER + - name: PROJECT + image: REGISTRY_HOSTNAME/PROJECT:TAG_VERSION + ports: + - containerPort: PORT_CONTAINER - env: - - name: POSTGRES_TYPE - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: POSTGRES_TYPE - - name: POSTGRES_MIGRATION_TABLE - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: POSTGRES_MIGRATION_TABLE - - name: SYNCHRONIZE - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: SYNCHRONIZE - - name: AUTOLOAD_ENTITIES - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: AUTOLOAD_ENTITIES - - name: DROP_SCHEMA - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: DROP_SCHEMA - - name: RMQ_URL - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: RMQ_URL - - name: FILES_SERVICE_URL - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: FILES_SERVICE_URL - - name: BUCKET - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: BUCKET - - name: FILES_SERVICE_POSTS_UPLOAD_PATH - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: FILES_SERVICE_POSTS_UPLOAD_PATH - - name: FILES_SERVICE_CHUNKS_DIR - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: FILES_SERVICE_CHUNKS_DIR - - name: USERS_SERVICE_URL - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: USERS_SERVICE_URL - - name: POSTGRES_URL - valueFrom: - secretKeyRef: - name: posts-yogram-production-config-secret - key: POSTGRES_URL + env: + - name: POSTGRES_TYPE + valueFrom: + secretKeyRef: + name: yogram-posts-production-config-secret + key: POSTGRES_TYPE + - name: RMQ_URL + valueFrom: + secretKeyRef: + name: yogram-posts-production-config-secret + key: RMQ_URL + - name: FILES_SERVICE_URL + valueFrom: + secretKeyRef: + name: yogram-posts-production-config-secret + key: FILES_SERVICE_URL + - name: BUCKET + valueFrom: + secretKeyRef: + name: yogram-posts-production-config-secret + key: BUCKET + - name: FILES_SERVICE_POSTS_UPLOAD_PATH + valueFrom: + secretKeyRef: + name: yogram-posts-production-config-secret + key: FILES_SERVICE_POSTS_UPLOAD_PATH + - name: FILES_SERVICE_CHUNKS_DIR + valueFrom: + secretKeyRef: + name: yogram-posts-production-config-secret + key: FILES_SERVICE_CHUNKS_DIR + - name: USERS_SERVICE_URL + valueFrom: + secretKeyRef: + name: yogram-posts-production-config-secret + key: USERS_SERVICE_URL + - name: POSTGRES_URL + valueFrom: + secretKeyRef: + name: yogram-posts-production-config-secret + key: POSTGRES_URL diff --git a/apps/users/Jenkinsfile b/apps/users/Jenkinsfile index c09a9c6a..e33f7d5a 100644 --- a/apps/users/Jenkinsfile +++ b/apps/users/Jenkinsfile @@ -3,13 +3,13 @@ pipeline { agent any environment { ENV_TYPE = "production" - PORT = 3870 + PORT = 4049 NAMESPACE = "yogram-ru" REGISTRY_HOSTNAME = "idogmat" - PROJECT = "users-yogram" + PROJECT = "yogram-users" SERVICE="users" REGISTRY = "registry.hub.docker.com" - DEPLOYMENT_NAME = "users-yogram-deployment" + DEPLOYMENT_NAME = "yogram-users-deployment" IMAGE_NAME = "${env.BUILD_ID}_${env.ENV_TYPE}_${env.GIT_COMMIT}" DOCKER_BUILD_NAME = "${env.REGISTRY_HOSTNAME}/${env.PROJECT}:${env.IMAGE_NAME}" } diff --git a/apps/users/deployment.yaml b/apps/users/deployment.yaml index ba25cb30..20fc4abd 100644 --- a/apps/users/deployment.yaml +++ b/apps/users/deployment.yaml @@ -22,63 +22,33 @@ spec: - containerPort: PORT_CONTAINER env: - - name: POSTGRES_TYPE - valueFrom: - secretKeyRef: - name: users-yogram-production-config-secret - key: POSTGRES_TYPE - - name: POSTGRES_MIGRATION_TABLE - valueFrom: - secretKeyRef: - name: users-yogram-production-config-secret - key: POSTGRES_MIGRATION_TABLE - - name: SYNCHRONIZE - valueFrom: - secretKeyRef: - name: users-yogram-production-config-secret - key: SYNCHRONIZE - - name: AUTOLOAD_ENTITIES - valueFrom: - secretKeyRef: - name: users-yogram-production-config-secret - key: AUTOLOAD_ENTITIES - - name: DROP_SCHEMA - valueFrom: - secretKeyRef: - name: users-yogram-production-config-secret - key: DROP_SCHEMA - - name: USERS_PROD_SERVICE_URL - valueFrom: - secretKeyRef: - name: users-yogram-production-config-secret - key: USERS_PROD_SERVICE_URL - name: RMQ_URL valueFrom: secretKeyRef: - name: users-yogram-production-config-secret + name: yogram-users-production-config-secret key: RMQ_URL - - name: FILES_SERVICE_URL - valueFrom: - secretKeyRef: - name: users-yogram-production-config-secret - key: FILES_SERVICE_URL - name: BUCKET valueFrom: secretKeyRef: - name: users-yogram-production-config-secret + name: yogram-users-production-config-secret key: BUCKET + - name: FILES_SERVICE_URL + valueFrom: + secretKeyRef: + name: yogram-users-production-config-secret + key: FILES_SERVICE_URL - name: FILES_SERVICE_AVATAR_UPLOAD_PATH valueFrom: secretKeyRef: - name: users-yogram-production-config-secret + name: yogram-users-production-config-secret key: FILES_SERVICE_AVATAR_UPLOAD_PATH - name: FILES_SERVICE_CHUNKS_DIR valueFrom: secretKeyRef: - name: users-yogram-production-config-secret + name: yogram-users-production-config-secret key: FILES_SERVICE_CHUNKS_DIR - name: POSTGRES_URL valueFrom: secretKeyRef: - name: users-yogram-production-config-secret + name: yogram-users-production-config-secret key: POSTGRES_URL diff --git a/apps/users/src/db/1759504948061-InitTable.ts b/apps/users/src/db/1761615094093-users_dev.ts similarity index 75% rename from apps/users/src/db/1759504948061-InitTable.ts rename to apps/users/src/db/1761615094093-users_dev.ts index 5dfd6887..fd90d536 100644 --- a/apps/users/src/db/1759504948061-InitTable.ts +++ b/apps/users/src/db/1761615094093-users_dev.ts @@ -1,6 +1,6 @@ import { MigrationInterface, QueryRunner } from "typeorm"; -export class InitTable1759504948061 implements MigrationInterface { +export class UsersDev1761615094093 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { } diff --git a/apps/users/src/db/migrations/1759504952602-InitTable.ts b/apps/users/src/db/migrations/1761615103355-users_dev.ts similarity index 97% rename from apps/users/src/db/migrations/1759504952602-InitTable.ts rename to apps/users/src/db/migrations/1761615103355-users_dev.ts index 62f791df..551caabc 100644 --- a/apps/users/src/db/migrations/1759504952602-InitTable.ts +++ b/apps/users/src/db/migrations/1761615103355-users_dev.ts @@ -1,7 +1,7 @@ import { MigrationInterface, QueryRunner } from "typeorm"; -export class InitTable1759504952602 implements MigrationInterface { - name = 'InitTable1759504952602' +export class UsersDev1761615103355 implements MigrationInterface { + name = 'UsersDev1761615103355' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` diff --git a/apps/users/src/users.controller.ts b/apps/users/src/users.controller.ts index 89b2f304..3a9ef8e7 100644 --- a/apps/users/src/users.controller.ts +++ b/apps/users/src/users.controller.ts @@ -102,6 +102,7 @@ export class UsersController { @Get('users/login/:email') async userLogin(@Param() email: string): Promise { + console.log('🚀 ~ UsersController ~ userLogin ~ email:', email); return await this.queryBus.execute(new UserLoginQuery(email['email'])); } diff --git a/package.json b/package.json index 60e89671..abd68382 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "sharp": "^0.34.2", + "socket.io-client": "^4.8.1", "typeorm": "^0.3.23", "uuid": "^11.1.0", "uuidv4": "^6.2.13" diff --git a/tsconfig.json b/tsconfig.json index 59bc2e8b..ff60c275 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,4 +30,4 @@ // "@files/*": ["apps/files/*"] } } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index f0695ab4..d733e760 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4883,6 +4883,17 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" +engine.io-client@~6.6.1: + version "6.6.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.6.3.tgz#815393fa24f30b8e6afa8f77ccca2f28146be6de" + integrity sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + xmlhttprequest-ssl "~2.1.1" + engine.io-parser@~5.2.1: version "5.2.3" resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" @@ -9161,6 +9172,16 @@ socket.io-adapter@~2.5.2: debug "~4.3.4" ws "~8.17.1" +socket.io-client@^4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb" + integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.6.1" + socket.io-parser "~4.2.4" + socket.io-parser@~4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" @@ -10150,6 +10171,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlhttprequest-ssl@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23" + integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ== + xss@^1.0.8: version "1.0.15" resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.15.tgz#96a0e13886f0661063028b410ed1b18670f4e59a"