From 97ff00c349a9a23069a320c2d7c4aec37c564cb9 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 13 Mar 2024 10:54:25 +0100 Subject: [PATCH 01/86] Finish --- src/app/api/idnot/UserController.ts | 6 ++- .../migration.sql | 16 ++++++++ src/common/databases/schema.prisma | 9 +++++ .../repositories/UserWhitelistRepository.ts | 39 +++++++++++++++++++ .../UserWhitelistService/WhitelistService.ts | 14 +++++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/common/databases/migrations/20240313090933_whitelist_by_idnot_id/migration.sql create mode 100644 src/common/repositories/UserWhitelistRepository.ts create mode 100644 src/services/common/UserWhitelistService/WhitelistService.ts diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 0521a747..01850231 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -38,7 +38,7 @@ export default class UserController extends ApiController { const user = await this.idNotService.getOrCreateUser(idNotToken); if(!user) { - this.httpUnauthorized(response, "Email not found"); + this.httpUnauthorized(response, "User not found"); return; } @@ -62,8 +62,10 @@ export default class UserController extends ApiController { } //Check if user is whitelisted + const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); - const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); + //When we'll switch to idNotId whitelisting + // const isWhitelisted = await this.userWhitelistService.getByIdNotId(user.idNot); //If not whitelisted, return 409 Not whitelisted if (!isWhitelisted || isWhitelisted.length === 0) { diff --git a/src/common/databases/migrations/20240313090933_whitelist_by_idnot_id/migration.sql b/src/common/databases/migrations/20240313090933_whitelist_by_idnot_id/migration.sql new file mode 100644 index 00000000..5581a06e --- /dev/null +++ b/src/common/databases/migrations/20240313090933_whitelist_by_idnot_id/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "user_whitelist" ( + "uid" TEXT NOT NULL, + "idNot" VARCHAR(255) NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "user_whitelist_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "user_whitelist_uid_key" ON "user_whitelist"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_whitelist_idNot_key" ON "user_whitelist"("idNot"); diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 53858e86..4aad566c 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -81,6 +81,15 @@ model Whitelist { @@map("whitelist") } +model UserWhitelist { + uid String @id @unique @default(uuid()) + idNot String @unique @db.VarChar(255) + active Boolean @default(true) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + @@map("user_whitelist") +} + model Offices { uid String @id @unique @default(uuid()) idNot String @unique @db.VarChar(255) diff --git a/src/common/repositories/UserWhitelistRepository.ts b/src/common/repositories/UserWhitelistRepository.ts new file mode 100644 index 00000000..eae35819 --- /dev/null +++ b/src/common/repositories/UserWhitelistRepository.ts @@ -0,0 +1,39 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Prisma } from "prisma/prisma-client"; + +@Service() +export default class UserWhitelistRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().userWhitelist; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many whitelist + */ + public async findMany(query: Prisma.UserWhitelistFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : find unique by email + */ + public async findOneByIdNotId(idNotId: string) { + return this.model.findMany({ + where: { + idNot: idNotId, + }, + }); + } + + + +} diff --git a/src/services/common/UserWhitelistService/WhitelistService.ts b/src/services/common/UserWhitelistService/WhitelistService.ts new file mode 100644 index 00000000..dd96268f --- /dev/null +++ b/src/services/common/UserWhitelistService/WhitelistService.ts @@ -0,0 +1,14 @@ +import UserWhitelistRepository from "@Repositories/UserWhitelistRepository"; +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; + +@Service() +export default class UserWhitelistService extends BaseService { + constructor(private userWhitelistRepository: UserWhitelistRepository) { + super(); + } + + public async getByIdNotId(idNotId: string): Promise { + return this.userWhitelistRepository.findOneByIdNotId(idNotId); + } +} From 0c8cee8b4ccee618fc838eaf3aa1dee799020651 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 27 Mar 2024 09:31:48 +0100 Subject: [PATCH 02/86] WIP --- src/app/api/admin/SubscriptionsController.ts | 47 +++++++++++++++++++ .../migration.sql | 34 ++++++++++++++ src/common/databases/schema.prisma | 22 +++++++++ .../repositories/SubscriptionsRepository.ts | 28 +++++++++++ .../SubscriptionsService.ts.ts | 22 +++++++++ 5 files changed, 153 insertions(+) create mode 100644 src/app/api/admin/SubscriptionsController.ts create mode 100644 src/common/databases/migrations/20240325150228_subscriptions/migration.sql create mode 100644 src/common/repositories/SubscriptionsRepository.ts create mode 100644 src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts new file mode 100644 index 00000000..c3a674b8 --- /dev/null +++ b/src/app/api/admin/SubscriptionsController.ts @@ -0,0 +1,47 @@ +import { Controller, Get } from "@ControllerPattern/index"; +import { Response, Request } from "express"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Service } from "typedi"; +import authHandler from "@App/middlewares/AuthHandler"; +import roleHandler from "@App/middlewares/RolesHandler"; +import ruleHandler from "@App/middlewares/RulesHandler"; +import { Prisma } from "@prisma/client"; +import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; + +@Controller() +@Service() +export default class SubscriptionsController extends ApiController { + constructor(private subscriptionsService: SubscriptionsService) { + super(); + } + + /** + * @description Get all subscriptions + */ + @Get("/api/v1/admin/subscriptions", [authHandler, roleHandler, ruleHandler]) + protected async get(req: Request, response: Response) { + try { + //get query + let query: Prisma.SubscriptionsFindManyArgs = {}; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + if (query.where?.uid) { + this.httpBadRequest(response, "You can't filter by uid"); + return; + } + } + + //call service to get prisma entity + const subscriptionsEntities = await this.subscriptionsService.get(query); + + //Hydrate ressource with prisma entity + // const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/common/databases/migrations/20240325150228_subscriptions/migration.sql b/src/common/databases/migrations/20240325150228_subscriptions/migration.sql new file mode 100644 index 00000000..8319eafb --- /dev/null +++ b/src/common/databases/migrations/20240325150228_subscriptions/migration.sql @@ -0,0 +1,34 @@ +-- CreateTable +CREATE TABLE "subscriptions" ( + "uid" TEXT NOT NULL, + "start_date" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "end_date" TIMESTAMP(3), + "nb_seats" INTEGER NOT NULL, + "office_uid" VARCHAR(255) NOT NULL, + + CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "seats" ( + "uid" TEXT NOT NULL, + "subscription_uid" VARCHAR(255) NOT NULL, + "user_uid" VARCHAR(255) NOT NULL, + + CONSTRAINT "seats_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "subscriptions_uid_key" ON "subscriptions"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "seats_uid_key" ON "seats"("uid"); + +-- AddForeignKey +ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_office_uid_fkey" FOREIGN KEY ("office_uid") REFERENCES "offices"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "seats" ADD CONSTRAINT "seats_subscription_uid_fkey" FOREIGN KEY ("subscription_uid") REFERENCES "subscriptions"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "seats" ADD CONSTRAINT "seats_user_uid_fkey" FOREIGN KEY ("user_uid") REFERENCES "users"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 4aad566c..170a228d 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -68,6 +68,7 @@ model Users { appointment Appointments[] votes Votes[] user_notifications UserNotifications[] + seats Seats[] @@map("users") } @@ -108,6 +109,7 @@ model Offices { office_folders OfficeFolders[] document_types DocumentTypes[] office_roles OfficeRoles[] + subscriptions Subscriptions[] @@map("offices") } @@ -375,6 +377,26 @@ model TotpCodes { @@map("totp_codes") } +model Subscriptions { + uid String @id @unique @default(uuid()) + start_date DateTime? @default(now()) + end_date DateTime? + nb_seats Int + office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) + office_uid String @db.VarChar(255) + seats Seats[] + @@map("subscriptions") +} + +model Seats { + uid String @id @unique @default(uuid()) + subscription Subscriptions @relation(fields: [subscription_uid], references: [uid], onDelete: Cascade) + subscription_uid String @db.VarChar(255) + user Users @relation(fields: [user_uid], references: [uid], onDelete: Cascade) + user_uid String @db.VarChar(255) + @@map("seats") +} + enum TotpCodesReasons { LOGIN RESET_PASSWORD diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts new file mode 100644 index 00000000..7d921c51 --- /dev/null +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -0,0 +1,28 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Prisma } from "@prisma/client"; + +@Service() +export default class SubscriptionsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().subscriptions; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many totp codes + */ + public async findMany(query: Prisma.SubscriptionsFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + if (!query.include) return this.model.findMany({ ...query }); + return this.model.findMany({ ...query }); + } + + +} diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts new file mode 100644 index 00000000..a884dc54 --- /dev/null +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -0,0 +1,22 @@ +import BaseService from "@Services/BaseService"; +import "reflect-metadata"; +import { Service } from "typedi"; +import { Prisma } from "@prisma/client"; +import SubscriptionsRepository from "@Repositories/SubscriptionsRepository"; + +@Service() +export default class SubscriptionsService extends BaseService { + constructor(private subscriptionsRepository: SubscriptionsRepository) { + super(); + } + + /** + * @description : Get all subscriptions + * @throws {Error} If subscriptions cannot be get + */ + public get(query: Prisma.SubscriptionsFindManyArgs) { + return this.subscriptionsRepository.findMany(query); + } + + +} From 9ebb4301bfd8c228b2ae223f204fff72bed309e1 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 28 Mar 2024 15:26:48 +0100 Subject: [PATCH 03/86] Entrypoint done --- package.json | 2 +- src/app/api/admin/SubscriptionsController.ts | 111 +++++++++++++++-- src/app/index.ts | 4 +- .../migration.sql | 12 ++ .../migration.sql | 11 ++ src/common/databases/schema.prisma | 11 +- .../repositories/SubscriptionsRepository.ts | 117 +++++++++++++++++- .../SubscriptionsService.ts.ts | 26 +++- 8 files changed, 280 insertions(+), 14 deletions(-) create mode 100644 src/common/databases/migrations/20240327160202_edit_subscriptions/migration.sql create mode 100644 src/common/databases/migrations/20240328104442_subscription_type/migration.sql diff --git a/package.json b/package.json index a6f832c1..f807f38f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.119", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.123", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index c3a674b8..20baac59 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -1,12 +1,15 @@ -import { Controller, Get } from "@ControllerPattern/index"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; import { Response, Request } from "express"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; -import authHandler from "@App/middlewares/AuthHandler"; -import roleHandler from "@App/middlewares/RolesHandler"; -import ruleHandler from "@App/middlewares/RulesHandler"; +// import authHandler from "@App/middlewares/AuthHandler"; +// import roleHandler from "@App/middlewares/RolesHandler"; +// import ruleHandler from "@App/middlewares/RulesHandler"; import { Prisma } from "@prisma/client"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; +import { Subscription } from "le-coffre-resources/dist/Admin"; +import { validateOrReject } from "class-validator"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; @Controller() @Service() @@ -18,7 +21,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Get all subscriptions */ - @Get("/api/v1/admin/subscriptions", [authHandler, roleHandler, ruleHandler]) + @Get("/api/v1/admin/subscriptions") protected async get(req: Request, response: Response) { try { //get query @@ -35,10 +38,104 @@ export default class SubscriptionsController extends ApiController { const subscriptionsEntities = await this.subscriptionsService.get(query); //Hydrate ressource with prisma entity - // const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); + const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); //success - this.httpSuccess(response); + this.httpSuccess(response, subscriptions); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Get a specific documentType by uid + */ + @Get("/api/v1/admin/subscriptions/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + //get query + let query; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + } + + const subscriptionEntity = await this.subscriptionsService.getByUid(uid, query); + + //Hydrate resource with prisma entity + const subscription = ObjectHydrate.hydrate(new Subscription(), subscriptionEntity!, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, subscription); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Create a new documentType + */ + @Post("/api/v1/admin/subscriptions") + protected async post(req: Request, response: Response) { + try { + //init Subscription resource with request body values + const subscriptionEntity = Subscription.hydrate(req.body); + //validate user + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + //call service to get prisma entity + const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity); + //Hydrate ressource with prisma entity + const subscription = Subscription.hydrate(subscriptionEntityCreated, { + strategy: "excludeAll", + }); + //success + this.httpCreated(response, subscription); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Update a subscription + */ + @Put("/api/v1/admin/subscriptions/:uid") + protected async put(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const subscriptionFound = await this.subscriptionsService.getByUid(uid); + + if (!subscriptionFound) { + this.httpNotFoundRequest(response, "subscription not found"); + return; + } + + //init Subscription resource with request body values + const subscriptionEntity = Subscription.hydrate(req.body); + + + //call service to get prisma entity + const subscriptionEntityUpdated = await this.subscriptionsService.update(uid, subscriptionEntity); + + //Hydrate ressource with prisma entity + const subscription = Subscription.hydrate(subscriptionEntityUpdated, { + strategy: "excludeAll", + }); + + //success + this.httpSuccess(response, subscription); + } catch (error) { this.httpInternalError(response, error); return; diff --git a/src/app/index.ts b/src/app/index.ts index 20343be2..1d9b1acc 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -49,6 +49,7 @@ import AuthController from "./api/customer/AuthController"; import NotaryOfficeRibController from "./api/notary/OfficeRibController"; import CustomerOfficeRibController from "./api/customer/OfficeRibController"; import IdNotOfficeController from "./api/idnot/OfficeController"; +import SubscriptionsController from "./api/admin/SubscriptionsController"; /** * @description This allow to declare all controllers used in the application */ @@ -104,6 +105,7 @@ export default { Container.get(AuthController); Container.get(NotaryOfficeRibController); Container.get(CustomerOfficeRibController); - Container.get(IdNotOfficeController) + Container.get(IdNotOfficeController); + Container.get(SubscriptionsController); }, }; diff --git a/src/common/databases/migrations/20240327160202_edit_subscriptions/migration.sql b/src/common/databases/migrations/20240327160202_edit_subscriptions/migration.sql new file mode 100644 index 00000000..f460f2f5 --- /dev/null +++ b/src/common/databases/migrations/20240327160202_edit_subscriptions/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - Added the required column `priceId` to the `subscriptions` table without a default value. This is not possible if the table is not empty. + - Made the column `start_date` on table `subscriptions` required. This step will fail if there are existing NULL values in that column. + - Made the column `end_date` on table `subscriptions` required. This step will fail if there are existing NULL values in that column. + +*/ +-- AlterTable +ALTER TABLE "subscriptions" ADD COLUMN "priceId" VARCHAR(255) NOT NULL, +ALTER COLUMN "start_date" SET NOT NULL, +ALTER COLUMN "end_date" SET NOT NULL; diff --git a/src/common/databases/migrations/20240328104442_subscription_type/migration.sql b/src/common/databases/migrations/20240328104442_subscription_type/migration.sql new file mode 100644 index 00000000..6931a9db --- /dev/null +++ b/src/common/databases/migrations/20240328104442_subscription_type/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - Added the required column `type` to the `subscriptions` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "ESubscriptionType" AS ENUM ('STANDARD', 'UNLIMITED'); + +-- AlterTable +ALTER TABLE "subscriptions" ADD COLUMN "type" "ESubscriptionType" NOT NULL; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 170a228d..d6f48d96 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -379,8 +379,10 @@ model TotpCodes { model Subscriptions { uid String @id @unique @default(uuid()) - start_date DateTime? @default(now()) - end_date DateTime? + type ESubscriptionType + priceId String @db.VarChar(255) + start_date DateTime @default(now()) + end_date DateTime nb_seats Int office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) office_uid String @db.VarChar(255) @@ -397,6 +399,11 @@ model Seats { @@map("seats") } +enum ESubscriptionType { + STANDARD + UNLIMITED +} + enum TotpCodesReasons { LOGIN RESET_PASSWORD diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts index 7d921c51..1078deff 100644 --- a/src/common/repositories/SubscriptionsRepository.ts +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -1,7 +1,8 @@ import Database from "@Common/databases/database"; import BaseRepository from "@Repositories/BaseRepository"; import { Service } from "typedi"; -import { Prisma } from "@prisma/client"; +import { ESubscriptionType, Prisma, Subscriptions } from "@prisma/client"; +import { Subscription } from "le-coffre-resources/dist/Admin"; @Service() export default class SubscriptionsRepository extends BaseRepository { @@ -16,7 +17,7 @@ export default class SubscriptionsRepository extends BaseRepository { } /** - * @description : Find many totp codes + * @description : Find many subscriptions */ public async findMany(query: Prisma.SubscriptionsFindManyArgs) { query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); @@ -24,5 +25,117 @@ export default class SubscriptionsRepository extends BaseRepository { return this.model.findMany({ ...query }); } + /** + * @description : find unique subscription + */ + public async findOneByUid(uid: string, query?: Prisma.SubscriptionsInclude): Promise { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: query, + }); + } + + /** + * @description : Create a subscription + */ + public async create(subscription: Subscription): Promise { + if(subscription.type === "STANDARD") + { + const createArgs: Prisma.SubscriptionsCreateArgs = { + data: { + start_date: subscription.start_date, + end_date: subscription.end_date, + type: ESubscriptionType.STANDARD, + nb_seats: subscription.nb_seats!, + priceId: subscription.priceId, + office: { + connect: { + uid: subscription.office!.uid, + }, + }, + seats: { + create: subscription.seats!.map(seat => ({ + user: { + connect: { + uid: seat.user!.uid, + }, + }, + })), + }, + }, + }; + return this.model.create(createArgs); + } + else + { + const createArgs: Prisma.SubscriptionsCreateArgs = { + data: { + start_date: subscription.start_date, + end_date: subscription.end_date, + type: ESubscriptionType.UNLIMITED, + nb_seats: 0, + priceId: subscription.priceId, + office: { + connect: { + uid: subscription.office!.uid, + }, + }, + }, + }; + return this.model.create(createArgs); + } + + } + + /** + * @description : update given subscription + */ + public async update(uid: string, subscription: Subscription): Promise { + + if(subscription.type === "STANDARD") + { + const updateArgs: Prisma.SubscriptionsUpdateArgs = { + where: { + uid: uid, + }, + data: { + end_date: subscription.end_date, + type: ESubscriptionType.STANDARD, + nb_seats: subscription.nb_seats!, + seats: { + deleteMany: {}, + create: subscription.seats!.map(seat => ({ + user: { + connect: { + uid: seat.user!.uid, + }, + }, + })), + } + }, + }; + return this.model.update(updateArgs); + } + else + { + const updateArgs: Prisma.SubscriptionsUpdateArgs = { + where: { + uid: uid, + }, + data: { + end_date: subscription.end_date, + type: ESubscriptionType.UNLIMITED, + nb_seats: 0, + seats: { + deleteMany: {}, + }, + }, + }; + return this.model.update(updateArgs); + } + } + } diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts index a884dc54..11c85146 100644 --- a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -1,8 +1,9 @@ import BaseService from "@Services/BaseService"; import "reflect-metadata"; import { Service } from "typedi"; -import { Prisma } from "@prisma/client"; +import { Prisma, Subscriptions } from "@prisma/client"; import SubscriptionsRepository from "@Repositories/SubscriptionsRepository"; +import { Subscription } from "le-coffre-resources/dist/Admin"; @Service() export default class SubscriptionsService extends BaseService { @@ -18,5 +19,28 @@ export default class SubscriptionsService extends BaseService { return this.subscriptionsRepository.findMany(query); } + /** + * @description : Get a subscription by uid + * @throws {Error} If subscription is not found + */ + public async getByUid(uid: string, query?: Prisma.SubscriptionsInclude) { + return this.subscriptionsRepository.findOneByUid(uid, query); + } + + /** + * @description : Create a new subscription + * @throws {Error} If subsctiption cannot be created + */ + public async create(subscriptionEntity: Subscription): Promise { + return this.subscriptionsRepository.create(subscriptionEntity); + } + + /** + * @description : Modify a subscription + * @throws {Error} If subscription cannot be modified + */ + public async update(uid: string, subscriptionEntity: Subscription): Promise { + return this.subscriptionsRepository.update(uid, subscriptionEntity); + } } From 5ff99c49221f2d01e906dbc0cc4a101ca23fa715 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 29 Mar 2024 15:04:02 +0100 Subject: [PATCH 04/86] Subscribe finished --- package.json | 3 +- src/app/api/admin/StripeController.ts | 35 ++++++++++ src/app/api/admin/SubscriptionsController.ts | 5 +- src/app/index.ts | 4 ++ src/common/config/variables/Variables.ts | 20 ++++++ .../migration.sql | 10 +++ .../migration.sql | 5 ++ .../migration.sql | 2 + src/common/databases/schema.prisma | 8 ++- .../repositories/SubscriptionsRepository.ts | 10 ++- src/common/webhooks/stripeWebhooks.ts | 64 +++++++++++++++++++ .../common/StripeService/StripeService.ts | 36 +++++++++++ 12 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 src/app/api/admin/StripeController.ts create mode 100644 src/common/databases/migrations/20240329094447_stripe_subscription_id/migration.sql create mode 100644 src/common/databases/migrations/20240329095902_subscription_status/migration.sql create mode 100644 src/common/databases/migrations/20240329135543_subscription_status_default_active/migration.sql create mode 100644 src/common/webhooks/stripeWebhooks.ts create mode 100644 src/services/common/StripeService/StripeService.ts diff --git a/package.json b/package.json index f807f38f..c1e7881f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.123", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v1.125", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", @@ -70,6 +70,7 @@ "prisma-query": "^2.0.0", "puppeteer": "^21.3.4", "reflect-metadata": "^0.1.13", + "stripe": "^14.22.0", "ts-node": "^10.9.1", "tslib": "^2.4.1", "typedi": "^0.10.0", diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts new file mode 100644 index 00000000..f411bda3 --- /dev/null +++ b/src/app/api/admin/StripeController.ts @@ -0,0 +1,35 @@ +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Controller, Post } from "@ControllerPattern/index"; +import StripeService from "@Services/common/StripeService/StripeService"; +import { validateOrReject } from "class-validator"; +import { Request, Response } from "express"; +import { Subscription } from "le-coffre-resources/dist/Admin"; +import { Service } from "typedi"; + +@Controller() +@Service() +export default class StripeController extends ApiController { + constructor(private stripeService: StripeService) { + super(); + } + + /** + * @description Create a new checkout session + */ + @Post("/api/v1/admin/stripe", []) + protected async post(req: Request, response: Response) { + try { + //init Subscription resource with request body values + const subscriptionEntity = Subscription.hydrate(req.body); + + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + + const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); + + this.httpCreated(response, stripeSession); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} \ No newline at end of file diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index 20baac59..1e3dd0a1 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -86,8 +86,8 @@ export default class SubscriptionsController extends ApiController { try { //init Subscription resource with request body values const subscriptionEntity = Subscription.hydrate(req.body); - //validate user - await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + //validate subscription + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); //call service to get prisma entity const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity); //Hydrate ressource with prisma entity @@ -124,7 +124,6 @@ export default class SubscriptionsController extends ApiController { //init Subscription resource with request body values const subscriptionEntity = Subscription.hydrate(req.body); - //call service to get prisma entity const subscriptionEntityUpdated = await this.subscriptionsService.update(uid, subscriptionEntity); diff --git a/src/app/index.ts b/src/app/index.ts index 1d9b1acc..174b9abd 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -50,6 +50,8 @@ import NotaryOfficeRibController from "./api/notary/OfficeRibController"; import CustomerOfficeRibController from "./api/customer/OfficeRibController"; import IdNotOfficeController from "./api/idnot/OfficeController"; import SubscriptionsController from "./api/admin/SubscriptionsController"; +import StripeController from "./api/admin/StripeController"; +import StripeWebhooks from "@Common/webhooks/stripeWebhooks"; /** * @description This allow to declare all controllers used in the application */ @@ -107,5 +109,7 @@ export default { Container.get(CustomerOfficeRibController); Container.get(IdNotOfficeController); Container.get(SubscriptionsController); + Container.get(StripeController); + Container.get(StripeWebhooks); }, }; diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index 10df52e7..16872c03 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -142,6 +142,21 @@ export class BackendVariables { @IsNotEmpty() public readonly SCW_BUCKET_NAME!: string; + @IsNotEmpty() + public readonly STRIPE_SECRET_KEY!: string; + + @IsNotEmpty() + public readonly STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID!: string; + + @IsNotEmpty() + public readonly STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID!: string; + + @IsNotEmpty() + public readonly STRIPE_PAYMENT_SUCCESS_URL!: string; + + @IsNotEmpty() + public readonly STRIPE_PAYMENT_CANCEL_URL!: string; + public constructor() { dotenv.config(); this.DATABASE_PORT = process.env["DATABASE_PORT"]!; @@ -190,6 +205,11 @@ export class BackendVariables { this.SCW_ACCESS_KEY_SECRET = process.env["SCW_ACCESS_KEY_SECRET"]!; this.SCW_BUCKET_ENDPOINT = process.env["SCW_BUCKET_ENDPOINT"]!; this.SCW_BUCKET_NAME = process.env["SCW_BUCKET_NAME"]!; + this.STRIPE_SECRET_KEY = process.env["STRIPE_SECRET_KEY"]!; + this.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID"]!; + this.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID"]!; + this.STRIPE_PAYMENT_SUCCESS_URL = process.env["STRIPE_PAYMENT_SUCCESS_URL"]!; + this.STRIPE_PAYMENT_CANCEL_URL = process.env["STRIPE_PAYMENT_CANCEL_URL"]!; } public async validate(groups?: string[]) { const validationOptions = groups ? { groups } : undefined; diff --git a/src/common/databases/migrations/20240329094447_stripe_subscription_id/migration.sql b/src/common/databases/migrations/20240329094447_stripe_subscription_id/migration.sql new file mode 100644 index 00000000..02e49af0 --- /dev/null +++ b/src/common/databases/migrations/20240329094447_stripe_subscription_id/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `priceId` on the `subscriptions` table. All the data in the column will be lost. + - Added the required column `stripe_subscription_id` to the `subscriptions` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "subscriptions" DROP COLUMN "priceId", +ADD COLUMN "stripe_subscription_id" VARCHAR(255) NOT NULL; diff --git a/src/common/databases/migrations/20240329095902_subscription_status/migration.sql b/src/common/databases/migrations/20240329095902_subscription_status/migration.sql new file mode 100644 index 00000000..8c9d5f83 --- /dev/null +++ b/src/common/databases/migrations/20240329095902_subscription_status/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "ESubscriptionStatus" AS ENUM ('ACTIVE', 'INACTIVE'); + +-- AlterTable +ALTER TABLE "subscriptions" ADD COLUMN "status" "ESubscriptionStatus" NOT NULL DEFAULT 'INACTIVE'; diff --git a/src/common/databases/migrations/20240329135543_subscription_status_default_active/migration.sql b/src/common/databases/migrations/20240329135543_subscription_status_default_active/migration.sql new file mode 100644 index 00000000..e036e137 --- /dev/null +++ b/src/common/databases/migrations/20240329135543_subscription_status_default_active/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "subscriptions" ALTER COLUMN "status" SET DEFAULT 'ACTIVE'; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index d6f48d96..06d8c782 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -380,7 +380,8 @@ model TotpCodes { model Subscriptions { uid String @id @unique @default(uuid()) type ESubscriptionType - priceId String @db.VarChar(255) + status ESubscriptionStatus @default(ACTIVE) + stripe_subscription_id String @db.VarChar(255) start_date DateTime @default(now()) end_date DateTime nb_seats Int @@ -399,6 +400,11 @@ model Seats { @@map("seats") } +enum ESubscriptionStatus { + ACTIVE + INACTIVE +} + enum ESubscriptionType { STANDARD UNLIMITED diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts index 1078deff..a8203886 100644 --- a/src/common/repositories/SubscriptionsRepository.ts +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -1,7 +1,7 @@ import Database from "@Common/databases/database"; import BaseRepository from "@Repositories/BaseRepository"; import { Service } from "typedi"; -import { ESubscriptionType, Prisma, Subscriptions } from "@prisma/client"; +import { ESubscriptionStatus, ESubscriptionType, Prisma, Subscriptions } from "@prisma/client"; import { Subscription } from "le-coffre-resources/dist/Admin"; @Service() @@ -48,8 +48,9 @@ export default class SubscriptionsRepository extends BaseRepository { start_date: subscription.start_date, end_date: subscription.end_date, type: ESubscriptionType.STANDARD, + status: ESubscriptionStatus.ACTIVE, nb_seats: subscription.nb_seats!, - priceId: subscription.priceId, + stripe_subscription_id: subscription.stripe_subscription_id || "", office: { connect: { uid: subscription.office!.uid, @@ -75,8 +76,9 @@ export default class SubscriptionsRepository extends BaseRepository { start_date: subscription.start_date, end_date: subscription.end_date, type: ESubscriptionType.UNLIMITED, + status: ESubscriptionStatus.ACTIVE, nb_seats: 0, - priceId: subscription.priceId, + stripe_subscription_id: subscription.stripe_subscription_id || "", office: { connect: { uid: subscription.office!.uid, @@ -103,6 +105,7 @@ export default class SubscriptionsRepository extends BaseRepository { data: { end_date: subscription.end_date, type: ESubscriptionType.STANDARD, + status: subscription.status as ESubscriptionStatus, nb_seats: subscription.nb_seats!, seats: { deleteMany: {}, @@ -127,6 +130,7 @@ export default class SubscriptionsRepository extends BaseRepository { data: { end_date: subscription.end_date, type: ESubscriptionType.UNLIMITED, + status: subscription.status as ESubscriptionStatus, nb_seats: 0, seats: { deleteMany: {}, diff --git a/src/common/webhooks/stripeWebhooks.ts b/src/common/webhooks/stripeWebhooks.ts new file mode 100644 index 00000000..9463eadc --- /dev/null +++ b/src/common/webhooks/stripeWebhooks.ts @@ -0,0 +1,64 @@ +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Controller, Post } from "@ControllerPattern/index"; +import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; +import StripeService from "@Services/common/StripeService/StripeService"; +import { validateOrReject } from "class-validator"; +import { Request, Response } from "express"; +import { Subscription } from "le-coffre-resources/dist/Admin"; +import { Service } from "typedi"; + +@Controller() +@Service() +export default class StripeWebhooks extends ApiController { + constructor(private stripeService: StripeService, private subscriptionsService: SubscriptionsService) { + super(); + } + + /** + * @description Create a new checkout session + */ + @Post("/api/v1/webhooks/stripe") + protected async post(req: Request, response: Response) { + try { + // const sig = req.headers["stripe-signature"]; + // const endpointSecret = "whsec_c4088876914bc166ff5c39253207f84900820b67f7bba3b2669c0ff392cbc838"; + // const stripe = this.stripeService.getClient(); + // let event: Stripe.Event; + + // if (!sig || !endpointSecret) { + // throw new Error("Signature verification failed"); + // } + // event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); + const event = req.body; + + switch (event.type) { + case "checkout.session.completed": + if (event.data.object.status !== "complete") break; + + const subscription = JSON.parse(event.data.object.metadata.subscription); + subscription.stripe_subscription_id = event.data.object.subscription; + + const subscriptionInfo = await this.stripeService + .getClient() + .subscriptions.retrieve(subscription.stripe_subscription_id); + + subscription.start_date = new Date(subscriptionInfo.current_period_start * 1000); + subscription.end_date = new Date(subscriptionInfo.current_period_end * 1000); + + const subscriptionEntity = Subscription.hydrate(subscription); + + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + + await this.subscriptionsService.create(subscriptionEntity); + break; + default: + break; + } + + response.json({ received: true }); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts new file mode 100644 index 00000000..59c261f5 --- /dev/null +++ b/src/services/common/StripeService/StripeService.ts @@ -0,0 +1,36 @@ +import { BackendVariables } from "@Common/config/variables/Variables"; +import { Subscription } from "le-coffre-resources/dist/Admin"; +import Stripe from "stripe"; +import { Service } from "typedi"; + +@Service() +export default class StripeService { + private client: Stripe; + constructor(protected variables: BackendVariables) { + this.client = new Stripe(variables.STRIPE_SECRET_KEY); + } + + public getClient(): Stripe { + return this.client; + } + + public async createCheckoutSession(subscription: Subscription) { + const priceId = subscription.type === "STANDARD" ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID; + return this.client.checkout.sessions.create({ + mode: "subscription", + payment_method_types: ["card", "paypal"], + billing_address_collection: "auto", + line_items: [ + { + price: priceId, + quantity: subscription.nb_seats, + }, + ], + success_url: this.variables.STRIPE_PAYMENT_SUCCESS_URL, + cancel_url: this.variables.STRIPE_PAYMENT_CANCEL_URL, + metadata: { + subscription: JSON.stringify(subscription), + }, + }); + } +} \ No newline at end of file From d1c7d803b1b50a422f0453875a0aeadd95c2c050 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 15:57:11 +0200 Subject: [PATCH 05/86] Get office membership + create user if not exist --- src/app/api/admin/SubscriptionsController.ts | 10 +++-- src/app/api/idnot/OfficeController.ts | 21 ++++++--- src/common/repositories/UsersRepository.ts | 39 ++++++++++++++++ .../super-admin/UsersService/UsersService.ts | 44 ++++++++++++++++++- .../services/super-admin/UsersService.test.ts | 3 +- 5 files changed, 105 insertions(+), 12 deletions(-) diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index 1e3dd0a1..4de51386 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -10,6 +10,8 @@ import SubscriptionsService from "@Services/admin/SubscriptionsService/Subscript import { Subscription } from "le-coffre-resources/dist/Admin"; import { validateOrReject } from "class-validator"; import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import roleHandler from "@App/middlewares/RolesHandler"; +import authHandler from "@App/middlewares/AuthHandler"; @Controller() @Service() @@ -21,7 +23,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Get all subscriptions */ - @Get("/api/v1/admin/subscriptions") + @Get("/api/v1/admin/subscriptions", [authHandler, roleHandler]) protected async get(req: Request, response: Response) { try { //get query @@ -51,7 +53,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Get a specific documentType by uid */ - @Get("/api/v1/admin/subscriptions/:uid") + @Get("/api/v1/admin/subscriptions/:uid", [authHandler, roleHandler]) protected async getOneByUid(req: Request, response: Response) { try { const uid = req.params["uid"]; @@ -81,7 +83,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Create a new documentType */ - @Post("/api/v1/admin/subscriptions") + @Post("/api/v1/admin/subscriptions", [authHandler, roleHandler]) protected async post(req: Request, response: Response) { try { //init Subscription resource with request body values @@ -105,7 +107,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Update a subscription */ - @Put("/api/v1/admin/subscriptions/:uid") + @Put("/api/v1/admin/subscriptions/:uid", [authHandler, roleHandler]) protected async put(req: Request, response: Response) { try { const uid = req.params["uid"]; diff --git a/src/app/api/idnot/OfficeController.ts b/src/app/api/idnot/OfficeController.ts index 3e00e7bf..bbbc19d2 100644 --- a/src/app/api/idnot/OfficeController.ts +++ b/src/app/api/idnot/OfficeController.ts @@ -3,25 +3,36 @@ import { Controller, Get } from "@ControllerPattern/index"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; import IdNotService from "@Services/common/IdNotService/IdNotService"; +import UsersService from "@Services/super-admin/UsersService/UsersService"; +import User from "le-coffre-resources/dist/Notary/User"; +import userHandler from "@App/middlewares/OfficeMembershipHandlers/UserHandler"; +import authHandler from "@App/middlewares/AuthHandler"; @Controller() @Service() export default class UserController extends ApiController { - constructor (private idNotService: IdNotService) { + constructor (private idNotService: IdNotService, private userService: UsersService) { super(); } - @Get("/api/v1/idnot/office/:uid/office-memberships") + @Get("/api/v1/idnot/office/:uid/office-memberships", [authHandler, userHandler]) protected async getOfficeMemberships(req: Request, response: Response) { - try { + try { const uid = req.params["uid"]; if (!uid) { this.httpBadRequest(response, "uid is required"); return; } - const officeMemberships = await this.idNotService.getOfficeMemberships(uid); - this.httpSuccess(response, officeMemberships); + const officeMemberships = await this.idNotService.getOfficeMemberships(uid); + + await this.userService.getOrCreateUsers(uid, officeMemberships.result); + + const usersEntities = await this.userService.get({ where: { office_uid: uid }, include: { contact: true}}); + + const users = User.hydrateArray(usersEntities, { strategy: "excludeAll" }); + + this.httpSuccess(response, users); } catch (error) { console.log(error); this.httpInternalError(response); diff --git a/src/common/repositories/UsersRepository.ts b/src/common/repositories/UsersRepository.ts index c0f466e6..538c601d 100644 --- a/src/common/repositories/UsersRepository.ts +++ b/src/common/repositories/UsersRepository.ts @@ -143,6 +143,45 @@ export default class UsersRepository extends BaseRepository { return this.model.update({ ...updateArgs, include: { contact: true, office_membership: { include: { address: true } } } }); } + public async getOrCreate(usersToAdd: User[]) { + + let users: User[] = []; + + await Promise.all(usersToAdd.map(async (user) => { + const upsertUser = await this.model.upsert({ + where: { + idNot: user.idNot, + }, + update: {}, + create: { + idNot: user.idNot, + office_membership: { + connect: { + uid: user.office_membership!.uid, + } + }, + contact: { + create: { + first_name: user.contact!.first_name, + last_name: user.contact!.last_name, + email: user.contact!.email, + civility: ECivility[user.contact?.civility as keyof typeof ECivility], + }, + }, + role: { + connect: { + uid: user.role!.uid, + }, + }, + }, + }); + users.push(upsertUser); + })); + + return users; + + } + /** * @description : Update check date of a user */ diff --git a/src/services/super-admin/UsersService/UsersService.ts b/src/services/super-admin/UsersService/UsersService.ts index 3598e945..77dea910 100644 --- a/src/services/super-admin/UsersService/UsersService.ts +++ b/src/services/super-admin/UsersService/UsersService.ts @@ -2,12 +2,13 @@ import BaseService from "@Services/BaseService"; import "reflect-metadata"; import { Service } from "typedi"; import UsersRepository from "@Repositories/UsersRepository"; -import User from "le-coffre-resources/dist/SuperAdmin"; +import User from "le-coffre-resources/dist/Admin"; import { Prisma, Users } from "@prisma/client"; +import RolesService from "@Services/admin/RolesService/RolesService"; @Service() export default class UsersService extends BaseService { - constructor(private userRepository: UsersRepository) { + constructor(private userRepository: UsersRepository, private rolesService: RolesService) { super(); } @@ -91,4 +92,43 @@ export default class UsersService extends BaseService { return this.userRepository.findManyToCheck(); } + public async getOrCreateUsers(officeId: string, usersToCreate: [{}]) { + let users : User[] = []; + const roleNotary = await this.rolesService.get({ where: { name: "notary" } }); + + usersToCreate.forEach((user: any) => { + const userEntity: User = { + idNot: user.uid, + contact: { + first_name: user.prenom, + last_name: user.nomUsuel, + civility: user.civilite, + email: "", + created_at: new Date(), + updated_at: new Date(), + }, + office_membership: { + uid: officeId, + name: "", + crpcen: "", + created_at: new Date(), + updated_at: new Date(), + }, + role: { + uid: roleNotary[0]?.uid, + name: "", + label: "", + created_at: new Date(), + updated_at: new Date(), + }, + created_at: new Date(), + updated_at: new Date(), + }; + users.push(userEntity); + }); + + return await this.userRepository.getOrCreate(users); + } + + } diff --git a/src/test/services/super-admin/UsersService.test.ts b/src/test/services/super-admin/UsersService.test.ts index 5b7d6708..6d1f7910 100644 --- a/src/test/services/super-admin/UsersService.test.ts +++ b/src/test/services/super-admin/UsersService.test.ts @@ -6,10 +6,11 @@ import { PrismaClient } from "@prisma/client"; import { user, userContact, userContact_, user_ } from "@Test/config/MockedData"; import UsersRepository from "@Repositories/UsersRepository"; import Container from "typedi"; +import RolesService from "@Services/admin/RolesService/RolesService"; const prisma = new PrismaClient(); -const UsersServiceTest = new UsersService(Container.get(UsersRepository)); +const UsersServiceTest = new UsersService(Container.get(UsersRepository), Container.get(RolesService)); afterAll(async () => { /* From 90486335b878cefe9f9b0db16cbec3c9242d7adf Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 16:05:21 +0200 Subject: [PATCH 06/86] Added authHandler to strip entrypoint --- src/app/api/admin/StripeController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index f411bda3..43721201 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,3 +1,4 @@ +import authHandler from "@App/middlewares/AuthHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; @@ -16,7 +17,7 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe", []) + @Post("/api/v1/admin/stripe", [authHandler]) protected async post(req: Request, response: Response) { try { //init Subscription resource with request body values From 860ab20b0f4e7807a6f2f59bb91bded98275c556 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 16:07:55 +0200 Subject: [PATCH 07/86] Some middlewares --- src/app/api/admin/StripeController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index 43721201..c873dca6 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,4 +1,5 @@ import authHandler from "@App/middlewares/AuthHandler"; +import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; @@ -17,7 +18,7 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe", [authHandler]) + @Post("/api/v1/admin/stripe", [authHandler, roleHandler]) protected async post(req: Request, response: Response) { try { //init Subscription resource with request body values From 1e902fdb25b91af40b7012e2dece26d8f014c903 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 17:59:56 +0200 Subject: [PATCH 08/86] Removed getOrCreate user + Add feature first to the seat --- package.json | 2 +- src/app/api/admin/StripeController.ts | 6 +- src/app/api/idnot/OfficeController.ts | 12 +--- src/app/api/idnot/UserController.ts | 50 +++++++++++++- src/common/repositories/SeatsRepository.ts | 67 +++++++++++++++++++ src/common/repositories/UsersRepository.ts | 39 ----------- .../admin/SeatsService/SeatsService.ts | 37 ++++++++++ .../SubscriptionsService.ts.ts | 1 - .../super-admin/UsersService/UsersService.ts | 41 +----------- .../services/super-admin/UsersService.test.ts | 3 +- 10 files changed, 160 insertions(+), 98 deletions(-) create mode 100644 src/common/repositories/SeatsRepository.ts create mode 100644 src/services/admin/SeatsService/SeatsService.ts diff --git a/package.json b/package.json index c1e7881f..97210854 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v1.125", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.126", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index c873dca6..ba8a87b2 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,5 +1,5 @@ -import authHandler from "@App/middlewares/AuthHandler"; -import roleHandler from "@App/middlewares/RolesHandler"; +// import authHandler from "@App/middlewares/AuthHandler"; +// import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; @@ -18,7 +18,7 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe", [authHandler, roleHandler]) + @Post("/api/v1/admin/stripe") protected async post(req: Request, response: Response) { try { //init Subscription resource with request body values diff --git a/src/app/api/idnot/OfficeController.ts b/src/app/api/idnot/OfficeController.ts index bbbc19d2..32afb042 100644 --- a/src/app/api/idnot/OfficeController.ts +++ b/src/app/api/idnot/OfficeController.ts @@ -3,15 +3,13 @@ import { Controller, Get } from "@ControllerPattern/index"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; import IdNotService from "@Services/common/IdNotService/IdNotService"; -import UsersService from "@Services/super-admin/UsersService/UsersService"; -import User from "le-coffre-resources/dist/Notary/User"; import userHandler from "@App/middlewares/OfficeMembershipHandlers/UserHandler"; import authHandler from "@App/middlewares/AuthHandler"; @Controller() @Service() export default class UserController extends ApiController { - constructor (private idNotService: IdNotService, private userService: UsersService) { + constructor (private idNotService: IdNotService) { super(); } @@ -26,13 +24,7 @@ export default class UserController extends ApiController { const officeMemberships = await this.idNotService.getOfficeMemberships(uid); - await this.userService.getOrCreateUsers(uid, officeMemberships.result); - - const usersEntities = await this.userService.get({ where: { office_uid: uid }, include: { contact: true}}); - - const users = User.hydrateArray(usersEntities, { strategy: "excludeAll" }); - - this.httpSuccess(response, users); + this.httpSuccess(response, officeMemberships); } catch (error) { console.log(error); this.httpInternalError(response); diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 01850231..6a4341ac 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -8,11 +8,14 @@ import IdNotService from "@Services/common/IdNotService/IdNotService"; import WhitelistService from "@Services/common/WhitelistService/WhitelistService"; import User from "le-coffre-resources/dist/SuperAdmin"; import UsersService from "@Services/super-admin/UsersService/UsersService"; +import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; +import { ESubscriptionStatus } from "@prisma/client"; +import SeatsService from "@Services/admin/SeatsService/SeatsService"; @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService) { + constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { super(); } @@ -61,8 +64,51 @@ export default class UserController extends ApiController { return; } + let isSubscribed = false; + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + + if(!subscriptions || subscriptions.length === 0) { + this.httpUnauthorized(response, "No subscription found"); + return; + } + + if(subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { + this.httpUnauthorized(response, "Subscription inactive"); + return; + } + + const hasSeat = await this.subscriptionsService.get({ where: {status: ESubscriptionStatus.ACTIVE, seats: {some : {user_uid : userHydrated.uid }} } }); + + if (hasSeat && hasSeat.length > 0) { + isSubscribed = true; + } + else { + const nbMaxSeats = subscriptions[0]!.nb_seats; + + const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid }}); + + //if nbMaxSeats < nbCurrentSeats, create a new seat for the user + if (nbMaxSeats > nbCurrentSeats.length) { + const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); + if (seatAdded) { + isSubscribed = true; + } + } + else{ + this.httpUnauthorized(response, "No seat available"); + return; + } + } + + if(!isSubscribed) { + this.httpUnauthorized(response, "User not subscribed"); + return; + } + + //Check if user is whitelisted - const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); + const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); + //When we'll switch to idNotId whitelisting // const isWhitelisted = await this.userWhitelistService.getByIdNotId(user.idNot); diff --git a/src/common/repositories/SeatsRepository.ts b/src/common/repositories/SeatsRepository.ts new file mode 100644 index 00000000..3806fda2 --- /dev/null +++ b/src/common/repositories/SeatsRepository.ts @@ -0,0 +1,67 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Prisma, Seats } from "@prisma/client"; + +@Service() +export default class SeatsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().seats; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many subscriptions + */ + public async findMany(query: Prisma.SeatsFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + if (!query.include) return this.model.findMany({ ...query }); + return this.model.findMany({ ...query }); + } + + /** + * @description : find unique subscription + */ + public async findOneByUid(uid: string, query?: Prisma.SeatsInclude): Promise { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: query, + }); + } + + /** + * @description : Create a subscription + */ + public async create(userUid: string, subscriptionUid: string): Promise { + + const createArgs: Prisma.SeatsCreateArgs = { + data: { + subscription: { + connect: { + uid: subscriptionUid + }, + }, + user: { + connect: { + uid: userUid + }, + }, + }, + }; + { + + return this.model.create(createArgs); + } + + + } + + +} diff --git a/src/common/repositories/UsersRepository.ts b/src/common/repositories/UsersRepository.ts index 538c601d..c0f466e6 100644 --- a/src/common/repositories/UsersRepository.ts +++ b/src/common/repositories/UsersRepository.ts @@ -143,45 +143,6 @@ export default class UsersRepository extends BaseRepository { return this.model.update({ ...updateArgs, include: { contact: true, office_membership: { include: { address: true } } } }); } - public async getOrCreate(usersToAdd: User[]) { - - let users: User[] = []; - - await Promise.all(usersToAdd.map(async (user) => { - const upsertUser = await this.model.upsert({ - where: { - idNot: user.idNot, - }, - update: {}, - create: { - idNot: user.idNot, - office_membership: { - connect: { - uid: user.office_membership!.uid, - } - }, - contact: { - create: { - first_name: user.contact!.first_name, - last_name: user.contact!.last_name, - email: user.contact!.email, - civility: ECivility[user.contact?.civility as keyof typeof ECivility], - }, - }, - role: { - connect: { - uid: user.role!.uid, - }, - }, - }, - }); - users.push(upsertUser); - })); - - return users; - - } - /** * @description : Update check date of a user */ diff --git a/src/services/admin/SeatsService/SeatsService.ts b/src/services/admin/SeatsService/SeatsService.ts new file mode 100644 index 00000000..77af21a0 --- /dev/null +++ b/src/services/admin/SeatsService/SeatsService.ts @@ -0,0 +1,37 @@ +import BaseService from "@Services/BaseService"; +import "reflect-metadata"; +import { Service } from "typedi"; +import { Prisma, Seats } from "@prisma/client"; +import SeatsRepository from "@Repositories/SeatsRepository"; + +@Service() +export default class SeatsService extends BaseService { + constructor(private seatsRepository: SeatsRepository) { + super(); + } + + /** + * @description : Get all seats + * @throws {Error} If seats cannot be get + */ + public get(query: Prisma.SeatsFindManyArgs) { + return this.seatsRepository.findMany(query); + } + + /** + * @description : Get a seat by uid + * @throws {Error} If seat is not found + */ + public async getByUid(uid: string, query?: Prisma.SeatsInclude) { + return this.seatsRepository.findOneByUid(uid, query); + } + + /** + * @description : Create a new seat + * @throws {Error} If seat cannot be created + */ + public async create(subscriptionUid: string, userUid: string): Promise { + return this.seatsRepository.create(subscriptionUid, userUid); + } + +} diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts index 11c85146..71e31fd9 100644 --- a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -42,5 +42,4 @@ export default class SubscriptionsService extends BaseService { public async update(uid: string, subscriptionEntity: Subscription): Promise { return this.subscriptionsRepository.update(uid, subscriptionEntity); } - } diff --git a/src/services/super-admin/UsersService/UsersService.ts b/src/services/super-admin/UsersService/UsersService.ts index 77dea910..0781623c 100644 --- a/src/services/super-admin/UsersService/UsersService.ts +++ b/src/services/super-admin/UsersService/UsersService.ts @@ -4,11 +4,10 @@ import { Service } from "typedi"; import UsersRepository from "@Repositories/UsersRepository"; import User from "le-coffre-resources/dist/Admin"; import { Prisma, Users } from "@prisma/client"; -import RolesService from "@Services/admin/RolesService/RolesService"; @Service() export default class UsersService extends BaseService { - constructor(private userRepository: UsersRepository, private rolesService: RolesService) { + constructor(private userRepository: UsersRepository) { super(); } @@ -92,43 +91,5 @@ export default class UsersService extends BaseService { return this.userRepository.findManyToCheck(); } - public async getOrCreateUsers(officeId: string, usersToCreate: [{}]) { - let users : User[] = []; - const roleNotary = await this.rolesService.get({ where: { name: "notary" } }); - - usersToCreate.forEach((user: any) => { - const userEntity: User = { - idNot: user.uid, - contact: { - first_name: user.prenom, - last_name: user.nomUsuel, - civility: user.civilite, - email: "", - created_at: new Date(), - updated_at: new Date(), - }, - office_membership: { - uid: officeId, - name: "", - crpcen: "", - created_at: new Date(), - updated_at: new Date(), - }, - role: { - uid: roleNotary[0]?.uid, - name: "", - label: "", - created_at: new Date(), - updated_at: new Date(), - }, - created_at: new Date(), - updated_at: new Date(), - }; - users.push(userEntity); - }); - - return await this.userRepository.getOrCreate(users); - } - } diff --git a/src/test/services/super-admin/UsersService.test.ts b/src/test/services/super-admin/UsersService.test.ts index 6d1f7910..5b7d6708 100644 --- a/src/test/services/super-admin/UsersService.test.ts +++ b/src/test/services/super-admin/UsersService.test.ts @@ -6,11 +6,10 @@ import { PrismaClient } from "@prisma/client"; import { user, userContact, userContact_, user_ } from "@Test/config/MockedData"; import UsersRepository from "@Repositories/UsersRepository"; import Container from "typedi"; -import RolesService from "@Services/admin/RolesService/RolesService"; const prisma = new PrismaClient(); -const UsersServiceTest = new UsersService(Container.get(UsersRepository), Container.get(RolesService)); +const UsersServiceTest = new UsersService(Container.get(UsersRepository)); afterAll(async () => { /* From 3820c24549e7881a0b2a6a14d464da4f137e0d14 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 18:05:05 +0200 Subject: [PATCH 09/86] Removed subscription requirements for dev enf --- src/app/api/idnot/UserController.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 6a4341ac..b2063e72 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -11,11 +11,12 @@ import UsersService from "@Services/super-admin/UsersService/UsersService"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; import { ESubscriptionStatus } from "@prisma/client"; import SeatsService from "@Services/admin/SeatsService/SeatsService"; +import { BackendVariables } from "@Common/config/variables/Variables"; @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { + constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService, private backendVariables: BackendVariables) { super(); } @@ -68,12 +69,12 @@ export default class UserController extends ApiController { const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); if(!subscriptions || subscriptions.length === 0) { - this.httpUnauthorized(response, "No subscription found"); + isSubscribed = false; return; } if(subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { - this.httpUnauthorized(response, "Subscription inactive"); + isSubscribed = false; return; } @@ -95,12 +96,12 @@ export default class UserController extends ApiController { } } else{ - this.httpUnauthorized(response, "No seat available"); + isSubscribed = false; return; } } - if(!isSubscribed) { + if(this.backendVariables.ENV !== 'dev' && !isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); return; } From daa79e5cb646e867749fa9ff2ce31c0c7fb1e018 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Thu, 4 Apr 2024 10:10:24 +0200 Subject: [PATCH 10/86] switching ppd ingress --- devops/ppd.values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devops/ppd.values.yaml b/devops/ppd.values.yaml index 855ab153..c196ad19 100644 --- a/devops/ppd.values.yaml +++ b/devops/ppd.values.yaml @@ -1,4 +1,4 @@ -dockerPullSecret: docker-pull-secret +-tpdockerPullSecret: docker-pull-secret scwSecretKey: AgChoEnPitXp4Ny/rVMEcevaWKNVpyj2cJYAcq+yFqKwVwnLB+ffDvwqz9XBHu+6d4Nyyjkf37zUAMoaM21lEDWA7x3zfG2/D/j+rvX1qxzZgLD0mjBk7fGElVm332I6JA83oInes8AMMYEDPLElzHnpKRb9KtkIP4NzgOcCeW0ijft3N7Vroez6LEHsBPCA1I9XjKSkGEDvrO0MhWX3iJOlfz+SPMfJAV7rPawOs0ZmohTHrPW8qIvGDn8HCzKyU8zRBoMt+Ogpf5pH4U3JryEFuqD61KAQgablAM8edPIvsgNno9HAEuC2QtRLYA9aUhuKdaKuS58c9P2E80PHWXIlbpFCg6EugQTgNfnYp+3qDUNz8edeCfapYLvF4s9eCMGyMsGnpDR8EDNOyuGy7Y3l7okX8Xqu464gMp9E+hX7bHkcD6a4xfyIgJcWxsku0tm1TH1dpn4M1UXRuyZZif8P08nuE6MTUL67sAR9J1lpn4lVEL4kflk0pP2tZ5ncgPQFafJrRz05krMb0eU5tb2H4gs7ao/LL6idWo8MM9K1yr8lIuT5x2WW5CX+RjA+i50ex114V6vX3PNP5oVyt+DynTUB9QmXzVm3oLfDc3Cae1uqh7X0CFd+xiztJBtg0VtJaD/xUJcuWfY4cV2lERo9fRrykltzlJqiXHO4nowt8OtN0BcViVV8NJhPhYFzyb4ympxpOlTjm3GETuT2TYhUqdgS9nzleEAbOmOHZdIO2COunPE= @@ -18,7 +18,7 @@ lecoffreBack: limits: memory: 2Gi ingress: - host: api.ppd.lecoffre.smart-chain.fr + host: api-tp.ppd.lecoffre.smart-chain.fr tls: hosts: - api.ppd.lecoffre.smart-chain.fr From 6ca4f04bf21b85eb11c35f4aff0176b553207a15 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 10:30:32 +0200 Subject: [PATCH 11/86] Subscription --- src/app/api/admin/StripeController.ts | 17 +++++---- src/app/api/idnot/UserController.ts | 52 +++++++++++++-------------- src/common/webhooks/stripeWebhooks.ts | 2 ++ 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index ba8a87b2..ae4c211d 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,4 +1,4 @@ -// import authHandler from "@App/middlewares/AuthHandler"; +import authHandler from "@App/middlewares/AuthHandler"; // import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; @@ -18,15 +18,20 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe") + @Post("/api/v1/admin/stripe", [authHandler]) protected async post(req: Request, response: Response) { - try { + try { + const officeId: string = req.body.user.office_Id; + + //add office id to request body + req.body.office = {uid: officeId}; + //init Subscription resource with request body values - const subscriptionEntity = Subscription.hydrate(req.body); + const subscriptionEntity = Subscription.hydrate(req.body, { strategy: "excludeAll" }); - await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); - const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); + const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); this.httpCreated(response, stripeSession); } catch (error) { diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index b2063e72..fcf4b7b3 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -6,17 +6,17 @@ import AuthService, { IUserJwtPayload } from "@Services/common/AuthService/AuthS import IdNotService from "@Services/common/IdNotService/IdNotService"; import WhitelistService from "@Services/common/WhitelistService/WhitelistService"; -import User from "le-coffre-resources/dist/SuperAdmin"; +import User from "le-coffre-resources/dist/Admin"; import UsersService from "@Services/super-admin/UsersService/UsersService"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; import { ESubscriptionStatus } from "@prisma/client"; import SeatsService from "@Services/admin/SeatsService/SeatsService"; -import { BackendVariables } from "@Common/config/variables/Variables"; +import { EType } from "le-coffre-resources/dist/Admin/Subscription"; @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService, private backendVariables: BackendVariables) { + constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { super(); } @@ -68,45 +68,41 @@ export default class UserController extends ApiController { let isSubscribed = false; const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); - if(!subscriptions || subscriptions.length === 0) { + if(!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { + this.httpUnauthorized(response, "User not subscribed"); isSubscribed = false; return; } - if(subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { - isSubscribed = false; - return; - } - - const hasSeat = await this.subscriptionsService.get({ where: {status: ESubscriptionStatus.ACTIVE, seats: {some : {user_uid : userHydrated.uid }} } }); - - if (hasSeat && hasSeat.length > 0) { + if(subscriptions[0]?.type === EType.Unlimited) { isSubscribed = true; } - else { - const nbMaxSeats = subscriptions[0]!.nb_seats; - - const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid }}); - - //if nbMaxSeats < nbCurrentSeats, create a new seat for the user - if (nbMaxSeats > nbCurrentSeats.length) { - const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); - if (seatAdded) { - isSubscribed = true; - } + else{ + const hasSeat = await this.subscriptionsService.get({ where: {status: ESubscriptionStatus.ACTIVE, seats: {some : {user_uid : userHydrated.uid }} } }); + + if (hasSeat && hasSeat.length > 0) { + isSubscribed = true; } - else{ - isSubscribed = false; - return; + else { + const nbMaxSeats = subscriptions[0]!.nb_seats; + + const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid }}); + + //if nbMaxSeats < nbCurrentSeats, create a new seat for the user + if (nbMaxSeats > nbCurrentSeats.length) { + const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); + if (seatAdded) { + isSubscribed = true; + } + } } } - if(this.backendVariables.ENV !== 'dev' && !isSubscribed) { + if(!isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); return; } - //Check if user is whitelisted const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); diff --git a/src/common/webhooks/stripeWebhooks.ts b/src/common/webhooks/stripeWebhooks.ts index 9463eadc..03ba1363 100644 --- a/src/common/webhooks/stripeWebhooks.ts +++ b/src/common/webhooks/stripeWebhooks.ts @@ -34,6 +34,8 @@ export default class StripeWebhooks extends ApiController { switch (event.type) { case "checkout.session.completed": if (event.data.object.status !== "complete") break; + console.log(event.data.object); + const subscription = JSON.parse(event.data.object.metadata.subscription); subscription.stripe_subscription_id = event.data.object.subscription; From 14e390a44a660950a6b4382bb677f5dda4de7928 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 13 Mar 2024 10:54:25 +0100 Subject: [PATCH 12/86] Finish --- src/app/api/idnot/UserController.ts | 6 ++- .../migration.sql | 16 ++++++++ src/common/databases/schema.prisma | 9 +++++ .../repositories/UserWhitelistRepository.ts | 39 +++++++++++++++++++ .../UserWhitelistService/WhitelistService.ts | 14 +++++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/common/databases/migrations/20240313090933_whitelist_by_idnot_id/migration.sql create mode 100644 src/common/repositories/UserWhitelistRepository.ts create mode 100644 src/services/common/UserWhitelistService/WhitelistService.ts diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 0521a747..01850231 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -38,7 +38,7 @@ export default class UserController extends ApiController { const user = await this.idNotService.getOrCreateUser(idNotToken); if(!user) { - this.httpUnauthorized(response, "Email not found"); + this.httpUnauthorized(response, "User not found"); return; } @@ -62,8 +62,10 @@ export default class UserController extends ApiController { } //Check if user is whitelisted + const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); - const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); + //When we'll switch to idNotId whitelisting + // const isWhitelisted = await this.userWhitelistService.getByIdNotId(user.idNot); //If not whitelisted, return 409 Not whitelisted if (!isWhitelisted || isWhitelisted.length === 0) { diff --git a/src/common/databases/migrations/20240313090933_whitelist_by_idnot_id/migration.sql b/src/common/databases/migrations/20240313090933_whitelist_by_idnot_id/migration.sql new file mode 100644 index 00000000..5581a06e --- /dev/null +++ b/src/common/databases/migrations/20240313090933_whitelist_by_idnot_id/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "user_whitelist" ( + "uid" TEXT NOT NULL, + "idNot" VARCHAR(255) NOT NULL, + "active" BOOLEAN NOT NULL DEFAULT true, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "user_whitelist_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "user_whitelist_uid_key" ON "user_whitelist"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_whitelist_idNot_key" ON "user_whitelist"("idNot"); diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 53858e86..4aad566c 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -81,6 +81,15 @@ model Whitelist { @@map("whitelist") } +model UserWhitelist { + uid String @id @unique @default(uuid()) + idNot String @unique @db.VarChar(255) + active Boolean @default(true) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + @@map("user_whitelist") +} + model Offices { uid String @id @unique @default(uuid()) idNot String @unique @db.VarChar(255) diff --git a/src/common/repositories/UserWhitelistRepository.ts b/src/common/repositories/UserWhitelistRepository.ts new file mode 100644 index 00000000..eae35819 --- /dev/null +++ b/src/common/repositories/UserWhitelistRepository.ts @@ -0,0 +1,39 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Prisma } from "prisma/prisma-client"; + +@Service() +export default class UserWhitelistRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().userWhitelist; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many whitelist + */ + public async findMany(query: Prisma.UserWhitelistFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + return this.model.findMany(query); + } + + /** + * @description : find unique by email + */ + public async findOneByIdNotId(idNotId: string) { + return this.model.findMany({ + where: { + idNot: idNotId, + }, + }); + } + + + +} diff --git a/src/services/common/UserWhitelistService/WhitelistService.ts b/src/services/common/UserWhitelistService/WhitelistService.ts new file mode 100644 index 00000000..dd96268f --- /dev/null +++ b/src/services/common/UserWhitelistService/WhitelistService.ts @@ -0,0 +1,14 @@ +import UserWhitelistRepository from "@Repositories/UserWhitelistRepository"; +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; + +@Service() +export default class UserWhitelistService extends BaseService { + constructor(private userWhitelistRepository: UserWhitelistRepository) { + super(); + } + + public async getByIdNotId(idNotId: string): Promise { + return this.userWhitelistRepository.findOneByIdNotId(idNotId); + } +} From d8ea47b4778e7713ee0951ea19e99c80728ee620 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 27 Mar 2024 09:31:48 +0100 Subject: [PATCH 13/86] WIP --- src/app/api/admin/SubscriptionsController.ts | 47 +++++++++++++++++++ .../migration.sql | 34 ++++++++++++++ src/common/databases/schema.prisma | 22 +++++++++ .../repositories/SubscriptionsRepository.ts | 28 +++++++++++ .../SubscriptionsService.ts.ts | 22 +++++++++ 5 files changed, 153 insertions(+) create mode 100644 src/app/api/admin/SubscriptionsController.ts create mode 100644 src/common/databases/migrations/20240325150228_subscriptions/migration.sql create mode 100644 src/common/repositories/SubscriptionsRepository.ts create mode 100644 src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts new file mode 100644 index 00000000..c3a674b8 --- /dev/null +++ b/src/app/api/admin/SubscriptionsController.ts @@ -0,0 +1,47 @@ +import { Controller, Get } from "@ControllerPattern/index"; +import { Response, Request } from "express"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Service } from "typedi"; +import authHandler from "@App/middlewares/AuthHandler"; +import roleHandler from "@App/middlewares/RolesHandler"; +import ruleHandler from "@App/middlewares/RulesHandler"; +import { Prisma } from "@prisma/client"; +import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; + +@Controller() +@Service() +export default class SubscriptionsController extends ApiController { + constructor(private subscriptionsService: SubscriptionsService) { + super(); + } + + /** + * @description Get all subscriptions + */ + @Get("/api/v1/admin/subscriptions", [authHandler, roleHandler, ruleHandler]) + protected async get(req: Request, response: Response) { + try { + //get query + let query: Prisma.SubscriptionsFindManyArgs = {}; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + if (query.where?.uid) { + this.httpBadRequest(response, "You can't filter by uid"); + return; + } + } + + //call service to get prisma entity + const subscriptionsEntities = await this.subscriptionsService.get(query); + + //Hydrate ressource with prisma entity + // const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/common/databases/migrations/20240325150228_subscriptions/migration.sql b/src/common/databases/migrations/20240325150228_subscriptions/migration.sql new file mode 100644 index 00000000..8319eafb --- /dev/null +++ b/src/common/databases/migrations/20240325150228_subscriptions/migration.sql @@ -0,0 +1,34 @@ +-- CreateTable +CREATE TABLE "subscriptions" ( + "uid" TEXT NOT NULL, + "start_date" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "end_date" TIMESTAMP(3), + "nb_seats" INTEGER NOT NULL, + "office_uid" VARCHAR(255) NOT NULL, + + CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "seats" ( + "uid" TEXT NOT NULL, + "subscription_uid" VARCHAR(255) NOT NULL, + "user_uid" VARCHAR(255) NOT NULL, + + CONSTRAINT "seats_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "subscriptions_uid_key" ON "subscriptions"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "seats_uid_key" ON "seats"("uid"); + +-- AddForeignKey +ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_office_uid_fkey" FOREIGN KEY ("office_uid") REFERENCES "offices"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "seats" ADD CONSTRAINT "seats_subscription_uid_fkey" FOREIGN KEY ("subscription_uid") REFERENCES "subscriptions"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "seats" ADD CONSTRAINT "seats_user_uid_fkey" FOREIGN KEY ("user_uid") REFERENCES "users"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 4aad566c..170a228d 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -68,6 +68,7 @@ model Users { appointment Appointments[] votes Votes[] user_notifications UserNotifications[] + seats Seats[] @@map("users") } @@ -108,6 +109,7 @@ model Offices { office_folders OfficeFolders[] document_types DocumentTypes[] office_roles OfficeRoles[] + subscriptions Subscriptions[] @@map("offices") } @@ -375,6 +377,26 @@ model TotpCodes { @@map("totp_codes") } +model Subscriptions { + uid String @id @unique @default(uuid()) + start_date DateTime? @default(now()) + end_date DateTime? + nb_seats Int + office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) + office_uid String @db.VarChar(255) + seats Seats[] + @@map("subscriptions") +} + +model Seats { + uid String @id @unique @default(uuid()) + subscription Subscriptions @relation(fields: [subscription_uid], references: [uid], onDelete: Cascade) + subscription_uid String @db.VarChar(255) + user Users @relation(fields: [user_uid], references: [uid], onDelete: Cascade) + user_uid String @db.VarChar(255) + @@map("seats") +} + enum TotpCodesReasons { LOGIN RESET_PASSWORD diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts new file mode 100644 index 00000000..7d921c51 --- /dev/null +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -0,0 +1,28 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Prisma } from "@prisma/client"; + +@Service() +export default class SubscriptionsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().subscriptions; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many totp codes + */ + public async findMany(query: Prisma.SubscriptionsFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + if (!query.include) return this.model.findMany({ ...query }); + return this.model.findMany({ ...query }); + } + + +} diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts new file mode 100644 index 00000000..a884dc54 --- /dev/null +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -0,0 +1,22 @@ +import BaseService from "@Services/BaseService"; +import "reflect-metadata"; +import { Service } from "typedi"; +import { Prisma } from "@prisma/client"; +import SubscriptionsRepository from "@Repositories/SubscriptionsRepository"; + +@Service() +export default class SubscriptionsService extends BaseService { + constructor(private subscriptionsRepository: SubscriptionsRepository) { + super(); + } + + /** + * @description : Get all subscriptions + * @throws {Error} If subscriptions cannot be get + */ + public get(query: Prisma.SubscriptionsFindManyArgs) { + return this.subscriptionsRepository.findMany(query); + } + + +} From 7cc68600aa592047c6b63fd29bb5e7b8284b6b21 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 28 Mar 2024 15:26:48 +0100 Subject: [PATCH 14/86] Entrypoint done --- package.json | 2 +- src/app/api/admin/SubscriptionsController.ts | 111 +++++++++++++++-- src/app/index.ts | 4 +- .../migration.sql | 12 ++ .../migration.sql | 11 ++ src/common/databases/schema.prisma | 11 +- .../repositories/SubscriptionsRepository.ts | 117 +++++++++++++++++- .../SubscriptionsService.ts.ts | 26 +++- 8 files changed, 280 insertions(+), 14 deletions(-) create mode 100644 src/common/databases/migrations/20240327160202_edit_subscriptions/migration.sql create mode 100644 src/common/databases/migrations/20240328104442_subscription_type/migration.sql diff --git a/package.json b/package.json index a6f832c1..f807f38f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.119", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.123", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index c3a674b8..20baac59 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -1,12 +1,15 @@ -import { Controller, Get } from "@ControllerPattern/index"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; import { Response, Request } from "express"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; -import authHandler from "@App/middlewares/AuthHandler"; -import roleHandler from "@App/middlewares/RolesHandler"; -import ruleHandler from "@App/middlewares/RulesHandler"; +// import authHandler from "@App/middlewares/AuthHandler"; +// import roleHandler from "@App/middlewares/RolesHandler"; +// import ruleHandler from "@App/middlewares/RulesHandler"; import { Prisma } from "@prisma/client"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; +import { Subscription } from "le-coffre-resources/dist/Admin"; +import { validateOrReject } from "class-validator"; +import ObjectHydrate from "@Common/helpers/ObjectHydrate"; @Controller() @Service() @@ -18,7 +21,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Get all subscriptions */ - @Get("/api/v1/admin/subscriptions", [authHandler, roleHandler, ruleHandler]) + @Get("/api/v1/admin/subscriptions") protected async get(req: Request, response: Response) { try { //get query @@ -35,10 +38,104 @@ export default class SubscriptionsController extends ApiController { const subscriptionsEntities = await this.subscriptionsService.get(query); //Hydrate ressource with prisma entity - // const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); + const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); //success - this.httpSuccess(response); + this.httpSuccess(response, subscriptions); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Get a specific documentType by uid + */ + @Get("/api/v1/admin/subscriptions/:uid") + protected async getOneByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + //get query + let query; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + } + + const subscriptionEntity = await this.subscriptionsService.getByUid(uid, query); + + //Hydrate resource with prisma entity + const subscription = ObjectHydrate.hydrate(new Subscription(), subscriptionEntity!, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, subscription); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Create a new documentType + */ + @Post("/api/v1/admin/subscriptions") + protected async post(req: Request, response: Response) { + try { + //init Subscription resource with request body values + const subscriptionEntity = Subscription.hydrate(req.body); + //validate user + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + //call service to get prisma entity + const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity); + //Hydrate ressource with prisma entity + const subscription = Subscription.hydrate(subscriptionEntityCreated, { + strategy: "excludeAll", + }); + //success + this.httpCreated(response, subscription); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + /** + * @description Update a subscription + */ + @Put("/api/v1/admin/subscriptions/:uid") + protected async put(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const subscriptionFound = await this.subscriptionsService.getByUid(uid); + + if (!subscriptionFound) { + this.httpNotFoundRequest(response, "subscription not found"); + return; + } + + //init Subscription resource with request body values + const subscriptionEntity = Subscription.hydrate(req.body); + + + //call service to get prisma entity + const subscriptionEntityUpdated = await this.subscriptionsService.update(uid, subscriptionEntity); + + //Hydrate ressource with prisma entity + const subscription = Subscription.hydrate(subscriptionEntityUpdated, { + strategy: "excludeAll", + }); + + //success + this.httpSuccess(response, subscription); + } catch (error) { this.httpInternalError(response, error); return; diff --git a/src/app/index.ts b/src/app/index.ts index 20343be2..1d9b1acc 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -49,6 +49,7 @@ import AuthController from "./api/customer/AuthController"; import NotaryOfficeRibController from "./api/notary/OfficeRibController"; import CustomerOfficeRibController from "./api/customer/OfficeRibController"; import IdNotOfficeController from "./api/idnot/OfficeController"; +import SubscriptionsController from "./api/admin/SubscriptionsController"; /** * @description This allow to declare all controllers used in the application */ @@ -104,6 +105,7 @@ export default { Container.get(AuthController); Container.get(NotaryOfficeRibController); Container.get(CustomerOfficeRibController); - Container.get(IdNotOfficeController) + Container.get(IdNotOfficeController); + Container.get(SubscriptionsController); }, }; diff --git a/src/common/databases/migrations/20240327160202_edit_subscriptions/migration.sql b/src/common/databases/migrations/20240327160202_edit_subscriptions/migration.sql new file mode 100644 index 00000000..f460f2f5 --- /dev/null +++ b/src/common/databases/migrations/20240327160202_edit_subscriptions/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - Added the required column `priceId` to the `subscriptions` table without a default value. This is not possible if the table is not empty. + - Made the column `start_date` on table `subscriptions` required. This step will fail if there are existing NULL values in that column. + - Made the column `end_date` on table `subscriptions` required. This step will fail if there are existing NULL values in that column. + +*/ +-- AlterTable +ALTER TABLE "subscriptions" ADD COLUMN "priceId" VARCHAR(255) NOT NULL, +ALTER COLUMN "start_date" SET NOT NULL, +ALTER COLUMN "end_date" SET NOT NULL; diff --git a/src/common/databases/migrations/20240328104442_subscription_type/migration.sql b/src/common/databases/migrations/20240328104442_subscription_type/migration.sql new file mode 100644 index 00000000..6931a9db --- /dev/null +++ b/src/common/databases/migrations/20240328104442_subscription_type/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - Added the required column `type` to the `subscriptions` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "ESubscriptionType" AS ENUM ('STANDARD', 'UNLIMITED'); + +-- AlterTable +ALTER TABLE "subscriptions" ADD COLUMN "type" "ESubscriptionType" NOT NULL; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 170a228d..d6f48d96 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -379,8 +379,10 @@ model TotpCodes { model Subscriptions { uid String @id @unique @default(uuid()) - start_date DateTime? @default(now()) - end_date DateTime? + type ESubscriptionType + priceId String @db.VarChar(255) + start_date DateTime @default(now()) + end_date DateTime nb_seats Int office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) office_uid String @db.VarChar(255) @@ -397,6 +399,11 @@ model Seats { @@map("seats") } +enum ESubscriptionType { + STANDARD + UNLIMITED +} + enum TotpCodesReasons { LOGIN RESET_PASSWORD diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts index 7d921c51..1078deff 100644 --- a/src/common/repositories/SubscriptionsRepository.ts +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -1,7 +1,8 @@ import Database from "@Common/databases/database"; import BaseRepository from "@Repositories/BaseRepository"; import { Service } from "typedi"; -import { Prisma } from "@prisma/client"; +import { ESubscriptionType, Prisma, Subscriptions } from "@prisma/client"; +import { Subscription } from "le-coffre-resources/dist/Admin"; @Service() export default class SubscriptionsRepository extends BaseRepository { @@ -16,7 +17,7 @@ export default class SubscriptionsRepository extends BaseRepository { } /** - * @description : Find many totp codes + * @description : Find many subscriptions */ public async findMany(query: Prisma.SubscriptionsFindManyArgs) { query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); @@ -24,5 +25,117 @@ export default class SubscriptionsRepository extends BaseRepository { return this.model.findMany({ ...query }); } + /** + * @description : find unique subscription + */ + public async findOneByUid(uid: string, query?: Prisma.SubscriptionsInclude): Promise { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: query, + }); + } + + /** + * @description : Create a subscription + */ + public async create(subscription: Subscription): Promise { + if(subscription.type === "STANDARD") + { + const createArgs: Prisma.SubscriptionsCreateArgs = { + data: { + start_date: subscription.start_date, + end_date: subscription.end_date, + type: ESubscriptionType.STANDARD, + nb_seats: subscription.nb_seats!, + priceId: subscription.priceId, + office: { + connect: { + uid: subscription.office!.uid, + }, + }, + seats: { + create: subscription.seats!.map(seat => ({ + user: { + connect: { + uid: seat.user!.uid, + }, + }, + })), + }, + }, + }; + return this.model.create(createArgs); + } + else + { + const createArgs: Prisma.SubscriptionsCreateArgs = { + data: { + start_date: subscription.start_date, + end_date: subscription.end_date, + type: ESubscriptionType.UNLIMITED, + nb_seats: 0, + priceId: subscription.priceId, + office: { + connect: { + uid: subscription.office!.uid, + }, + }, + }, + }; + return this.model.create(createArgs); + } + + } + + /** + * @description : update given subscription + */ + public async update(uid: string, subscription: Subscription): Promise { + + if(subscription.type === "STANDARD") + { + const updateArgs: Prisma.SubscriptionsUpdateArgs = { + where: { + uid: uid, + }, + data: { + end_date: subscription.end_date, + type: ESubscriptionType.STANDARD, + nb_seats: subscription.nb_seats!, + seats: { + deleteMany: {}, + create: subscription.seats!.map(seat => ({ + user: { + connect: { + uid: seat.user!.uid, + }, + }, + })), + } + }, + }; + return this.model.update(updateArgs); + } + else + { + const updateArgs: Prisma.SubscriptionsUpdateArgs = { + where: { + uid: uid, + }, + data: { + end_date: subscription.end_date, + type: ESubscriptionType.UNLIMITED, + nb_seats: 0, + seats: { + deleteMany: {}, + }, + }, + }; + return this.model.update(updateArgs); + } + } + } diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts index a884dc54..11c85146 100644 --- a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -1,8 +1,9 @@ import BaseService from "@Services/BaseService"; import "reflect-metadata"; import { Service } from "typedi"; -import { Prisma } from "@prisma/client"; +import { Prisma, Subscriptions } from "@prisma/client"; import SubscriptionsRepository from "@Repositories/SubscriptionsRepository"; +import { Subscription } from "le-coffre-resources/dist/Admin"; @Service() export default class SubscriptionsService extends BaseService { @@ -18,5 +19,28 @@ export default class SubscriptionsService extends BaseService { return this.subscriptionsRepository.findMany(query); } + /** + * @description : Get a subscription by uid + * @throws {Error} If subscription is not found + */ + public async getByUid(uid: string, query?: Prisma.SubscriptionsInclude) { + return this.subscriptionsRepository.findOneByUid(uid, query); + } + + /** + * @description : Create a new subscription + * @throws {Error} If subsctiption cannot be created + */ + public async create(subscriptionEntity: Subscription): Promise { + return this.subscriptionsRepository.create(subscriptionEntity); + } + + /** + * @description : Modify a subscription + * @throws {Error} If subscription cannot be modified + */ + public async update(uid: string, subscriptionEntity: Subscription): Promise { + return this.subscriptionsRepository.update(uid, subscriptionEntity); + } } From 10348884c2596e2942337e9d7a79b1f2bd9b7b30 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 10:35:53 +0200 Subject: [PATCH 15/86] merged dev --- package.json | 3 +- src/app/api/admin/StripeController.ts | 35 ++++++++++ src/app/api/admin/SubscriptionsController.ts | 5 +- src/app/index.ts | 4 ++ src/common/config/variables/Variables.ts | 28 ++++++-- .../migration.sql | 10 +++ .../migration.sql | 5 ++ .../migration.sql | 2 + src/common/databases/schema.prisma | 8 ++- .../repositories/SubscriptionsRepository.ts | 10 ++- src/common/webhooks/stripeWebhooks.ts | 64 +++++++++++++++++++ .../common/StripeService/StripeService.ts | 36 +++++++++++ 12 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 src/app/api/admin/StripeController.ts create mode 100644 src/common/databases/migrations/20240329094447_stripe_subscription_id/migration.sql create mode 100644 src/common/databases/migrations/20240329095902_subscription_status/migration.sql create mode 100644 src/common/databases/migrations/20240329135543_subscription_status_default_active/migration.sql create mode 100644 src/common/webhooks/stripeWebhooks.ts create mode 100644 src/services/common/StripeService/StripeService.ts diff --git a/package.json b/package.json index f807f38f..c1e7881f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.123", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v1.125", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", @@ -70,6 +70,7 @@ "prisma-query": "^2.0.0", "puppeteer": "^21.3.4", "reflect-metadata": "^0.1.13", + "stripe": "^14.22.0", "ts-node": "^10.9.1", "tslib": "^2.4.1", "typedi": "^0.10.0", diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts new file mode 100644 index 00000000..f411bda3 --- /dev/null +++ b/src/app/api/admin/StripeController.ts @@ -0,0 +1,35 @@ +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Controller, Post } from "@ControllerPattern/index"; +import StripeService from "@Services/common/StripeService/StripeService"; +import { validateOrReject } from "class-validator"; +import { Request, Response } from "express"; +import { Subscription } from "le-coffre-resources/dist/Admin"; +import { Service } from "typedi"; + +@Controller() +@Service() +export default class StripeController extends ApiController { + constructor(private stripeService: StripeService) { + super(); + } + + /** + * @description Create a new checkout session + */ + @Post("/api/v1/admin/stripe", []) + protected async post(req: Request, response: Response) { + try { + //init Subscription resource with request body values + const subscriptionEntity = Subscription.hydrate(req.body); + + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + + const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); + + this.httpCreated(response, stripeSession); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} \ No newline at end of file diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index 20baac59..1e3dd0a1 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -86,8 +86,8 @@ export default class SubscriptionsController extends ApiController { try { //init Subscription resource with request body values const subscriptionEntity = Subscription.hydrate(req.body); - //validate user - await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + //validate subscription + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); //call service to get prisma entity const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity); //Hydrate ressource with prisma entity @@ -124,7 +124,6 @@ export default class SubscriptionsController extends ApiController { //init Subscription resource with request body values const subscriptionEntity = Subscription.hydrate(req.body); - //call service to get prisma entity const subscriptionEntityUpdated = await this.subscriptionsService.update(uid, subscriptionEntity); diff --git a/src/app/index.ts b/src/app/index.ts index 1d9b1acc..174b9abd 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -50,6 +50,8 @@ import NotaryOfficeRibController from "./api/notary/OfficeRibController"; import CustomerOfficeRibController from "./api/customer/OfficeRibController"; import IdNotOfficeController from "./api/idnot/OfficeController"; import SubscriptionsController from "./api/admin/SubscriptionsController"; +import StripeController from "./api/admin/StripeController"; +import StripeWebhooks from "@Common/webhooks/stripeWebhooks"; /** * @description This allow to declare all controllers used in the application */ @@ -107,5 +109,7 @@ export default { Container.get(CustomerOfficeRibController); Container.get(IdNotOfficeController); Container.get(SubscriptionsController); + Container.get(StripeController); + Container.get(StripeWebhooks); }, }; diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index 93a426b2..16872c03 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -142,6 +142,21 @@ export class BackendVariables { @IsNotEmpty() public readonly SCW_BUCKET_NAME!: string; + @IsNotEmpty() + public readonly STRIPE_SECRET_KEY!: string; + + @IsNotEmpty() + public readonly STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID!: string; + + @IsNotEmpty() + public readonly STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID!: string; + + @IsNotEmpty() + public readonly STRIPE_PAYMENT_SUCCESS_URL!: string; + + @IsNotEmpty() + public readonly STRIPE_PAYMENT_CANCEL_URL!: string; + public constructor() { dotenv.config(); this.DATABASE_PORT = process.env["DATABASE_PORT"]!; @@ -186,10 +201,15 @@ export class BackendVariables { this.OVH_CONSUMER_KEY = process.env["OVH_CONSUMER_KEY"]!; this.OVH_SMS_SERVICE_NAME = process.env["OVH_SMS_SERVICE_NAME"]!; this.SMS_FACTOR_TOKEN = process.env["SMS_FACTOR_TOKEN"]!; - this.SCW_ACCESS_KEY_ID = process.env["ACCESS_KEY_ID"]!; - this.SCW_ACCESS_KEY_SECRET = process.env["ACCESS_KEY_SECRET"]!; - this.SCW_BUCKET_ENDPOINT = process.env["BUCKET_ENDPOINT"]!; - this.SCW_BUCKET_NAME = process.env["BUCKET_NAME"]!; + this.SCW_ACCESS_KEY_ID = process.env["SCW_ACCESS_KEY_ID"]!; + this.SCW_ACCESS_KEY_SECRET = process.env["SCW_ACCESS_KEY_SECRET"]!; + this.SCW_BUCKET_ENDPOINT = process.env["SCW_BUCKET_ENDPOINT"]!; + this.SCW_BUCKET_NAME = process.env["SCW_BUCKET_NAME"]!; + this.STRIPE_SECRET_KEY = process.env["STRIPE_SECRET_KEY"]!; + this.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID"]!; + this.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID"]!; + this.STRIPE_PAYMENT_SUCCESS_URL = process.env["STRIPE_PAYMENT_SUCCESS_URL"]!; + this.STRIPE_PAYMENT_CANCEL_URL = process.env["STRIPE_PAYMENT_CANCEL_URL"]!; } public async validate(groups?: string[]) { const validationOptions = groups ? { groups } : undefined; diff --git a/src/common/databases/migrations/20240329094447_stripe_subscription_id/migration.sql b/src/common/databases/migrations/20240329094447_stripe_subscription_id/migration.sql new file mode 100644 index 00000000..02e49af0 --- /dev/null +++ b/src/common/databases/migrations/20240329094447_stripe_subscription_id/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `priceId` on the `subscriptions` table. All the data in the column will be lost. + - Added the required column `stripe_subscription_id` to the `subscriptions` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "subscriptions" DROP COLUMN "priceId", +ADD COLUMN "stripe_subscription_id" VARCHAR(255) NOT NULL; diff --git a/src/common/databases/migrations/20240329095902_subscription_status/migration.sql b/src/common/databases/migrations/20240329095902_subscription_status/migration.sql new file mode 100644 index 00000000..8c9d5f83 --- /dev/null +++ b/src/common/databases/migrations/20240329095902_subscription_status/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "ESubscriptionStatus" AS ENUM ('ACTIVE', 'INACTIVE'); + +-- AlterTable +ALTER TABLE "subscriptions" ADD COLUMN "status" "ESubscriptionStatus" NOT NULL DEFAULT 'INACTIVE'; diff --git a/src/common/databases/migrations/20240329135543_subscription_status_default_active/migration.sql b/src/common/databases/migrations/20240329135543_subscription_status_default_active/migration.sql new file mode 100644 index 00000000..e036e137 --- /dev/null +++ b/src/common/databases/migrations/20240329135543_subscription_status_default_active/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "subscriptions" ALTER COLUMN "status" SET DEFAULT 'ACTIVE'; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index d6f48d96..06d8c782 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -380,7 +380,8 @@ model TotpCodes { model Subscriptions { uid String @id @unique @default(uuid()) type ESubscriptionType - priceId String @db.VarChar(255) + status ESubscriptionStatus @default(ACTIVE) + stripe_subscription_id String @db.VarChar(255) start_date DateTime @default(now()) end_date DateTime nb_seats Int @@ -399,6 +400,11 @@ model Seats { @@map("seats") } +enum ESubscriptionStatus { + ACTIVE + INACTIVE +} + enum ESubscriptionType { STANDARD UNLIMITED diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts index 1078deff..a8203886 100644 --- a/src/common/repositories/SubscriptionsRepository.ts +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -1,7 +1,7 @@ import Database from "@Common/databases/database"; import BaseRepository from "@Repositories/BaseRepository"; import { Service } from "typedi"; -import { ESubscriptionType, Prisma, Subscriptions } from "@prisma/client"; +import { ESubscriptionStatus, ESubscriptionType, Prisma, Subscriptions } from "@prisma/client"; import { Subscription } from "le-coffre-resources/dist/Admin"; @Service() @@ -48,8 +48,9 @@ export default class SubscriptionsRepository extends BaseRepository { start_date: subscription.start_date, end_date: subscription.end_date, type: ESubscriptionType.STANDARD, + status: ESubscriptionStatus.ACTIVE, nb_seats: subscription.nb_seats!, - priceId: subscription.priceId, + stripe_subscription_id: subscription.stripe_subscription_id || "", office: { connect: { uid: subscription.office!.uid, @@ -75,8 +76,9 @@ export default class SubscriptionsRepository extends BaseRepository { start_date: subscription.start_date, end_date: subscription.end_date, type: ESubscriptionType.UNLIMITED, + status: ESubscriptionStatus.ACTIVE, nb_seats: 0, - priceId: subscription.priceId, + stripe_subscription_id: subscription.stripe_subscription_id || "", office: { connect: { uid: subscription.office!.uid, @@ -103,6 +105,7 @@ export default class SubscriptionsRepository extends BaseRepository { data: { end_date: subscription.end_date, type: ESubscriptionType.STANDARD, + status: subscription.status as ESubscriptionStatus, nb_seats: subscription.nb_seats!, seats: { deleteMany: {}, @@ -127,6 +130,7 @@ export default class SubscriptionsRepository extends BaseRepository { data: { end_date: subscription.end_date, type: ESubscriptionType.UNLIMITED, + status: subscription.status as ESubscriptionStatus, nb_seats: 0, seats: { deleteMany: {}, diff --git a/src/common/webhooks/stripeWebhooks.ts b/src/common/webhooks/stripeWebhooks.ts new file mode 100644 index 00000000..9463eadc --- /dev/null +++ b/src/common/webhooks/stripeWebhooks.ts @@ -0,0 +1,64 @@ +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Controller, Post } from "@ControllerPattern/index"; +import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; +import StripeService from "@Services/common/StripeService/StripeService"; +import { validateOrReject } from "class-validator"; +import { Request, Response } from "express"; +import { Subscription } from "le-coffre-resources/dist/Admin"; +import { Service } from "typedi"; + +@Controller() +@Service() +export default class StripeWebhooks extends ApiController { + constructor(private stripeService: StripeService, private subscriptionsService: SubscriptionsService) { + super(); + } + + /** + * @description Create a new checkout session + */ + @Post("/api/v1/webhooks/stripe") + protected async post(req: Request, response: Response) { + try { + // const sig = req.headers["stripe-signature"]; + // const endpointSecret = "whsec_c4088876914bc166ff5c39253207f84900820b67f7bba3b2669c0ff392cbc838"; + // const stripe = this.stripeService.getClient(); + // let event: Stripe.Event; + + // if (!sig || !endpointSecret) { + // throw new Error("Signature verification failed"); + // } + // event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); + const event = req.body; + + switch (event.type) { + case "checkout.session.completed": + if (event.data.object.status !== "complete") break; + + const subscription = JSON.parse(event.data.object.metadata.subscription); + subscription.stripe_subscription_id = event.data.object.subscription; + + const subscriptionInfo = await this.stripeService + .getClient() + .subscriptions.retrieve(subscription.stripe_subscription_id); + + subscription.start_date = new Date(subscriptionInfo.current_period_start * 1000); + subscription.end_date = new Date(subscriptionInfo.current_period_end * 1000); + + const subscriptionEntity = Subscription.hydrate(subscription); + + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + + await this.subscriptionsService.create(subscriptionEntity); + break; + default: + break; + } + + response.json({ received: true }); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts new file mode 100644 index 00000000..59c261f5 --- /dev/null +++ b/src/services/common/StripeService/StripeService.ts @@ -0,0 +1,36 @@ +import { BackendVariables } from "@Common/config/variables/Variables"; +import { Subscription } from "le-coffre-resources/dist/Admin"; +import Stripe from "stripe"; +import { Service } from "typedi"; + +@Service() +export default class StripeService { + private client: Stripe; + constructor(protected variables: BackendVariables) { + this.client = new Stripe(variables.STRIPE_SECRET_KEY); + } + + public getClient(): Stripe { + return this.client; + } + + public async createCheckoutSession(subscription: Subscription) { + const priceId = subscription.type === "STANDARD" ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID; + return this.client.checkout.sessions.create({ + mode: "subscription", + payment_method_types: ["card", "paypal"], + billing_address_collection: "auto", + line_items: [ + { + price: priceId, + quantity: subscription.nb_seats, + }, + ], + success_url: this.variables.STRIPE_PAYMENT_SUCCESS_URL, + cancel_url: this.variables.STRIPE_PAYMENT_CANCEL_URL, + metadata: { + subscription: JSON.stringify(subscription), + }, + }); + } +} \ No newline at end of file From 6d547a9931a9b21ff9657848e7b5642d52a23395 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 15:57:11 +0200 Subject: [PATCH 16/86] Get office membership + create user if not exist --- src/app/api/admin/SubscriptionsController.ts | 10 +++-- src/app/api/idnot/OfficeController.ts | 21 ++++++--- src/common/repositories/UsersRepository.ts | 39 ++++++++++++++++ .../super-admin/UsersService/UsersService.ts | 44 ++++++++++++++++++- .../services/super-admin/UsersService.test.ts | 3 +- 5 files changed, 105 insertions(+), 12 deletions(-) diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index 1e3dd0a1..4de51386 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -10,6 +10,8 @@ import SubscriptionsService from "@Services/admin/SubscriptionsService/Subscript import { Subscription } from "le-coffre-resources/dist/Admin"; import { validateOrReject } from "class-validator"; import ObjectHydrate from "@Common/helpers/ObjectHydrate"; +import roleHandler from "@App/middlewares/RolesHandler"; +import authHandler from "@App/middlewares/AuthHandler"; @Controller() @Service() @@ -21,7 +23,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Get all subscriptions */ - @Get("/api/v1/admin/subscriptions") + @Get("/api/v1/admin/subscriptions", [authHandler, roleHandler]) protected async get(req: Request, response: Response) { try { //get query @@ -51,7 +53,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Get a specific documentType by uid */ - @Get("/api/v1/admin/subscriptions/:uid") + @Get("/api/v1/admin/subscriptions/:uid", [authHandler, roleHandler]) protected async getOneByUid(req: Request, response: Response) { try { const uid = req.params["uid"]; @@ -81,7 +83,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Create a new documentType */ - @Post("/api/v1/admin/subscriptions") + @Post("/api/v1/admin/subscriptions", [authHandler, roleHandler]) protected async post(req: Request, response: Response) { try { //init Subscription resource with request body values @@ -105,7 +107,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Update a subscription */ - @Put("/api/v1/admin/subscriptions/:uid") + @Put("/api/v1/admin/subscriptions/:uid", [authHandler, roleHandler]) protected async put(req: Request, response: Response) { try { const uid = req.params["uid"]; diff --git a/src/app/api/idnot/OfficeController.ts b/src/app/api/idnot/OfficeController.ts index 3e00e7bf..bbbc19d2 100644 --- a/src/app/api/idnot/OfficeController.ts +++ b/src/app/api/idnot/OfficeController.ts @@ -3,25 +3,36 @@ import { Controller, Get } from "@ControllerPattern/index"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; import IdNotService from "@Services/common/IdNotService/IdNotService"; +import UsersService from "@Services/super-admin/UsersService/UsersService"; +import User from "le-coffre-resources/dist/Notary/User"; +import userHandler from "@App/middlewares/OfficeMembershipHandlers/UserHandler"; +import authHandler from "@App/middlewares/AuthHandler"; @Controller() @Service() export default class UserController extends ApiController { - constructor (private idNotService: IdNotService) { + constructor (private idNotService: IdNotService, private userService: UsersService) { super(); } - @Get("/api/v1/idnot/office/:uid/office-memberships") + @Get("/api/v1/idnot/office/:uid/office-memberships", [authHandler, userHandler]) protected async getOfficeMemberships(req: Request, response: Response) { - try { + try { const uid = req.params["uid"]; if (!uid) { this.httpBadRequest(response, "uid is required"); return; } - const officeMemberships = await this.idNotService.getOfficeMemberships(uid); - this.httpSuccess(response, officeMemberships); + const officeMemberships = await this.idNotService.getOfficeMemberships(uid); + + await this.userService.getOrCreateUsers(uid, officeMemberships.result); + + const usersEntities = await this.userService.get({ where: { office_uid: uid }, include: { contact: true}}); + + const users = User.hydrateArray(usersEntities, { strategy: "excludeAll" }); + + this.httpSuccess(response, users); } catch (error) { console.log(error); this.httpInternalError(response); diff --git a/src/common/repositories/UsersRepository.ts b/src/common/repositories/UsersRepository.ts index c0f466e6..538c601d 100644 --- a/src/common/repositories/UsersRepository.ts +++ b/src/common/repositories/UsersRepository.ts @@ -143,6 +143,45 @@ export default class UsersRepository extends BaseRepository { return this.model.update({ ...updateArgs, include: { contact: true, office_membership: { include: { address: true } } } }); } + public async getOrCreate(usersToAdd: User[]) { + + let users: User[] = []; + + await Promise.all(usersToAdd.map(async (user) => { + const upsertUser = await this.model.upsert({ + where: { + idNot: user.idNot, + }, + update: {}, + create: { + idNot: user.idNot, + office_membership: { + connect: { + uid: user.office_membership!.uid, + } + }, + contact: { + create: { + first_name: user.contact!.first_name, + last_name: user.contact!.last_name, + email: user.contact!.email, + civility: ECivility[user.contact?.civility as keyof typeof ECivility], + }, + }, + role: { + connect: { + uid: user.role!.uid, + }, + }, + }, + }); + users.push(upsertUser); + })); + + return users; + + } + /** * @description : Update check date of a user */ diff --git a/src/services/super-admin/UsersService/UsersService.ts b/src/services/super-admin/UsersService/UsersService.ts index 3598e945..77dea910 100644 --- a/src/services/super-admin/UsersService/UsersService.ts +++ b/src/services/super-admin/UsersService/UsersService.ts @@ -2,12 +2,13 @@ import BaseService from "@Services/BaseService"; import "reflect-metadata"; import { Service } from "typedi"; import UsersRepository from "@Repositories/UsersRepository"; -import User from "le-coffre-resources/dist/SuperAdmin"; +import User from "le-coffre-resources/dist/Admin"; import { Prisma, Users } from "@prisma/client"; +import RolesService from "@Services/admin/RolesService/RolesService"; @Service() export default class UsersService extends BaseService { - constructor(private userRepository: UsersRepository) { + constructor(private userRepository: UsersRepository, private rolesService: RolesService) { super(); } @@ -91,4 +92,43 @@ export default class UsersService extends BaseService { return this.userRepository.findManyToCheck(); } + public async getOrCreateUsers(officeId: string, usersToCreate: [{}]) { + let users : User[] = []; + const roleNotary = await this.rolesService.get({ where: { name: "notary" } }); + + usersToCreate.forEach((user: any) => { + const userEntity: User = { + idNot: user.uid, + contact: { + first_name: user.prenom, + last_name: user.nomUsuel, + civility: user.civilite, + email: "", + created_at: new Date(), + updated_at: new Date(), + }, + office_membership: { + uid: officeId, + name: "", + crpcen: "", + created_at: new Date(), + updated_at: new Date(), + }, + role: { + uid: roleNotary[0]?.uid, + name: "", + label: "", + created_at: new Date(), + updated_at: new Date(), + }, + created_at: new Date(), + updated_at: new Date(), + }; + users.push(userEntity); + }); + + return await this.userRepository.getOrCreate(users); + } + + } diff --git a/src/test/services/super-admin/UsersService.test.ts b/src/test/services/super-admin/UsersService.test.ts index 5b7d6708..6d1f7910 100644 --- a/src/test/services/super-admin/UsersService.test.ts +++ b/src/test/services/super-admin/UsersService.test.ts @@ -6,10 +6,11 @@ import { PrismaClient } from "@prisma/client"; import { user, userContact, userContact_, user_ } from "@Test/config/MockedData"; import UsersRepository from "@Repositories/UsersRepository"; import Container from "typedi"; +import RolesService from "@Services/admin/RolesService/RolesService"; const prisma = new PrismaClient(); -const UsersServiceTest = new UsersService(Container.get(UsersRepository)); +const UsersServiceTest = new UsersService(Container.get(UsersRepository), Container.get(RolesService)); afterAll(async () => { /* From 8e7db0c17a4a1dc5b0879eb2fa73696f8ccf6ed1 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 16:05:21 +0200 Subject: [PATCH 17/86] Added authHandler to strip entrypoint --- src/app/api/admin/StripeController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index f411bda3..43721201 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,3 +1,4 @@ +import authHandler from "@App/middlewares/AuthHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; @@ -16,7 +17,7 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe", []) + @Post("/api/v1/admin/stripe", [authHandler]) protected async post(req: Request, response: Response) { try { //init Subscription resource with request body values From 06fe8e25fb9b038833730704dbcc841f7d71fdad Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 16:07:55 +0200 Subject: [PATCH 18/86] Some middlewares --- src/app/api/admin/StripeController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index 43721201..c873dca6 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,4 +1,5 @@ import authHandler from "@App/middlewares/AuthHandler"; +import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; @@ -17,7 +18,7 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe", [authHandler]) + @Post("/api/v1/admin/stripe", [authHandler, roleHandler]) protected async post(req: Request, response: Response) { try { //init Subscription resource with request body values From 83c095f153feec55806a221d98f8749ff400c3b4 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 17:59:56 +0200 Subject: [PATCH 19/86] Removed getOrCreate user + Add feature first to the seat --- package.json | 2 +- src/app/api/admin/StripeController.ts | 6 +- src/app/api/idnot/OfficeController.ts | 12 +--- src/app/api/idnot/UserController.ts | 50 +++++++++++++- src/common/repositories/SeatsRepository.ts | 67 +++++++++++++++++++ src/common/repositories/UsersRepository.ts | 39 ----------- .../admin/SeatsService/SeatsService.ts | 37 ++++++++++ .../SubscriptionsService.ts.ts | 1 - .../super-admin/UsersService/UsersService.ts | 41 +----------- .../services/super-admin/UsersService.test.ts | 3 +- 10 files changed, 160 insertions(+), 98 deletions(-) create mode 100644 src/common/repositories/SeatsRepository.ts create mode 100644 src/services/admin/SeatsService/SeatsService.ts diff --git a/package.json b/package.json index c1e7881f..97210854 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v1.125", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.126", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index c873dca6..ba8a87b2 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,5 +1,5 @@ -import authHandler from "@App/middlewares/AuthHandler"; -import roleHandler from "@App/middlewares/RolesHandler"; +// import authHandler from "@App/middlewares/AuthHandler"; +// import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; @@ -18,7 +18,7 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe", [authHandler, roleHandler]) + @Post("/api/v1/admin/stripe") protected async post(req: Request, response: Response) { try { //init Subscription resource with request body values diff --git a/src/app/api/idnot/OfficeController.ts b/src/app/api/idnot/OfficeController.ts index bbbc19d2..32afb042 100644 --- a/src/app/api/idnot/OfficeController.ts +++ b/src/app/api/idnot/OfficeController.ts @@ -3,15 +3,13 @@ import { Controller, Get } from "@ControllerPattern/index"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; import IdNotService from "@Services/common/IdNotService/IdNotService"; -import UsersService from "@Services/super-admin/UsersService/UsersService"; -import User from "le-coffre-resources/dist/Notary/User"; import userHandler from "@App/middlewares/OfficeMembershipHandlers/UserHandler"; import authHandler from "@App/middlewares/AuthHandler"; @Controller() @Service() export default class UserController extends ApiController { - constructor (private idNotService: IdNotService, private userService: UsersService) { + constructor (private idNotService: IdNotService) { super(); } @@ -26,13 +24,7 @@ export default class UserController extends ApiController { const officeMemberships = await this.idNotService.getOfficeMemberships(uid); - await this.userService.getOrCreateUsers(uid, officeMemberships.result); - - const usersEntities = await this.userService.get({ where: { office_uid: uid }, include: { contact: true}}); - - const users = User.hydrateArray(usersEntities, { strategy: "excludeAll" }); - - this.httpSuccess(response, users); + this.httpSuccess(response, officeMemberships); } catch (error) { console.log(error); this.httpInternalError(response); diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 01850231..6a4341ac 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -8,11 +8,14 @@ import IdNotService from "@Services/common/IdNotService/IdNotService"; import WhitelistService from "@Services/common/WhitelistService/WhitelistService"; import User from "le-coffre-resources/dist/SuperAdmin"; import UsersService from "@Services/super-admin/UsersService/UsersService"; +import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; +import { ESubscriptionStatus } from "@prisma/client"; +import SeatsService from "@Services/admin/SeatsService/SeatsService"; @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService) { + constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { super(); } @@ -61,8 +64,51 @@ export default class UserController extends ApiController { return; } + let isSubscribed = false; + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + + if(!subscriptions || subscriptions.length === 0) { + this.httpUnauthorized(response, "No subscription found"); + return; + } + + if(subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { + this.httpUnauthorized(response, "Subscription inactive"); + return; + } + + const hasSeat = await this.subscriptionsService.get({ where: {status: ESubscriptionStatus.ACTIVE, seats: {some : {user_uid : userHydrated.uid }} } }); + + if (hasSeat && hasSeat.length > 0) { + isSubscribed = true; + } + else { + const nbMaxSeats = subscriptions[0]!.nb_seats; + + const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid }}); + + //if nbMaxSeats < nbCurrentSeats, create a new seat for the user + if (nbMaxSeats > nbCurrentSeats.length) { + const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); + if (seatAdded) { + isSubscribed = true; + } + } + else{ + this.httpUnauthorized(response, "No seat available"); + return; + } + } + + if(!isSubscribed) { + this.httpUnauthorized(response, "User not subscribed"); + return; + } + + //Check if user is whitelisted - const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); + const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); + //When we'll switch to idNotId whitelisting // const isWhitelisted = await this.userWhitelistService.getByIdNotId(user.idNot); diff --git a/src/common/repositories/SeatsRepository.ts b/src/common/repositories/SeatsRepository.ts new file mode 100644 index 00000000..3806fda2 --- /dev/null +++ b/src/common/repositories/SeatsRepository.ts @@ -0,0 +1,67 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Prisma, Seats } from "@prisma/client"; + +@Service() +export default class SeatsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().seats; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many subscriptions + */ + public async findMany(query: Prisma.SeatsFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + if (!query.include) return this.model.findMany({ ...query }); + return this.model.findMany({ ...query }); + } + + /** + * @description : find unique subscription + */ + public async findOneByUid(uid: string, query?: Prisma.SeatsInclude): Promise { + return this.model.findUnique({ + where: { + uid: uid, + }, + include: query, + }); + } + + /** + * @description : Create a subscription + */ + public async create(userUid: string, subscriptionUid: string): Promise { + + const createArgs: Prisma.SeatsCreateArgs = { + data: { + subscription: { + connect: { + uid: subscriptionUid + }, + }, + user: { + connect: { + uid: userUid + }, + }, + }, + }; + { + + return this.model.create(createArgs); + } + + + } + + +} diff --git a/src/common/repositories/UsersRepository.ts b/src/common/repositories/UsersRepository.ts index 538c601d..c0f466e6 100644 --- a/src/common/repositories/UsersRepository.ts +++ b/src/common/repositories/UsersRepository.ts @@ -143,45 +143,6 @@ export default class UsersRepository extends BaseRepository { return this.model.update({ ...updateArgs, include: { contact: true, office_membership: { include: { address: true } } } }); } - public async getOrCreate(usersToAdd: User[]) { - - let users: User[] = []; - - await Promise.all(usersToAdd.map(async (user) => { - const upsertUser = await this.model.upsert({ - where: { - idNot: user.idNot, - }, - update: {}, - create: { - idNot: user.idNot, - office_membership: { - connect: { - uid: user.office_membership!.uid, - } - }, - contact: { - create: { - first_name: user.contact!.first_name, - last_name: user.contact!.last_name, - email: user.contact!.email, - civility: ECivility[user.contact?.civility as keyof typeof ECivility], - }, - }, - role: { - connect: { - uid: user.role!.uid, - }, - }, - }, - }); - users.push(upsertUser); - })); - - return users; - - } - /** * @description : Update check date of a user */ diff --git a/src/services/admin/SeatsService/SeatsService.ts b/src/services/admin/SeatsService/SeatsService.ts new file mode 100644 index 00000000..77af21a0 --- /dev/null +++ b/src/services/admin/SeatsService/SeatsService.ts @@ -0,0 +1,37 @@ +import BaseService from "@Services/BaseService"; +import "reflect-metadata"; +import { Service } from "typedi"; +import { Prisma, Seats } from "@prisma/client"; +import SeatsRepository from "@Repositories/SeatsRepository"; + +@Service() +export default class SeatsService extends BaseService { + constructor(private seatsRepository: SeatsRepository) { + super(); + } + + /** + * @description : Get all seats + * @throws {Error} If seats cannot be get + */ + public get(query: Prisma.SeatsFindManyArgs) { + return this.seatsRepository.findMany(query); + } + + /** + * @description : Get a seat by uid + * @throws {Error} If seat is not found + */ + public async getByUid(uid: string, query?: Prisma.SeatsInclude) { + return this.seatsRepository.findOneByUid(uid, query); + } + + /** + * @description : Create a new seat + * @throws {Error} If seat cannot be created + */ + public async create(subscriptionUid: string, userUid: string): Promise { + return this.seatsRepository.create(subscriptionUid, userUid); + } + +} diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts index 11c85146..71e31fd9 100644 --- a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -42,5 +42,4 @@ export default class SubscriptionsService extends BaseService { public async update(uid: string, subscriptionEntity: Subscription): Promise { return this.subscriptionsRepository.update(uid, subscriptionEntity); } - } diff --git a/src/services/super-admin/UsersService/UsersService.ts b/src/services/super-admin/UsersService/UsersService.ts index 77dea910..0781623c 100644 --- a/src/services/super-admin/UsersService/UsersService.ts +++ b/src/services/super-admin/UsersService/UsersService.ts @@ -4,11 +4,10 @@ import { Service } from "typedi"; import UsersRepository from "@Repositories/UsersRepository"; import User from "le-coffre-resources/dist/Admin"; import { Prisma, Users } from "@prisma/client"; -import RolesService from "@Services/admin/RolesService/RolesService"; @Service() export default class UsersService extends BaseService { - constructor(private userRepository: UsersRepository, private rolesService: RolesService) { + constructor(private userRepository: UsersRepository) { super(); } @@ -92,43 +91,5 @@ export default class UsersService extends BaseService { return this.userRepository.findManyToCheck(); } - public async getOrCreateUsers(officeId: string, usersToCreate: [{}]) { - let users : User[] = []; - const roleNotary = await this.rolesService.get({ where: { name: "notary" } }); - - usersToCreate.forEach((user: any) => { - const userEntity: User = { - idNot: user.uid, - contact: { - first_name: user.prenom, - last_name: user.nomUsuel, - civility: user.civilite, - email: "", - created_at: new Date(), - updated_at: new Date(), - }, - office_membership: { - uid: officeId, - name: "", - crpcen: "", - created_at: new Date(), - updated_at: new Date(), - }, - role: { - uid: roleNotary[0]?.uid, - name: "", - label: "", - created_at: new Date(), - updated_at: new Date(), - }, - created_at: new Date(), - updated_at: new Date(), - }; - users.push(userEntity); - }); - - return await this.userRepository.getOrCreate(users); - } - } diff --git a/src/test/services/super-admin/UsersService.test.ts b/src/test/services/super-admin/UsersService.test.ts index 6d1f7910..5b7d6708 100644 --- a/src/test/services/super-admin/UsersService.test.ts +++ b/src/test/services/super-admin/UsersService.test.ts @@ -6,11 +6,10 @@ import { PrismaClient } from "@prisma/client"; import { user, userContact, userContact_, user_ } from "@Test/config/MockedData"; import UsersRepository from "@Repositories/UsersRepository"; import Container from "typedi"; -import RolesService from "@Services/admin/RolesService/RolesService"; const prisma = new PrismaClient(); -const UsersServiceTest = new UsersService(Container.get(UsersRepository), Container.get(RolesService)); +const UsersServiceTest = new UsersService(Container.get(UsersRepository)); afterAll(async () => { /* From 86cccab8da0a52e23ec3f28888e8da191afa1fe9 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 2 Apr 2024 18:05:05 +0200 Subject: [PATCH 20/86] Removed subscription requirements for dev enf --- src/app/api/idnot/UserController.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 6a4341ac..b2063e72 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -11,11 +11,12 @@ import UsersService from "@Services/super-admin/UsersService/UsersService"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; import { ESubscriptionStatus } from "@prisma/client"; import SeatsService from "@Services/admin/SeatsService/SeatsService"; +import { BackendVariables } from "@Common/config/variables/Variables"; @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { + constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService, private backendVariables: BackendVariables) { super(); } @@ -68,12 +69,12 @@ export default class UserController extends ApiController { const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); if(!subscriptions || subscriptions.length === 0) { - this.httpUnauthorized(response, "No subscription found"); + isSubscribed = false; return; } if(subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { - this.httpUnauthorized(response, "Subscription inactive"); + isSubscribed = false; return; } @@ -95,12 +96,12 @@ export default class UserController extends ApiController { } } else{ - this.httpUnauthorized(response, "No seat available"); + isSubscribed = false; return; } } - if(!isSubscribed) { + if(this.backendVariables.ENV !== 'dev' && !isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); return; } From a99224c3198714c16a4ce444d2cd9e15f384326a Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 10:30:32 +0200 Subject: [PATCH 21/86] Subscription --- src/app/api/admin/StripeController.ts | 17 +++++---- src/app/api/idnot/UserController.ts | 52 +++++++++++++-------------- src/common/webhooks/stripeWebhooks.ts | 2 ++ 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index ba8a87b2..ae4c211d 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,4 +1,4 @@ -// import authHandler from "@App/middlewares/AuthHandler"; +import authHandler from "@App/middlewares/AuthHandler"; // import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; @@ -18,15 +18,20 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe") + @Post("/api/v1/admin/stripe", [authHandler]) protected async post(req: Request, response: Response) { - try { + try { + const officeId: string = req.body.user.office_Id; + + //add office id to request body + req.body.office = {uid: officeId}; + //init Subscription resource with request body values - const subscriptionEntity = Subscription.hydrate(req.body); + const subscriptionEntity = Subscription.hydrate(req.body, { strategy: "excludeAll" }); - await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); - const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); + const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); this.httpCreated(response, stripeSession); } catch (error) { diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index b2063e72..fcf4b7b3 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -6,17 +6,17 @@ import AuthService, { IUserJwtPayload } from "@Services/common/AuthService/AuthS import IdNotService from "@Services/common/IdNotService/IdNotService"; import WhitelistService from "@Services/common/WhitelistService/WhitelistService"; -import User from "le-coffre-resources/dist/SuperAdmin"; +import User from "le-coffre-resources/dist/Admin"; import UsersService from "@Services/super-admin/UsersService/UsersService"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; import { ESubscriptionStatus } from "@prisma/client"; import SeatsService from "@Services/admin/SeatsService/SeatsService"; -import { BackendVariables } from "@Common/config/variables/Variables"; +import { EType } from "le-coffre-resources/dist/Admin/Subscription"; @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService, private backendVariables: BackendVariables) { + constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { super(); } @@ -68,45 +68,41 @@ export default class UserController extends ApiController { let isSubscribed = false; const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); - if(!subscriptions || subscriptions.length === 0) { + if(!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { + this.httpUnauthorized(response, "User not subscribed"); isSubscribed = false; return; } - if(subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { - isSubscribed = false; - return; - } - - const hasSeat = await this.subscriptionsService.get({ where: {status: ESubscriptionStatus.ACTIVE, seats: {some : {user_uid : userHydrated.uid }} } }); - - if (hasSeat && hasSeat.length > 0) { + if(subscriptions[0]?.type === EType.Unlimited) { isSubscribed = true; } - else { - const nbMaxSeats = subscriptions[0]!.nb_seats; - - const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid }}); - - //if nbMaxSeats < nbCurrentSeats, create a new seat for the user - if (nbMaxSeats > nbCurrentSeats.length) { - const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); - if (seatAdded) { - isSubscribed = true; - } + else{ + const hasSeat = await this.subscriptionsService.get({ where: {status: ESubscriptionStatus.ACTIVE, seats: {some : {user_uid : userHydrated.uid }} } }); + + if (hasSeat && hasSeat.length > 0) { + isSubscribed = true; } - else{ - isSubscribed = false; - return; + else { + const nbMaxSeats = subscriptions[0]!.nb_seats; + + const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid }}); + + //if nbMaxSeats < nbCurrentSeats, create a new seat for the user + if (nbMaxSeats > nbCurrentSeats.length) { + const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); + if (seatAdded) { + isSubscribed = true; + } + } } } - if(this.backendVariables.ENV !== 'dev' && !isSubscribed) { + if(!isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); return; } - //Check if user is whitelisted const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); diff --git a/src/common/webhooks/stripeWebhooks.ts b/src/common/webhooks/stripeWebhooks.ts index 9463eadc..03ba1363 100644 --- a/src/common/webhooks/stripeWebhooks.ts +++ b/src/common/webhooks/stripeWebhooks.ts @@ -34,6 +34,8 @@ export default class StripeWebhooks extends ApiController { switch (event.type) { case "checkout.session.completed": if (event.data.object.status !== "complete") break; + console.log(event.data.object); + const subscription = JSON.parse(event.data.object.metadata.subscription); subscription.stripe_subscription_id = event.data.object.subscription; From 44685ba0204349bb820b49b2e482c3e66827343d Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Thu, 4 Apr 2024 10:46:08 +0200 Subject: [PATCH 22/86] switching ppd ingress --- devops/ppd.values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devops/ppd.values.yaml b/devops/ppd.values.yaml index c196ad19..78ece8a3 100644 --- a/devops/ppd.values.yaml +++ b/devops/ppd.values.yaml @@ -21,7 +21,7 @@ lecoffreBack: host: api-tp.ppd.lecoffre.smart-chain.fr tls: hosts: - - api.ppd.lecoffre.smart-chain.fr + - api-tp.ppd.lecoffre.smart-chain.fr secretName: api-tls annotations: kubernetes.io/ingress.class: nginx From c0fbf598564bdbc0cd3879e32828bbaca1f476f2 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 10:55:19 +0200 Subject: [PATCH 23/86] Removed whitelist --- src/app/api/idnot/UserController.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index fcf4b7b3..53f62cee 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -5,7 +5,6 @@ import { Service } from "typedi"; import AuthService, { IUserJwtPayload } from "@Services/common/AuthService/AuthService"; import IdNotService from "@Services/common/IdNotService/IdNotService"; -import WhitelistService from "@Services/common/WhitelistService/WhitelistService"; import User from "le-coffre-resources/dist/Admin"; import UsersService from "@Services/super-admin/UsersService/UsersService"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; @@ -16,7 +15,7 @@ import { EType } from "le-coffre-resources/dist/Admin/Subscription"; @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService, private idNotService: IdNotService, private whitelistService: WhitelistService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { + constructor(private authService: AuthService, private idNotService: IdNotService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { super(); } @@ -104,17 +103,17 @@ export default class UserController extends ApiController { } //Check if user is whitelisted - const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); + // const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); //When we'll switch to idNotId whitelisting // const isWhitelisted = await this.userWhitelistService.getByIdNotId(user.idNot); //If not whitelisted, return 409 Not whitelisted - if (!isWhitelisted || isWhitelisted.length === 0) { - this.httpNotWhitelisted(response); - return; - } + // if (!isWhitelisted || isWhitelisted.length === 0) { + // this.httpNotWhitelisted(response); + // return; + // } await this.idNotService.updateOffice(user.office_uid); From fc75a78fff72b27440f92793a33ffe5c5c48b848 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 11:15:07 +0200 Subject: [PATCH 24/86] Removed SCW_ from variables --- src/common/config/variables/Variables.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index 16872c03..3b37f398 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -201,10 +201,10 @@ export class BackendVariables { this.OVH_CONSUMER_KEY = process.env["OVH_CONSUMER_KEY"]!; this.OVH_SMS_SERVICE_NAME = process.env["OVH_SMS_SERVICE_NAME"]!; this.SMS_FACTOR_TOKEN = process.env["SMS_FACTOR_TOKEN"]!; - this.SCW_ACCESS_KEY_ID = process.env["SCW_ACCESS_KEY_ID"]!; - this.SCW_ACCESS_KEY_SECRET = process.env["SCW_ACCESS_KEY_SECRET"]!; - this.SCW_BUCKET_ENDPOINT = process.env["SCW_BUCKET_ENDPOINT"]!; - this.SCW_BUCKET_NAME = process.env["SCW_BUCKET_NAME"]!; + this.SCW_ACCESS_KEY_ID = process.env["ACCESS_KEY_ID"]!; + this.SCW_ACCESS_KEY_SECRET = process.env["ACCESS_KEY_SECRET"]!; + this.SCW_BUCKET_ENDPOINT = process.env["BUCKET_ENDPOINT"]!; + this.SCW_BUCKET_NAME = process.env["BUCKET_NAME"]!; this.STRIPE_SECRET_KEY = process.env["STRIPE_SECRET_KEY"]!; this.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID"]!; this.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID"]!; From 832102d1fdbd970952d3873280df1464d6b4a730 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 11:41:12 +0200 Subject: [PATCH 25/86] allow admin to connect --- src/app/api/idnot/UserController.ts | 103 ++++++++++-------- .../common/IdNotService/IdNotService.ts | 6 +- 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 53f62cee..78eaab70 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -15,7 +15,13 @@ import { EType } from "le-coffre-resources/dist/Admin/Subscription"; @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService, private idNotService: IdNotService, private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService) { + constructor( + private authService: AuthService, + private idNotService: IdNotService, + private userService: UsersService, + private subscriptionsService: SubscriptionsService, + private seatsService: SeatsService, + ) { super(); } @@ -28,100 +34,101 @@ export default class UserController extends ApiController { protected async getUserInfosFromIdnot(req: Request, response: Response) { try { const code = req.params["code"]; - - if (!code) throw new Error("code is required"); + + if (!code) throw new Error("code is required"); const idNotToken = await this.idNotService.getIdNotToken(code); - - if(!idNotToken) { + + if (!idNotToken) { this.httpValidationError(response, "IdNot token undefined"); return; } - const user = await this.idNotService.getOrCreateUser(idNotToken); + const user = await this.idNotService.getOrCreateUser(idNotToken); - if(!user) { + if (!user) { this.httpUnauthorized(response, "User not found"); return; } - await this.idNotService.updateUser(user.uid); + await this.idNotService.updateUser(user.uid); //Whitelist feature //Get user with contact - const prismaUser = await this.userService.getByUid(user.uid, {contact: true }); - + const prismaUser = await this.userService.getByUid(user.uid, { contact: true, role: true }); + if (!prismaUser) { this.httpNotFoundRequest(response, "user not found"); return; - } + } //Hydrate user to be able to use his contact - const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); - - if(!userHydrated.contact?.email || userHydrated.contact?.email === "") { + const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); + + if (!userHydrated.contact?.email || userHydrated.contact?.email === "") { this.httpUnauthorized(response, "Email not found"); return; } - - let isSubscribed = false; - const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); - - if(!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { - this.httpUnauthorized(response, "User not subscribed"); - isSubscribed = false; - return; - } - - if(subscriptions[0]?.type === EType.Unlimited) { + let isSubscribed = false; + if (userHydrated.role?.name === "admin") { isSubscribed = true; - } - else{ - const hasSeat = await this.subscriptionsService.get({ where: {status: ESubscriptionStatus.ACTIVE, seats: {some : {user_uid : userHydrated.uid }} } }); - - if (hasSeat && hasSeat.length > 0) { - isSubscribed = true; + } else { + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + + if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { + this.httpUnauthorized(response, "User not subscribed"); + isSubscribed = false; + return; } - else { - const nbMaxSeats = subscriptions[0]!.nb_seats; - - const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid }}); - - //if nbMaxSeats < nbCurrentSeats, create a new seat for the user - if (nbMaxSeats > nbCurrentSeats.length) { - const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); - if (seatAdded) { - isSubscribed = true; + + if (subscriptions[0]?.type === EType.Unlimited) { + isSubscribed = true; + } else { + const hasSeat = await this.subscriptionsService.get({ + where: { status: ESubscriptionStatus.ACTIVE, seats: { some: { user_uid: userHydrated.uid } } }, + }); + + if (hasSeat && hasSeat.length > 0) { + isSubscribed = true; + } else { + const nbMaxSeats = subscriptions[0]!.nb_seats; + + const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid } }); + + //if nbMaxSeats < nbCurrentSeats, create a new seat for the user + if (nbMaxSeats > nbCurrentSeats.length) { + const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); + if (seatAdded) { + isSubscribed = true; + } } } } } - if(!isSubscribed) { + if (!isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); return; } //Check if user is whitelisted // const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); - - + //When we'll switch to idNotId whitelisting - // const isWhitelisted = await this.userWhitelistService.getByIdNotId(user.idNot); + // const isWhitelisted = await this.userWhitelistService.getByIdNotId(user.idNot); //If not whitelisted, return 409 Not whitelisted - // if (!isWhitelisted || isWhitelisted.length === 0) { + // if (!isWhitelisted || isWhitelisted.length === 0) { // this.httpNotWhitelisted(response); // return; // } - await this.idNotService.updateOffice(user.office_uid); const payload = await this.authService.getUserJwtPayload(user.idNot); const accessToken = this.authService.generateAccessToken(payload); const refreshToken = this.authService.generateRefreshToken(payload); - + this.httpSuccess(response, { accessToken, refreshToken }); } catch (error) { console.log(error); diff --git a/src/services/common/IdNotService/IdNotService.ts b/src/services/common/IdNotService/IdNotService.ts index 2cfe91ee..96de9fd5 100644 --- a/src/services/common/IdNotService/IdNotService.ts +++ b/src/services/common/IdNotService/IdNotService.ts @@ -147,7 +147,7 @@ export default class IdNotService extends BaseService { case EIdnotRole.SUPPLEANT: return (await this.rolesService.get({ where: { name: "notary" } }))[0]!; case EIdnotRole.ADMINISTRATEUR: - return (await this.rolesService.get({ where: { name: "notary" } }))[0]!; + return (await this.rolesService.get({ where: { name: "admin" } }))[0]!; case EIdnotRole.CURATEUR: return (await this.rolesService.get({ where: { name: "notary" } }))[0]!; default: @@ -346,6 +346,10 @@ export default class IdNotService extends BaseService { // } const role = await this.getRole(userData.typeLien.name); + console.log(role); + console.log(userData.typeLien); + + const userToAdd = { idNot: decodedToken.sub, From 948d661d3010641573592404cb2454e18b3f763c Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 13:35:06 +0200 Subject: [PATCH 26/86] Send invitation email ready --- src/app/api/admin/SubscriptionsController.ts | 31 ++++++++++++++++++- src/common/config/variables/Variables.ts | 4 +++ src/common/emails/EmailBuilder.ts | 28 +++++++++++++++++ src/common/emails/Templates/EmailTemplates.ts | 1 + 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index 4de51386..a79c2feb 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -12,11 +12,12 @@ import { validateOrReject } from "class-validator"; import ObjectHydrate from "@Common/helpers/ObjectHydrate"; import roleHandler from "@App/middlewares/RolesHandler"; import authHandler from "@App/middlewares/AuthHandler"; +import EmailBuilder from "@Common/emails/EmailBuilder"; @Controller() @Service() export default class SubscriptionsController extends ApiController { - constructor(private subscriptionsService: SubscriptionsService) { + constructor(private subscriptionsService: SubscriptionsService, private emailBuilder: EmailBuilder) { super(); } @@ -142,4 +143,32 @@ export default class SubscriptionsController extends ApiController { return; } } + + /** + * @description Invite collaborators to a subscription + */ + + @Post("/api/v1/admin/subscriptions/invite", [authHandler, roleHandler]) + protected async inviteCollaborators(req: Request, response: Response) { + try { + //get email list from body + const emails: [string] = req.body.emails; + if (!emails || emails.length < 1){ + this.httpBadRequest(response, "No emails provided"); + return; + } + + console.log(emails); + + //create emails for asked document + await this.emailBuilder.sendInvitationEmails(emails); + + //success + this.httpSuccess(response); + + } catch (error) { + this.httpInternalError(response, error); + return; + } + } } diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index 3b37f398..a65bd265 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -157,6 +157,9 @@ export class BackendVariables { @IsNotEmpty() public readonly STRIPE_PAYMENT_CANCEL_URL!: string; + @IsNotEmpty() + public readonly IDNOT_PROD_BASE_URL!: string; + public constructor() { dotenv.config(); this.DATABASE_PORT = process.env["DATABASE_PORT"]!; @@ -210,6 +213,7 @@ export class BackendVariables { this.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID"]!; this.STRIPE_PAYMENT_SUCCESS_URL = process.env["STRIPE_PAYMENT_SUCCESS_URL"]!; this.STRIPE_PAYMENT_CANCEL_URL = process.env["STRIPE_PAYMENT_CANCEL_URL"]!; + this.IDNOT_PROD_BASE_URL = process.env["IDNOT_PROD_BASE_URL"]!; } public async validate(groups?: string[]) { const validationOptions = groups ? { groups } : undefined; diff --git a/src/common/emails/EmailBuilder.ts b/src/common/emails/EmailBuilder.ts index 81eb1c1e..bc7757e2 100644 --- a/src/common/emails/EmailBuilder.ts +++ b/src/common/emails/EmailBuilder.ts @@ -106,4 +106,32 @@ export default class EmailBuilder { if (civility === "MALE") return "Mr"; else return "Mme"; } + + public async sendInvitationEmails(emails: string[]) { + emails.forEach((email) => { + const to = email; + + const templateVariables = { + link: this.variables.APP_HOST, + idNotLink: this.variables.IDNOT_PROD_BASE_URL, + }; + + const templateName = ETemplates.SUBSCRIPTION_INVITATION; + const subject = "Invitation abonnement LeCoffre"; + + this.mailchimpService.create({ + templateName, + to, + subject, + templateVariables, + uid: "", + from: null, + cc: [], + cci: [], + sentAt: null, + nbTrySend: null, + lastTrySendDate: null, + }); + }); + } } diff --git a/src/common/emails/Templates/EmailTemplates.ts b/src/common/emails/Templates/EmailTemplates.ts index 081cfadb..520098e2 100644 --- a/src/common/emails/Templates/EmailTemplates.ts +++ b/src/common/emails/Templates/EmailTemplates.ts @@ -2,4 +2,5 @@ export const ETemplates = { DOCUMENT_ASKED: "DOCUMENT_ASKED", DOCUMENT_REFUSED: "DOCUMENT_REFUSED", DOCUMENT_RECAP: "DOCUMENT_RECAP", + SUBSCRIPTION_INVITATION: "SUBSCRIPTION_INVITATION", }; \ No newline at end of file From 584f7bac5795d484100ac7121b9c87db450175f2 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 14:36:15 +0200 Subject: [PATCH 27/86] manage facturation --- src/app/api/admin/StripeController.ts | 20 ++++++- .../repositories/SubscriptionsRepository.ts | 11 +--- .../common/StripeService/StripeService.ts | 52 ++++++++++++------- 3 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index ae4c211d..84550974 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,7 +1,7 @@ import authHandler from "@App/middlewares/AuthHandler"; // import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; -import { Controller, Post } from "@ControllerPattern/index"; +import { Controller, Get, Post } from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; import { validateOrReject } from "class-validator"; import { Request, Response } from "express"; @@ -39,4 +39,22 @@ export default class StripeController extends ApiController { return; } } + + @Get("/api/v1/admin/stripe/:uid", [authHandler]) + protected async get(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const client_portal = await this.stripeService.createClientPortalSession(uid); + + this.httpSuccess(response, client_portal); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } } \ No newline at end of file diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts index a8203886..ee5f787f 100644 --- a/src/common/repositories/SubscriptionsRepository.ts +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -40,7 +40,7 @@ export default class SubscriptionsRepository extends BaseRepository { /** * @description : Create a subscription */ - public async create(subscription: Subscription): Promise { + public async create(subscription: Subscription): Promise { if(subscription.type === "STANDARD") { const createArgs: Prisma.SubscriptionsCreateArgs = { @@ -56,15 +56,6 @@ export default class SubscriptionsRepository extends BaseRepository { uid: subscription.office!.uid, }, }, - seats: { - create: subscription.seats!.map(seat => ({ - user: { - connect: { - uid: seat.user!.uid, - }, - }, - })), - }, }, }; return this.model.create(createArgs); diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index 59c261f5..ed963b10 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -14,23 +14,35 @@ export default class StripeService { return this.client; } - public async createCheckoutSession(subscription: Subscription) { - const priceId = subscription.type === "STANDARD" ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID; - return this.client.checkout.sessions.create({ - mode: "subscription", - payment_method_types: ["card", "paypal"], - billing_address_collection: "auto", - line_items: [ - { - price: priceId, - quantity: subscription.nb_seats, - }, - ], - success_url: this.variables.STRIPE_PAYMENT_SUCCESS_URL, - cancel_url: this.variables.STRIPE_PAYMENT_CANCEL_URL, - metadata: { - subscription: JSON.stringify(subscription), - }, - }); - } -} \ No newline at end of file + public async createCheckoutSession(subscription: Subscription) { + const priceId = + subscription.type === "STANDARD" + ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID + : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID; + return this.client.checkout.sessions.create({ + mode: "subscription", + payment_method_types: ["card", "paypal"], + billing_address_collection: "auto", + line_items: [ + { + price: priceId, + quantity: subscription.nb_seats, + }, + ], + success_url: this.variables.STRIPE_PAYMENT_SUCCESS_URL, + cancel_url: this.variables.STRIPE_PAYMENT_CANCEL_URL, + metadata: { + subscription: JSON.stringify(subscription), + }, + }); + } + + public async createClientPortalSession(subscriptionId: string) { + const subscription = await this.client.subscriptions.retrieve(subscriptionId); + + return this.client.billingPortal.sessions.create({ + customer: subscription.customer as string, + return_url: this.variables.STRIPE_PAYMENT_SUCCESS_URL, + }); + } +} From 4c6aed8c62791fc01bd3dfcd0b298c835928a3e7 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 15:03:17 +0200 Subject: [PATCH 28/86] Changed manage subscription return url --- src/services/common/StripeService/StripeService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index ed963b10..cae8d469 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -42,7 +42,7 @@ export default class StripeService { return this.client.billingPortal.sessions.create({ customer: subscription.customer as string, - return_url: this.variables.STRIPE_PAYMENT_SUCCESS_URL, + return_url: this.variables.APP_HOST + "/subscription/manage", }); } } From 8deaa8b967e8a75a354d61503979bf0aa920194b Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 15:33:22 +0200 Subject: [PATCH 29/86] Switched stripe succes/cancel url --- src/services/common/StripeService/StripeService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index cae8d469..96151a96 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -29,8 +29,8 @@ export default class StripeService { quantity: subscription.nb_seats, }, ], - success_url: this.variables.STRIPE_PAYMENT_SUCCESS_URL, - cancel_url: this.variables.STRIPE_PAYMENT_CANCEL_URL, + success_url: this.variables.APP_HOST + "/subscription/success", + cancel_url: this.variables.APP_HOST + "/subscription/error", metadata: { subscription: JSON.stringify(subscription), }, From 8b821ee785c34747069a855c1d9aa4d534c4c0ce Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 15:37:06 +0200 Subject: [PATCH 30/86] Removed console.log --- src/app/api/admin/SubscriptionsController.ts | 2 -- src/common/webhooks/stripeWebhooks.ts | 4 +--- src/services/common/IdNotService/IdNotService.ts | 4 ---- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index a79c2feb..56c190d6 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -158,8 +158,6 @@ export default class SubscriptionsController extends ApiController { return; } - console.log(emails); - //create emails for asked document await this.emailBuilder.sendInvitationEmails(emails); diff --git a/src/common/webhooks/stripeWebhooks.ts b/src/common/webhooks/stripeWebhooks.ts index 03ba1363..fee0e6bd 100644 --- a/src/common/webhooks/stripeWebhooks.ts +++ b/src/common/webhooks/stripeWebhooks.ts @@ -33,9 +33,7 @@ export default class StripeWebhooks extends ApiController { switch (event.type) { case "checkout.session.completed": - if (event.data.object.status !== "complete") break; - console.log(event.data.object); - + if (event.data.object.status !== "complete") break; const subscription = JSON.parse(event.data.object.metadata.subscription); subscription.stripe_subscription_id = event.data.object.subscription; diff --git a/src/services/common/IdNotService/IdNotService.ts b/src/services/common/IdNotService/IdNotService.ts index 96de9fd5..9820fd86 100644 --- a/src/services/common/IdNotService/IdNotService.ts +++ b/src/services/common/IdNotService/IdNotService.ts @@ -346,10 +346,6 @@ export default class IdNotService extends BaseService { // } const role = await this.getRole(userData.typeLien.name); - console.log(role); - console.log(userData.typeLien); - - const userToAdd = { idNot: decodedToken.sub, From a77b111e62d0f3fc94613366fb1657e5aa12c8d7 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 4 Apr 2024 16:51:57 +0200 Subject: [PATCH 31/86] Fixed invite return --- src/app/api/admin/SubscriptionsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index 56c190d6..f082ad9c 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -162,7 +162,7 @@ export default class SubscriptionsController extends ApiController { await this.emailBuilder.sendInvitationEmails(emails); //success - this.httpSuccess(response); + this.httpSuccess(response, {message: "Invitations sent"}); } catch (error) { this.httpInternalError(response, error); From a125f3482c23cecffcabe53b5e515e370ecc27e4 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 5 Apr 2024 07:08:45 +0200 Subject: [PATCH 32/86] Staging send emails directly --- src/common/emails/EmailBuilder.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/emails/EmailBuilder.ts b/src/common/emails/EmailBuilder.ts index bc7757e2..9f5741c1 100644 --- a/src/common/emails/EmailBuilder.ts +++ b/src/common/emails/EmailBuilder.ts @@ -133,5 +133,7 @@ export default class EmailBuilder { lastTrySendDate: null, }); }); + + if(this.variables.ENV !== "dev") await this.mailchimpService.sendEmails(); } } From 254b5cb374251e309050e14998b53c29a427def5 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 5 Apr 2024 07:50:25 +0200 Subject: [PATCH 33/86] Temp send email auto without cron --- src/common/emails/EmailBuilder.ts | 3 ++- src/services/common/MailchimpService/MailchimpService.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/emails/EmailBuilder.ts b/src/common/emails/EmailBuilder.ts index 9f5741c1..59aefda8 100644 --- a/src/common/emails/EmailBuilder.ts +++ b/src/common/emails/EmailBuilder.ts @@ -132,8 +132,9 @@ export default class EmailBuilder { nbTrySend: null, lastTrySendDate: null, }); + this.mailchimpService.sendEmails(); }); - if(this.variables.ENV !== "dev") await this.mailchimpService.sendEmails(); + } } diff --git a/src/services/common/MailchimpService/MailchimpService.ts b/src/services/common/MailchimpService/MailchimpService.ts index 03df091e..9e0f4d09 100644 --- a/src/services/common/MailchimpService/MailchimpService.ts +++ b/src/services/common/MailchimpService/MailchimpService.ts @@ -52,7 +52,7 @@ export default class MailchimpService extends BaseService { * @throws {Error} If email cannot be sent */ public async sendEmails() { - const emailsToSend = await this.get({ where: { sentAt: null } }); + const emailsToSend = await this.get({ where: { sentAt: null } }); const currentDate = new Date(); let nextTrySendDate = null; From 755cac0bc6de115960c321eb38ffff9572fa9409 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 5 Apr 2024 11:48:58 +0200 Subject: [PATCH 34/86] Get customer from subscription --- src/app/api/admin/StripeController.ts | 33 ++++++++++++++----- .../common/StripeService/StripeService.ts | 5 +++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index 84550974..cfbf0ad1 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -19,19 +19,19 @@ export default class StripeController extends ApiController { * @description Create a new checkout session */ @Post("/api/v1/admin/stripe", [authHandler]) - protected async post(req: Request, response: Response) { - try { + protected async createStripeSubscriptionCheckout(req: Request, response: Response) { + try { const officeId: string = req.body.user.office_Id; //add office id to request body - req.body.office = {uid: officeId}; + req.body.office = { uid: officeId }; - //init Subscription resource with request body values + //init Subscription resource with request body values const subscriptionEntity = Subscription.hydrate(req.body, { strategy: "excludeAll" }); - await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); - const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); + const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); this.httpCreated(response, stripeSession); } catch (error) { @@ -41,7 +41,7 @@ export default class StripeController extends ApiController { } @Get("/api/v1/admin/stripe/:uid", [authHandler]) - protected async get(req: Request, response: Response) { + protected async getClientPortalSession(req: Request, response: Response) { try { const uid = req.params["uid"]; if (!uid) { @@ -57,4 +57,21 @@ export default class StripeController extends ApiController { return; } } -} \ No newline at end of file + + @Get("/api/v1/admin/stripe/:uid/customer") + protected async getCustomerBySubscription(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const customer = await this.stripeService.getCustomerBySubscription(uid); + this.httpSuccess(response, customer); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index 96151a96..85dde3a1 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -45,4 +45,9 @@ export default class StripeService { return_url: this.variables.APP_HOST + "/subscription/manage", }); } + + public async getCustomerBySubscription(subscriptionId: string) { + const subscription = await this.client.subscriptions.retrieve(subscriptionId); + return this.client.customers.retrieve(subscription.customer as string); + } } From df00b78d80beb4f5d86d87023034a01e9b05319f Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Fri, 5 Apr 2024 15:33:53 +0200 Subject: [PATCH 35/86] :sparkles: quantity one --- src/services/common/StripeService/StripeService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index 85dde3a1..a6163dfa 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -26,7 +26,7 @@ export default class StripeService { line_items: [ { price: priceId, - quantity: subscription.nb_seats, + quantity: subscription.type === "STANDARD" ? subscription.nb_seats : 1, }, ], success_url: this.variables.APP_HOST + "/subscription/success", @@ -39,7 +39,7 @@ export default class StripeService { public async createClientPortalSession(subscriptionId: string) { const subscription = await this.client.subscriptions.retrieve(subscriptionId); - + return this.client.billingPortal.sessions.create({ customer: subscription.customer as string, return_url: this.variables.APP_HOST + "/subscription/manage", From c22a9a911b6026c6b837e21db27d81c1991323e1 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Fri, 5 Apr 2024 15:34:27 +0200 Subject: [PATCH 36/86] logging cron error --- src/common/config/variables/Variables.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index 93a426b2..c6c44c8a 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -200,6 +200,7 @@ export class BackendVariables { if (process.env["ENV"] === "dev" || process.env["ENV"] === "stg") { throw error; } + console.error(error); throw new Error("Some env variables are required!"); } return this; From abd4a689f2007d3473f842d9f8f7ff4fd57361b7 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Fri, 5 Apr 2024 16:00:24 +0200 Subject: [PATCH 37/86] logging cron error --- src/common/config/variables/Variables.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index c6c44c8a..39628530 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -200,8 +200,8 @@ export class BackendVariables { if (process.env["ENV"] === "dev" || process.env["ENV"] === "stg") { throw error; } - console.error(error); - throw new Error("Some env variables are required!"); + throw new Error(error); + // throw new Error("Some env variables are required!"); } return this; } From df08465d6475ea0b5eba479ff8d0edf60315c467 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Fri, 5 Apr 2024 16:15:56 +0200 Subject: [PATCH 38/86] remove error logging --- src/common/config/variables/Variables.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index 39628530..93a426b2 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -200,8 +200,7 @@ export class BackendVariables { if (process.env["ENV"] === "dev" || process.env["ENV"] === "stg") { throw error; } - throw new Error(error); - // throw new Error("Some env variables are required!"); + throw new Error("Some env variables are required!"); } return this; } From 0337ed5366e59924bea69a3ae96dd365a6027ad4 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Fri, 5 Apr 2024 16:16:13 +0200 Subject: [PATCH 39/86] copy dockerfile from back for cron --- Dockerfile-Cron | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Dockerfile-Cron b/Dockerfile-Cron index 3dbfefe4..77780e6f 100644 --- a/Dockerfile-Cron +++ b/Dockerfile-Cron @@ -15,13 +15,6 @@ RUN ssh-keyscan github.com smart-chain-fr/leCoffre-resources.git >> /root/.ssh/k RUN npm install --frozen-lockfile -# Rebuild the source code only when needed -FROM node:19-alpine AS builder - -WORKDIR leCoffre - -COPY --from=deps leCoffre/node_modules ./node_modules -COPY --from=deps leCoffre/package.json package.json COPY tsconfig.json tsconfig.json COPY src src @@ -35,11 +28,12 @@ WORKDIR leCoffre RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser . -COPY --from=builder --chown=lecoffreuser leCoffre/node_modules ./node_modules -COPY --from=builder --chown=lecoffreuser leCoffre/dist dist -COPY --from=builder --chown=lecoffreuser leCoffre/package.json ./package.json -COPY --from=builder --chown=lecoffreuser leCoffre/src/common/databases ./src/common/databases +COPY --from=deps --chown=lecoffreuser leCoffre/node_modules ./node_modules +COPY --from=deps --chown=lecoffreuser leCoffre/dist dist +COPY --from=deps --chown=lecoffreuser leCoffre/package.json ./package.json +COPY --from=deps --chown=lecoffreuser leCoffre/src/common/databases ./src/common/databases +RUN apk update && apk add chromium USER lecoffreuser CMD ["npm", "run", "cron"] From d3b76928237c0fa80c0033ffe4d1e1d8a014d477 Mon Sep 17 00:00:00 2001 From: Vins Date: Mon, 8 Apr 2024 15:31:16 +0200 Subject: [PATCH 40/86] Change / cancel plan though stripe customer dashboard --- src/app/api/admin/StripeController.ts | 29 +++++- .../repositories/SubscriptionsRepository.ts | 24 ++--- src/common/webhooks/stripeWebhooks.ts | 39 +++++--- .../SubscriptionsService.ts.ts | 10 ++- .../common/StripeService/StripeService.ts | 88 +++++++++++++++---- 5 files changed, 145 insertions(+), 45 deletions(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index cfbf0ad1..5586432c 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,7 +1,7 @@ import authHandler from "@App/middlewares/AuthHandler"; // import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; -import { Controller, Get, Post } from "@ControllerPattern/index"; +import { Controller, Get, Post, Put } from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; import { validateOrReject } from "class-validator"; import { Request, Response } from "express"; @@ -40,6 +40,33 @@ export default class StripeController extends ApiController { } } + @Put("/api/v1/admin/stripe/:uid") + protected async createStripeSubscriptionUpdateCheckout(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + const officeId: string = req.body.user.office_Id; + + //add office id to request body + req.body.office = { uid: officeId }; + + //init Subscription resource with request body values + const subscriptionEntity = Subscription.hydrate(req.body, { strategy: "excludeAll" }); + + await validateOrReject(subscriptionEntity, { groups: ["updateSubscription"], forbidUnknownValues: false }); + + const stripeSession = await this.stripeService.createCheckoutSessionUpdate(uid, subscriptionEntity); + + this.httpCreated(response, stripeSession); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + @Get("/api/v1/admin/stripe/:uid", [authHandler]) protected async getClientPortalSession(req: Request, response: Response) { try { diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts index ee5f787f..0b6fa624 100644 --- a/src/common/repositories/SubscriptionsRepository.ts +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -85,8 +85,7 @@ export default class SubscriptionsRepository extends BaseRepository { /** * @description : update given subscription */ - public async update(uid: string, subscription: Subscription): Promise { - + public async update(uid: string, subscription: Subscription): Promise { if(subscription.type === "STANDARD") { const updateArgs: Prisma.SubscriptionsUpdateArgs = { @@ -98,16 +97,6 @@ export default class SubscriptionsRepository extends BaseRepository { type: ESubscriptionType.STANDARD, status: subscription.status as ESubscriptionStatus, nb_seats: subscription.nb_seats!, - seats: { - deleteMany: {}, - create: subscription.seats!.map(seat => ({ - user: { - connect: { - uid: seat.user!.uid, - }, - }, - })), - } }, }; return this.model.update(updateArgs); @@ -132,5 +121,16 @@ export default class SubscriptionsRepository extends BaseRepository { } } + /** + * @description : Delete a subscription + */ + public async delete(uid: string) { + return this.model.delete({ + where: { + uid: uid, + }, + }); + } + } diff --git a/src/common/webhooks/stripeWebhooks.ts b/src/common/webhooks/stripeWebhooks.ts index fee0e6bd..b0832ece 100644 --- a/src/common/webhooks/stripeWebhooks.ts +++ b/src/common/webhooks/stripeWebhooks.ts @@ -1,3 +1,4 @@ +import { BackendVariables } from "@Common/config/variables/Variables"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Post } from "@ControllerPattern/index"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; @@ -10,7 +11,7 @@ import { Service } from "typedi"; @Controller() @Service() export default class StripeWebhooks extends ApiController { - constructor(private stripeService: StripeService, private subscriptionsService: SubscriptionsService) { + constructor(private stripeService: StripeService, private subscriptionsService: SubscriptionsService, private backendVariables: BackendVariables) { super(); } @@ -20,22 +21,32 @@ export default class StripeWebhooks extends ApiController { @Post("/api/v1/webhooks/stripe") protected async post(req: Request, response: Response) { try { - // const sig = req.headers["stripe-signature"]; - // const endpointSecret = "whsec_c4088876914bc166ff5c39253207f84900820b67f7bba3b2669c0ff392cbc838"; - // const stripe = this.stripeService.getClient(); - // let event: Stripe.Event; - - // if (!sig || !endpointSecret) { - // throw new Error("Signature verification failed"); - // } - // event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); const event = req.body; switch (event.type) { + case "invoice.payment_succeeded": + if (event.data.object.billing_reason !== "subscription_update") break; + const stripeSubscription = await this.stripeService.getClient().subscriptions.retrieve(event.data.object.subscription); + const existingSubscription = await this.subscriptionsService.get({where : {stripe_subscription_id : stripeSubscription.id}}); + if(!existingSubscription[0]) break; + + const subscriptionUpdate: any = {}; + subscriptionUpdate.start_date = new Date(stripeSubscription.current_period_start * 1000); + subscriptionUpdate.end_date = new Date(stripeSubscription.current_period_end * 1000); + subscriptionUpdate.nb_seats = stripeSubscription.items.data[0]?.quantity; + subscriptionUpdate.type = stripeSubscription.items.data[0]?.price?.id === this.backendVariables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID ? "STANDARD" : "UNLIMITED"; + + const subscriptionEntityUpdate = Subscription.hydrate(subscriptionUpdate); + + await validateOrReject(subscriptionEntityUpdate, { groups: ["updateSubscription"], forbidUnknownValues: false }); + + await this.subscriptionsService.update(existingSubscription[0].uid ,subscriptionEntityUpdate); + case "checkout.session.completed": - if (event.data.object.status !== "complete") break; + if (event.data.object.status !== "complete") break; const subscription = JSON.parse(event.data.object.metadata.subscription); + subscription.stripe_subscription_id = event.data.object.subscription; const subscriptionInfo = await this.stripeService @@ -48,9 +59,13 @@ export default class StripeWebhooks extends ApiController { const subscriptionEntity = Subscription.hydrate(subscription); await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); - await this.subscriptionsService.create(subscriptionEntity); break; + + case "customer.subscription.deleted": + const subscriptionToDelete = await this.subscriptionsService.get({where : {stripe_subscription_id : event.data.object.id}}); + if(!subscriptionToDelete[0]) break; + await this.subscriptionsService.delete(subscriptionToDelete[0].uid); default: break; } diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts index 71e31fd9..0f311f03 100644 --- a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -39,7 +39,15 @@ export default class SubscriptionsService extends BaseService { * @description : Modify a subscription * @throws {Error} If subscription cannot be modified */ - public async update(uid: string, subscriptionEntity: Subscription): Promise { + public async update(uid: string, subscriptionEntity: Subscription): Promise { return this.subscriptionsRepository.update(uid, subscriptionEntity); } + + /** + * @description : Delete a subscription + * @throws {Error} If subscription cannot be deleted + */ + public async delete(uid: string) { + return this.subscriptionsRepository.delete(uid); + } } diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index a6163dfa..e8e1ee8e 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -15,26 +15,76 @@ export default class StripeService { } public async createCheckoutSession(subscription: Subscription) { - const priceId = - subscription.type === "STANDARD" - ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID - : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID; - return this.client.checkout.sessions.create({ - mode: "subscription", - payment_method_types: ["card", "paypal"], - billing_address_collection: "auto", - line_items: [ - { - price: priceId, - quantity: subscription.type === "STANDARD" ? subscription.nb_seats : 1, + const priceId = subscription.type === "STANDARD" ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID; + return this.client.checkout.sessions.create({ + mode: "subscription", + payment_method_types: ["card", "paypal"], + billing_address_collection: "auto", + line_items: [ + { + price: priceId, + quantity: subscription.type === "STANDARD" ? subscription.nb_seats : 1, + }, + ], + success_url: this.variables.APP_HOST + "/subscription/success", + cancel_url: this.variables.APP_HOST + "/subscription/error", + metadata: { + subscription: JSON.stringify(subscription), }, - ], - success_url: this.variables.APP_HOST + "/subscription/success", - cancel_url: this.variables.APP_HOST + "/subscription/error", - metadata: { - subscription: JSON.stringify(subscription), - }, - }); + }); + + + + } + + public async createCheckoutSessionUpdate(uid: string, subscription: Subscription) { + + + + // return this.client.checkout.sessions.create({ + // mode: "payment", + // payment_method_types: ["card", "paypal"], + // billing_address_collection: "auto", + // success_url: this.variables.APP_HOST + "/subscription/success", + // cancel_url: this.variables.APP_HOST + "/subscription/error", + // metadata: { + // subscription: JSON.stringify(subscription), + // }, + // }); + + // const priceId = + // subscription.type === "STANDARD" + // ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID + // : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID; + + // return this.client.checkout.sessions.create({ + // mode: "subscription", + // payment_method_types: ["card", "paypal"], + // billing_address_collection: "auto", + // line_items: [ + // { + // price: priceId, + // quantity: subscription.type === "STANDARD" ? subscription.nb_seats : 1, + // }, + // ], + // success_url: this.variables.APP_HOST + "/subscription/success", + // cancel_url: this.variables.APP_HOST + "/subscription/error", + // metadata: { + // subscription: JSON.stringify(subscription), + // }, + // }); + // const subscriptions = await this.client.subscriptions.retrieve(uid); + // const itemId = subscriptions.items.data[0]?.id; + + // return await this.client.subscriptions.update(uid, { + // items: [ + // { + // id: itemId, + // price: priceId, + // quantity: subscription.nb_seats, + // }, + // ], + // }); } public async createClientPortalSession(subscriptionId: string) { From a59013436946c9fdfb3983f21bd54e1f8381d8cf Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 11 Apr 2024 11:35:07 +0200 Subject: [PATCH 41/86] Managecollaborators --- src/app/api/admin/SubscriptionsController.ts | 44 +++++++++++++++- src/app/api/idnot/UserController.ts | 51 ++++++++++--------- .../migration.sql | 7 +++ src/common/databases/schema.prisma | 4 ++ src/common/repositories/SeatsRepository.ts | 17 +++++-- .../repositories/SubscriptionsRepository.ts | 20 +++++++- .../admin/SeatsService/SeatsService.ts | 8 +++ .../SubscriptionsService.ts.ts | 16 +++++- 8 files changed, 135 insertions(+), 32 deletions(-) create mode 100644 src/common/databases/migrations/20240408143448_subscription_seats_date/migration.sql diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index f082ad9c..cbf50ed8 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -41,7 +41,10 @@ export default class SubscriptionsController extends ApiController { const subscriptionsEntities = await this.subscriptionsService.get(query); //Hydrate ressource with prisma entity - const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); + const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); + + console.log(subscriptions[0]?.seats); + //success this.httpSuccess(response, subscriptions); @@ -169,4 +172,43 @@ export default class SubscriptionsController extends ApiController { return; } } + + // /** + // * @description Update a subscription seats + // */ + // @Put("/api/v1/admin/subscriptions/:uid/seats", [authHandler, roleHandler]) + // protected async updateSubscriptionSeats(req: Request, response: Response) { + // try { + // const uid = req.params["uid"]; + // if (!uid) { + // this.httpBadRequest(response, "No uid provided"); + // return; + // } + + // const subscriptionFound = await this.subscriptionsService.getByUid(uid); + + // if (!subscriptionFound) { + // this.httpNotFoundRequest(response, "subscription not found"); + // return; + // } + + // //init Subscription resource with request body values + // const seatEntities = Seat.hydrateArray(req.body); + + // //call service to get prisma entity + // const subscriptionEntityUpdated = await this.subscriptionsService.update(uid, subscriptionEntity); + + // //Hydrate ressource with prisma entity + // const subscription = Subscription.hydrate(subscriptionEntityUpdated, { + // strategy: "excludeAll", + // }); + + // //success + // this.httpSuccess(response, subscription); + + // } catch (error) { + // this.httpInternalError(response, error); + // return; + // } + // } } diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 78eaab70..3819ed6e 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -69,43 +69,44 @@ export default class UserController extends ApiController { this.httpUnauthorized(response, "Email not found"); return; } - let isSubscribed = false; - if (userHydrated.role?.name === "admin") { + let isSubscribed = false; + + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + + if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { + this.httpUnauthorized(response, "User not subscribed"); + isSubscribed = false; + return; + } + + if (subscriptions[0]?.type === EType.Unlimited) { isSubscribed = true; } else { - const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + const hasSeat = await this.subscriptionsService.get({ + where: { status: ESubscriptionStatus.ACTIVE, seats: { some: { user_uid: userHydrated.uid } } }, + }); - if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { - this.httpUnauthorized(response, "User not subscribed"); - isSubscribed = false; - return; - } - - if (subscriptions[0]?.type === EType.Unlimited) { + if (hasSeat && hasSeat.length > 0) { isSubscribed = true; } else { - const hasSeat = await this.subscriptionsService.get({ - where: { status: ESubscriptionStatus.ACTIVE, seats: { some: { user_uid: userHydrated.uid } } }, - }); + const nbMaxSeats = subscriptions[0]!.nb_seats; - if (hasSeat && hasSeat.length > 0) { - isSubscribed = true; - } else { - const nbMaxSeats = subscriptions[0]!.nb_seats; + const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid } }); - const nbCurrentSeats = await this.seatsService.get({ where: { subscription_uid: subscriptions[0]!.uid } }); - - //if nbMaxSeats < nbCurrentSeats, create a new seat for the user - if (nbMaxSeats > nbCurrentSeats.length) { - const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); - if (seatAdded) { - isSubscribed = true; - } + //if nbMaxSeats < nbCurrentSeats, create a new seat for the user + if (nbMaxSeats > nbCurrentSeats.length) { + const seatAdded = await this.seatsService.create(user.uid, subscriptions[0]!.uid); + if (seatAdded) { + isSubscribed = true; } } } } + if (userHydrated.role?.name === "admin") { + isSubscribed = true; + } + if (!isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); return; diff --git a/src/common/databases/migrations/20240408143448_subscription_seats_date/migration.sql b/src/common/databases/migrations/20240408143448_subscription_seats_date/migration.sql new file mode 100644 index 00000000..d66ecc67 --- /dev/null +++ b/src/common/databases/migrations/20240408143448_subscription_seats_date/migration.sql @@ -0,0 +1,7 @@ +-- AlterTable +ALTER TABLE "seats" ADD COLUMN "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updated_at" TIMESTAMP(3); + +-- AlterTable +ALTER TABLE "subscriptions" ADD COLUMN "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updated_at" TIMESTAMP(3); diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 06d8c782..9f21c34d 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -388,6 +388,8 @@ model Subscriptions { office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) office_uid String @db.VarChar(255) seats Seats[] + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt @@map("subscriptions") } @@ -397,6 +399,8 @@ model Seats { subscription_uid String @db.VarChar(255) user Users @relation(fields: [user_uid], references: [uid], onDelete: Cascade) user_uid String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt @@map("seats") } diff --git a/src/common/repositories/SeatsRepository.ts b/src/common/repositories/SeatsRepository.ts index 3806fda2..4a3a255d 100644 --- a/src/common/repositories/SeatsRepository.ts +++ b/src/common/repositories/SeatsRepository.ts @@ -16,7 +16,7 @@ export default class SeatsRepository extends BaseRepository { } /** - * @description : Find many subscriptions + * @description : Find many seats */ public async findMany(query: Prisma.SeatsFindManyArgs) { query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); @@ -25,7 +25,7 @@ export default class SeatsRepository extends BaseRepository { } /** - * @description : find unique subscription + * @description : find unique seat */ public async findOneByUid(uid: string, query?: Prisma.SeatsInclude): Promise { return this.model.findUnique({ @@ -37,7 +37,7 @@ export default class SeatsRepository extends BaseRepository { } /** - * @description : Create a subscription + * @description : Create a seat */ public async create(userUid: string, subscriptionUid: string): Promise { @@ -63,5 +63,16 @@ export default class SeatsRepository extends BaseRepository { } + /** + * @description : Delete a seat + */ + public async delete(uid: string) { + return this.model.delete({ + where: { + uid: uid, + }, + }); + } + } diff --git a/src/common/repositories/SubscriptionsRepository.ts b/src/common/repositories/SubscriptionsRepository.ts index 0b6fa624..4394495b 100644 --- a/src/common/repositories/SubscriptionsRepository.ts +++ b/src/common/repositories/SubscriptionsRepository.ts @@ -85,7 +85,25 @@ export default class SubscriptionsRepository extends BaseRepository { /** * @description : update given subscription */ - public async update(uid: string, subscription: Subscription): Promise { + public async update(uid: string, subscription: Subscription): Promise { + if(!subscription.type || subscription.type === ""){ + const updateArgs: Prisma.SubscriptionsUpdateArgs = { + where: { + uid: uid, + }, + data: { + seats:{ + deleteMany: {}, + createMany: { + data: subscription.seats?.map((seat) => ({ + user_uid: seat.user.uid || "", + })) ?? [], + }, + } + }, + }; + return this.model.update(updateArgs); + } if(subscription.type === "STANDARD") { const updateArgs: Prisma.SubscriptionsUpdateArgs = { diff --git a/src/services/admin/SeatsService/SeatsService.ts b/src/services/admin/SeatsService/SeatsService.ts index 77af21a0..f4f2dcbb 100644 --- a/src/services/admin/SeatsService/SeatsService.ts +++ b/src/services/admin/SeatsService/SeatsService.ts @@ -34,4 +34,12 @@ export default class SeatsService extends BaseService { return this.seatsRepository.create(subscriptionUid, userUid); } + /** + * @description : Delete a seat + * @throws {Error} If seat cannot be deleted + */ + public async delete(uid: string) { + return this.seatsRepository.delete(uid); + } + } diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts index 0f311f03..3c44fd96 100644 --- a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -4,10 +4,11 @@ import { Service } from "typedi"; import { Prisma, Subscriptions } from "@prisma/client"; import SubscriptionsRepository from "@Repositories/SubscriptionsRepository"; import { Subscription } from "le-coffre-resources/dist/Admin"; +import SeatsService from "../SeatsService/SeatsService"; @Service() export default class SubscriptionsService extends BaseService { - constructor(private subscriptionsRepository: SubscriptionsRepository) { + constructor(private subscriptionsRepository: SubscriptionsRepository, private seatsService: SeatsService) { super(); } @@ -39,7 +40,18 @@ export default class SubscriptionsService extends BaseService { * @description : Modify a subscription * @throws {Error} If subscription cannot be modified */ - public async update(uid: string, subscriptionEntity: Subscription): Promise { + public async update(uid: string, subscriptionEntity: Subscription): Promise { + console.log(subscriptionEntity); + + if(subscriptionEntity.type === "STANDARD"){ + const seats = await this.seatsService.get({ where: { subscription: { uid: uid } }, orderBy: {created_at: 'asc'} }); + const seatsToKeep = subscriptionEntity.nb_seats; + const seatsToDelete = seats.slice(seatsToKeep); + + for (const seat of seatsToDelete) { + await this.seatsService.delete(seat.uid); + } + } return this.subscriptionsRepository.update(uid, subscriptionEntity); } From 21ad084112537c2d341a8200d2486a2d71e1b90a Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 11 Apr 2024 11:35:19 +0200 Subject: [PATCH 42/86] Removed console log --- .../admin/SubscriptionsService/SubscriptionsService.ts.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts index 3c44fd96..77120e70 100644 --- a/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts +++ b/src/services/admin/SubscriptionsService/SubscriptionsService.ts.ts @@ -40,9 +40,7 @@ export default class SubscriptionsService extends BaseService { * @description : Modify a subscription * @throws {Error} If subscription cannot be modified */ - public async update(uid: string, subscriptionEntity: Subscription): Promise { - console.log(subscriptionEntity); - + public async update(uid: string, subscriptionEntity: Subscription): Promise { if(subscriptionEntity.type === "STANDARD"){ const seats = await this.seatsService.get({ where: { subscription: { uid: uid } }, orderBy: {created_at: 'asc'} }); const seatsToKeep = subscriptionEntity.nb_seats; From de0d1f4e7ad2e379b70ccd189382593e7cfedcbe Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 11 Apr 2024 11:36:10 +0200 Subject: [PATCH 43/86] Removed console log --- src/app/api/admin/SubscriptionsController.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index cbf50ed8..c8f4d455 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -41,10 +41,7 @@ export default class SubscriptionsController extends ApiController { const subscriptionsEntities = await this.subscriptionsService.get(query); //Hydrate ressource with prisma entity - const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); - - console.log(subscriptions[0]?.seats); - + const subscriptions = Subscription.hydrateArray(subscriptionsEntities, { strategy: "excludeAll" }); //success this.httpSuccess(response, subscriptions); From d7e6dff5f1b9e4ed9a8438484192ee2dd5e6f159 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 12 Apr 2024 11:02:05 +0200 Subject: [PATCH 44/86] Fixed connexion --- src/app/api/idnot/UserController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 3819ed6e..980a5ee6 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -74,7 +74,6 @@ export default class UserController extends ApiController { const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { - this.httpUnauthorized(response, "User not subscribed"); isSubscribed = false; return; } From 743934120e9f0a05de1c4f54f9718d086c05f707 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 12 Apr 2024 15:09:20 +0200 Subject: [PATCH 45/86] redeploy --- src/app/api/admin/StripeController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index 5586432c..cd618d29 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -34,6 +34,7 @@ export default class StripeController extends ApiController { const stripeSession = await this.stripeService.createCheckoutSession(subscriptionEntity); this.httpCreated(response, stripeSession); + } catch (error) { this.httpInternalError(response, error); return; From 79edaaefa464622062a811b8b27438260105d543 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 12 Apr 2024 16:11:00 +0200 Subject: [PATCH 46/86] Fixed login --- src/app/api/idnot/UserController.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 980a5ee6..9c77f745 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -37,14 +37,14 @@ export default class UserController extends ApiController { if (!code) throw new Error("code is required"); - const idNotToken = await this.idNotService.getIdNotToken(code); + const idNotToken = await this.idNotService.getIdNotToken(code); if (!idNotToken) { this.httpValidationError(response, "IdNot token undefined"); return; } - const user = await this.idNotService.getOrCreateUser(idNotToken); + const user = await this.idNotService.getOrCreateUser(idNotToken); if (!user) { this.httpUnauthorized(response, "User not found"); @@ -55,7 +55,7 @@ export default class UserController extends ApiController { //Whitelist feature //Get user with contact - const prismaUser = await this.userService.getByUid(user.uid, { contact: true, role: true }); + const prismaUser = await this.userService.getByUid(user.uid, { contact: true, role: true }); if (!prismaUser) { this.httpNotFoundRequest(response, "user not found"); @@ -63,7 +63,7 @@ export default class UserController extends ApiController { } //Hydrate user to be able to use his contact - const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); + const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); if (!userHydrated.contact?.email || userHydrated.contact?.email === "") { this.httpUnauthorized(response, "Email not found"); @@ -71,14 +71,12 @@ export default class UserController extends ApiController { } let isSubscribed = false; - const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { isSubscribed = false; - return; } - - if (subscriptions[0]?.type === EType.Unlimited) { + else if (subscriptions[0]?.type === EType.Unlimited) { isSubscribed = true; } else { const hasSeat = await this.subscriptionsService.get({ From 5c7522e7bc850f70d08731c8ea1305245890e0ca Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 16 Apr 2024 10:03:15 +0200 Subject: [PATCH 47/86] redeploy --- src/app/api/admin/StripeController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index cd618d29..1d58253b 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -50,7 +50,6 @@ export default class StripeController extends ApiController { return; } const officeId: string = req.body.user.office_Id; - //add office id to request body req.body.office = { uid: officeId }; From 70815657e861f5db620d1b7e87321ae31bdd0327 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 16 Apr 2024 10:57:36 +0200 Subject: [PATCH 48/86] Updated seeder with subscriptions rules --- src/app/api/admin/StripeController.ts | 44 +++++++++--------- src/app/api/admin/SubscriptionsController.ts | 47 ++++++++++---------- src/common/databases/seeders/seeder.ts | 35 +++++++++++++++ 3 files changed, 80 insertions(+), 46 deletions(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index 1d58253b..5dd28d3a 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,7 +1,7 @@ import authHandler from "@App/middlewares/AuthHandler"; // import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; -import { Controller, Get, Post, Put } from "@ControllerPattern/index"; +import { Controller, Get, Post} from "@ControllerPattern/index"; import StripeService from "@Services/common/StripeService/StripeService"; import { validateOrReject } from "class-validator"; import { Request, Response } from "express"; @@ -41,31 +41,31 @@ export default class StripeController extends ApiController { } } - @Put("/api/v1/admin/stripe/:uid") - protected async createStripeSubscriptionUpdateCheckout(req: Request, response: Response) { - try { - const uid = req.params["uid"]; - if (!uid) { - this.httpBadRequest(response, "No uid provided"); - return; - } - const officeId: string = req.body.user.office_Id; - //add office id to request body - req.body.office = { uid: officeId }; + // @Put("/api/v1/admin/stripe/:uid") + // protected async createStripeSubscriptionUpdateCheckout(req: Request, response: Response) { + // try { + // const uid = req.params["uid"]; + // if (!uid) { + // this.httpBadRequest(response, "No uid provided"); + // return; + // } + // const officeId: string = req.body.user.office_Id; + // //add office id to request body + // req.body.office = { uid: officeId }; - //init Subscription resource with request body values - const subscriptionEntity = Subscription.hydrate(req.body, { strategy: "excludeAll" }); + // //init Subscription resource with request body values + // const subscriptionEntity = Subscription.hydrate(req.body, { strategy: "excludeAll" }); - await validateOrReject(subscriptionEntity, { groups: ["updateSubscription"], forbidUnknownValues: false }); + // await validateOrReject(subscriptionEntity, { groups: ["updateSubscription"], forbidUnknownValues: false }); - const stripeSession = await this.stripeService.createCheckoutSessionUpdate(uid, subscriptionEntity); + // const stripeSession = await this.stripeService.createCheckoutSessionUpdate(uid, subscriptionEntity); - this.httpCreated(response, stripeSession); - } catch (error) { - this.httpInternalError(response, error); - return; - } - } + // this.httpCreated(response, stripeSession); + // } catch (error) { + // this.httpInternalError(response, error); + // return; + // } + // } @Get("/api/v1/admin/stripe/:uid", [authHandler]) protected async getClientPortalSession(req: Request, response: Response) { diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index c8f4d455..3de788b2 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -8,7 +8,6 @@ import { Service } from "typedi"; import { Prisma } from "@prisma/client"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; import { Subscription } from "le-coffre-resources/dist/Admin"; -import { validateOrReject } from "class-validator"; import ObjectHydrate from "@Common/helpers/ObjectHydrate"; import roleHandler from "@App/middlewares/RolesHandler"; import authHandler from "@App/middlewares/AuthHandler"; @@ -81,29 +80,29 @@ export default class SubscriptionsController extends ApiController { } } - /** - * @description Create a new documentType - */ - @Post("/api/v1/admin/subscriptions", [authHandler, roleHandler]) - protected async post(req: Request, response: Response) { - try { - //init Subscription resource with request body values - const subscriptionEntity = Subscription.hydrate(req.body); - //validate subscription - await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); - //call service to get prisma entity - const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity); - //Hydrate ressource with prisma entity - const subscription = Subscription.hydrate(subscriptionEntityCreated, { - strategy: "excludeAll", - }); - //success - this.httpCreated(response, subscription); - } catch (error) { - this.httpInternalError(response, error); - return; - } - } + // /** + // * @description Create a new documentType + // */ + // @Post("/api/v1/admin/subscriptions", [authHandler, roleHandler]) + // protected async post(req: Request, response: Response) { + // try { + // //init Subscription resource with request body values + // const subscriptionEntity = Subscription.hydrate(req.body); + // //validate subscription + // await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + // //call service to get prisma entity + // const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity); + // //Hydrate ressource with prisma entity + // const subscription = Subscription.hydrate(subscriptionEntityCreated, { + // strategy: "excludeAll", + // }); + // //success + // this.httpCreated(response, subscription); + // } catch (error) { + // this.httpInternalError(response, error); + // return; + // } + // } /** * @description Update a subscription diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index d8f1339c..24c2997b 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -795,6 +795,41 @@ export default async function main() { updated_at: new Date(), namespace: "notary", }, + { + name: "GET subscriptions", + label: "Récupérer les abonnements", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST subscriptions", + label: "Inviter un collaborateur à l'abonnement", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "PUT subscriptions", + label: "Modifier l'abonnement", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "GET stripe", + label: "Gérer l'abonnement de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST stripe", + label: "Payer un abonnement", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, ]; const collaboratorRules = rules.filter((rule) => rule.namespace === "collaborator"); From 58665e1356adae1b223e21322d0c6bbd62d0a618 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 16 Apr 2024 16:34:17 +0200 Subject: [PATCH 49/86] New annual prices + fix switch subscription --- src/common/config/variables/Variables.ts | 8 ++++++++ src/common/webhooks/stripeWebhooks.ts | 14 ++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index a65bd265..4ff86cab 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -148,9 +148,15 @@ export class BackendVariables { @IsNotEmpty() public readonly STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID!: string; + @IsNotEmpty() + public readonly STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID!: string; + @IsNotEmpty() public readonly STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID!: string; + @IsNotEmpty() + public readonly STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID!: string; + @IsNotEmpty() public readonly STRIPE_PAYMENT_SUCCESS_URL!: string; @@ -210,7 +216,9 @@ export class BackendVariables { this.SCW_BUCKET_NAME = process.env["BUCKET_NAME"]!; this.STRIPE_SECRET_KEY = process.env["STRIPE_SECRET_KEY"]!; this.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID"]!; + this.STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID"]!; this.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID"]!; + this.STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID"]!; this.STRIPE_PAYMENT_SUCCESS_URL = process.env["STRIPE_PAYMENT_SUCCESS_URL"]!; this.STRIPE_PAYMENT_CANCEL_URL = process.env["STRIPE_PAYMENT_CANCEL_URL"]!; this.IDNOT_PROD_BASE_URL = process.env["IDNOT_PROD_BASE_URL"]!; diff --git a/src/common/webhooks/stripeWebhooks.ts b/src/common/webhooks/stripeWebhooks.ts index b0832ece..85fc45f7 100644 --- a/src/common/webhooks/stripeWebhooks.ts +++ b/src/common/webhooks/stripeWebhooks.ts @@ -24,7 +24,7 @@ export default class StripeWebhooks extends ApiController { const event = req.body; switch (event.type) { - case "invoice.payment_succeeded": + case "invoice.payment_succeeded": if (event.data.object.billing_reason !== "subscription_update") break; const stripeSubscription = await this.stripeService.getClient().subscriptions.retrieve(event.data.object.subscription); const existingSubscription = await this.subscriptionsService.get({where : {stripe_subscription_id : stripeSubscription.id}}); @@ -34,11 +34,17 @@ export default class StripeWebhooks extends ApiController { subscriptionUpdate.start_date = new Date(stripeSubscription.current_period_start * 1000); subscriptionUpdate.end_date = new Date(stripeSubscription.current_period_end * 1000); subscriptionUpdate.nb_seats = stripeSubscription.items.data[0]?.quantity; - subscriptionUpdate.type = stripeSubscription.items.data[0]?.price?.id === this.backendVariables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID ? "STANDARD" : "UNLIMITED"; - const subscriptionEntityUpdate = Subscription.hydrate(subscriptionUpdate); + if(stripeSubscription.items.data[0]?.price?.id === this.backendVariables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID || stripeSubscription.items.data[0]?.price?.id === this.backendVariables.STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID){ + subscriptionUpdate.type = "STANDARD"; + } + else{ + subscriptionUpdate.type = "UNLIMITED"; + } - await validateOrReject(subscriptionEntityUpdate, { groups: ["updateSubscription"], forbidUnknownValues: false }); + const subscriptionEntityUpdate = Subscription.hydrate(subscriptionUpdate); + + await validateOrReject(subscriptionEntityUpdate, { groups: ["updateSubscription"], forbidUnknownValues: false }); await this.subscriptionsService.update(existingSubscription[0].uid ,subscriptionEntityUpdate); From 55519a845aa86f865176b8c62a40a72c84296550 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 18 Apr 2024 09:29:21 +0200 Subject: [PATCH 50/86] RulesGroup --- .../20240417122422_rules_groups/migration.sql | 30 +++++++++++++++++++ src/common/databases/schema.prisma | 10 +++++++ 2 files changed, 40 insertions(+) create mode 100644 src/common/databases/migrations/20240417122422_rules_groups/migration.sql diff --git a/src/common/databases/migrations/20240417122422_rules_groups/migration.sql b/src/common/databases/migrations/20240417122422_rules_groups/migration.sql new file mode 100644 index 00000000..77df62a3 --- /dev/null +++ b/src/common/databases/migrations/20240417122422_rules_groups/migration.sql @@ -0,0 +1,30 @@ +-- CreateTable +CREATE TABLE "rules_groups" ( + "uid" TEXT NOT NULL, + "name" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "rules_groups_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "_RulesGroupsHasRules" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "rules_groups_uid_key" ON "rules_groups"("uid"); + +-- CreateIndex +CREATE UNIQUE INDEX "_RulesGroupsHasRules_AB_unique" ON "_RulesGroupsHasRules"("A", "B"); + +-- CreateIndex +CREATE INDEX "_RulesGroupsHasRules_B_index" ON "_RulesGroupsHasRules"("B"); + +-- AddForeignKey +ALTER TABLE "_RulesGroupsHasRules" ADD CONSTRAINT "_RulesGroupsHasRules_A_fkey" FOREIGN KEY ("A") REFERENCES "rules"("uid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_RulesGroupsHasRules" ADD CONSTRAINT "_RulesGroupsHasRules_B_fkey" FOREIGN KEY ("B") REFERENCES "rules_groups"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 9f21c34d..3d813215 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -323,9 +323,19 @@ model Rules { role Roles[] @relation("RolesHasRules") office_roles OfficeRoles[] @relation("OfficeRolesHasRules") namespace String @db.VarChar(255) @default("notary") + groups RulesGroups[] @relation("RulesGroupsHasRules") @@map("rules") } +model RulesGroups { + uid String @id @unique @default(uuid()) + name String @db.VarChar(255) + created_at DateTime? @default(now()) + updated_at DateTime? @updatedAt + rules Rules[] @relation("RulesGroupsHasRules") + @@map("rules_groups") +} + model Emails { uid String @id @unique @default(uuid()) templateName String @db.VarChar(255) From dacb9f0b90490f224a9a30ccf421f5422736637c Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 18 Apr 2024 11:31:00 +0200 Subject: [PATCH 51/86] Fixed webhooks by env --- src/common/webhooks/stripeWebhooks.ts | 29 +++++++++++++++---- .../common/StripeService/StripeService.ts | 1 + 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/common/webhooks/stripeWebhooks.ts b/src/common/webhooks/stripeWebhooks.ts index 85fc45f7..5885b85c 100644 --- a/src/common/webhooks/stripeWebhooks.ts +++ b/src/common/webhooks/stripeWebhooks.ts @@ -24,10 +24,14 @@ export default class StripeWebhooks extends ApiController { const event = req.body; switch (event.type) { - case "invoice.payment_succeeded": - if (event.data.object.billing_reason !== "subscription_update") break; + //Manage plan switch + recurring payment + case "invoice.payment_succeeded": + if (event.data.object.billing_reason !== "subscription_update" && event.data.object.billing_reason !== "subscription_cycle") break; const stripeSubscription = await this.stripeService.getClient().subscriptions.retrieve(event.data.object.subscription); + if(stripeSubscription.metadata['env'] !== this.backendVariables.ENV) break; + const existingSubscription = await this.subscriptionsService.get({where : {stripe_subscription_id : stripeSubscription.id}}); + if(!existingSubscription[0]) break; const subscriptionUpdate: any = {}; @@ -42,19 +46,30 @@ export default class StripeWebhooks extends ApiController { subscriptionUpdate.type = "UNLIMITED"; } - const subscriptionEntityUpdate = Subscription.hydrate(subscriptionUpdate); + const subscriptionEntityUpdate = Subscription.hydrate(subscriptionUpdate); - await validateOrReject(subscriptionEntityUpdate, { groups: ["updateSubscription"], forbidUnknownValues: false }); + await validateOrReject(subscriptionEntityUpdate, { groups: ["updateSubscription"], forbidUnknownValues: false }); await this.subscriptionsService.update(existingSubscription[0].uid ,subscriptionEntityUpdate); - + + //Manage subscription creation and first payment case "checkout.session.completed": if (event.data.object.status !== "complete") break; const subscription = JSON.parse(event.data.object.metadata.subscription); + const env = event.data.object.metadata.env; + + if (env !== this.backendVariables.ENV) break; subscription.stripe_subscription_id = event.data.object.subscription; + await this.stripeService.getClient().subscriptions.update(subscription.stripe_subscription_id, { + metadata: { + env: env, + }, + } + ) + const subscriptionInfo = await this.stripeService .getClient() .subscriptions.retrieve(subscription.stripe_subscription_id); @@ -65,10 +80,14 @@ export default class StripeWebhooks extends ApiController { const subscriptionEntity = Subscription.hydrate(subscription); await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); + await this.subscriptionsService.create(subscriptionEntity); break; + //Manage plan expiration case "customer.subscription.deleted": + const currentSubscription = await this.stripeService.getClient().subscriptions.retrieve(event.data.object.id); + if(currentSubscription.metadata['env'] !== this.backendVariables.ENV) break; const subscriptionToDelete = await this.subscriptionsService.get({where : {stripe_subscription_id : event.data.object.id}}); if(!subscriptionToDelete[0]) break; await this.subscriptionsService.delete(subscriptionToDelete[0].uid); diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index e8e1ee8e..8087b5a5 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -30,6 +30,7 @@ export default class StripeService { cancel_url: this.variables.APP_HOST + "/subscription/error", metadata: { subscription: JSON.stringify(subscription), + env: this.variables.ENV, }, }); From c5d83f23dceb4f58d89ac467d21ea20508f32428 Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Thu, 18 Apr 2024 11:48:13 +0200 Subject: [PATCH 52/86] :sparkles: new rsources --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97210854..4f214bbb 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.126", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.130", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", From cef7819c34c58b2d346ace13d44610ae9d1557c2 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 18 Apr 2024 12:07:26 +0200 Subject: [PATCH 53/86] New rulesGroups seeder --- src/common/databases/seeders/seeder.ts | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 24c2997b..5c007372 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -832,6 +832,39 @@ export default async function main() { }, ]; + const rulesGroups = [ + { + name: "Gestion des types d'actes et des documents", + created_at: new Date(), + updated_at: new Date(), + rules: [ + "POST deeds", + "PUT deeds", + "DELETE deeds", + ] + }, + { + name: "Gestion de l'abonnement", + created_at: new Date(), + updated_at: new Date(), + rules : [ + "GET subscriptions", + "POST subscriptions", + "PUT subscriptions", + "GET stripe", + "POST stripe", + ] + }, + { + name: "Gestion du RIB", + created_at: new Date(), + updated_at: new Date(), + rules : [ + "PUT rib" + ] + }, + ]; + const collaboratorRules = rules.filter((rule) => rule.namespace === "collaborator"); const notaryRules = [...collaboratorRules, ...rules.filter((rule) => rule.namespace === "notary")]; const adminRules = [...notaryRules, ...rules.filter((rule) => rule.namespace === "admin")]; @@ -1904,6 +1937,19 @@ export default async function main() { role.uid = roleCreated.uid; } + for (const ruleGroup of rulesGroups) { + await prisma.rulesGroups.create({ + data: { + name: ruleGroup.name, + rules: { + connect: ruleGroup.rules?.map((rule) => ({ + uid: rules.find((r) => r.name === rule)!.uid!, + })), + }, + }, + }); + } + for (const officeRole of officeRoles) { const officeRoleCreated = await prisma.officeRoles.create({ data: { From ad767c7a2ea8263e4ce665bb9402ac0142a45491 Mon Sep 17 00:00:00 2001 From: Vins Date: Thu, 18 Apr 2024 14:40:44 +0200 Subject: [PATCH 54/86] RulesGroupsController --- package.json | 2 +- src/app/api/admin/RulesGroupsController.ts | 49 +++++++++++++++++++ src/app/index.ts | 3 ++ .../repositories/RulesGroupsRepository.ts | 27 ++++++++++ .../RulesGroupsService/RulesGroupsService.ts | 20 ++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/app/api/admin/RulesGroupsController.ts create mode 100644 src/common/repositories/RulesGroupsRepository.ts create mode 100644 src/services/admin/RulesGroupsService/RulesGroupsService.ts diff --git a/package.json b/package.json index 4f214bbb..5afa8195 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.130", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.132", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", diff --git a/src/app/api/admin/RulesGroupsController.ts b/src/app/api/admin/RulesGroupsController.ts new file mode 100644 index 00000000..0d691fe2 --- /dev/null +++ b/src/app/api/admin/RulesGroupsController.ts @@ -0,0 +1,49 @@ +import { Controller, Get} from "@ControllerPattern/index"; +import { Response, Request } from "express"; +import ApiController from "@Common/system/controller-pattern/ApiController"; +import { Service } from "typedi"; +import { Prisma } from "@prisma/client"; +import { RulesGroup } from "le-coffre-resources/dist/Admin"; +import roleHandler from "@App/middlewares/RolesHandler"; +import authHandler from "@App/middlewares/AuthHandler"; +import RulesGroupsService from "@Services/admin/RulesGroupsService/RulesGroupsService"; + +@Controller() +@Service() +export default class RulesRoupsController extends ApiController { + constructor(private rulesGroupsService: RulesGroupsService) { + super(); + } + + /** + * @description Get all subscriptions + */ + @Get("/api/v1/admin/rules-groups", [authHandler, roleHandler] ) + protected async get(req: Request, response: Response) { + try { + //get query + let query: Prisma.RulesGroupsFindManyArgs = {}; + if (req.query["q"]) { + query = JSON.parse(req.query["q"] as string); + if (query.where?.uid) { + this.httpBadRequest(response, "You can't filter by uid"); + return; + } + } + + //call service to get prisma entity + const rulesGroupsEntities = await this.rulesGroupsService.get(query); + + //Hydrate ressource with prisma entity + const rulesGroups = RulesGroup.hydrateArray(rulesGroupsEntities, { strategy: "excludeAll" }); + + //success + this.httpSuccess(response, rulesGroups); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + +} diff --git a/src/app/index.ts b/src/app/index.ts index 174b9abd..1590b6f1 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -52,6 +52,8 @@ import IdNotOfficeController from "./api/idnot/OfficeController"; import SubscriptionsController from "./api/admin/SubscriptionsController"; import StripeController from "./api/admin/StripeController"; import StripeWebhooks from "@Common/webhooks/stripeWebhooks"; +import RulesGroupsController from "./api/admin/RulesGroupsController"; + /** * @description This allow to declare all controllers used in the application */ @@ -111,5 +113,6 @@ export default { Container.get(SubscriptionsController); Container.get(StripeController); Container.get(StripeWebhooks); + Container.get(RulesGroupsController); }, }; diff --git a/src/common/repositories/RulesGroupsRepository.ts b/src/common/repositories/RulesGroupsRepository.ts new file mode 100644 index 00000000..6c263cb1 --- /dev/null +++ b/src/common/repositories/RulesGroupsRepository.ts @@ -0,0 +1,27 @@ +import Database from "@Common/databases/database"; +import BaseRepository from "@Repositories/BaseRepository"; +import { Service } from "typedi"; +import { Prisma } from "@prisma/client"; + +@Service() +export default class RulesGroupsRepository extends BaseRepository { + constructor(private database: Database) { + super(); + } + protected get model() { + return this.database.getClient().rulesGroups; + } + protected get instanceDb() { + return this.database.getClient(); + } + + /** + * @description : Find many subscriptions + */ + public async findMany(query: Prisma.RulesGroupsFindManyArgs) { + query.take = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows); + if (!query.include) return this.model.findMany({ ...query }); + return this.model.findMany({ ...query }); + } + +} diff --git a/src/services/admin/RulesGroupsService/RulesGroupsService.ts b/src/services/admin/RulesGroupsService/RulesGroupsService.ts new file mode 100644 index 00000000..64dd738c --- /dev/null +++ b/src/services/admin/RulesGroupsService/RulesGroupsService.ts @@ -0,0 +1,20 @@ +import BaseService from "@Services/BaseService"; +import "reflect-metadata"; +import { Service } from "typedi"; +import { Prisma } from "@prisma/client"; +import RulesGroupsRepository from "@Repositories/RulesGroupsRepository"; + +@Service() +export default class RulesGroupsService extends BaseService { + constructor(private rulesGroupsRepository: RulesGroupsRepository) { + super(); + } + + /** + * @description : Get all subscriptions + * @throws {Error} If subscriptions cannot be get + */ + public get(query: Prisma.RulesGroupsFindManyArgs) { + return this.rulesGroupsRepository.findMany(query); + } +} From bbecec09a3c5484df5418ae14271185b560aa189 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 19 Apr 2024 11:47:55 +0200 Subject: [PATCH 55/86] Rules and user access --- src/app/api/admin/OfficeRolesController.ts | 14 ++++ src/app/api/admin/StripeController.ts | 31 +------- src/app/api/admin/SubscriptionsController.ts | 78 ++------------------ src/app/api/idnot/UserController.ts | 2 +- src/app/api/notary/OfficeRibController.ts | 7 +- src/common/databases/seeders/seeder.ts | 21 ++++++ src/common/emails/EmailBuilder.ts | 2 +- 7 files changed, 50 insertions(+), 105 deletions(-) diff --git a/src/app/api/admin/OfficeRolesController.ts b/src/app/api/admin/OfficeRolesController.ts index cb3162bd..816d4cbb 100644 --- a/src/app/api/admin/OfficeRolesController.ts +++ b/src/app/api/admin/OfficeRolesController.ts @@ -75,6 +75,20 @@ export default class OfficeRolesController extends ApiController { //init IOfficeRole resource with request body values const officeRoleEntity = OfficeRole.hydrate(req.body); + const allRules = await this.rulesService.get({ + where: { + OR: [ + { + namespace: "notary", + }, + { + namespace: "collaborator", + }, + ], + }, + }); + + officeRoleEntity.rules = allRules; //validate officeRole await validateOrReject(officeRoleEntity, { groups: ["createOfficeRole"] }); diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index 5dd28d3a..24b778f2 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -1,4 +1,5 @@ import authHandler from "@App/middlewares/AuthHandler"; +import ruleHandler from "@App/middlewares/RulesHandler"; // import roleHandler from "@App/middlewares/RolesHandler"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Controller, Get, Post} from "@ControllerPattern/index"; @@ -18,7 +19,7 @@ export default class StripeController extends ApiController { /** * @description Create a new checkout session */ - @Post("/api/v1/admin/stripe", [authHandler]) + @Post("/api/v1/admin/stripe", [authHandler, ruleHandler]) protected async createStripeSubscriptionCheckout(req: Request, response: Response) { try { const officeId: string = req.body.user.office_Id; @@ -41,33 +42,7 @@ export default class StripeController extends ApiController { } } - // @Put("/api/v1/admin/stripe/:uid") - // protected async createStripeSubscriptionUpdateCheckout(req: Request, response: Response) { - // try { - // const uid = req.params["uid"]; - // if (!uid) { - // this.httpBadRequest(response, "No uid provided"); - // return; - // } - // const officeId: string = req.body.user.office_Id; - // //add office id to request body - // req.body.office = { uid: officeId }; - - // //init Subscription resource with request body values - // const subscriptionEntity = Subscription.hydrate(req.body, { strategy: "excludeAll" }); - - // await validateOrReject(subscriptionEntity, { groups: ["updateSubscription"], forbidUnknownValues: false }); - - // const stripeSession = await this.stripeService.createCheckoutSessionUpdate(uid, subscriptionEntity); - - // this.httpCreated(response, stripeSession); - // } catch (error) { - // this.httpInternalError(response, error); - // return; - // } - // } - - @Get("/api/v1/admin/stripe/:uid", [authHandler]) + @Get("/api/v1/admin/stripe/:uid", [authHandler, ruleHandler]) protected async getClientPortalSession(req: Request, response: Response) { try { const uid = req.params["uid"]; diff --git a/src/app/api/admin/SubscriptionsController.ts b/src/app/api/admin/SubscriptionsController.ts index 3de788b2..c0f49241 100644 --- a/src/app/api/admin/SubscriptionsController.ts +++ b/src/app/api/admin/SubscriptionsController.ts @@ -2,16 +2,13 @@ import { Controller, Get, Post, Put } from "@ControllerPattern/index"; import { Response, Request } from "express"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; -// import authHandler from "@App/middlewares/AuthHandler"; -// import roleHandler from "@App/middlewares/RolesHandler"; -// import ruleHandler from "@App/middlewares/RulesHandler"; import { Prisma } from "@prisma/client"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; import { Subscription } from "le-coffre-resources/dist/Admin"; import ObjectHydrate from "@Common/helpers/ObjectHydrate"; -import roleHandler from "@App/middlewares/RolesHandler"; import authHandler from "@App/middlewares/AuthHandler"; import EmailBuilder from "@Common/emails/EmailBuilder"; +import ruleHandler from "@App/middlewares/RulesHandler"; @Controller() @Service() @@ -23,7 +20,7 @@ export default class SubscriptionsController extends ApiController { /** * @description Get all subscriptions */ - @Get("/api/v1/admin/subscriptions", [authHandler, roleHandler]) + @Get("/api/v1/admin/subscriptions", [authHandler, ruleHandler]) protected async get(req: Request, response: Response) { try { //get query @@ -51,9 +48,9 @@ export default class SubscriptionsController extends ApiController { } /** - * @description Get a specific documentType by uid + * @description Get a specific subscription by uid */ - @Get("/api/v1/admin/subscriptions/:uid", [authHandler, roleHandler]) + @Get("/api/v1/admin/subscriptions/:uid", [authHandler, ruleHandler]) protected async getOneByUid(req: Request, response: Response) { try { const uid = req.params["uid"]; @@ -80,34 +77,10 @@ export default class SubscriptionsController extends ApiController { } } - // /** - // * @description Create a new documentType - // */ - // @Post("/api/v1/admin/subscriptions", [authHandler, roleHandler]) - // protected async post(req: Request, response: Response) { - // try { - // //init Subscription resource with request body values - // const subscriptionEntity = Subscription.hydrate(req.body); - // //validate subscription - // await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false }); - // //call service to get prisma entity - // const subscriptionEntityCreated = await this.subscriptionsService.create(subscriptionEntity); - // //Hydrate ressource with prisma entity - // const subscription = Subscription.hydrate(subscriptionEntityCreated, { - // strategy: "excludeAll", - // }); - // //success - // this.httpCreated(response, subscription); - // } catch (error) { - // this.httpInternalError(response, error); - // return; - // } - // } - /** * @description Update a subscription */ - @Put("/api/v1/admin/subscriptions/:uid", [authHandler, roleHandler]) + @Put("/api/v1/admin/subscriptions/:uid", [authHandler, ruleHandler]) protected async put(req: Request, response: Response) { try { const uid = req.params["uid"]; @@ -147,7 +120,7 @@ export default class SubscriptionsController extends ApiController { * @description Invite collaborators to a subscription */ - @Post("/api/v1/admin/subscriptions/invite", [authHandler, roleHandler]) + @Post("/api/v1/admin/subscriptions/invite", [authHandler, ruleHandler]) protected async inviteCollaborators(req: Request, response: Response) { try { //get email list from body @@ -168,43 +141,4 @@ export default class SubscriptionsController extends ApiController { return; } } - - // /** - // * @description Update a subscription seats - // */ - // @Put("/api/v1/admin/subscriptions/:uid/seats", [authHandler, roleHandler]) - // protected async updateSubscriptionSeats(req: Request, response: Response) { - // try { - // const uid = req.params["uid"]; - // if (!uid) { - // this.httpBadRequest(response, "No uid provided"); - // return; - // } - - // const subscriptionFound = await this.subscriptionsService.getByUid(uid); - - // if (!subscriptionFound) { - // this.httpNotFoundRequest(response, "subscription not found"); - // return; - // } - - // //init Subscription resource with request body values - // const seatEntities = Seat.hydrateArray(req.body); - - // //call service to get prisma entity - // const subscriptionEntityUpdated = await this.subscriptionsService.update(uid, subscriptionEntity); - - // //Hydrate ressource with prisma entity - // const subscription = Subscription.hydrate(subscriptionEntityUpdated, { - // strategy: "excludeAll", - // }); - - // //success - // this.httpSuccess(response, subscription); - - // } catch (error) { - // this.httpInternalError(response, error); - // return; - // } - // } } diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 9c77f745..44a62b42 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -100,7 +100,7 @@ export default class UserController extends ApiController { } } - if (userHydrated.role?.name === "admin") { + if (userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin") { isSubscribed = true; } diff --git a/src/app/api/notary/OfficeRibController.ts b/src/app/api/notary/OfficeRibController.ts index 3a0fb9ae..035919e4 100644 --- a/src/app/api/notary/OfficeRibController.ts +++ b/src/app/api/notary/OfficeRibController.ts @@ -7,6 +7,7 @@ import OfficerRibService from "@Services/common/OfficeRibService/OfficeRibServic import authHandler from "@App/middlewares/AuthHandler"; import OfficesService from "@Services/notary/OfficesService/OfficesService"; import { Office as OfficeResource } from "le-coffre-resources/dist/Notary"; +import ruleHandler from "@App/middlewares/RulesHandler"; @Controller() @Service() @@ -15,7 +16,7 @@ export default class OfficeRibController extends ApiController { super(); } - @Get("/api/v1/notary/office/rib", [authHandler]) + @Get("/api/v1/notary/rib", [authHandler, ruleHandler]) protected async getRibStream(req: Request, response: Response) { const officeId: string = req.body.user.office_Id; if (!officeId) throw new Error("No officeId provided"); @@ -43,7 +44,7 @@ export default class OfficeRibController extends ApiController { } } - @Post("/api/v1/notary/office/rib", [authHandler]) + @Post("/api/v1/notary/rib", [authHandler, ruleHandler]) protected async post(req: Request, response: Response) { try { const officeId: string = req.body.user.office_Id; @@ -82,7 +83,7 @@ export default class OfficeRibController extends ApiController { } } - @Delete("/api/v1/notary/office/rib", [authHandler]) + @Delete("/api/v1/notary/rib", [authHandler, ruleHandler]) protected async delete(req: Request, response: Response) { try { const officeId: string = req.body.user.office_Id; diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 5c007372..3f5b6b48 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -795,6 +795,27 @@ export default async function main() { updated_at: new Date(), namespace: "notary", }, + { + name: "GET rib", + label: "Lire le RIB de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST rib", + label: "Déposer le RIB de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "DELETE rib", + label: "Supprimer le RIB de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, { name: "GET subscriptions", label: "Récupérer les abonnements", diff --git a/src/common/emails/EmailBuilder.ts b/src/common/emails/EmailBuilder.ts index 59aefda8..5dcfb7b8 100644 --- a/src/common/emails/EmailBuilder.ts +++ b/src/common/emails/EmailBuilder.ts @@ -132,7 +132,7 @@ export default class EmailBuilder { nbTrySend: null, lastTrySendDate: null, }); - this.mailchimpService.sendEmails(); + // this.mailchimpService.sendEmails(); }); From 54af3cb2fb2dbf41f19e94e79feb7fffbea04a7c Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 19 Apr 2024 11:48:23 +0200 Subject: [PATCH 56/86] Seeder update for rib --- src/common/databases/seeders/seeder.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 3f5b6b48..706fe62e 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -881,7 +881,10 @@ export default async function main() { created_at: new Date(), updated_at: new Date(), rules : [ - "PUT rib" + "PUT rib", + "GET rib", + "POST rib", + "DELETE rib", ] }, ]; From 8254d33b35e56e3cd9d102df5e07efd4a4760648 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 19 Apr 2024 14:36:06 +0200 Subject: [PATCH 57/86] RulesGroups wording seeder --- src/common/databases/seeders/seeder.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 706fe62e..eb0f5efa 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -855,7 +855,7 @@ export default async function main() { const rulesGroups = [ { - name: "Gestion des types d'actes et des documents", + name: "Gestion des matrices d'actes et des documents", created_at: new Date(), updated_at: new Date(), rules: [ @@ -877,7 +877,7 @@ export default async function main() { ] }, { - name: "Gestion du RIB", + name: "Intégration du RIB", created_at: new Date(), updated_at: new Date(), rules : [ From d5ab1a2b2f0661e90f85bb157e6baf74924e656c Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 19 Apr 2024 15:11:21 +0200 Subject: [PATCH 58/86] Get stripe subscription by uid --- src/app/api/admin/StripeController.ts | 20 +++++++- .../common/StripeService/StripeService.ts | 50 +------------------ 2 files changed, 21 insertions(+), 49 deletions(-) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index 24b778f2..ed227eb6 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -42,7 +42,25 @@ export default class StripeController extends ApiController { } } - @Get("/api/v1/admin/stripe/:uid", [authHandler, ruleHandler]) + @Get("/api/v1/admin/stripe/:uid") + protected async getStripeSubscriptionByUid(req: Request, response: Response) { + try { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const stripe_subscription = await this.stripeService.getStripeSubscriptionByUid(uid); + + this.httpSuccess(response, stripe_subscription); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + @Get("/api/v1/admin/stripe/:uid/client-portal", [authHandler, ruleHandler]) protected async getClientPortalSession(req: Request, response: Response) { try { const uid = req.params["uid"]; diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index 8087b5a5..9bda32b9 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -38,54 +38,8 @@ export default class StripeService { } - public async createCheckoutSessionUpdate(uid: string, subscription: Subscription) { - - - - // return this.client.checkout.sessions.create({ - // mode: "payment", - // payment_method_types: ["card", "paypal"], - // billing_address_collection: "auto", - // success_url: this.variables.APP_HOST + "/subscription/success", - // cancel_url: this.variables.APP_HOST + "/subscription/error", - // metadata: { - // subscription: JSON.stringify(subscription), - // }, - // }); - - // const priceId = - // subscription.type === "STANDARD" - // ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID - // : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID; - - // return this.client.checkout.sessions.create({ - // mode: "subscription", - // payment_method_types: ["card", "paypal"], - // billing_address_collection: "auto", - // line_items: [ - // { - // price: priceId, - // quantity: subscription.type === "STANDARD" ? subscription.nb_seats : 1, - // }, - // ], - // success_url: this.variables.APP_HOST + "/subscription/success", - // cancel_url: this.variables.APP_HOST + "/subscription/error", - // metadata: { - // subscription: JSON.stringify(subscription), - // }, - // }); - // const subscriptions = await this.client.subscriptions.retrieve(uid); - // const itemId = subscriptions.items.data[0]?.id; - - // return await this.client.subscriptions.update(uid, { - // items: [ - // { - // id: itemId, - // price: priceId, - // quantity: subscription.nb_seats, - // }, - // ], - // }); + public async getStripeSubscriptionByUid(subscriptionId: string) { + return await this.client.subscriptions.retrieve(subscriptionId); } public async createClientPortalSession(subscriptionId: string) { From c1a3fb68c460bb7722523c1de092bf001613b353 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 19 Apr 2024 15:40:22 +0200 Subject: [PATCH 59/86] Changed seeder for RulesGroups deedtypes --- src/common/databases/seeders/seeder.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index eb0f5efa..067cbf8f 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -859,9 +859,18 @@ export default async function main() { created_at: new Date(), updated_at: new Date(), rules: [ + "GET deeds", "POST deeds", "PUT deeds", "DELETE deeds", + "GET document-types", + "DELETE deed-types", + "PUT deed-types", + "DELETE document-types", + "GET deed-types", + "POST document-types", + "POST deed-types", + "PUT document-types", ] }, { From 4abe5822f2fa08692caf6fc8ff6ed88f250b07bf Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 19 Apr 2024 15:42:05 +0200 Subject: [PATCH 60/86] Fixed seeder --- src/common/databases/seeders/seeder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 067cbf8f..160fe314 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -859,7 +859,6 @@ export default async function main() { created_at: new Date(), updated_at: new Date(), rules: [ - "GET deeds", "POST deeds", "PUT deeds", "DELETE deeds", From a2a9370e18336e69ab53145e59c24567742947b9 Mon Sep 17 00:00:00 2001 From: Vins Date: Mon, 22 Apr 2024 11:18:03 +0200 Subject: [PATCH 61/86] Fix rules --- src/app/api/admin/OfficeRolesController.ts | 30 +- src/common/databases/seeders/seeder.ts | 83 +- src/common/databases/seeders/seederOld.ts | 2194 ++++++++++++++++++++ 3 files changed, 2252 insertions(+), 55 deletions(-) create mode 100644 src/common/databases/seeders/seederOld.ts diff --git a/src/app/api/admin/OfficeRolesController.ts b/src/app/api/admin/OfficeRolesController.ts index 816d4cbb..649e4ade 100644 --- a/src/app/api/admin/OfficeRolesController.ts +++ b/src/app/api/admin/OfficeRolesController.ts @@ -29,7 +29,7 @@ export default class OfficeRolesController extends ApiController { let query: Prisma.OfficeRolesFindManyArgs = {}; if (req.query["q"]) { query = JSON.parse(req.query["q"] as string); - if(query.where?.uid) { + if (query.where?.uid) { this.httpBadRequest(response, "You can't filter by uid"); return; } @@ -79,10 +79,7 @@ export default class OfficeRolesController extends ApiController { where: { OR: [ { - namespace: "notary", - }, - { - namespace: "collaborator", + namespace: "global", }, ], }, @@ -128,23 +125,28 @@ export default class OfficeRolesController extends ApiController { } if (req.body.rules) { + console.log(req.body.rules); + const allRules = await this.rulesService.get({ where: { - OR: [ - { - namespace: "notary", - }, - { - namespace: "collaborator", - }, - ], + namespace: "global", + }, + }); + + const specificRules = await this.rulesService.get({ + where: { + namespace: "collaborator", }, }); req.body.rules = req.body.rules.filter((rule: any) => { - const ruleFound = allRules.find((r) => r.uid === rule.uid && (r.namespace === "notary" || r.namespace === "collaborator")); + const ruleFound = specificRules.find( + (r) => r.uid === rule.uid && (r.namespace === "collaborator"), + ); return ruleFound; }); + + req.body.rules = req.body.rules.concat(allRules); } //init IOfficeRole resource with request body values const officeRoleEntity = OfficeRole.hydrate(req.body); diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 160fe314..70b74df4 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -527,28 +527,28 @@ export default async function main() { label: "Lecture des utilisateurs", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET offices", label: "Afficher des offices", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET customers", label: "Afficher des clients", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET deeds", label: "Voir des types d'acte", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET deed-types", @@ -562,7 +562,7 @@ export default async function main() { label: "Afficher des documents", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET document-types", @@ -576,112 +576,112 @@ export default async function main() { label: "Lecture des fichiers", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET folders", label: "Afficher les dossiers", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET roles", label: "Afficher les rôles", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET rules", label: "Afficher les droits", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "GET office-roles", label: "Lecture des rôles d'office", created_at: new Date(), updated_at: new Date(), - namespace: "collaborator", + namespace: "global", }, { name: "POST deeds", label: "Créer un template de type d'acte", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "PUT deeds", label: "Modifier un type d'acte", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "DELETE deeds", label: "Supprimer des types d'actes", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "POST folders", label: "Créer un dossier", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "PUT folders", label: "Modifier des dossiers", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "DELETE folders", label: "Supprimer un dossier vide", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "POST documents", label: "Demander des documents à un client", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "PUT documents", label: "Valider des documents", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "DELETE documents", label: "Supprimer un document demandé", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "POST customers", label: "Créer des clients", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "PUT customers", label: "Modifier des clients", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "DELETE customers", @@ -695,56 +695,56 @@ export default async function main() { label: "Ancrer un dossier", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "GET anchors", label: "Vérifier l'ancrage un dossier", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "global", }, { name: "POST deed-types", label: "Création des types d'actes", created_at: new Date(), updated_at: new Date(), - namespace: "admin", + namespace: "collaborator", }, { name: "PUT deed-types", label: "Modification des types d'actes", created_at: new Date(), updated_at: new Date(), - namespace: "admin", + namespace: "collaborator", }, { name: "DELETE deed-types", label: "Suppression des types d'actes", created_at: new Date(), updated_at: new Date(), - namespace: "admin", + namespace: "collaborator", }, { name: "POST document-types", label: "Création des types de documents", created_at: new Date(), updated_at: new Date(), - namespace: "admin", + namespace: "collaborator", }, { name: "PUT document-types", label: "Modification des types de documents", created_at: new Date(), updated_at: new Date(), - namespace: "admin", + namespace: "collaborator", }, { name: "DELETE document-types", label: "Suppression des types de documents", created_at: new Date(), updated_at: new Date(), - namespace: "admin", + namespace: "collaborator", }, { name: "POST office-roles", @@ -793,63 +793,63 @@ export default async function main() { label: "Editer le RIB de l'office", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "GET rib", label: "Lire le RIB de l'office", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "POST rib", label: "Déposer le RIB de l'office", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "DELETE rib", label: "Supprimer le RIB de l'office", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "GET subscriptions", label: "Récupérer les abonnements", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "POST subscriptions", label: "Inviter un collaborateur à l'abonnement", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "PUT subscriptions", label: "Modifier l'abonnement", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "GET stripe", label: "Gérer l'abonnement de l'office", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, { name: "POST stripe", label: "Payer un abonnement", created_at: new Date(), updated_at: new Date(), - namespace: "notary", + namespace: "collaborator", }, ]; @@ -899,7 +899,8 @@ export default async function main() { const collaboratorRules = rules.filter((rule) => rule.namespace === "collaborator"); const notaryRules = [...collaboratorRules, ...rules.filter((rule) => rule.namespace === "notary")]; - const adminRules = [...notaryRules, ...rules.filter((rule) => rule.namespace === "admin")]; + const globalRules = [...notaryRules, ...rules.filter((rule) => rule.namespace === "global")]; + const adminRules = [...globalRules, ...rules.filter((rule) => rule.namespace === "admin")]; const superAdminRules = [...adminRules, ...rules.filter((rule) => rule.namespace === "super-admin")]; const roles: Role[] = [ @@ -939,14 +940,14 @@ export default async function main() { created_at: new Date(), updated_at: new Date(), office: offices[0]!, - rules: notaryRules, + rules: globalRules, }, { name: "Collaborateur", created_at: new Date(), updated_at: new Date(), office: offices[0]!, - rules: notaryRules, + rules: globalRules, }, ]; diff --git a/src/common/databases/seeders/seederOld.ts b/src/common/databases/seeders/seederOld.ts new file mode 100644 index 00000000..160fe314 --- /dev/null +++ b/src/common/databases/seeders/seederOld.ts @@ -0,0 +1,2194 @@ +import { ECivility, ECustomerStatus, EFolderStatus, EOfficeStatus, Prisma, PrismaClient } from "@prisma/client"; +import User, { + Address, + Contact, + Customer, + Deed, + DeedType, + DocumentType, + Office, + OfficeFolder, + OfficeRole, + Role, + Rule, +} from "le-coffre-resources/dist/SuperAdmin"; + +import "module-alias/register"; + +export default async function main() { + try { + const prisma = new PrismaClient(); + + const idNot3 = "rleenrenlnr"; + const idNot4 = "ljfeflecnmd"; + const idNot5 = "rflrefrjf"; + const idNot6 = "er3ojfdlfnd"; + + const addresses: Address[] = [ + { + address: "template", + city: "template", + zip_code: 0o00, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Pierre Emillion", + city: "Pacé", + zip_code: 35740, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Pierre Charles", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Pierre Pologne", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Pierre Marcel", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Pierre Jacques", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Pierre Pascal", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Maxime Henry", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Maxime Francis", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Avenue Paul Roger", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Avenue Paul Franck", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Avenue Paul Maréchal", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Avenue Marcel Denis", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Place Alexandre", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Place Alexandre Jacques", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Place Alexandre 2", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue du livre", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Place de la joie", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Paul Henry", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + { + address: "Rue Marcelin", + city: "Rennes", + zip_code: 35000, + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const contacts: Contact[] = [ + { + address: addresses[0], + first_name: "Angela", + last_name: "Dubois", + email: "angela.dubois@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[1], + first_name: "Maxime", + last_name: "Lalo", + email: "maxime.lalo@smart-chain.fr", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[2], + first_name: "Vincent", + last_name: "Alamelle", + email: "vincent.alamelle@smart-chain.fr", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[3], + first_name: "Melissa", + last_name: "Desde", + email: "melissa.desde@smart-chain.fr", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[4], + first_name: "Maxime", + last_name: "Leroy", + email: "maxime.leroy@hotmail.fr", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[5], + first_name: "Paul", + last_name: "Dupont", + email: "paul.dupont@outlook.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[6], + first_name: "Jean", + last_name: "Dubignot", + email: "jean.dubignot@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[7], + first_name: "Vincent", + last_name: "Martin", + email: "vincent.martin@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[8], + first_name: "Lucie", + last_name: "Chevalier", + email: "lucie.chevalier@outlook.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[9], + first_name: "Sébastien", + last_name: "Dubois", + email: "sebastien.dubois@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[10], + first_name: "Mathilde", + last_name: "Durand", + email: "mathilde.durand@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[11], + first_name: "Antoine", + last_name: "Bernard", + email: "antoine.bernard@outlook.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[12], + first_name: "Camille", + last_name: "Laurent", + email: "camille.laurent@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[13], + first_name: "Julien", + last_name: "Mercier", + email: "julien.mercier@hotmail.fr", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[14], + first_name: "Charlotte", + last_name: "Lefebvre", + email: "charlotte.lefebvre@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[15], + first_name: "Caroline", + last_name: "Pallut", + email: "caroline.pallut@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[16], + first_name: "Nadège", + last_name: "Gauchet", + email: "nedege.gauchet@outlook.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[17], + first_name: "Matthieu", + last_name: "Bougeard", + email: "matthieu.bougeard@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + { + address: addresses[18], + first_name: "Cécile", + last_name: "Celton", + email: "cecile.celton@outlook.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.FEMALE, + }, + { + address: addresses[19], + first_name: "Gwendal", + last_name: "Texier", + email: "gwendal.texier@gmail.com", + phone_number: "+33785186013", + cell_phone_number: "+33785186013", + birthdate: null, + created_at: new Date(), + updated_at: new Date(), + civility: ECivility.MALE, + }, + ]; + + const offices: Office[] = [ + { + idNot: "0000", + name: "Office Template", + crpcen: "0000", + address: addresses[0], + created_at: new Date(), + updated_at: new Date(), + office_status: EOfficeStatus.ACTIVATED, + }, + ]; + + const customers: Customer[] = [ + { + contact: contacts[0], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[5], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[6], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[7], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[8], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[9], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[10], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[11], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[12], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[13], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[14], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[15], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[16], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[17], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[18], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + { + contact: contacts[19], + created_at: new Date(), + updated_at: new Date(), + status: ECustomerStatus.PENDING, + }, + ]; + + const rules: Rule[] = [ + { + name: "GET users", + label: "Lecture des utilisateurs", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET offices", + label: "Afficher des offices", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET customers", + label: "Afficher des clients", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET deeds", + label: "Voir des types d'acte", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET deed-types", + label: "Lecture des types d'actes", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET documents", + label: "Afficher des documents", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET document-types", + label: "Lecture des types de documents", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET files", + label: "Lecture des fichiers", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET folders", + label: "Afficher les dossiers", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET roles", + label: "Afficher les rôles", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET rules", + label: "Afficher les droits", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "GET office-roles", + label: "Lecture des rôles d'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "collaborator", + }, + { + name: "POST deeds", + label: "Créer un template de type d'acte", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "PUT deeds", + label: "Modifier un type d'acte", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "DELETE deeds", + label: "Supprimer des types d'actes", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST folders", + label: "Créer un dossier", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "PUT folders", + label: "Modifier des dossiers", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "DELETE folders", + label: "Supprimer un dossier vide", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST documents", + label: "Demander des documents à un client", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "PUT documents", + label: "Valider des documents", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "DELETE documents", + label: "Supprimer un document demandé", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST customers", + label: "Créer des clients", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "PUT customers", + label: "Modifier des clients", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "DELETE customers", + label: "Supprimer un client", + created_at: new Date(), + updated_at: new Date(), + namespace: "super-admin", + }, + { + name: "POST anchors", + label: "Ancrer un dossier", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "GET anchors", + label: "Vérifier l'ancrage un dossier", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST deed-types", + label: "Création des types d'actes", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "PUT deed-types", + label: "Modification des types d'actes", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "DELETE deed-types", + label: "Suppression des types d'actes", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "POST document-types", + label: "Création des types de documents", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "PUT document-types", + label: "Modification des types de documents", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "DELETE document-types", + label: "Suppression des types de documents", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "POST office-roles", + label: "Création des rôles d'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "PUT office-roles", + label: "Modification des rôles d'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "DELETE office-roles", + label: "Suppression des rôles d'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "PUT users", + label: "Modification des utilisateurs", + created_at: new Date(), + updated_at: new Date(), + namespace: "admin", + }, + { + name: "DELETE office-roles", + label: "Suppression des rôles d'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "super-admin", + }, + { + name: "PUT offices", + label: "Modification des offices", + created_at: new Date(), + updated_at: new Date(), + namespace: "super-admin", + }, + { + name: "PUT rib", + label: "Editer le RIB de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "GET rib", + label: "Lire le RIB de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST rib", + label: "Déposer le RIB de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "DELETE rib", + label: "Supprimer le RIB de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "GET subscriptions", + label: "Récupérer les abonnements", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST subscriptions", + label: "Inviter un collaborateur à l'abonnement", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "PUT subscriptions", + label: "Modifier l'abonnement", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "GET stripe", + label: "Gérer l'abonnement de l'office", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + { + name: "POST stripe", + label: "Payer un abonnement", + created_at: new Date(), + updated_at: new Date(), + namespace: "notary", + }, + ]; + + const rulesGroups = [ + { + name: "Gestion des matrices d'actes et des documents", + created_at: new Date(), + updated_at: new Date(), + rules: [ + "POST deeds", + "PUT deeds", + "DELETE deeds", + "GET document-types", + "DELETE deed-types", + "PUT deed-types", + "DELETE document-types", + "GET deed-types", + "POST document-types", + "POST deed-types", + "PUT document-types", + ] + }, + { + name: "Gestion de l'abonnement", + created_at: new Date(), + updated_at: new Date(), + rules : [ + "GET subscriptions", + "POST subscriptions", + "PUT subscriptions", + "GET stripe", + "POST stripe", + ] + }, + { + name: "Intégration du RIB", + created_at: new Date(), + updated_at: new Date(), + rules : [ + "PUT rib", + "GET rib", + "POST rib", + "DELETE rib", + ] + }, + ]; + + const collaboratorRules = rules.filter((rule) => rule.namespace === "collaborator"); + const notaryRules = [...collaboratorRules, ...rules.filter((rule) => rule.namespace === "notary")]; + const adminRules = [...notaryRules, ...rules.filter((rule) => rule.namespace === "admin")]; + const superAdminRules = [...adminRules, ...rules.filter((rule) => rule.namespace === "super-admin")]; + + const roles: Role[] = [ + { + name: "super-admin", + label: "Super administrateur", + created_at: new Date(), + updated_at: new Date(), + rules: superAdminRules, + }, + { + name: "admin", + label: "Administrateur", + created_at: new Date(), + updated_at: new Date(), + rules: adminRules, + }, + { + name: "notary", + label: "Notaire", + created_at: new Date(), + updated_at: new Date(), + rules: [], + }, + { + name: "default", + label: "Utilisateur", + created_at: new Date(), + updated_at: new Date(), + rules: [], + }, + ]; + + const officeRoles: OfficeRole[] = [ + { + name: "Notaire", + created_at: new Date(), + updated_at: new Date(), + office: offices[0]!, + rules: notaryRules, + }, + { + name: "Collaborateur", + created_at: new Date(), + updated_at: new Date(), + office: offices[0]!, + rules: notaryRules, + }, + ]; + + const users: User[] = [ + { + created_at: new Date(), + updated_at: new Date(), + idNot: idNot3, + contact: contacts[1], + office_membership: offices[0], + role: roles[1], + }, + { + created_at: new Date(), + updated_at: new Date(), + idNot: idNot4, + contact: contacts[2], + office_membership: offices[0], + role: roles[2], + office_role: officeRoles[2], + }, + { + created_at: new Date(), + updated_at: new Date(), + idNot: idNot5, + contact: contacts[3], + office_membership: offices[0], + role: roles[1], + office_role: officeRoles[3], + }, + { + created_at: new Date(), + updated_at: new Date(), + idNot: idNot6, + contact: contacts[4], + office_membership: offices[0], + role: roles[3], + }, + ]; + + const documentTypes: DocumentType[] = [ + { + archived_at: null, + name: "Carte Nationale d'Identité recto-verso", + office: offices[0], + private_description: "Document d'identité", + public_description: "Document d'identité", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Contrat de mariage", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Convention de PACS", + office: offices[0], + private_description: " ", + public_description: "Avec précision du régime", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Titre de propriété", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Cahier des charges du lotissement", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Règlement du lotissement", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Cahier de prescriptions architecturales", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Statuts de l'association syndicale libre", + office: offices[0], + private_description: " ", + public_description: "Avec nom et adresse du président", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Etat descriptif de division et règlement de copropriété", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Etat descriptif de division et règlement de copropriété modificatifs", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Carnet d'entretien", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Trois derniers PV D'AG", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Trois derniers relevés de charge", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Bail ou congé délivré", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Certificat d'assainissement", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Facture ou contrat d'electricité", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Facture ou contrat de gaz", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Loi carrez", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Etat parasitaire", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Amiante", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Facture des travaux", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Garanties décénales des entreprises", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "PV de réception des travaux", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Permis de construire et modificatifs", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Déclaration attestation l'achèvement des travaux", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Attestation de non opposition", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Assurance décenale", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Dommage ouvrage", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Dernière facture ramonage", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Liste du mobilier", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "RIB daté et signé", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Extrait KBIS et statuts de la société", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Document arpentage et plan de division", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Accord de prêt", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Offre de prêt", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Attestation dépôt de permis de construire", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Permis de construire", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Extrait acte de décès", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Fiche de renseignements d'états civils", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Donation entre epoux", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Testament", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Liste des banques avce un compte bancaire", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Contrat d'assurance-vie", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Liste des caisses versant une retraite ou une pension", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Dernière déclaration de revenus", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Avis d'imposition (revenus et ISF)", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Avis d'imposition (foncier et habitation)", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Liste des emprunts en cours", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Actes de donation", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Déclarations de dons faites aux impôts", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Actes de succession", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Actes de vente de biens", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Factures de travaux", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Bail de location", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Nom et adresse du Syndic", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Liste des autres biens", + office: offices[0], + private_description: " ", + public_description: + "Exemple: PEE, PER, parts de société, bijoux de valeur et oeuvres d'art, fonds de commerce, fonds artisanal, entreprise commerciale, exploitation agricole...", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Caution, Prestation compensatoire, Pension alimentaire", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Titre de propriété des biens", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Tableaux d'amortissement des prêts immobiliers", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Cartes grises et cotes ARGUS des véhicules", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Etat des avoirs bancaires", + office: offices[0], + private_description: " ", + public_description: "Joints ou individuels au jour de la séparation", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Avoirs mobiliers", + office: offices[0], + private_description: " ", + public_description: + "arrêtés à la date de la séparation (relevés de comptes épargne entreprise, épargne retraite, assurance-vie...)", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Livret de famille", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Dernières régularisations de charges", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Diagnostic de Performance Energétique (DPE)", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Avis d'imposition N-1", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Avis d'imposition N-2", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Contrat de travail", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Trois derniers bulletins de salaire", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Bilan comptable", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Trois dernières quittances de loyers ou attestation d'hébergement", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Taxe foncière", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + archived_at: null, + name: "Justificatif de domicile", + office: offices[0], + private_description: " ", + public_description: " ", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "Autres documents", + archived_at: null, + public_description: "Autres documents", + private_description: "Autres documents", + office: offices[0], + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const deedTypes: DeedType[] = [ + { + name: "Promesse de vente", + archived_at: null, + description: "Promesse de vente entre deux partis", + office: offices[0], + created_at: new Date(), + updated_at: new Date(), + document_types: [...documentTypes.slice(0, 31), documentTypes[66]!], + }, + { + name: "Acte de vente", + archived_at: null, + description: "Acte de vente", + office: offices[0], + created_at: new Date(), + updated_at: new Date(), + document_types: documentTypes.slice(30, 36), + }, + { + name: "Acte de succession", + archived_at: null, + description: "Acte de succession", + office: offices[0], + created_at: new Date(), + updated_at: new Date(), + document_types: [...documentTypes.slice(36, 56), documentTypes[62]!, documentTypes[30]!], + }, + { + name: "Acte de divorce", + archived_at: null, + description: "Acte de divorce", + office: offices[0], + created_at: new Date(), + updated_at: new Date(), + document_types: [...documentTypes.slice(57, 62), documentTypes[30]!, documentTypes[0]!], + }, + { + name: "Acte de donation", + archived_at: null, + description: "Acte de donation", + office: offices[0], + created_at: new Date(), + updated_at: new Date(), + document_types: [documentTypes[0]!, documentTypes[62]!, documentTypes[57]!], + }, + { + name: "Bail d'habitation", + archived_at: null, + description: "Bail d'habitation", + office: offices[0], + created_at: new Date(), + updated_at: new Date(), + document_types: [documentTypes[0]!, documentTypes[30]!, documentTypes[57]!, ...documentTypes.slice(62)], + }, + ]; + + const deeds: Deed[] = [ + { + deed_type: deedTypes[0], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[0], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[0], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[0], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[0], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[0], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[0], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[1], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[1], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[1], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[1], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[1], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[1], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[1], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[2], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[2], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[2], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[2], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[2], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[2], + created_at: new Date(), + updated_at: new Date(), + }, + { + deed_type: deedTypes[2], + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const officeFolders: OfficeFolder[] = [ + { + folder_number: "0001", + name: "Vente par Mme. Simon au profit de Mme. Lextrait", + deed: deeds[0], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + { + folder_number: "0002", + name: "Donation des parts sociales de la société SARL FLORE", + deed: deeds[1], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + { + folder_number: "0003", + name: "Vente par Mme. Lefebvre au profit de Mme. Mathieu", + deed: deeds[2], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + { + folder_number: "0004", + name: "Vente par Mme. Chevalier au profit de M. Daveau", + deed: deeds[3], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + { + folder_number: "0005", + name: "Vente par M. Lalo au profit de Mme. Pigale", + deed: deeds[4], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + { + folder_number: "0006", + name: "Donation des parts sociales de la société SMART-TALENT", + deed: deeds[5], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + { + folder_number: "0007", + name: "Vente par M. Girard au profit de M. Louis", + deed: deeds[6], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + { + folder_number: "0008", + name: "Vente par Mme. Leclerc au profit de M. Louis", + deed: deeds[7], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + { + folder_number: "0009", + name: "Vente par M. Lambert au profit de M. Holmes", + deed: deeds[8], + status: EFolderStatus.LIVE, + created_at: new Date(), + updated_at: new Date(), + office: offices[0], + description: null, + archived_description: null, + }, + ]; + + for (const office of offices) { + const officeCreated = await prisma.offices.create({ + data: { + idNot: office.idNot!, + name: office.name, + crpcen: office.crpcen, + address: { + create: { + address: office.address!.address, + zip_code: office.address!.zip_code, + city: office.address!.city, + }, + }, + office_status: EOfficeStatus.ACTIVATED, + }, + }); + office.uid = officeCreated.uid; + } + for (const rule of rules) { + const ruleCreated = await prisma.rules.create({ + data: { + name: rule.name, + label: rule.label, + namespace: rule.namespace, + }, + }); + rule.uid = ruleCreated.uid; + } + + for (const role of roles) { + const roleCreated = await prisma.roles.create({ + data: { + name: role.name, + label: role.label, + rules: { + connect: role.rules?.map((rule) => ({ + uid: rule.uid!, + })), + }, + }, + }); + role.uid = roleCreated.uid; + } + + for (const ruleGroup of rulesGroups) { + await prisma.rulesGroups.create({ + data: { + name: ruleGroup.name, + rules: { + connect: ruleGroup.rules?.map((rule) => ({ + uid: rules.find((r) => r.name === rule)!.uid!, + })), + }, + }, + }); + } + + for (const officeRole of officeRoles) { + const officeRoleCreated = await prisma.officeRoles.create({ + data: { + name: officeRole.name, + office: { + connect: { + uid: officeRole.office.uid, + }, + }, + rules: { + connect: officeRole.rules?.map((rule) => ({ + uid: rule.uid!, + })), + }, + }, + }); + officeRole.uid = officeRoleCreated.uid; + } + + for (const user of users) { + const createArgs: Prisma.UsersCreateArgs = { + data: { + idNot: user.idNot, + office_membership: { + connectOrCreate: { + where: { + idNot: user.office_membership!.idNot, + }, + create: { + idNot: user.office_membership!.idNot!, + name: user.office_membership!.name, + crpcen: user.office_membership!.crpcen, + address: { + create: { + address: user.office_membership!.address!.address, + zip_code: user.office_membership!.address!.zip_code, + city: user.office_membership!.address!.city, + }, + }, + }, + }, + }, + contact: { + create: { + first_name: user.contact!.first_name, + last_name: user.contact!.last_name, + email: user.contact!.email, + phone_number: user.contact!.phone_number, + cell_phone_number: user.contact!.cell_phone_number, + civility: ECivility[user.contact!.civility as keyof typeof ECivility], + address: {}, + }, + }, + role: { + connect: { + uid: user.role!.uid, + }, + }, + }, + }; + if (user.contact!.address) { + createArgs.data.contact!.create!.address!.create = { + address: user.contact!.address.address, + zip_code: user.contact!.address.zip_code, + city: user.contact!.address.city, + }; + } + if (user.office_role) { + createArgs.data.office_role = { + connect: { + uid: user.office_role.uid, + }, + }; + } + const userCreated = await prisma.users.create(createArgs); + user.uid = userCreated.uid; + } + + for (const customer of customers) { + const createArgs: Prisma.CustomersCreateArgs = { + data: { + status: ECustomerStatus.PENDING, + contact: { + create: { + first_name: customer.contact!.first_name, + last_name: customer.contact!.last_name, + email: customer.contact!.email, + phone_number: customer.contact!.phone_number, + cell_phone_number: customer.contact!.cell_phone_number, + civility: ECivility[customer.contact!.civility as keyof typeof ECivility], + address: {}, + }, + }, + }, + }; + + if (customer.contact?.address) { + createArgs.data.contact!.create!.address = { + create: { + address: customer.contact?.address?.address, + zip_code: customer.contact?.address?.zip_code, + city: customer.contact?.address?.city, + }, + }; + } + const customersCreated = await prisma.customers.create(createArgs); + customer.uid = customersCreated.uid; + } + + for (const documentType of documentTypes) { + const documentTypeCreated = await prisma.documentTypes.create({ + data: { + name: documentType.name, + public_description: documentType.public_description || "", + private_description: documentType.private_description, + office: { + connect: { + uid: documentType.office!.uid, + }, + }, + }, + }); + documentType.uid = documentTypeCreated.uid; + } + + for (const deedType of deedTypes) { + const createArgs: Prisma.DeedTypesCreateArgs = { + data: { + name: deedType.name, + description: deedType.description || "", + office: { + connect: { + uid: deedType.office!.uid, + }, + }, + }, + }; + if (deedType.document_types) { + createArgs.data.document_types = { + connect: deedType.document_types.map((documentType) => ({ + uid: documentType.uid, + })), + }; + } + const deedTypeCreated = await prisma.deedTypes.create(createArgs); + deedType.uid = deedTypeCreated.uid; + } + + for (const deed of deeds) { + const createArgs: Prisma.DeedsCreateArgs = { + data: { + deed_type: { + connect: { + uid: deed.deed_type!.uid, + }, + }, + }, + }; + const deedTypeWithDocumentTypes = await prisma.deedTypes.findUniqueOrThrow({ + where: { + uid: deed.deed_type!.uid, + }, + include: { document_types: true }, + }); + + if (deedTypeWithDocumentTypes.document_types) { + createArgs.data.document_types = { + connect: deedTypeWithDocumentTypes.document_types.map((documentType) => ({ + uid: documentType.uid, + })), + }; + } + const deedCreated = await prisma.deeds.create(createArgs); + deed.uid = deedCreated.uid; + } + + for (const officeFolder of officeFolders) { + const officeFolderCreated = await prisma.officeFolders.create({ + data: { + folder_number: officeFolder.folder_number, + name: officeFolder.name, + description: officeFolder.description, + status: EFolderStatus.LIVE, + deed: { + connect: { + uid: officeFolder.deed?.uid, + }, + }, + office: { + connect: { + uid: officeFolder.office!.uid, + }, + }, + stakeholders: { + connect: officeFolder.stakeholders?.map((stakeholder) => ({ + uid: stakeholder.uid!, + })), + }, + }, + }); + officeFolder.uid = officeFolderCreated.uid; + } + + console.log(">MOCK DATA - Seeding completed!"); + } catch (error) { + console.log(error); + console.log("Data already seeded, skiping"); + } +} +main(); From ce8ce2970ac04656d379b957763e6a8ccd48d477 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Mon, 22 Apr 2024 11:21:37 +0200 Subject: [PATCH 62/86] Adding LEcoffre deploy to cicd --- .github/workflows/stg.yml | 103 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/.github/workflows/stg.yml b/.github/workflows/stg.yml index baceacb7..90fc7369 100644 --- a/.github/workflows/stg.yml +++ b/.github/workflows/stg.yml @@ -8,6 +8,11 @@ env: PROJECT_ID: c0ed1e9e-d945-461f-920c-98c844ef1ad4 NAMESPACE_ID: 9f949ff2-97bc-4979-ade2-1994dcaabde0 CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffrestgqhhn4ixh + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: f8137e85-47ad-46a5-9e2e-18af5de829c5 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffrestgbqbfhtv6 + IMAGE_NAME: back CONTAINER_NAME: back @@ -40,6 +45,34 @@ jobs: run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron - name: Push the Cron Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron + build-and-push-images-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Back Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Back Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Build the Cron Image + run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron + - name: Push the Cron Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron deploy-back: needs: build-and-push-images runs-on: ubuntu-latest @@ -75,6 +108,41 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-back-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: staging + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{ env.NAMESPACE_ID_LECOFFRE }} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} deploy-cron: needs: build-and-push-images runs-on: ubuntu-latest @@ -110,3 +178,38 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-cron-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: staging + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID}} -o json | jq -r '.[] | select(.name == "cron") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} From d5e3a4983f53930bdbb137d7ef13cfbc852bd9f9 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Mon, 22 Apr 2024 15:18:11 +0200 Subject: [PATCH 63/86] copy dockerfile from back for cron --- .github/workflows/stg.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stg.yml b/.github/workflows/stg.yml index 90fc7369..6db2e297 100644 --- a/.github/workflows/stg.yml +++ b/.github/workflows/stg.yml @@ -187,7 +187,7 @@ jobs: uses: scaleway/action-scw@v0 - name: Get container ID run: | - echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID}} -o json | jq -r '.[] | select(.name == "cron") | .id')" >> $GITHUB_ENV + echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "cron") | .id')" >> $GITHUB_ENV env: SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} From 10445a22bd4f6c305bea46ec130da8e5eb51a393 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Mon, 22 Apr 2024 15:39:46 +0200 Subject: [PATCH 64/86] Adding LEcoffre deploy to cicd --- .github/workflows/ppd.yml | 106 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/.github/workflows/ppd.yml b/.github/workflows/ppd.yml index 9e603652..085af1e9 100644 --- a/.github/workflows/ppd.yml +++ b/.github/workflows/ppd.yml @@ -8,6 +8,11 @@ env: PROJECT_ID: c0ed1e9e-d945-461f-920c-98c844ef1ad4 NAMESPACE_ID: a052faf9-a712-41d7-bbfa-8293ee948e70 CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffreppdw9e10llz + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: e975f056-967e-43fe-b237-84bfa8032e64 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreppdmp73pool + IMAGE_NAME: back CONTAINER_NAME: back @@ -40,6 +45,35 @@ jobs: run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron - name: Push the Cron Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron + build-and-push-images-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Back Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Back Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Build the Cron Image + run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron + - name: Push the Cron Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron + deploy-back: needs: build-and-push-images runs-on: ubuntu-latest @@ -75,6 +109,42 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-back-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: staging + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{ env.NAMESPACE_ID_LECOFFRE }} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + deploy-cron: needs: build-and-push-images runs-on: ubuntu-latest @@ -110,3 +180,39 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-cron-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: staging + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "cron") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + \ No newline at end of file From dd6a73d7ad09b896da05f1c07ce98c30b9da4320 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Mon, 22 Apr 2024 15:41:46 +0200 Subject: [PATCH 65/86] Adding LEcoffre deploy to cicd --- .github/workflows/prd.yml | 104 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/.github/workflows/prd.yml b/.github/workflows/prd.yml index 4128eea5..4be80445 100644 --- a/.github/workflows/prd.yml +++ b/.github/workflows/prd.yml @@ -8,6 +8,11 @@ env: PROJECT_ID: c0ed1e9e-d945-461f-920c-98c844ef1ad4 NAMESPACE_ID: 17374437-5428-468c-9f41-d89787ffce0e CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffreprdg7h5bbub + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: 8fbbce9d-31d1-4368-94c4-445e79f10834 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreprdjulp9mam + IMAGE_NAME: back CONTAINER_NAME: back @@ -40,6 +45,34 @@ jobs: run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron - name: Push the Cron Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron + build-and-push-images-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Back Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Back Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Build the Cron Image + run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron + - name: Push the Cron Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron deploy-back: needs: build-and-push-images runs-on: ubuntu-latest @@ -75,6 +108,41 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-back-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: staging + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{ env.NAMESPACE_ID_LECOFFRE }} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} deploy-cron: needs: build-and-push-images runs-on: ubuntu-latest @@ -110,3 +178,39 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-cron-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: staging + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "cron") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + \ No newline at end of file From 489f88bf20489a002b44b1b59ca438add8b8ce87 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Mon, 22 Apr 2024 16:14:44 +0200 Subject: [PATCH 66/86] Adding LEcoffre deploy to cicd --- .github/workflows/ppd.yml | 106 ++++++++++++++++++++++++++++++++++++++ .github/workflows/prd.yml | 104 +++++++++++++++++++++++++++++++++++++ .github/workflows/stg.yml | 103 ++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) diff --git a/.github/workflows/ppd.yml b/.github/workflows/ppd.yml index 9e603652..820d214e 100644 --- a/.github/workflows/ppd.yml +++ b/.github/workflows/ppd.yml @@ -8,6 +8,11 @@ env: PROJECT_ID: c0ed1e9e-d945-461f-920c-98c844ef1ad4 NAMESPACE_ID: a052faf9-a712-41d7-bbfa-8293ee948e70 CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffreppdw9e10llz + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: e975f056-967e-43fe-b237-84bfa8032e64 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreppdmp73pool + IMAGE_NAME: back CONTAINER_NAME: back @@ -40,6 +45,35 @@ jobs: run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron - name: Push the Cron Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron + build-and-push-images-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Back Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Back Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Build the Cron Image + run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron + - name: Push the Cron Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron + deploy-back: needs: build-and-push-images runs-on: ubuntu-latest @@ -75,6 +109,42 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-back-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: preprod + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{ env.NAMESPACE_ID_LECOFFRE }} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + deploy-cron: needs: build-and-push-images runs-on: ubuntu-latest @@ -110,3 +180,39 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-cron-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: preprod + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "cron") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + \ No newline at end of file diff --git a/.github/workflows/prd.yml b/.github/workflows/prd.yml index 4128eea5..a2d26631 100644 --- a/.github/workflows/prd.yml +++ b/.github/workflows/prd.yml @@ -8,6 +8,11 @@ env: PROJECT_ID: c0ed1e9e-d945-461f-920c-98c844ef1ad4 NAMESPACE_ID: 17374437-5428-468c-9f41-d89787ffce0e CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffreprdg7h5bbub + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: 8fbbce9d-31d1-4368-94c4-445e79f10834 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreprdjulp9mam + IMAGE_NAME: back CONTAINER_NAME: back @@ -40,6 +45,34 @@ jobs: run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron - name: Push the Cron Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron + build-and-push-images-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Back Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Back Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Build the Cron Image + run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron + - name: Push the Cron Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron deploy-back: needs: build-and-push-images runs-on: ubuntu-latest @@ -75,6 +108,41 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-back-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: prod + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{ env.NAMESPACE_ID_LECOFFRE }} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} deploy-cron: needs: build-and-push-images runs-on: ubuntu-latest @@ -110,3 +178,39 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-cron-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: prod + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "cron") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + \ No newline at end of file diff --git a/.github/workflows/stg.yml b/.github/workflows/stg.yml index baceacb7..eb340f7f 100644 --- a/.github/workflows/stg.yml +++ b/.github/workflows/stg.yml @@ -8,6 +8,11 @@ env: PROJECT_ID: c0ed1e9e-d945-461f-920c-98c844ef1ad4 NAMESPACE_ID: 9f949ff2-97bc-4979-ade2-1994dcaabde0 CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffrestgqhhn4ixh + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: f8137e85-47ad-46a5-9e2e-18af5de829c5 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffrestgbqbfhtv6 + IMAGE_NAME: back CONTAINER_NAME: back @@ -40,6 +45,34 @@ jobs: run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron - name: Push the Cron Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT }}/cron + build-and-push-images-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Back Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Back Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Build the Cron Image + run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron + - name: Push the Cron Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron deploy-back: needs: build-and-push-images runs-on: ubuntu-latest @@ -75,6 +108,41 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-back-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: staging + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{ env.NAMESPACE_ID_LECOFFRE }} -o json | jq -r '.[] | select(.name == "${{ env.CONTAINER_NAME }}") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} deploy-cron: needs: build-and-push-images runs-on: ubuntu-latest @@ -110,3 +178,38 @@ jobs: SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID }} SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }} + deploy-cron-lecoffre: + needs: build-and-push-images-lecoffre + runs-on: ubuntu-latest + environment: staging + steps: + - name: Install CLI + uses: scaleway/action-scw@v0 + - name: Get container ID + run: | + echo "CONTAINER_ID=$(scw container container list namespace-id=${{env.NAMESPACE_ID_LECOFFRE}} -o json | jq -r '.[] | select(.name == "cron") | .id')" >> $GITHUB_ENV + env: + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} + - name: Deploy the container based on the new image + run: | + env_string="" + while IFS= read -r line; do + if [[ "$line" == *"="* ]]; then + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + if [[ -n "$key" ]]; then + env_string+="environment-variables.$key=$value " + fi + fi + done <<< "$ENV_VARS" + env_string=$(echo $env_string | sed 's/ $//') + scw container container update ${{ env.CONTAINER_ID }} $env_string + env: + ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} + SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} + SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID_LECOFFRE }} \ No newline at end of file From 4b8b26dddd5e5af39dcc4c648cc3aec7baff5ce2 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 23 Apr 2024 09:38:54 +0200 Subject: [PATCH 67/86] Adding promo code to checkout --- src/services/common/StripeService/StripeService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/common/StripeService/StripeService.ts b/src/services/common/StripeService/StripeService.ts index 9bda32b9..3ff92724 100644 --- a/src/services/common/StripeService/StripeService.ts +++ b/src/services/common/StripeService/StripeService.ts @@ -32,6 +32,7 @@ export default class StripeService { subscription: JSON.stringify(subscription), env: this.variables.ENV, }, + allow_promotion_codes: true, }); From 0b2519fc70be69441a5177224b0f28b42b8059ba Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Tue, 23 Apr 2024 10:10:56 +0200 Subject: [PATCH 68/86] =?UTF-8?q?Fixing=20ci=20duplication=20files=20?= =?UTF-8?q?=E2=99=BB=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ppd.yml | 34 ---------------------------------- .github/workflows/prd.yml | 33 --------------------------------- 2 files changed, 67 deletions(-) diff --git a/.github/workflows/ppd.yml b/.github/workflows/ppd.yml index ef29a0c1..820d214e 100644 --- a/.github/workflows/ppd.yml +++ b/.github/workflows/ppd.yml @@ -9,11 +9,6 @@ env: NAMESPACE_ID: a052faf9-a712-41d7-bbfa-8293ee948e70 CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffreppdw9e10llz - PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a - NAMESPACE_ID_LECOFFRE: e975f056-967e-43fe-b237-84bfa8032e64 - CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreppdmp73pool - - PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a NAMESPACE_ID_LECOFFRE: e975f056-967e-43fe-b237-84bfa8032e64 CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreppdmp73pool @@ -79,35 +74,6 @@ jobs: - name: Push the Cron Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron - build-and-push-images-lecoffre: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup SSH - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts - env: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - - name: Copy SSH - run: cp ~/.ssh/id_rsa id_rsa - - name: Login to Scaleway Container Registry - uses: docker/login-action@v3 - with: - username: nologin - password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} - registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} - - name: Build the Back Image - run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - - name: Push the Back Image to Scaleway Container Registry - run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - - name: Build the Cron Image - run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron - - name: Push the Cron Image to Scaleway Container Registry - run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron - deploy-back: needs: build-and-push-images runs-on: ubuntu-latest diff --git a/.github/workflows/prd.yml b/.github/workflows/prd.yml index abcffcc1..a2d26631 100644 --- a/.github/workflows/prd.yml +++ b/.github/workflows/prd.yml @@ -9,11 +9,6 @@ env: NAMESPACE_ID: 17374437-5428-468c-9f41-d89787ffce0e CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffreprdg7h5bbub - PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a - NAMESPACE_ID_LECOFFRE: 8fbbce9d-31d1-4368-94c4-445e79f10834 - CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreprdjulp9mam - - PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a NAMESPACE_ID_LECOFFRE: 8fbbce9d-31d1-4368-94c4-445e79f10834 CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreprdjulp9mam @@ -78,34 +73,6 @@ jobs: run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron - name: Push the Cron Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron - build-and-push-images-lecoffre: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup SSH - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts - env: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - - name: Copy SSH - run: cp ~/.ssh/id_rsa id_rsa - - name: Login to Scaleway Container Registry - uses: docker/login-action@v3 - with: - username: nologin - password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} - registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} - - name: Build the Back Image - run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - - name: Push the Back Image to Scaleway Container Registry - run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - - name: Build the Cron Image - run: docker build -f Dockerfile-Cron . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron - - name: Push the Cron Image to Scaleway Container Registry - run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/cron deploy-back: needs: build-and-push-images runs-on: ubuntu-latest From 84d36140fd4701c24a9b508bce516d2e213fac8b Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 23 Apr 2024 10:54:08 +0200 Subject: [PATCH 69/86] fix admin without subscription --- src/app/api/idnot/UserController.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 44a62b42..58fd5f64 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -5,12 +5,13 @@ import { Service } from "typedi"; import AuthService, { IUserJwtPayload } from "@Services/common/AuthService/AuthService"; import IdNotService from "@Services/common/IdNotService/IdNotService"; -import User from "le-coffre-resources/dist/Admin"; +import User, { RulesGroup } from "le-coffre-resources/dist/Admin"; import UsersService from "@Services/super-admin/UsersService/UsersService"; import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts"; import { ESubscriptionStatus } from "@prisma/client"; import SeatsService from "@Services/admin/SeatsService/SeatsService"; import { EType } from "le-coffre-resources/dist/Admin/Subscription"; +import RulesGroupsService from "@Services/admin/RulesGroupsService/RulesGroupsService"; @Controller() @Service() @@ -21,6 +22,7 @@ export default class UserController extends ApiController { private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService, + private rulesGroupsService: RulesGroupsService ) { super(); } @@ -100,10 +102,6 @@ export default class UserController extends ApiController { } } - if (userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin") { - isSubscribed = true; - } - if (!isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); return; @@ -124,6 +122,17 @@ export default class UserController extends ApiController { await this.idNotService.updateOffice(user.office_uid); const payload = await this.authService.getUserJwtPayload(user.idNot); + if(!payload) return; + + + if(!isSubscribed && userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin"){ + const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { name: "Gestion de l'abonnement" }, include: { rules: true } }); + const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); + if(!manageSubscriptionRules[0]) return; + + payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; + } + const accessToken = this.authService.generateAccessToken(payload); const refreshToken = this.authService.generateRefreshToken(payload); From 9701ea6301de5e7d5532672f9658e014440a77af Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 23 Apr 2024 11:05:05 +0200 Subject: [PATCH 70/86] fix --- src/app/api/idnot/UserController.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 58fd5f64..4135f211 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -102,11 +102,6 @@ export default class UserController extends ApiController { } } - if (!isSubscribed) { - this.httpUnauthorized(response, "User not subscribed"); - return; - } - //Check if user is whitelisted // const isWhitelisted = await this.whitelistService.getByEmail(userHydrated.contact!.email); @@ -130,7 +125,13 @@ export default class UserController extends ApiController { const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); if(!manageSubscriptionRules[0]) return; - payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; + payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; + isSubscribed = true; + } + + if (!isSubscribed) { + this.httpUnauthorized(response, "User not subscribed"); + return; } const accessToken = this.authService.generateAccessToken(payload); From 1998dad3c543937b877fb8bc202f94b37c5cd658 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Tue, 23 Apr 2024 17:06:42 +0200 Subject: [PATCH 71/86] Fix envs on crons --- .github/workflows/ppd.yml | 2 +- .github/workflows/prd.yml | 2 +- .github/workflows/stg.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ppd.yml b/.github/workflows/ppd.yml index 085af1e9..eff21432 100644 --- a/.github/workflows/ppd.yml +++ b/.github/workflows/ppd.yml @@ -210,7 +210,7 @@ jobs: env_string=$(echo $env_string | sed 's/ $//') scw container container update ${{ env.CONTAINER_ID }} $env_string env: - ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + ENV_VARS: ${{ secrets.ENV }} SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} diff --git a/.github/workflows/prd.yml b/.github/workflows/prd.yml index 4be80445..d19ff352 100644 --- a/.github/workflows/prd.yml +++ b/.github/workflows/prd.yml @@ -208,7 +208,7 @@ jobs: env_string=$(echo $env_string | sed 's/ $//') scw container container update ${{ env.CONTAINER_ID }} $env_string env: - ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + ENV_VARS: ${{ secrets.ENV }} SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} diff --git a/.github/workflows/stg.yml b/.github/workflows/stg.yml index 6db2e297..b4ceffe2 100644 --- a/.github/workflows/stg.yml +++ b/.github/workflows/stg.yml @@ -208,7 +208,7 @@ jobs: env_string=$(echo $env_string | sed 's/ $//') scw container container update ${{ env.CONTAINER_ID }} $env_string env: - ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + ENV_VARS: ${{ secrets.ENV }} SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} From 5fe2ccdd8c694969c4ebad3d6f5d2cd99f09f518 Mon Sep 17 00:00:00 2001 From: Yanis JEDRZEJCZAK Date: Tue, 23 Apr 2024 17:07:31 +0200 Subject: [PATCH 72/86] Fix envs on crons --- .github/workflows/ppd.yml | 2 +- .github/workflows/prd.yml | 2 +- .github/workflows/stg.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ppd.yml b/.github/workflows/ppd.yml index 820d214e..7623ef62 100644 --- a/.github/workflows/ppd.yml +++ b/.github/workflows/ppd.yml @@ -210,7 +210,7 @@ jobs: env_string=$(echo $env_string | sed 's/ $//') scw container container update ${{ env.CONTAINER_ID }} $env_string env: - ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + ENV_VARS: ${{ secrets.ENV }} SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} diff --git a/.github/workflows/prd.yml b/.github/workflows/prd.yml index a2d26631..1d769392 100644 --- a/.github/workflows/prd.yml +++ b/.github/workflows/prd.yml @@ -208,7 +208,7 @@ jobs: env_string=$(echo $env_string | sed 's/ $//') scw container container update ${{ env.CONTAINER_ID }} $env_string env: - ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + ENV_VARS: ${{ secrets.ENV }} SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} diff --git a/.github/workflows/stg.yml b/.github/workflows/stg.yml index 6db2e297..b4ceffe2 100644 --- a/.github/workflows/stg.yml +++ b/.github/workflows/stg.yml @@ -208,7 +208,7 @@ jobs: env_string=$(echo $env_string | sed 's/ $//') scw container container update ${{ env.CONTAINER_ID }} $env_string env: - ENV_VARS: ${{ secrets.ENV_LECOFFRE }} + ENV_VARS: ${{ secrets.ENV }} SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY_LECOFFRE }} SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} SCW_DEFAULT_PROJECT_ID: ${{ env.PROJECT_ID_LECOFFRE }} From 3686edb1ce53cf5a6ef23743126286e291fb5ecd Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 23 Apr 2024 17:48:35 +0200 Subject: [PATCH 73/86] Test --- src/app/api/idnot/UserController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 4135f211..d5ad7924 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -128,6 +128,7 @@ export default class UserController extends ApiController { payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; isSubscribed = true; } + if (!isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); From 5b31f74e33098f2a57698edb05fa6d3e73f391c6 Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 23 Apr 2024 17:51:52 +0200 Subject: [PATCH 74/86] test log for stg --- src/app/api/idnot/UserController.ts | 16 +++++++++++++--- src/services/common/IdNotService/IdNotService.ts | 8 ++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index d5ad7924..63490c17 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -36,10 +36,14 @@ export default class UserController extends ApiController { protected async getUserInfosFromIdnot(req: Request, response: Response) { try { const code = req.params["code"]; + console.log("code", code); + if (!code) throw new Error("code is required"); - const idNotToken = await this.idNotService.getIdNotToken(code); + const idNotToken = await this.idNotService.getIdNotToken(code); + console.log("idNotToken", idNotToken); + if (!idNotToken) { this.httpValidationError(response, "IdNot token undefined"); @@ -73,7 +77,9 @@ export default class UserController extends ApiController { } let isSubscribed = false; - const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + console.log("subscriptions", subscriptions); + if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { isSubscribed = false; @@ -117,6 +123,8 @@ export default class UserController extends ApiController { await this.idNotService.updateOffice(user.office_uid); const payload = await this.authService.getUserJwtPayload(user.idNot); + console.log("payload", payload); + if(!payload) return; @@ -126,9 +134,11 @@ export default class UserController extends ApiController { if(!manageSubscriptionRules[0]) return; payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; + console.log("payload with subscription rules", payload); + isSubscribed = true; } - + if (!isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); diff --git a/src/services/common/IdNotService/IdNotService.ts b/src/services/common/IdNotService/IdNotService.ts index 9820fd86..4a843917 100644 --- a/src/services/common/IdNotService/IdNotService.ts +++ b/src/services/common/IdNotService/IdNotService.ts @@ -121,14 +121,22 @@ export default class IdNotService extends BaseService { code: code, grant_type: "authorization_code", }); + console.log(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query); + const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" }); + console.log(token); + if(token.status !== 200) console.error(await token.text()); const decodedToken = (await token.json()) as IIdNotToken; + console.log("decodedToken", decodedToken); + const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload; + console.log("decodedIdToken", decodedIdToken); + return decodedIdToken; From 98ecd404343ff1da0ab4bdc86e3e108fbd77c18b Mon Sep 17 00:00:00 2001 From: Vins Date: Tue, 23 Apr 2024 18:02:55 +0200 Subject: [PATCH 75/86] More test log --- src/app/api/idnot/UserController.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 63490c17..d5683cac 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -130,7 +130,11 @@ export default class UserController extends ApiController { if(!isSubscribed && userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin"){ const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { name: "Gestion de l'abonnement" }, include: { rules: true } }); + console.log("manageSubscriptionRulesEntity", manageSubscriptionRulesEntity); + const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); + console.log("manageSubscriptionRules", manageSubscriptionRules); + if(!manageSubscriptionRules[0]) return; payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; From 577edf556677d3452e53ab7cc9e947b82caf6230 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 08:50:12 +0200 Subject: [PATCH 76/86] logs --- src/app/api/idnot/UserController.ts | 28 +++++-------------- src/entries/App.ts | 2 ++ .../common/IdNotService/IdNotService.ts | 9 +----- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index d5683cac..f151c8f6 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -35,15 +35,11 @@ export default class UserController extends ApiController { @Post("/api/v1/idnot/user/:code") protected async getUserInfosFromIdnot(req: Request, response: Response) { try { - const code = req.params["code"]; - console.log("code", code); - + const code = req.params["code"]; if (!code) throw new Error("code is required"); - const idNotToken = await this.idNotService.getIdNotToken(code); - console.log("idNotToken", idNotToken); - + const idNotToken = await this.idNotService.getIdNotToken(code); if (!idNotToken) { this.httpValidationError(response, "IdNot token undefined"); @@ -77,9 +73,7 @@ export default class UserController extends ApiController { } let isSubscribed = false; - const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); - console.log("subscriptions", subscriptions); - + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { isSubscribed = false; @@ -122,24 +116,16 @@ export default class UserController extends ApiController { await this.idNotService.updateOffice(user.office_uid); - const payload = await this.authService.getUserJwtPayload(user.idNot); - console.log("payload", payload); - + const payload = await this.authService.getUserJwtPayload(user.idNot); if(!payload) return; if(!isSubscribed && userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin"){ - const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { name: "Gestion de l'abonnement" }, include: { rules: true } }); - console.log("manageSubscriptionRulesEntity", manageSubscriptionRulesEntity); - - const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); - console.log("manageSubscriptionRules", manageSubscriptionRules); - + const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { name: "Gestion de l'abonnement" }, include: { rules: true } }); + const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); if(!manageSubscriptionRules[0]) return; - payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; - console.log("payload with subscription rules", payload); - + payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; isSubscribed = true; } diff --git a/src/entries/App.ts b/src/entries/App.ts index 5dd1bd04..e04e9db3 100644 --- a/src/entries/App.ts +++ b/src/entries/App.ts @@ -15,6 +15,8 @@ const storage = multer.memoryStorage(); (async () => { try { const variables = await Container.get(BackendVariables).validate(); + console.log(variables.DATABASE_NAME); + const port = variables.APP_PORT; const rootUrl = variables.APP_ROOT_URL; const label = variables.APP_LABEL ?? "Unknown Service"; diff --git a/src/services/common/IdNotService/IdNotService.ts b/src/services/common/IdNotService/IdNotService.ts index 4a843917..7104c4c5 100644 --- a/src/services/common/IdNotService/IdNotService.ts +++ b/src/services/common/IdNotService/IdNotService.ts @@ -121,22 +121,15 @@ export default class IdNotService extends BaseService { code: code, grant_type: "authorization_code", }); - console.log(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query); const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" }); - console.log(token); - if(token.status !== 200) console.error(await token.text()); const decodedToken = (await token.json()) as IIdNotToken; - console.log("decodedToken", decodedToken); - - - const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload; - console.log("decodedIdToken", decodedIdToken); + const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload; return decodedIdToken; From f70085a7aca13279355d90d8b2fb6abbf1420631 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 09:50:26 +0200 Subject: [PATCH 77/86] Fixed login --- src/app/api/admin/OfficeRolesController.ts | 4 +--- src/app/api/idnot/UserController.ts | 3 ++- src/common/config/variables/Variables.ts | 8 -------- src/common/databases/seeders/seeder.ts | 2 ++ src/entries/App.ts | 4 +--- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/app/api/admin/OfficeRolesController.ts b/src/app/api/admin/OfficeRolesController.ts index 649e4ade..29cec7ad 100644 --- a/src/app/api/admin/OfficeRolesController.ts +++ b/src/app/api/admin/OfficeRolesController.ts @@ -124,9 +124,7 @@ export default class OfficeRolesController extends ApiController { return; } - if (req.body.rules) { - console.log(req.body.rules); - + if (req.body.rules) { const allRules = await this.rulesService.get({ where: { namespace: "global", diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index f151c8f6..5d8bc8e4 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -121,7 +121,8 @@ export default class UserController extends ApiController { if(!isSubscribed && userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin"){ - const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { name: "Gestion de l'abonnement" }, include: { rules: true } }); + const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" }, include: { rules: true } }); + console.log(manageSubscriptionRulesEntity); const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); if(!manageSubscriptionRules[0]) return; diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index 4ff86cab..ac3179db 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -157,12 +157,6 @@ export class BackendVariables { @IsNotEmpty() public readonly STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID!: string; - @IsNotEmpty() - public readonly STRIPE_PAYMENT_SUCCESS_URL!: string; - - @IsNotEmpty() - public readonly STRIPE_PAYMENT_CANCEL_URL!: string; - @IsNotEmpty() public readonly IDNOT_PROD_BASE_URL!: string; @@ -219,8 +213,6 @@ export class BackendVariables { this.STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID"]!; this.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID"]!; this.STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID = process.env["STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID"]!; - this.STRIPE_PAYMENT_SUCCESS_URL = process.env["STRIPE_PAYMENT_SUCCESS_URL"]!; - this.STRIPE_PAYMENT_CANCEL_URL = process.env["STRIPE_PAYMENT_CANCEL_URL"]!; this.IDNOT_PROD_BASE_URL = process.env["IDNOT_PROD_BASE_URL"]!; } public async validate(groups?: string[]) { diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 70b74df4..ce7da8bb 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -874,6 +874,7 @@ export default async function main() { }, { name: "Gestion de l'abonnement", + uid: "94343601-04c8-44ef-afb9-3047597528a9", created_at: new Date(), updated_at: new Date(), rules : [ @@ -1973,6 +1974,7 @@ export default async function main() { for (const ruleGroup of rulesGroups) { await prisma.rulesGroups.create({ data: { + uid: ruleGroup.uid, name: ruleGroup.name, rules: { connect: ruleGroup.rules?.map((rule) => ({ diff --git a/src/entries/App.ts b/src/entries/App.ts index e04e9db3..d94c9766 100644 --- a/src/entries/App.ts +++ b/src/entries/App.ts @@ -14,9 +14,7 @@ const storage = multer.memoryStorage(); (async () => { try { - const variables = await Container.get(BackendVariables).validate(); - console.log(variables.DATABASE_NAME); - + const variables = await Container.get(BackendVariables).validate(); const port = variables.APP_PORT; const rootUrl = variables.APP_ROOT_URL; const label = variables.APP_LABEL ?? "Unknown Service"; From 4a318674b8b8b531fb4f9583c4dd788d5eee7746 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 09:52:22 +0200 Subject: [PATCH 78/86] removed logs --- src/app/api/idnot/UserController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 5d8bc8e4..588782c8 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -122,7 +122,6 @@ export default class UserController extends ApiController { if(!isSubscribed && userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin"){ const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" }, include: { rules: true } }); - console.log(manageSubscriptionRulesEntity); const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); if(!manageSubscriptionRules[0]) return; From cd101d2f110138379412a283d6f70a80021451cc Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 10:18:57 +0200 Subject: [PATCH 79/86] logs --- src/app/api/idnot/UserController.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 588782c8..fdaa9096 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -125,7 +125,9 @@ export default class UserController extends ApiController { const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); if(!manageSubscriptionRules[0]) return; - payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; + payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; + console.log(payload); + isSubscribed = true; } From cd4c32702e94ce5edc34b159c60147f42dbcb2ed Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 10:32:26 +0200 Subject: [PATCH 80/86] logs --- src/app/api/idnot/UserController.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index fdaa9096..5df8d371 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -114,14 +114,17 @@ export default class UserController extends ApiController { // return; // } - await this.idNotService.updateOffice(user.office_uid); + await this.idNotService.updateOffice(user.office_uid); const payload = await this.authService.getUserJwtPayload(user.idNot); if(!payload) return; - + console.log(isSubscribed, userHydrated.role?.name); + if(!isSubscribed && userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin"){ const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" }, include: { rules: true } }); + console.log(manageSubscriptionRulesEntity); + const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); if(!manageSubscriptionRules[0]) return; From 6fd0cb67da940953f773c99c504f35a79e4d1d02 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 10:51:15 +0200 Subject: [PATCH 81/86] logs --- src/app/api/idnot/UserController.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 5df8d371..4aa16418 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -73,14 +73,22 @@ export default class UserController extends ApiController { } let isSubscribed = false; - const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + console.log(subscriptions); + if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { + console.log("no subscription"); + isSubscribed = false; } else if (subscriptions[0]?.type === EType.Unlimited) { + console.log("unlimited subscription"); + isSubscribed = true; } else { + console.log("Seats"); + const hasSeat = await this.subscriptionsService.get({ where: { status: ESubscriptionStatus.ACTIVE, seats: { some: { user_uid: userHydrated.uid } } }, }); From 32f92787ccf8cc4f8aba45ce5ce330e3fec32251 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 11:05:18 +0200 Subject: [PATCH 82/86] logs --- src/app/api/idnot/UserController.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 4aa16418..327db94b 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -46,7 +46,9 @@ export default class UserController extends ApiController { return; } - const user = await this.idNotService.getOrCreateUser(idNotToken); + const user = await this.idNotService.getOrCreateUser(idNotToken); + console.log(user); + if (!user) { this.httpUnauthorized(response, "User not found"); @@ -57,7 +59,9 @@ export default class UserController extends ApiController { //Whitelist feature //Get user with contact - const prismaUser = await this.userService.getByUid(user.uid, { contact: true, role: true }); + const prismaUser = await this.userService.getByUid(user.uid, { contact: true, role: true }); + console.log(prismaUser); + if (!prismaUser) { this.httpNotFoundRequest(response, "user not found"); @@ -65,7 +69,9 @@ export default class UserController extends ApiController { } //Hydrate user to be able to use his contact - const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); + const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); + console.log(userHydrated); + if (!userHydrated.contact?.email || userHydrated.contact?.email === "") { this.httpUnauthorized(response, "Email not found"); @@ -75,8 +81,7 @@ export default class UserController extends ApiController { const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); console.log(subscriptions); - - + if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { console.log("no subscription"); From e4f40336c739efb7da2b05df7f8eac75b13cbf1c Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Wed, 24 Apr 2024 11:23:01 +0200 Subject: [PATCH 83/86] :sparkles: refresh token refreshes rules --- src/app/api/idnot/UserController.ts | 71 ++++++++++--------- .../common/AuthService/AuthService.ts | 2 +- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index 327db94b..f8c43ba2 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -2,7 +2,7 @@ import { Response, Request } from "express"; import { Controller, Post } from "@ControllerPattern/index"; import ApiController from "@Common/system/controller-pattern/ApiController"; import { Service } from "typedi"; -import AuthService, { IUserJwtPayload } from "@Services/common/AuthService/AuthService"; +import AuthService, { IUserJwtPayload, PROVIDER_OPENID } from "@Services/common/AuthService/AuthService"; import IdNotService from "@Services/common/IdNotService/IdNotService"; import User, { RulesGroup } from "le-coffre-resources/dist/Admin"; @@ -22,7 +22,7 @@ export default class UserController extends ApiController { private userService: UsersService, private subscriptionsService: SubscriptionsService, private seatsService: SeatsService, - private rulesGroupsService: RulesGroupsService + private rulesGroupsService: RulesGroupsService, ) { super(); } @@ -35,11 +35,11 @@ export default class UserController extends ApiController { @Post("/api/v1/idnot/user/:code") protected async getUserInfosFromIdnot(req: Request, response: Response) { try { - const code = req.params["code"]; + const code = req.params["code"]; if (!code) throw new Error("code is required"); - const idNotToken = await this.idNotService.getIdNotToken(code); + const idNotToken = await this.idNotService.getIdNotToken(code); if (!idNotToken) { this.httpValidationError(response, "IdNot token undefined"); @@ -48,7 +48,6 @@ export default class UserController extends ApiController { const user = await this.idNotService.getOrCreateUser(idNotToken); console.log(user); - if (!user) { this.httpUnauthorized(response, "User not found"); @@ -61,7 +60,6 @@ export default class UserController extends ApiController { //Get user with contact const prismaUser = await this.userService.getByUid(user.uid, { contact: true, role: true }); console.log(prismaUser); - if (!prismaUser) { this.httpNotFoundRequest(response, "user not found"); @@ -71,7 +69,6 @@ export default class UserController extends ApiController { //Hydrate user to be able to use his contact const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); console.log(userHydrated); - if (!userHydrated.contact?.email || userHydrated.contact?.email === "") { this.httpUnauthorized(response, "Email not found"); @@ -79,21 +76,20 @@ export default class UserController extends ApiController { } let isSubscribed = false; - const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); console.log(subscriptions); - + if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { console.log("no subscription"); - + isSubscribed = false; - } - else if (subscriptions[0]?.type === EType.Unlimited) { + } else if (subscriptions[0]?.type === EType.Unlimited) { console.log("unlimited subscription"); - + isSubscribed = true; } else { console.log("Seats"); - + const hasSeat = await this.subscriptionsService.get({ where: { status: ESubscriptionStatus.ACTIVE, seats: { some: { user_uid: userHydrated.uid } } }, }); @@ -127,32 +123,36 @@ export default class UserController extends ApiController { // return; // } - await this.idNotService.updateOffice(user.office_uid); + await this.idNotService.updateOffice(user.office_uid); - const payload = await this.authService.getUserJwtPayload(user.idNot); - if(!payload) return; + const payload = await this.authService.getUserJwtPayload(user.idNot); + if (!payload) return; console.log(isSubscribed, userHydrated.role?.name); - - if(!isSubscribed && userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin"){ - const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" }, include: { rules: true } }); - console.log(manageSubscriptionRulesEntity); - - const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); - if(!manageSubscriptionRules[0]) return; - - payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; - console.log(payload); - - isSubscribed = true; - } + if ((!isSubscribed && userHydrated.role?.name === "admin") || userHydrated.role?.name === "super-admin") { + const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ + where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" }, + include: { rules: true }, + }); + console.log(manageSubscriptionRulesEntity); + + const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { + strategy: "excludeAll", + }); + if (!manageSubscriptionRules[0]) return; + + payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; + console.log(payload); + + isSubscribed = true; + } if (!isSubscribed) { this.httpUnauthorized(response, "User not subscribed"); return; } - + const accessToken = this.authService.generateAccessToken(payload); const refreshToken = this.authService.generateRefreshToken(payload); @@ -176,21 +176,24 @@ export default class UserController extends ApiController { } let accessToken; - this.authService.verifyRefreshToken(token, (err, userPayload) => { + this.authService.verifyRefreshToken(token, async (err, userPayload) => { if (err) { console.log(err); this.httpUnauthorized(response); return; } - const user = userPayload as IUserJwtPayload; + const openId = (userPayload as IUserJwtPayload).openId.userId; + if (!openId) return; + const newUserPayload = await this.authService.getUserJwtPayload(openId.toString(), PROVIDER_OPENID.idNot); + const user = newUserPayload as IUserJwtPayload; delete user.iat; delete user.exp; accessToken = this.authService.generateAccessToken(user); + this.httpSuccess(response, { accessToken }); }); //success - this.httpSuccess(response, { accessToken }); } catch (error) { console.log(error); this.httpInternalError(response); diff --git a/src/services/common/AuthService/AuthService.ts b/src/services/common/AuthService/AuthService.ts index 6be3ae25..b3fa7591 100644 --- a/src/services/common/AuthService/AuthService.ts +++ b/src/services/common/AuthService/AuthService.ts @@ -8,7 +8,7 @@ import { ECustomerStatus } from "@prisma/client"; import { Customer } from "le-coffre-resources/dist/Notary"; import bcrypt from "bcrypt"; -enum PROVIDER_OPENID { +export enum PROVIDER_OPENID { idNot = "idNot", } From 7d16243f3269e88b86a5d097e06f4732a736550c Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 11:26:54 +0200 Subject: [PATCH 84/86] all fix --- src/app/api/idnot/UserController.ts | 63 ++++++++++------------------- 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index f8c43ba2..dc749489 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -46,8 +46,7 @@ export default class UserController extends ApiController { return; } - const user = await this.idNotService.getOrCreateUser(idNotToken); - console.log(user); + const user = await this.idNotService.getOrCreateUser(idNotToken); if (!user) { this.httpUnauthorized(response, "User not found"); @@ -58,38 +57,29 @@ export default class UserController extends ApiController { //Whitelist feature //Get user with contact - const prismaUser = await this.userService.getByUid(user.uid, { contact: true, role: true }); - console.log(prismaUser); + const prismaUser = await this.userService.getByUid(user.uid, { contact: true, role: true, office_membership: true}); if (!prismaUser) { this.httpNotFoundRequest(response, "user not found"); return; - } - + } //Hydrate user to be able to use his contact const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); - console.log(userHydrated); - + if (!userHydrated.contact?.email || userHydrated.contact?.email === "") { this.httpUnauthorized(response, "Email not found"); return; } let isSubscribed = false; - const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); - console.log(subscriptions); - - if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { - console.log("no subscription"); - + const subscriptions = await this.subscriptionsService.get({ where: { office_uid: userHydrated.office_membership?.uid } }); + + if (!subscriptions || subscriptions.length === 0 || subscriptions[0]?.status === ESubscriptionStatus.INACTIVE) { isSubscribed = false; - } else if (subscriptions[0]?.type === EType.Unlimited) { - console.log("unlimited subscription"); - + } + else if (subscriptions[0]?.type === EType.Unlimited) { isSubscribed = true; - } else { - console.log("Seats"); - + } else { const hasSeat = await this.subscriptionsService.get({ where: { status: ESubscriptionStatus.ACTIVE, seats: { some: { user_uid: userHydrated.uid } } }, }); @@ -125,27 +115,18 @@ export default class UserController extends ApiController { await this.idNotService.updateOffice(user.office_uid); - const payload = await this.authService.getUserJwtPayload(user.idNot); - if (!payload) return; - - console.log(isSubscribed, userHydrated.role?.name); - - if ((!isSubscribed && userHydrated.role?.name === "admin") || userHydrated.role?.name === "super-admin") { - const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ - where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" }, - include: { rules: true }, - }); - console.log(manageSubscriptionRulesEntity); - - const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { - strategy: "excludeAll", - }); - if (!manageSubscriptionRules[0]) return; - - payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; - console.log(payload); - - isSubscribed = true; + const payload = await this.authService.getUserJwtPayload(user.idNot); + if(!payload) return; + + if(!isSubscribed && userHydrated.role?.name === "admin" || userHydrated.role?.name === "super-admin"){ + const manageSubscriptionRulesEntity = await this.rulesGroupsService.get({ where: { uid: "94343601-04c8-44ef-afb9-3047597528a9" }, include: { rules: true } }); + + const manageSubscriptionRules = RulesGroup.hydrateArray(manageSubscriptionRulesEntity, { strategy: "excludeAll" }); + if(!manageSubscriptionRules[0]) return; + + payload.rules = manageSubscriptionRules[0].rules!.map((rule) => rule.name) || []; + + isSubscribed = true; } if (!isSubscribed) { From 54c451d6cf6356b9c585fc24ff9148c15addd70d Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 11:35:02 +0200 Subject: [PATCH 85/86] New ovh api key --- src/app/api/idnot/UserController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index dc749489..0b8fec33 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -63,6 +63,7 @@ export default class UserController extends ApiController { this.httpNotFoundRequest(response, "user not found"); return; } + //Hydrate user to be able to use his contact const userHydrated = User.hydrate(prismaUser, { strategy: "excludeAll" }); From 5b12917ef7d47b28f20a8321c387a6e7ec199cc2 Mon Sep 17 00:00:00 2001 From: Vins Date: Wed, 24 Apr 2024 12:11:30 +0200 Subject: [PATCH 86/86] redeploy with old ovh secret --- src/app/api/admin/StripeController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/admin/StripeController.ts b/src/app/api/admin/StripeController.ts index ed227eb6..9f3338bb 100644 --- a/src/app/api/admin/StripeController.ts +++ b/src/app/api/admin/StripeController.ts @@ -63,6 +63,7 @@ export default class StripeController extends ApiController { @Get("/api/v1/admin/stripe/:uid/client-portal", [authHandler, ruleHandler]) protected async getClientPortalSession(req: Request, response: Response) { try { + const uid = req.params["uid"]; if (!uid) { this.httpBadRequest(response, "No uid provided");