From e9ee426fc8632e8d975486ba1fb45cb6377d8a28 Mon Sep 17 00:00:00 2001 From: SanchitUke Date: Fri, 22 Sep 2023 11:33:44 +0530 Subject: [PATCH 01/28] apis init --- .gitignore | 1 + package.json | 3 + .../migration.sql | 8 ++ .../migration.sql | 12 ++ prisma/schema.prisma | 12 +- prisma/seed.ts | 23 ++++ src/admin/admin.controller.ts | 112 +++++++++++++++++- src/admin/admin.module.ts | 6 +- src/admin/admin.service.ts | 25 +++- src/main.ts | 13 +- src/provider/provider.controller.ts | 26 +++- src/provider/provider.module.ts | 4 +- src/provider/provider.service.ts | 35 +++++- src/transactions/transactions.service.ts | 65 ++++++++++ src/user/user.controller.ts | 61 +++++++++- src/user/user.module.ts | 5 +- src/user/user.service.ts | 49 +++++++- src/wallet/wallet.service.ts | 27 +++++ 18 files changed, 467 insertions(+), 20 deletions(-) create mode 100644 prisma/migrations/20230921053937_wallet_user_id_uniq/migration.sql create mode 100644 prisma/migrations/20230921071650_transaction_type/migration.sql create mode 100644 prisma/seed.ts create mode 100644 src/transactions/transactions.service.ts create mode 100644 src/wallet/wallet.service.ts diff --git a/.gitignore b/.gitignore index 3c3629e..76add87 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +dist \ No newline at end of file diff --git a/package.json b/package.json index 8427f9b..b7e603d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, + "prisma": { + "seed": "ts-node prisma/seed.ts" + }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", diff --git a/prisma/migrations/20230921053937_wallet_user_id_uniq/migration.sql b/prisma/migrations/20230921053937_wallet_user_id_uniq/migration.sql new file mode 100644 index 0000000..94a2549 --- /dev/null +++ b/prisma/migrations/20230921053937_wallet_user_id_uniq/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[userId]` on the table `wallets` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "wallets_userId_key" ON "wallets"("userId"); diff --git a/prisma/migrations/20230921071650_transaction_type/migration.sql b/prisma/migrations/20230921071650_transaction_type/migration.sql new file mode 100644 index 0000000..9cfe279 --- /dev/null +++ b/prisma/migrations/20230921071650_transaction_type/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - Added the required column `type` to the `transactions` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "TransactionType" AS ENUM ('purchase', 'creditRequest', 'settlement'); + +-- AlterTable +ALTER TABLE "transactions" ADD COLUMN "type" "TransactionType" NOT NULL, +ALTER COLUMN "description" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9b28819..f81c36f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,9 +22,16 @@ enum WalletStatus { frozen } +enum TransactionType { + purchase + creditRequest + settlement +} + + model wallets { walletId Int @id @default(autoincrement()) - userId Int + userId Int @unique type WalletType status WalletStatus credits Int @@ -39,7 +46,8 @@ model transactions { fromId Int toId Int credits Int - description String + type TransactionType + description String? createdAt DateTime @default(now()) from wallets @relation("FromTransaction", fields:[fromId], references:[walletId]) to wallets @relation("ToTransaction", fields:[toId], references:[walletId]) diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..1b9988a --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,23 @@ +import { PrismaClient, TransactionType, WalletStatus, WalletType } from '@prisma/client' +const prisma = new PrismaClient() +async function main() { + const wallet = await prisma.transactions.create({ + data: { + credits: 15, + fromId: 6, + toId: 10, + type: TransactionType.settlement, + + } + }) + console.log({ wallet }) +} +main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) \ No newline at end of file diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 7b5d784..efacdaf 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -1,4 +1,112 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Get, Param, ParseIntPipe, Post, UnauthorizedException } from '@nestjs/common'; +import { AdminService } from './admin.service'; +import { TransactionService } from 'src/transactions/transactions.service'; +import { UserService } from 'src/user/user.service'; +import { TransactionType, WalletType } from '@prisma/client'; +import { ProviderService } from 'src/provider/provider.service'; @Controller('admin') -export class AdminController {} +export class AdminController { + constructor( + private transactionService: TransactionService, + private userService: UserService, + private adminService: AdminService, + private providerService: ProviderService + ) {} + + @Get("/:adminId/transactions/users") + // get all transactions of all users + async getAllUsersTransactions( + @Param("adminId", ParseIntPipe) adminId: number + ) { + // check admin + await this.adminService.getAdminWallet(adminId); + + // fetch transactions + const transactions = await this.transactionService.fetchAllUsersTransactions(); + return transactions; + } + + @Get("/:adminId/transactions/users/:userId") + // get all transactions of a particular user + async getUserTransactions( + @Param("adminId", ParseIntPipe) adminId: number, + @Param("userId", ParseIntPipe) userId: number + ) { + // check admin + await this.adminService.getAdminWallet(adminId); + + // check user + await this.userService.getUserWallet(userId); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(userId); + return transactions; + } + + @Get("/:adminId/transactions/providers") + // get all transactions between all providers and admins + async getAllAdminProvidersTransactions( + @Param("adminId", ParseIntPipe) adminId: number + ) { + // check admin + await this.adminService.getAdminWallet(adminId); + + // fetch transactions + const transactions = await this.transactionService.fetchAllAdminProviderTransactions(); + return transactions; + } + + @Get("/:adminId/transactions/providers/:providerId") + // get all transactions of a particular provider + async getProviderTransactions( + @Param("adminId", ParseIntPipe) adminId: number, + @Param("providerId", ParseIntPipe) providerId: number + ) { + // check admin + await this.adminService.getAdminWallet(adminId); + + // check provider + await this.providerService.getProviderWallet(providerId); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(providerId); + return transactions; + } + + @Post("/:adminId/add-credits") + // add credits to a user's wallet + async addCredits( + @Param("adminId", ParseIntPipe) adminId: number, + @Body("userId") userId: number, + @Body("credits") credits: number + ) { + // check admin + const adminWallet = await this.adminService.getAdminWallet(adminId); + + // update wallet + const userWallet = await this.userService.addCreditsToUser(userId, credits); + + // create transaction + await this.transactionService.createTransaction(credits, adminWallet.walletId, userWallet.walletId, TransactionType.creditRequest); + return userWallet.credits; + } + + @Post("/:adminId/reduce-credits") + // reduce credits from a user's wallet + async reduceCredits( + @Param("adminId", ParseIntPipe) adminId: number, + @Body("userId") userId: number, + @Body("credits") credits: number + ) { + // check admin + const adminWallet = await this.adminService.getAdminWallet(adminId); + + // update wallet + const userWallet = await this.userService.reduceUserCredits(userId, credits); + + // create transaction + await this.transactionService.createTransaction(credits, userWallet.walletId, adminWallet.walletId, TransactionType.creditRequest); + return userWallet.credits; + } +} diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts index 4b37e20..4a8faad 100644 --- a/src/admin/admin.module.ts +++ b/src/admin/admin.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common'; import { AdminController } from './admin.controller'; import { AdminService } from './admin.service'; +import { TransactionService } from 'src/transactions/transactions.service'; +import { WalletService } from 'src/wallet/wallet.service'; +import { UserService } from 'src/user/user.service'; +import { ProviderService } from 'src/provider/provider.service'; @Module({ controllers: [AdminController], - providers: [AdminService] + providers: [AdminService, TransactionService, WalletService, UserService, ProviderService] }) export class AdminModule {} diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts index 796f9fd..b61d1d2 100644 --- a/src/admin/admin.service.ts +++ b/src/admin/admin.service.ts @@ -1,4 +1,25 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { WalletType } from '@prisma/client'; +import { prisma } from 'src/main'; +import { WalletService } from 'src/wallet/wallet.service'; @Injectable() -export class AdminService {} +export class AdminService { + constructor( + private walletService: WalletService, + ) {} + + async getAdminWallet(adminId: number) { + // get admin wallet + const adminWallet = await this.walletService.fetchWallet(adminId); + if(adminWallet == null) { + throw new NotFoundException; + } + + // check admin + if(adminWallet.type != WalletType.admin) { + throw new UnauthorizedException; + } + return adminWallet; + } +} diff --git a/src/main.ts b/src/main.ts index 13cad38..7fa1be1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,19 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { PrismaClient } from '@prisma/client'; + +export const prisma = new PrismaClient(); async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } -bootstrap(); +bootstrap() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) diff --git a/src/provider/provider.controller.ts b/src/provider/provider.controller.ts index d3259fc..5065112 100644 --- a/src/provider/provider.controller.ts +++ b/src/provider/provider.controller.ts @@ -1,4 +1,24 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { TransactionService } from 'src/transactions/transactions.service'; +import { ProviderService } from './provider.service'; -@Controller('provider') -export class ProviderController {} +@Controller('providers') +export class ProviderController { + constructor( + private transactionService: TransactionService, + private providerService: ProviderService + ) {} + + @Get("/:providerId/transactions") + // get all transactions of a particular provider + async getProviderTransactions( + @Param("providerId", ParseIntPipe) providerId: number, + ) { + // check provider + await this.providerService.getProviderWallet(providerId); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(providerId); + return transactions; + } +} diff --git a/src/provider/provider.module.ts b/src/provider/provider.module.ts index a285743..3497ef6 100644 --- a/src/provider/provider.module.ts +++ b/src/provider/provider.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; import { ProviderController } from './provider.controller'; import { ProviderService } from './provider.service'; +import { TransactionService } from 'src/transactions/transactions.service'; +import { WalletService } from 'src/wallet/wallet.service'; @Module({ controllers: [ProviderController], - providers: [ProviderService] + providers: [ProviderService, TransactionService, WalletService] }) export class ProviderModule {} diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index 8c3b0f9..78a2269 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -1,4 +1,35 @@ -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { WalletType } from '@prisma/client'; +import { WalletService } from 'src/wallet/wallet.service'; @Injectable() -export class ProviderService {} +export class ProviderService { + constructor( + private walletService: WalletService, + ) {} + + async getProviderWallet(providerId: number) { + // get provider wallet + const providerWallet = await this.walletService.fetchWallet(providerId) + if(providerWallet == null) { + throw new NotFoundException; + } + + // check provider + if(providerWallet.type != WalletType.provider) { + throw new BadRequestException; + } + return providerWallet; + } + + async addCreditsToProvider(providerId: number, credits: number) { + + // check provider + let providerWallet = await this.getProviderWallet(providerId) + + // update provider wallet + providerWallet = await this.walletService.updateWalletCredits(providerId, providerWallet.credits + credits); + return providerWallet; + } + +} diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts new file mode 100644 index 0000000..fbce0ae --- /dev/null +++ b/src/transactions/transactions.service.ts @@ -0,0 +1,65 @@ +import { Injectable } from '@nestjs/common'; +import { TransactionType, WalletType } from '@prisma/client'; +import { prisma } from 'src/main'; + +@Injectable() +export class TransactionService { + + fetchAllUsersTransactions() { + return prisma.transactions.findMany({ + where: { + OR: [{ + from: { + type: WalletType.user + } + }, { + to: { + type: WalletType.user + } + }] + } + }); + } + + fetchTransactionsOfOneSystemActor(userId: number) { + return prisma.transactions.findMany({ + where: { + OR: [{ + from: { + userId: userId, + } + }, { + to: { + userId: userId, + } + }] + } + }); + } + + fetchAllAdminProviderTransactions() { + return prisma.transactions.findMany({ + where: { + from: { + type: WalletType.provider + }, + to: { + type: WalletType.admin + } + + } + }); + } + + createTransaction(credits: number, fromWalletId: number, toWalletId: number, transactionType: TransactionType, description?: string) { + return prisma.transactions.create({ + data: { + credits: credits, + type: transactionType, + description: description, + fromId: fromWalletId, + toId: toWalletId, + } + }); + } +} diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index ad8c2a6..3e8f12a 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,4 +1,59 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Get, NotFoundException, Param, ParseIntPipe, Post, UnauthorizedException } from '@nestjs/common'; +import { TransactionType, WalletType } from '@prisma/client'; +import { TransactionService } from 'src/transactions/transactions.service'; +import { UserService } from './user.service'; +import { ProviderService } from 'src/provider/provider.service'; -@Controller('user') -export class UserController {} +@Controller('users') +export class UserController { + constructor( + private transactionService: TransactionService, + private userService: UserService, + private providerService: ProviderService + ) {} + + @Get("/:userId/credits") + // get credits of a particular user + async getCredits( + @Param("userId", ParseIntPipe) userId: number, + ) { + // fetch wallet + const wallet = await this.userService.getUserWallet(userId); + + return wallet.credits; + } + + @Get("/:userId/transactions") + // get all transactions of a particular user + async getUserTransactions( + @Param("userId", ParseIntPipe) userId: number + ) { + // check user + await this.userService.getUserWallet(userId); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(userId); + return transactions; + } + + @Post("/:userId/purchase") + // decrease credits from a user's wallet due to purchase + async handlePurchase( + @Param("userId", ParseIntPipe) userId: number, + @Body("credits") credits: number, + @Body("providerId") providerId: number + ) { + // update user wallet + const userWalletPromise = this.userService.reduceUserCredits(userId, credits); + + // update provider wallet + const providerWalletPromise = this.providerService.addCreditsToProvider(providerId, credits); + + const [userWallet, providerWallet] = await Promise.all([userWalletPromise, providerWalletPromise]); + + // create transaction + const transaction = await this.transactionService.createTransaction(credits, userWallet.walletId, providerWallet.walletId, TransactionType.purchase); + + return transaction.transactionId; + } +} diff --git a/src/user/user.module.ts b/src/user/user.module.ts index b3801b5..2cc37d1 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -1,9 +1,12 @@ import { Module } from '@nestjs/common'; import { UserController } from './user.controller'; import { UserService } from './user.service'; +import { TransactionService } from 'src/transactions/transactions.service'; +import { WalletService } from 'src/wallet/wallet.service'; +import { ProviderService } from 'src/provider/provider.service'; @Module({ controllers: [UserController], - providers: [UserService] + providers: [UserService, TransactionService, WalletService, ProviderService] }) export class UserModule {} diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 668a7d6..0ef4e00 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,4 +1,49 @@ -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { WalletType } from '@prisma/client'; +import { WalletService } from 'src/wallet/wallet.service'; @Injectable() -export class UserService {} +export class UserService { + constructor( + private walletService: WalletService, + ) {} + + async getUserWallet(userId: number) { + // get user wallet + const userWallet = await this.walletService.fetchWallet(userId) + if(userWallet == null) { + throw new NotFoundException; + } + // check user + if(userWallet.type != WalletType.user) { + throw new BadRequestException; + } + return userWallet; + } + + async reduceUserCredits(userId: number, credits: number) { + + // fetch user wallet + let userWallet = await this.getUserWallet(userId); + + // check credits + if(userWallet.credits < credits) { + throw new BadRequestException; + } + // update user wallet + userWallet = await this.walletService.updateWalletCredits(userId, userWallet.credits - credits); + + return userWallet; + } + + async addCreditsToUser(userId: number, credits: number) { + + // fetch user wallet + let userWallet = await this.getUserWallet(userId); + + // update user wallet + userWallet = await this.walletService.updateWalletCredits(userId, userWallet.credits + credits); + + return userWallet; + } +} diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts new file mode 100644 index 0000000..f43f3bb --- /dev/null +++ b/src/wallet/wallet.service.ts @@ -0,0 +1,27 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { prisma } from 'src/main'; + +@Injectable() +export class WalletService { + + fetchWallet(userId: number) { + return prisma.wallets.findUnique({ + where: { + userId: userId + } + }) + } + + updateWalletCredits(userId: number, newCreditsAmount: number) { + return prisma.wallets.update({ + where: { + userId: userId + }, + data: { + credits: { + set: newCreditsAmount + } + } + }); + } +} \ No newline at end of file From 723f1f4a4a4b6312a83cb84cad2bec88006e9f7c Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Fri, 22 Sep 2023 16:23:50 +0530 Subject: [PATCH 02/28] response objects --- package-lock.json | 35 ++++++++++++ package.json | 2 + src/admin/admin.controller.ts | 69 ++++++++++++++++++------ src/admin/admin.module.ts | 2 + src/admin/admin.service.ts | 7 ++- src/admin/dto/credits.dto.ts | 14 +++++ src/main.ts | 13 ++--- src/prisma/prisma.module.ts | 3 +- src/prisma/prisma.service.ts | 3 +- src/provider/provider.controller.ts | 10 +++- src/provider/provider.module.ts | 2 + src/provider/provider.service.ts | 6 +-- src/transactions/transactions.service.ts | 13 +++-- src/user/dto/purchase.dto.ts | 14 +++++ src/user/user.controller.ts | 40 ++++++++++---- src/user/user.module.ts | 2 + src/user/user.service.ts | 8 +-- src/wallet/wallet.service.ts | 11 ++-- 18 files changed, 193 insertions(+), 61 deletions(-) create mode 100644 src/admin/dto/credits.dto.ts create mode 100644 src/user/dto/purchase.dto.ts diff --git a/package-lock.json b/package-lock.json index 2b753dc..ef84019 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.3.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, @@ -2072,6 +2074,11 @@ "@types/superagent": "*" } }, + "node_modules/@types/validator": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.1.tgz", + "integrity": "sha512-d/MUkJYdOeKycmm75Arql4M5+UuXmf4cHdHKsyw1GcvnNgL6s77UkgSgJ8TE/rI5PYsnwYq5jkcWBLuN/MpQ1A==" + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -3147,6 +3154,21 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", + "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "dependencies": { + "@types/validator": "^13.7.10", + "libphonenumber-js": "^1.10.14", + "validator": "^13.7.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5909,6 +5931,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.10.44", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.44.tgz", + "integrity": "sha512-svlRdNBI5WgBjRC20GrCfbFiclbF0Cx+sCcQob/C1r57nsoq0xg8r65QbTyVyweQIlB33P+Uahyho6EMYgcOyQ==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8193,6 +8220,14 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index b7e603d..6ca549c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@prisma/client": "^5.3.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index efacdaf..20f8010 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -1,9 +1,10 @@ -import { Body, Controller, Get, Param, ParseIntPipe, Post, UnauthorizedException } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post } from '@nestjs/common'; import { AdminService } from './admin.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { UserService } from 'src/user/user.service'; import { TransactionType, WalletType } from '@prisma/client'; import { ProviderService } from 'src/provider/provider.service'; +import { CreditsDto } from './dto/credits.dto'; @Controller('admin') export class AdminController { @@ -24,7 +25,13 @@ export class AdminController { // fetch transactions const transactions = await this.transactionService.fetchAllUsersTransactions(); - return transactions; + return { + statusCode: HttpStatus.OK, + message: "transactions fetched successfully", + body: { + transactions + } + } } @Get("/:adminId/transactions/users/:userId") @@ -41,7 +48,13 @@ export class AdminController { // fetch transactions const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(userId); - return transactions; + return { + statusCode: HttpStatus.OK, + message: "transactions fetched successfully", + body: { + transactions + } + } } @Get("/:adminId/transactions/providers") @@ -54,7 +67,13 @@ export class AdminController { // fetch transactions const transactions = await this.transactionService.fetchAllAdminProviderTransactions(); - return transactions; + return { + statusCode: HttpStatus.OK, + message: "transactions fetched successfully", + body: { + transactions + } + } } @Get("/:adminId/transactions/providers/:providerId") @@ -71,42 +90,60 @@ export class AdminController { // fetch transactions const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(providerId); - return transactions; + return { + statusCode: HttpStatus.OK, + message: "transactions fetched successfully", + body: { + transactions + } + } } @Post("/:adminId/add-credits") // add credits to a user's wallet async addCredits( @Param("adminId", ParseIntPipe) adminId: number, - @Body("userId") userId: number, - @Body("credits") credits: number + @Body() creditsDto: CreditsDto ) { // check admin const adminWallet = await this.adminService.getAdminWallet(adminId); // update wallet - const userWallet = await this.userService.addCreditsToUser(userId, credits); - + const userWallet = await this.userService.addCreditsToUser(creditsDto.userId, creditsDto.credits); + // create transaction - await this.transactionService.createTransaction(credits, adminWallet.walletId, userWallet.walletId, TransactionType.creditRequest); - return userWallet.credits; + await this.transactionService.createTransaction(creditsDto.credits, adminWallet.walletId, userWallet.walletId, TransactionType.creditRequest); + + return { + statusCode: HttpStatus.OK, + message: "Credits added successfully", + body: { + credits: userWallet.credits + } + } } @Post("/:adminId/reduce-credits") // reduce credits from a user's wallet async reduceCredits( @Param("adminId", ParseIntPipe) adminId: number, - @Body("userId") userId: number, - @Body("credits") credits: number + @Body() creditsDto: CreditsDto ) { // check admin const adminWallet = await this.adminService.getAdminWallet(adminId); // update wallet - const userWallet = await this.userService.reduceUserCredits(userId, credits); + const userWallet = await this.userService.reduceUserCredits(creditsDto.userId, creditsDto.credits); // create transaction - await this.transactionService.createTransaction(credits, userWallet.walletId, adminWallet.walletId, TransactionType.creditRequest); - return userWallet.credits; + await this.transactionService.createTransaction(creditsDto.credits, userWallet.walletId, adminWallet.walletId, TransactionType.creditRequest); + + return { + statusCode: HttpStatus.OK, + message: "Credits reduced successfully", + body: { + credits: userWallet.credits + } + } } } diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts index 4a8faad..2af598f 100644 --- a/src/admin/admin.module.ts +++ b/src/admin/admin.module.ts @@ -5,8 +5,10 @@ import { TransactionService } from 'src/transactions/transactions.service'; import { WalletService } from 'src/wallet/wallet.service'; import { UserService } from 'src/user/user.service'; import { ProviderService } from 'src/provider/provider.service'; +import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [AdminController], providers: [AdminService, TransactionService, WalletService, UserService, ProviderService] }) diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts index b61d1d2..0ea3fdd 100644 --- a/src/admin/admin.service.ts +++ b/src/admin/admin.service.ts @@ -1,6 +1,5 @@ -import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { WalletType } from '@prisma/client'; -import { prisma } from 'src/main'; import { WalletService } from 'src/wallet/wallet.service'; @Injectable() @@ -13,12 +12,12 @@ export class AdminService { // get admin wallet const adminWallet = await this.walletService.fetchWallet(adminId); if(adminWallet == null) { - throw new NotFoundException; + throw new HttpException("Wallet does not exist", HttpStatus.NOT_FOUND); } // check admin if(adminWallet.type != WalletType.admin) { - throw new UnauthorizedException; + throw new HttpException("Wallet does not belong to admin", HttpStatus.BAD_REQUEST); } return adminWallet; } diff --git a/src/admin/dto/credits.dto.ts b/src/admin/dto/credits.dto.ts new file mode 100644 index 0000000..5abe6ee --- /dev/null +++ b/src/admin/dto/credits.dto.ts @@ -0,0 +1,14 @@ +import { IsInt, IsNotEmpty } from "class-validator"; + + +export class CreditsDto { + + // End user ID + @IsNotEmpty() + @IsInt() + userId: number; + + // Number of credits transferred + @IsInt() + credits: number; +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 7fa1be1..bcc1f2c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,19 +1,12 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { PrismaClient } from '@prisma/client'; +import { ValidationPipe } from '@nestjs/common'; -export const prisma = new PrismaClient(); async function bootstrap() { const app = await NestFactory.create(AppModule); + app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } -bootstrap() - .then(async () => { - await prisma.$disconnect() - }) - .catch(async (e) => { - console.error(e) - await prisma.$disconnect() - process.exit(1) - }) +bootstrap() \ No newline at end of file diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts index f7868ac..e569e2d 100644 --- a/src/prisma/prisma.module.ts +++ b/src/prisma/prisma.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { PrismaService } from './prisma.service'; @Module({ - providers: [PrismaService] + providers: [PrismaService], + exports: [PrismaService] }) export class PrismaModule {} diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index 3c7a915..2c93095 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; @Injectable() -export class PrismaService {} +export class PrismaService extends PrismaClient {} diff --git a/src/provider/provider.controller.ts b/src/provider/provider.controller.ts index 5065112..c459818 100644 --- a/src/provider/provider.controller.ts +++ b/src/provider/provider.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Param, ParseIntPipe } from '@nestjs/common'; import { TransactionService } from 'src/transactions/transactions.service'; import { ProviderService } from './provider.service'; @@ -19,6 +19,12 @@ export class ProviderController { // fetch transactions const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(providerId); - return transactions; + return { + statusCode: HttpStatus.OK, + message: "transactions fetched successfully", + body: { + transactions + } + } } } diff --git a/src/provider/provider.module.ts b/src/provider/provider.module.ts index 3497ef6..0685a2a 100644 --- a/src/provider/provider.module.ts +++ b/src/provider/provider.module.ts @@ -3,8 +3,10 @@ import { ProviderController } from './provider.controller'; import { ProviderService } from './provider.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { WalletService } from 'src/wallet/wallet.service'; +import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [ProviderController], providers: [ProviderService, TransactionService, WalletService] }) diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index 78a2269..5d991e8 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { WalletType } from '@prisma/client'; import { WalletService } from 'src/wallet/wallet.service'; @@ -12,12 +12,12 @@ export class ProviderService { // get provider wallet const providerWallet = await this.walletService.fetchWallet(providerId) if(providerWallet == null) { - throw new NotFoundException; + throw new HttpException("Wallet does not exist", HttpStatus.NOT_FOUND); } // check provider if(providerWallet.type != WalletType.provider) { - throw new BadRequestException; + throw new HttpException("Wallet does not belong to provider", HttpStatus.BAD_REQUEST); } return providerWallet; } diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index fbce0ae..6cd04da 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -1,12 +1,15 @@ import { Injectable } from '@nestjs/common'; import { TransactionType, WalletType } from '@prisma/client'; -import { prisma } from 'src/main'; +import { PrismaService } from 'src/prisma/prisma.service'; @Injectable() export class TransactionService { + constructor( + private prisma: PrismaService, + ) {} fetchAllUsersTransactions() { - return prisma.transactions.findMany({ + return this.prisma.transactions.findMany({ where: { OR: [{ from: { @@ -22,7 +25,7 @@ export class TransactionService { } fetchTransactionsOfOneSystemActor(userId: number) { - return prisma.transactions.findMany({ + return this.prisma.transactions.findMany({ where: { OR: [{ from: { @@ -38,7 +41,7 @@ export class TransactionService { } fetchAllAdminProviderTransactions() { - return prisma.transactions.findMany({ + return this.prisma.transactions.findMany({ where: { from: { type: WalletType.provider @@ -52,7 +55,7 @@ export class TransactionService { } createTransaction(credits: number, fromWalletId: number, toWalletId: number, transactionType: TransactionType, description?: string) { - return prisma.transactions.create({ + return this.prisma.transactions.create({ data: { credits: credits, type: transactionType, diff --git a/src/user/dto/purchase.dto.ts b/src/user/dto/purchase.dto.ts new file mode 100644 index 0000000..49556a1 --- /dev/null +++ b/src/user/dto/purchase.dto.ts @@ -0,0 +1,14 @@ +import { IsInt, IsNotEmpty } from "class-validator"; + + +export class PurchaseDto { + + // Third party course provider ID + @IsNotEmpty() + @IsInt() + providerId: number; + + // Number of credits involved in purchase + @IsInt() + credits: number; +} \ No newline at end of file diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 3e8f12a..b50eab0 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,8 +1,9 @@ -import { Body, Controller, Get, NotFoundException, Param, ParseIntPipe, Post, UnauthorizedException } from '@nestjs/common'; -import { TransactionType, WalletType } from '@prisma/client'; +import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; +import { TransactionType } from '@prisma/client'; import { TransactionService } from 'src/transactions/transactions.service'; import { UserService } from './user.service'; import { ProviderService } from 'src/provider/provider.service'; +import { PurchaseDto } from './dto/purchase.dto'; @Controller('users') export class UserController { @@ -15,12 +16,18 @@ export class UserController { @Get("/:userId/credits") // get credits of a particular user async getCredits( - @Param("userId", ParseIntPipe) userId: number, + @Param("userId", ParseIntPipe) userId: number ) { // fetch wallet const wallet = await this.userService.getUserWallet(userId); - return wallet.credits; + return { + statusCode: HttpStatus.OK, + message: "Credits fetched successfully", + body: { + credits: wallet.credits + } + } } @Get("/:userId/transactions") @@ -33,27 +40,38 @@ export class UserController { // fetch transactions const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(userId); - return transactions; + return { + statusCode: HttpStatus.OK, + message: "transactions fetched successfully", + body: { + transactions + } + } } @Post("/:userId/purchase") // decrease credits from a user's wallet due to purchase async handlePurchase( @Param("userId", ParseIntPipe) userId: number, - @Body("credits") credits: number, - @Body("providerId") providerId: number + @Body() purchaseDto: PurchaseDto ) { // update user wallet - const userWalletPromise = this.userService.reduceUserCredits(userId, credits); + const userWalletPromise = this.userService.reduceUserCredits(userId, purchaseDto.credits); // update provider wallet - const providerWalletPromise = this.providerService.addCreditsToProvider(providerId, credits); + const providerWalletPromise = this.providerService.addCreditsToProvider(purchaseDto.providerId, purchaseDto.credits); const [userWallet, providerWallet] = await Promise.all([userWalletPromise, providerWalletPromise]); // create transaction - const transaction = await this.transactionService.createTransaction(credits, userWallet.walletId, providerWallet.walletId, TransactionType.purchase); + const transaction = await this.transactionService.createTransaction(purchaseDto.credits, userWallet.walletId, providerWallet.walletId, TransactionType.purchase); - return transaction.transactionId; + return { + statusCode: HttpStatus.OK, + message: "purchase successful", + body: { + transactionId: transaction.transactionId + } + } } } diff --git a/src/user/user.module.ts b/src/user/user.module.ts index 2cc37d1..4cc1d76 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -4,8 +4,10 @@ import { UserService } from './user.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { WalletService } from 'src/wallet/wallet.service'; import { ProviderService } from 'src/provider/provider.service'; +import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [UserController], providers: [UserService, TransactionService, WalletService, ProviderService] }) diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 0ef4e00..d12aa33 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { WalletType } from '@prisma/client'; import { WalletService } from 'src/wallet/wallet.service'; @@ -12,11 +12,11 @@ export class UserService { // get user wallet const userWallet = await this.walletService.fetchWallet(userId) if(userWallet == null) { - throw new NotFoundException; + throw new HttpException("Wallet does not exist", HttpStatus.NOT_FOUND); } // check user if(userWallet.type != WalletType.user) { - throw new BadRequestException; + throw new HttpException("Wallet does not belong to a user", HttpStatus.BAD_REQUEST); } return userWallet; } @@ -28,7 +28,7 @@ export class UserService { // check credits if(userWallet.credits < credits) { - throw new BadRequestException; + throw new HttpException("Not enough credits", HttpStatus.BAD_REQUEST); } // update user wallet userWallet = await this.walletService.updateWalletCredits(userId, userWallet.credits - credits); diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index f43f3bb..25b7471 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -1,11 +1,14 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { prisma } from 'src/main'; +import { Injectable } from '@nestjs/common'; +import { PrismaService } from 'src/prisma/prisma.service'; @Injectable() export class WalletService { + constructor( + private prisma: PrismaService, + ) {} fetchWallet(userId: number) { - return prisma.wallets.findUnique({ + return this.prisma.wallets.findUnique({ where: { userId: userId } @@ -13,7 +16,7 @@ export class WalletService { } updateWalletCredits(userId: number, newCreditsAmount: number) { - return prisma.wallets.update({ + return this.prisma.wallets.update({ where: { userId: userId }, From 153295815943b0606b0bbae52df17d99546f334d Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Sat, 23 Sep 2023 00:55:07 +0530 Subject: [PATCH 03/28] swagger module implementation --- package-lock.json | 63 ++++++++++++++++++++++-- package.json | 1 + src/admin/admin.controller.ts | 19 ++++++- src/admin/dto/credits.dto.ts | 3 ++ src/main.ts | 12 ++++- src/provider/provider.controller.ts | 7 ++- src/transactions/dto/transactions.dto.ts | 25 ++++++++++ src/user/dto/purchase.dto.ts | 3 ++ src/user/user.controller.ts | 12 ++++- src/wallet/dto/wallet.dto.ts | 7 +++ 10 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 src/transactions/dto/transactions.dto.ts create mode 100644 src/wallet/dto/wallet.dto.ts diff --git a/package-lock.json b/package-lock.json index ef84019..239bec9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^7.1.12", "@prisma/client": "^5.3.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", @@ -1550,6 +1551,25 @@ } } }, + "node_modules/@nestjs/mapped-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz", + "integrity": "sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/platform-express": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.5.tgz", @@ -1642,6 +1662,37 @@ "node": ">=12" } }, + "node_modules/@nestjs/swagger": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.12.tgz", + "integrity": "sha512-Q1P/IE+cws0sJeNtbs+8uDalcVylpmAnaEUFenGOa3KSNnXF/8DOE84mET/uUhFXsiz9PLHK8Hy7o7B6fRpMhg==", + "dependencies": { + "@nestjs/mapped-types": "2.0.2", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "3.2.0", + "swagger-ui-dist": "5.7.2" + }, + "peerDependencies": { + "@fastify/static": "^6.0.0", + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/testing": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.5.tgz", @@ -2611,8 +2662,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-flatten": { "version": "1.1.1", @@ -5817,7 +5867,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -5969,8 +6018,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -7663,6 +7711,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-ui-dist": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.7.2.tgz", + "integrity": "sha512-mVZc9QVQ6pTCV5crli3+Ng+DoMPwdtMHK8QLk2oX8Mtamp4D/hV+uYdC3lV0JZrDgpNEcjs0RrWTqMwwosuLPQ==" + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", diff --git a/package.json b/package.json index 6ca549c..a459c33 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^7.1.12", "@prisma/client": "^5.3.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 20f8010..684c909 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -2,10 +2,15 @@ import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post } from '@n import { AdminService } from './admin.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { UserService } from 'src/user/user.service'; -import { TransactionType, WalletType } from '@prisma/client'; +import { TransactionType } from '@prisma/client'; import { ProviderService } from 'src/provider/provider.service'; import { CreditsDto } from './dto/credits.dto'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { Transaction } from 'src/transactions/dto/transactions.dto'; +import { WalletCredits } from 'src/wallet/dto/wallet.dto'; + +@ApiTags('admin') @Controller('admin') export class AdminController { constructor( @@ -15,6 +20,8 @@ export class AdminController { private providerService: ProviderService ) {} + @ApiOperation({ summary: 'Get All Users Transactions' }) + @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:adminId/transactions/users") // get all transactions of all users async getAllUsersTransactions( @@ -34,6 +41,8 @@ export class AdminController { } } + @ApiOperation({ summary: 'Get One User Transactions' }) + @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:adminId/transactions/users/:userId") // get all transactions of a particular user async getUserTransactions( @@ -57,6 +66,8 @@ export class AdminController { } } + @ApiOperation({ summary: 'Get All Admin Providers Transactions' }) + @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:adminId/transactions/providers") // get all transactions between all providers and admins async getAllAdminProvidersTransactions( @@ -76,6 +87,8 @@ export class AdminController { } } + @ApiOperation({ summary: 'Get One Provider Transactions' }) + @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:adminId/transactions/providers/:providerId") // get all transactions of a particular provider async getProviderTransactions( @@ -99,6 +112,8 @@ export class AdminController { } } + @ApiOperation({ summary: 'Add Credits' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Credits added successfully', type: WalletCredits }) @Post("/:adminId/add-credits") // add credits to a user's wallet async addCredits( @@ -123,6 +138,8 @@ export class AdminController { } } + @ApiOperation({ summary: 'Reduce Credits' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Credits added successfully', type: WalletCredits }) @Post("/:adminId/reduce-credits") // reduce credits from a user's wallet async reduceCredits( diff --git a/src/admin/dto/credits.dto.ts b/src/admin/dto/credits.dto.ts index 5abe6ee..dd30f26 100644 --- a/src/admin/dto/credits.dto.ts +++ b/src/admin/dto/credits.dto.ts @@ -1,14 +1,17 @@ +import { ApiProperty } from "@nestjs/swagger"; import { IsInt, IsNotEmpty } from "class-validator"; export class CreditsDto { // End user ID + @ApiProperty() @IsNotEmpty() @IsInt() userId: number; // Number of credits transferred + @ApiProperty() @IsInt() credits: number; } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index bcc1f2c..727ca3b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,22 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -import { PrismaClient } from '@prisma/client'; import { ValidationPipe } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); + + const config = new DocumentBuilder() + .setTitle('Wallet Service') + // .setDescription('') + .setVersion('1.0') + // .addTag('') + .build(); + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api', app, document); + await app.listen(3000); } bootstrap() \ No newline at end of file diff --git a/src/provider/provider.controller.ts b/src/provider/provider.controller.ts index c459818..44e2ff1 100644 --- a/src/provider/provider.controller.ts +++ b/src/provider/provider.controller.ts @@ -1,14 +1,19 @@ import { Controller, Get, HttpStatus, Param, ParseIntPipe } from '@nestjs/common'; import { TransactionService } from 'src/transactions/transactions.service'; import { ProviderService } from './provider.service'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { Transaction } from 'src/transactions/dto/transactions.dto'; +@ApiTags('providers') @Controller('providers') export class ProviderController { constructor( private transactionService: TransactionService, private providerService: ProviderService ) {} - + + @ApiOperation({ summary: 'Get Provider Transactions' }) + @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:providerId/transactions") // get all transactions of a particular provider async getProviderTransactions( diff --git a/src/transactions/dto/transactions.dto.ts b/src/transactions/dto/transactions.dto.ts new file mode 100644 index 0000000..7755270 --- /dev/null +++ b/src/transactions/dto/transactions.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { TransactionType } from "@prisma/client"; + +export class Transaction { + @ApiProperty() + transactionId: number; + + @ApiProperty() + fromId: number; + + @ApiProperty() + toId: number; + + @ApiProperty() + credits: number; + + @ApiProperty() + type: TransactionType; + + @ApiProperty() + description: string; + + @ApiProperty() + createdAt: Date; +} \ No newline at end of file diff --git a/src/user/dto/purchase.dto.ts b/src/user/dto/purchase.dto.ts index 49556a1..2c03804 100644 --- a/src/user/dto/purchase.dto.ts +++ b/src/user/dto/purchase.dto.ts @@ -1,14 +1,17 @@ +import { ApiProperty } from "@nestjs/swagger"; import { IsInt, IsNotEmpty } from "class-validator"; export class PurchaseDto { // Third party course provider ID + @ApiProperty() @IsNotEmpty() @IsInt() providerId: number; // Number of credits involved in purchase + @ApiProperty() @IsInt() credits: number; } \ No newline at end of file diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index b50eab0..655d15b 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -4,7 +4,11 @@ import { TransactionService } from 'src/transactions/transactions.service'; import { UserService } from './user.service'; import { ProviderService } from 'src/provider/provider.service'; import { PurchaseDto } from './dto/purchase.dto'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { WalletCredits } from 'src/wallet/dto/wallet.dto'; +import { Transaction } from 'src/transactions/dto/transactions.dto'; +@ApiTags('users') @Controller('users') export class UserController { constructor( @@ -13,6 +17,8 @@ export class UserController { private providerService: ProviderService ) {} + @ApiOperation({ summary: 'Get User Credits' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Credits fetched successfully', type: WalletCredits }) @Get("/:userId/credits") // get credits of a particular user async getCredits( @@ -30,6 +36,8 @@ export class UserController { } } + @ApiOperation({ summary: 'Get User Transactions' }) + @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:userId/transactions") // get all transactions of a particular user async getUserTransactions( @@ -49,6 +57,8 @@ export class UserController { } } + @ApiOperation({ summary: 'Handle Purchase' }) + @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: Transaction }) @Post("/:userId/purchase") // decrease credits from a user's wallet due to purchase async handlePurchase( @@ -70,7 +80,7 @@ export class UserController { statusCode: HttpStatus.OK, message: "purchase successful", body: { - transactionId: transaction.transactionId + transaction } } } diff --git a/src/wallet/dto/wallet.dto.ts b/src/wallet/dto/wallet.dto.ts new file mode 100644 index 0000000..02b3060 --- /dev/null +++ b/src/wallet/dto/wallet.dto.ts @@ -0,0 +1,7 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export class WalletCredits { + + @ApiProperty() + credits: number; +} \ No newline at end of file From bf22ee3c12730ee3194697b29495a9616657433d Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Tue, 3 Oct 2023 12:18:29 +0530 Subject: [PATCH 04/28] review changes and docker --- .dockerignore | 6 + .gitignore | 3 +- Dockerfile | 15 +++ docker-compose.yml | 32 +++-- .../20230920065408_init/migration.sql | 36 ------ .../migration.sql | 8 -- .../migration.sql | 12 -- prisma/schema.prisma | 2 +- src/admin/admin.controller.ts | 111 +++++++++--------- src/admin/admin.module.ts | 4 +- src/admin/admin.service.ts | 16 ++- src/admin/dto/credits.dto.ts | 17 --- src/app.controller.ts | 6 +- src/app.module.ts | 4 +- src/dto/credits.dto.ts | 50 ++++++++ .../enduser.controller.spec.ts} | 10 +- src/enduser/enduser.controller.ts | 87 ++++++++++++++ .../enduser.module.ts} | 10 +- .../enduser.service.spec.ts} | 10 +- src/enduser/enduser.service.ts | 49 ++++++++ src/main.ts | 7 +- src/provider/provider.controller.ts | 46 ++++++-- src/provider/provider.module.ts | 3 +- src/provider/provider.service.ts | 20 +++- src/transactions/dto/transactions.dto.ts | 29 ++--- src/transactions/transactions.service.ts | 12 +- src/user/dto/purchase.dto.ts | 17 --- src/user/user.controller.ts | 87 -------------- src/user/user.service.ts | 49 -------- src/wallet/dto/wallet.dto.ts | 3 + src/wallet/wallet.service.ts | 4 +- 31 files changed, 407 insertions(+), 358 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile delete mode 100644 prisma/migrations/20230920065408_init/migration.sql delete mode 100644 prisma/migrations/20230921053937_wallet_user_id_uniq/migration.sql delete mode 100644 prisma/migrations/20230921071650_transaction_type/migration.sql delete mode 100644 src/admin/dto/credits.dto.ts create mode 100644 src/dto/credits.dto.ts rename src/{user/user.controller.spec.ts => enduser/enduser.controller.spec.ts} (51%) create mode 100644 src/enduser/enduser.controller.ts rename src/{user/user.module.ts => enduser/enduser.module.ts} (56%) rename src/{user/user.service.spec.ts => enduser/enduser.service.spec.ts} (54%) create mode 100644 src/enduser/enduser.service.ts delete mode 100644 src/user/dto/purchase.dto.ts delete mode 100644 src/user/user.controller.ts delete mode 100644 src/user/user.service.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8c6af15 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +dist +prisma/migrations \ No newline at end of file diff --git a/.gitignore b/.gitignore index 76add87..a0d218e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -dist \ No newline at end of file +dist +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7b949d3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:16 + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +RUN npx prisma migrate dev + +RUN npm run build + +CMD [ "npm", "run", "start:dev" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 1096b87..89dd8fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,34 @@ version: '3.8' services: - - postgres: + db: image: postgres:13.5 + container_name: postgres restart: always environment: - - POSTGRES_USER=username - - POSTGRES_PASSWORD=password + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} volumes: - postgres:/var/lib/postgresql/data ports: - - '5434:5432' - + - '5432:5432' + networks: + - nestjs + app: + build: + context: . + dockerfile: Dockerfile + container_name: marketplace-walletservice + environment: + - PORT=${PORT} + ports: + - '${PORT}:4000' + depends_on: + - db + volumes: + - ./src:/app/src + volumes: - postgres: \ No newline at end of file + postgres: +networks: + nestjs: \ No newline at end of file diff --git a/prisma/migrations/20230920065408_init/migration.sql b/prisma/migrations/20230920065408_init/migration.sql deleted file mode 100644 index 9991203..0000000 --- a/prisma/migrations/20230920065408_init/migration.sql +++ /dev/null @@ -1,36 +0,0 @@ --- CreateEnum -CREATE TYPE "WalletType" AS ENUM ('admin', 'provider', 'user'); - --- CreateEnum -CREATE TYPE "WalletStatus" AS ENUM ('active', 'inactive', 'frozen'); - --- CreateTable -CREATE TABLE "wallets" ( - "walletId" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "type" "WalletType" NOT NULL, - "status" "WalletStatus" NOT NULL, - "credits" INTEGER NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "wallets_pkey" PRIMARY KEY ("walletId") -); - --- CreateTable -CREATE TABLE "transactions" ( - "transactionId" SERIAL NOT NULL, - "fromId" INTEGER NOT NULL, - "toId" INTEGER NOT NULL, - "credits" INTEGER NOT NULL, - "description" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "transactions_pkey" PRIMARY KEY ("transactionId") -); - --- AddForeignKey -ALTER TABLE "transactions" ADD CONSTRAINT "transactions_fromId_fkey" FOREIGN KEY ("fromId") REFERENCES "wallets"("walletId") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "transactions" ADD CONSTRAINT "transactions_toId_fkey" FOREIGN KEY ("toId") REFERENCES "wallets"("walletId") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20230921053937_wallet_user_id_uniq/migration.sql b/prisma/migrations/20230921053937_wallet_user_id_uniq/migration.sql deleted file mode 100644 index 94a2549..0000000 --- a/prisma/migrations/20230921053937_wallet_user_id_uniq/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[userId]` on the table `wallets` will be added. If there are existing duplicate values, this will fail. - -*/ --- CreateIndex -CREATE UNIQUE INDEX "wallets_userId_key" ON "wallets"("userId"); diff --git a/prisma/migrations/20230921071650_transaction_type/migration.sql b/prisma/migrations/20230921071650_transaction_type/migration.sql deleted file mode 100644 index 9cfe279..0000000 --- a/prisma/migrations/20230921071650_transaction_type/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - Warnings: - - - Added the required column `type` to the `transactions` table without a default value. This is not possible if the table is not empty. - -*/ --- CreateEnum -CREATE TYPE "TransactionType" AS ENUM ('purchase', 'creditRequest', 'settlement'); - --- AlterTable -ALTER TABLE "transactions" ADD COLUMN "type" "TransactionType" NOT NULL, -ALTER COLUMN "description" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f81c36f..f4a904e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,7 +13,7 @@ datasource db { enum WalletType { admin provider - user + enduser } enum WalletStatus { diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 684c909..0714091 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -1,10 +1,10 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; import { AdminService } from './admin.service'; import { TransactionService } from 'src/transactions/transactions.service'; -import { UserService } from 'src/user/user.service'; +import { EnduserService } from 'src/enduser/enduser.service'; import { TransactionType } from '@prisma/client'; import { ProviderService } from 'src/provider/provider.service'; -import { CreditsDto } from './dto/credits.dto'; +import { CreditsDto } from '../dto/credits.dto'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Transaction } from 'src/transactions/dto/transactions.dto'; import { WalletCredits } from 'src/wallet/dto/wallet.dto'; @@ -15,55 +15,56 @@ import { WalletCredits } from 'src/wallet/dto/wallet.dto'; export class AdminController { constructor( private transactionService: TransactionService, - private userService: UserService, + private enduserService: EnduserService, private adminService: AdminService, private providerService: ProviderService ) {} - @ApiOperation({ summary: 'Get All Users Transactions' }) + @ApiOperation({ summary: 'Get All Endusers Transactions' }) @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) - @Get("/:adminId/transactions/users") - // get all transactions of all users - async getAllUsersTransactions( - @Param("adminId", ParseIntPipe) adminId: number + @Get("/:adminId/transactions/endusers") + // get all transactions of all endusers + async getAllEndusersTransactions( + @Param("adminId", ParseIntPipe) adminId: number, + @Res() res ) { // check admin await this.adminService.getAdminWallet(adminId); // fetch transactions - const transactions = await this.transactionService.fetchAllUsersTransactions(); - return { - statusCode: HttpStatus.OK, + const transactions = await this.transactionService.fetchAllEndusersTransactions(); + + return res.status(HttpStatus.OK).json({ message: "transactions fetched successfully", - body: { + data: { transactions } - } + }) } - @ApiOperation({ summary: 'Get One User Transactions' }) + @ApiOperation({ summary: 'Get One Enduser Transactions' }) @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) - @Get("/:adminId/transactions/users/:userId") - // get all transactions of a particular user - async getUserTransactions( + @Get("/:adminId/transactions/endusers/:enduserId") + // get all transactions of a particular enduser + async getEnduserTransactions( @Param("adminId", ParseIntPipe) adminId: number, - @Param("userId", ParseIntPipe) userId: number + @Param("enduserId", ParseIntPipe) enduserId: number, + @Res() res ) { // check admin await this.adminService.getAdminWallet(adminId); - // check user - await this.userService.getUserWallet(userId); + // check enduser + await this.enduserService.getEnduserWallet(enduserId); // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(userId); - return { - statusCode: HttpStatus.OK, + const transactions = await this.transactionService.fetchTransactionsOfOneUser(enduserId); + return res.status(HttpStatus.OK).json({ message: "transactions fetched successfully", - body: { + data: { transactions } - } + }) } @ApiOperation({ summary: 'Get All Admin Providers Transactions' }) @@ -71,20 +72,20 @@ export class AdminController { @Get("/:adminId/transactions/providers") // get all transactions between all providers and admins async getAllAdminProvidersTransactions( - @Param("adminId", ParseIntPipe) adminId: number + @Param("adminId", ParseIntPipe) adminId: number, + @Res() res ) { // check admin await this.adminService.getAdminWallet(adminId); // fetch transactions const transactions = await this.transactionService.fetchAllAdminProviderTransactions(); - return { - statusCode: HttpStatus.OK, + return res.status(HttpStatus.OK).json({ message: "transactions fetched successfully", - body: { + data: { transactions } - } + }) } @ApiOperation({ summary: 'Get One Provider Transactions' }) @@ -93,7 +94,8 @@ export class AdminController { // get all transactions of a particular provider async getProviderTransactions( @Param("adminId", ParseIntPipe) adminId: number, - @Param("providerId", ParseIntPipe) providerId: number + @Param("providerId", ParseIntPipe) providerId: number, + @Res() res ) { // check admin await this.adminService.getAdminWallet(adminId); @@ -102,65 +104,64 @@ export class AdminController { await this.providerService.getProviderWallet(providerId); // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(providerId); - return { - statusCode: HttpStatus.OK, + const transactions = await this.transactionService.fetchTransactionsOfOneUser(providerId); + return res.status(HttpStatus.OK).json({ message: "transactions fetched successfully", - body: { + data: { transactions } - } + }) } @ApiOperation({ summary: 'Add Credits' }) @ApiResponse({ status: HttpStatus.OK, description: 'Credits added successfully', type: WalletCredits }) @Post("/:adminId/add-credits") - // add credits to a user's wallet + // add credits to a enduser's wallet async addCredits( @Param("adminId", ParseIntPipe) adminId: number, - @Body() creditsDto: CreditsDto + @Body() creditsDto: CreditsDto, + @Res() res ) { // check admin const adminWallet = await this.adminService.getAdminWallet(adminId); // update wallet - const userWallet = await this.userService.addCreditsToUser(creditsDto.userId, creditsDto.credits); + const enduserWallet = await this.enduserService.addCreditsToEnduser(creditsDto.enduserId, creditsDto.credits); // create transaction - await this.transactionService.createTransaction(creditsDto.credits, adminWallet.walletId, userWallet.walletId, TransactionType.creditRequest); + await this.transactionService.createTransaction(creditsDto.credits, adminWallet.walletId, enduserWallet.walletId, TransactionType.creditRequest); - return { - statusCode: HttpStatus.OK, + return res.status(HttpStatus.OK).json({ message: "Credits added successfully", - body: { - credits: userWallet.credits + data: { + credits: enduserWallet.credits } - } + }) } @ApiOperation({ summary: 'Reduce Credits' }) @ApiResponse({ status: HttpStatus.OK, description: 'Credits added successfully', type: WalletCredits }) @Post("/:adminId/reduce-credits") - // reduce credits from a user's wallet + // reduce credits from a enduser's wallet async reduceCredits( @Param("adminId", ParseIntPipe) adminId: number, - @Body() creditsDto: CreditsDto + @Body() creditsDto: CreditsDto, + @Res() res ) { // check admin const adminWallet = await this.adminService.getAdminWallet(adminId); // update wallet - const userWallet = await this.userService.reduceUserCredits(creditsDto.userId, creditsDto.credits); + const enduserWallet = await this.enduserService.reduceEnduserCredits(creditsDto.enduserId, creditsDto.credits); // create transaction - await this.transactionService.createTransaction(creditsDto.credits, userWallet.walletId, adminWallet.walletId, TransactionType.creditRequest); + await this.transactionService.createTransaction(creditsDto.credits, enduserWallet.walletId, adminWallet.walletId, TransactionType.creditRequest); - return { - statusCode: HttpStatus.OK, + return res.status(HttpStatus.OK).json({ message: "Credits reduced successfully", - body: { - credits: userWallet.credits + data: { + credits: enduserWallet.credits } - } + }) } } diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts index 2af598f..efaa0e2 100644 --- a/src/admin/admin.module.ts +++ b/src/admin/admin.module.ts @@ -3,13 +3,13 @@ import { AdminController } from './admin.controller'; import { AdminService } from './admin.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { WalletService } from 'src/wallet/wallet.service'; -import { UserService } from 'src/user/user.service'; +import { EnduserService } from 'src/enduser/enduser.service'; import { ProviderService } from 'src/provider/provider.service'; import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ imports: [PrismaModule], controllers: [AdminController], - providers: [AdminService, TransactionService, WalletService, UserService, ProviderService] + providers: [AdminService, TransactionService, WalletService, EnduserService, ProviderService] }) export class AdminModule {} diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts index 0ea3fdd..355bced 100644 --- a/src/admin/admin.service.ts +++ b/src/admin/admin.service.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { WalletType } from '@prisma/client'; import { WalletService } from 'src/wallet/wallet.service'; @@ -12,13 +12,23 @@ export class AdminService { // get admin wallet const adminWallet = await this.walletService.fetchWallet(adminId); if(adminWallet == null) { - throw new HttpException("Wallet does not exist", HttpStatus.NOT_FOUND); + throw new NotFoundException("Wallet does not exist"); } // check admin if(adminWallet.type != WalletType.admin) { - throw new HttpException("Wallet does not belong to admin", HttpStatus.BAD_REQUEST); + throw new BadRequestException("Wallet does not belong to admin"); } return adminWallet; } + + async addCreditsToAdmin(adminId: number, credits: number) { + + // check admin + let adminWallet = await this.getAdminWallet(adminId) + + // update provider wallet + adminWallet = await this.walletService.updateWalletCredits(adminId, adminWallet.credits + credits); + return adminWallet; + } } diff --git a/src/admin/dto/credits.dto.ts b/src/admin/dto/credits.dto.ts deleted file mode 100644 index dd30f26..0000000 --- a/src/admin/dto/credits.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { IsInt, IsNotEmpty } from "class-validator"; - - -export class CreditsDto { - - // End user ID - @ApiProperty() - @IsNotEmpty() - @IsInt() - userId: number; - - // Number of credits transferred - @ApiProperty() - @IsInt() - credits: number; -} \ No newline at end of file diff --git a/src/app.controller.ts b/src/app.controller.ts index cce879e..cebbbbb 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -4,9 +4,5 @@ import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} - - @Get() - getHello(): string { - return this.appService.getHello(); - } + } diff --git a/src/app.module.ts b/src/app.module.ts index f5f837c..220f824 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,11 +3,11 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AdminModule } from './admin/admin.module'; import { ProviderModule } from './provider/provider.module'; -import { UserModule } from './user/user.module'; +import { EnduserModule } from './enduser/enduser.module'; import { PrismaModule } from './prisma/prisma.module'; @Module({ - imports: [AdminModule, ProviderModule, UserModule, PrismaModule], + imports: [AdminModule, ProviderModule, EnduserModule, PrismaModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/dto/credits.dto.ts b/src/dto/credits.dto.ts new file mode 100644 index 0000000..089b074 --- /dev/null +++ b/src/dto/credits.dto.ts @@ -0,0 +1,50 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsInt, IsNotEmpty, Min } from "class-validator"; + +export class CreditsDto { + + // enduser ID + @ApiProperty() + @IsNotEmpty() + @IsInt() + @Min(1) + enduserId: number; + + // Number of credits transferred + @ApiProperty() + @IsInt() + @Min(0) + credits: number; +} + +export class PurchaseDto { + + // provider ID + @ApiProperty() + @IsNotEmpty() + @IsInt() + @Min(1) + providerId: number; + + // Number of credits transferred + @ApiProperty() + @IsInt() + @Min(0) + credits: number; +} + +export class SettlementDto { + + // admin ID + @ApiProperty() + @IsNotEmpty() + @IsInt() + @Min(1) + adminId: number; + + // Number of credits transferred + @ApiProperty() + @IsInt() + @Min(0) + credits: number; +} diff --git a/src/user/user.controller.spec.ts b/src/enduser/enduser.controller.spec.ts similarity index 51% rename from src/user/user.controller.spec.ts rename to src/enduser/enduser.controller.spec.ts index 7057a1a..c6b334d 100644 --- a/src/user/user.controller.spec.ts +++ b/src/enduser/enduser.controller.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { UserController } from './user.controller'; +import { EnduserController } from './enduser.controller'; -describe('UserController', () => { - let controller: UserController; +describe('EnduserController', () => { + let controller: EnduserController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - controllers: [UserController], + controllers: [EnduserController], }).compile(); - controller = module.get(UserController); + controller = module.get(EnduserController); }); it('should be defined', () => { diff --git a/src/enduser/enduser.controller.ts b/src/enduser/enduser.controller.ts new file mode 100644 index 0000000..fef0a2e --- /dev/null +++ b/src/enduser/enduser.controller.ts @@ -0,0 +1,87 @@ +import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; +import { TransactionType } from '@prisma/client'; +import { TransactionService } from 'src/transactions/transactions.service'; +import { EnduserService } from './enduser.service'; +import { ProviderService } from 'src/provider/provider.service'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { WalletCredits } from 'src/wallet/dto/wallet.dto'; +import { Transaction } from 'src/transactions/dto/transactions.dto'; +import { PurchaseDto } from 'src/dto/credits.dto'; + +@ApiTags('endusers') +@Controller('endusers') +export class EnduserController { + constructor( + private transactionService: TransactionService, + private enduserService: EnduserService, + private providerService: ProviderService + ) {} + + @ApiOperation({ summary: 'Get Enduser Credits' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Credits fetched successfully', type: WalletCredits }) + @Get("/:enduserId/credits") + // get credits of a particular enduser + async getCredits( + @Param("enduserId", ParseIntPipe) enduserId: number, + @Res() res + ) { + // fetch wallet + const wallet = await this.enduserService.getEnduserWallet(enduserId); + + return res.status(HttpStatus.OK).json({ + message: "Credits fetched successfully", + data: { + credits: wallet.credits + } + }) + } + + @ApiOperation({ summary: 'Get Enduser Transactions' }) + @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) + @Get("/:enduserId/transactions") + // get all transactions of a particular enduser + async getEnduserTransactions( + @Param("enduserId", ParseIntPipe) enduserId: number, + @Res() res + ) { + // check enduser + await this.enduserService.getEnduserWallet(enduserId); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneUser(enduserId); + return res.status(HttpStatus.OK).json({ + message: "transactions fetched successfully", + data: { + transactions + } + }) + } + + @ApiOperation({ summary: 'Handle Purchase' }) + @ApiResponse({ status: HttpStatus.OK, description: 'purchase successful', type: Transaction }) + @Post("/:enduserId/purchase") + // transfer credits from endenduser's wallet to provider wallet for purchase + async handlePurchase( + @Param("enduserId", ParseIntPipe) enduserId: number, + @Body() purchaseDto: PurchaseDto, + @Res() res + ) { + // update enduser wallet + const enduserWalletPromise = this.enduserService.reduceEnduserCredits(enduserId, purchaseDto.credits); + + // update provider wallet + const providerWalletPromise = this.providerService.addCreditsToProvider(purchaseDto.providerId, purchaseDto.credits); + + const [enduserWallet, providerWallet] = await Promise.all([enduserWalletPromise, providerWalletPromise]); + + // create transaction + const transaction = await this.transactionService.createTransaction(purchaseDto.credits, enduserWallet.walletId, providerWallet.walletId, TransactionType.purchase); + + return res.status(HttpStatus.OK).json({ + message: "purchase successful", + data: { + transaction + } + }) + } +} diff --git a/src/user/user.module.ts b/src/enduser/enduser.module.ts similarity index 56% rename from src/user/user.module.ts rename to src/enduser/enduser.module.ts index 4cc1d76..d366f02 100644 --- a/src/user/user.module.ts +++ b/src/enduser/enduser.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { UserController } from './user.controller'; -import { UserService } from './user.service'; +import { EnduserController } from './enduser.controller'; +import { EnduserService } from './enduser.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { WalletService } from 'src/wallet/wallet.service'; import { ProviderService } from 'src/provider/provider.service'; @@ -8,7 +8,7 @@ import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ imports: [PrismaModule], - controllers: [UserController], - providers: [UserService, TransactionService, WalletService, ProviderService] + controllers: [EnduserController], + providers: [EnduserService, TransactionService, WalletService, ProviderService] }) -export class UserModule {} +export class EnduserModule {} diff --git a/src/user/user.service.spec.ts b/src/enduser/enduser.service.spec.ts similarity index 54% rename from src/user/user.service.spec.ts rename to src/enduser/enduser.service.spec.ts index 873de8a..ad93eae 100644 --- a/src/user/user.service.spec.ts +++ b/src/enduser/enduser.service.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { UserService } from './user.service'; +import { EnduserService } from './enduser.service'; -describe('UserService', () => { - let service: UserService; +describe('EnduserService', () => { + let service: EnduserService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [UserService], + providers: [EnduserService], }).compile(); - service = module.get(UserService); + service = module.get(EnduserService); }); it('should be defined', () => { diff --git a/src/enduser/enduser.service.ts b/src/enduser/enduser.service.ts new file mode 100644 index 0000000..42250f3 --- /dev/null +++ b/src/enduser/enduser.service.ts @@ -0,0 +1,49 @@ +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { WalletType } from '@prisma/client'; +import { WalletService } from 'src/wallet/wallet.service'; + +@Injectable() +export class EnduserService { + constructor( + private walletService: WalletService, + ) {} + + async getEnduserWallet(enduserId: number) { + // get enduser wallet + const enduserWallet = await this.walletService.fetchWallet(enduserId) + if(enduserWallet == null) { + throw new NotFoundException("Wallet does not exist"); + } + // check enduser + if(enduserWallet.type != WalletType.enduser) { + throw new BadRequestException("Wallet does not belong to a enduser"); + } + return enduserWallet; + } + + async reduceEnduserCredits(enduserId: number, credits: number) { + + // fetch enduser wallet + let enduserWallet = await this.getEnduserWallet(enduserId); + + // check credits + if(enduserWallet.credits < credits) { + throw new BadRequestException("Not enough credits"); + } + // update enduser wallet + enduserWallet = await this.walletService.updateWalletCredits(enduserId, enduserWallet.credits - credits); + + return enduserWallet; + } + + async addCreditsToEnduser(enduserId: number, credits: number) { + + // fetch enduser wallet + let enduserWallet = await this.getEnduserWallet(enduserId); + + // update enduser wallet + enduserWallet = await this.walletService.updateWalletCredits(enduserId, enduserWallet.credits + credits); + + return enduserWallet; + } +} diff --git a/src/main.ts b/src/main.ts index 727ca3b..0e20ffe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,8 +6,13 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); - app.useGlobalPipes(new ValidationPipe()); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + }), + ); + const config = new DocumentBuilder() .setTitle('Wallet Service') // .setDescription('') diff --git a/src/provider/provider.controller.ts b/src/provider/provider.controller.ts index 44e2ff1..567ee26 100644 --- a/src/provider/provider.controller.ts +++ b/src/provider/provider.controller.ts @@ -1,15 +1,19 @@ -import { Controller, Get, HttpStatus, Param, ParseIntPipe } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; import { TransactionService } from 'src/transactions/transactions.service'; import { ProviderService } from './provider.service'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Transaction } from 'src/transactions/dto/transactions.dto'; +import { SettlementDto } from 'src/dto/credits.dto'; +import { AdminService } from 'src/admin/admin.service'; +import { TransactionType } from '@prisma/client'; @ApiTags('providers') @Controller('providers') export class ProviderController { constructor( private transactionService: TransactionService, - private providerService: ProviderService + private providerService: ProviderService, + private adminService: AdminService ) {} @ApiOperation({ summary: 'Get Provider Transactions' }) @@ -18,18 +22,46 @@ export class ProviderController { // get all transactions of a particular provider async getProviderTransactions( @Param("providerId", ParseIntPipe) providerId: number, + @Res() res ) { // check provider await this.providerService.getProviderWallet(providerId); // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(providerId); - return { - statusCode: HttpStatus.OK, + const transactions = await this.transactionService.fetchTransactionsOfOneUser(providerId); + return res.status(HttpStatus.OK).json({ message: "transactions fetched successfully", - body: { + data: { transactions } - } + }) + } + + @ApiOperation({ summary: 'Transfer settlement credits and record transaction' }) + @ApiResponse({ status: HttpStatus.OK, description: 'credits transferred successfully', type: Transaction }) + @Post("/:providerId/settlement-transaction") + // Transfer credits from provider wallet to admin wallet + async settleProviderWallet( + @Param("providerId", ParseIntPipe) providerId: number, + @Body() settlementDto: SettlementDto, + @Res() res + ) { + // update provider wallet + const providerWalletPromise = this.providerService.reduceProviderCredits(providerId, settlementDto.credits); + + // update admin wallet + const adminWalletPromise = this.adminService.addCreditsToAdmin(settlementDto.adminId, settlementDto.credits); + + const [providerWallet, adminWallet] = await Promise.all([providerWalletPromise, adminWalletPromise]); + + // create transaction + const transaction = await this.transactionService.createTransaction(settlementDto.credits, providerWallet.walletId, adminWallet.walletId, TransactionType.settlement); + + return res.status(HttpStatus.OK).json({ + message: "credits transferred successfully", + data: { + transaction + } + }) } } diff --git a/src/provider/provider.module.ts b/src/provider/provider.module.ts index 0685a2a..9f65c5e 100644 --- a/src/provider/provider.module.ts +++ b/src/provider/provider.module.ts @@ -4,10 +4,11 @@ import { ProviderService } from './provider.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { WalletService } from 'src/wallet/wallet.service'; import { PrismaModule } from 'src/prisma/prisma.module'; +import { AdminService } from 'src/admin/admin.service'; @Module({ imports: [PrismaModule], controllers: [ProviderController], - providers: [ProviderService, TransactionService, WalletService] + providers: [ProviderService, TransactionService, WalletService, AdminService] }) export class ProviderModule {} diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index 5d991e8..25a9e2e 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { WalletType } from '@prisma/client'; import { WalletService } from 'src/wallet/wallet.service'; @@ -12,12 +12,12 @@ export class ProviderService { // get provider wallet const providerWallet = await this.walletService.fetchWallet(providerId) if(providerWallet == null) { - throw new HttpException("Wallet does not exist", HttpStatus.NOT_FOUND); + throw new NotFoundException("Wallet does not exist"); } // check provider if(providerWallet.type != WalletType.provider) { - throw new HttpException("Wallet does not belong to provider", HttpStatus.BAD_REQUEST); + throw new BadRequestException("Wallet does not belong to provider"); } return providerWallet; } @@ -32,4 +32,18 @@ export class ProviderService { return providerWallet; } + async reduceProviderCredits(enduserId: number, credits: number) { + + // fetch wallet + let providerWallet = await this.getProviderWallet(enduserId); + + // check credits + if(providerWallet.credits < credits) { + throw new BadRequestException("Not enough credits"); + } + // update wallet + providerWallet = await this.walletService.updateWalletCredits(enduserId, providerWallet.credits - credits); + + return providerWallet; + } } diff --git a/src/transactions/dto/transactions.dto.ts b/src/transactions/dto/transactions.dto.ts index 7755270..0a99d71 100644 --- a/src/transactions/dto/transactions.dto.ts +++ b/src/transactions/dto/transactions.dto.ts @@ -1,25 +1,12 @@ -import { ApiProperty } from "@nestjs/swagger"; import { TransactionType } from "@prisma/client"; export class Transaction { - @ApiProperty() - transactionId: number; - - @ApiProperty() - fromId: number; - - @ApiProperty() - toId: number; - - @ApiProperty() - credits: number; - - @ApiProperty() - type: TransactionType; - - @ApiProperty() - description: string; - - @ApiProperty() - createdAt: Date; + + readonly transactionId: number; + readonly fromId: number; + readonly toId: number; + readonly credits: number; + readonly type: TransactionType; + readonly description: string; + readonly createdAt: Date; } \ No newline at end of file diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index 6cd04da..1d4f567 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -8,32 +8,32 @@ export class TransactionService { private prisma: PrismaService, ) {} - fetchAllUsersTransactions() { + fetchAllEndusersTransactions() { return this.prisma.transactions.findMany({ where: { OR: [{ from: { - type: WalletType.user + type: WalletType.enduser } }, { to: { - type: WalletType.user + type: WalletType.enduser } }] } }); } - fetchTransactionsOfOneSystemActor(userId: number) { + fetchTransactionsOfOneUser(userId: number) { return this.prisma.transactions.findMany({ where: { OR: [{ from: { - userId: userId, + userId, } }, { to: { - userId: userId, + userId, } }] } diff --git a/src/user/dto/purchase.dto.ts b/src/user/dto/purchase.dto.ts deleted file mode 100644 index 2c03804..0000000 --- a/src/user/dto/purchase.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { IsInt, IsNotEmpty } from "class-validator"; - - -export class PurchaseDto { - - // Third party course provider ID - @ApiProperty() - @IsNotEmpty() - @IsInt() - providerId: number; - - // Number of credits involved in purchase - @ApiProperty() - @IsInt() - credits: number; -} \ No newline at end of file diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts deleted file mode 100644 index 655d15b..0000000 --- a/src/user/user.controller.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; -import { TransactionType } from '@prisma/client'; -import { TransactionService } from 'src/transactions/transactions.service'; -import { UserService } from './user.service'; -import { ProviderService } from 'src/provider/provider.service'; -import { PurchaseDto } from './dto/purchase.dto'; -import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { WalletCredits } from 'src/wallet/dto/wallet.dto'; -import { Transaction } from 'src/transactions/dto/transactions.dto'; - -@ApiTags('users') -@Controller('users') -export class UserController { - constructor( - private transactionService: TransactionService, - private userService: UserService, - private providerService: ProviderService - ) {} - - @ApiOperation({ summary: 'Get User Credits' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Credits fetched successfully', type: WalletCredits }) - @Get("/:userId/credits") - // get credits of a particular user - async getCredits( - @Param("userId", ParseIntPipe) userId: number - ) { - // fetch wallet - const wallet = await this.userService.getUserWallet(userId); - - return { - statusCode: HttpStatus.OK, - message: "Credits fetched successfully", - body: { - credits: wallet.credits - } - } - } - - @ApiOperation({ summary: 'Get User Transactions' }) - @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) - @Get("/:userId/transactions") - // get all transactions of a particular user - async getUserTransactions( - @Param("userId", ParseIntPipe) userId: number - ) { - // check user - await this.userService.getUserWallet(userId); - - // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneSystemActor(userId); - return { - statusCode: HttpStatus.OK, - message: "transactions fetched successfully", - body: { - transactions - } - } - } - - @ApiOperation({ summary: 'Handle Purchase' }) - @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: Transaction }) - @Post("/:userId/purchase") - // decrease credits from a user's wallet due to purchase - async handlePurchase( - @Param("userId", ParseIntPipe) userId: number, - @Body() purchaseDto: PurchaseDto - ) { - // update user wallet - const userWalletPromise = this.userService.reduceUserCredits(userId, purchaseDto.credits); - - // update provider wallet - const providerWalletPromise = this.providerService.addCreditsToProvider(purchaseDto.providerId, purchaseDto.credits); - - const [userWallet, providerWallet] = await Promise.all([userWalletPromise, providerWalletPromise]); - - // create transaction - const transaction = await this.transactionService.createTransaction(purchaseDto.credits, userWallet.walletId, providerWallet.walletId, TransactionType.purchase); - - return { - statusCode: HttpStatus.OK, - message: "purchase successful", - body: { - transaction - } - } - } -} diff --git a/src/user/user.service.ts b/src/user/user.service.ts deleted file mode 100644 index d12aa33..0000000 --- a/src/user/user.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { WalletType } from '@prisma/client'; -import { WalletService } from 'src/wallet/wallet.service'; - -@Injectable() -export class UserService { - constructor( - private walletService: WalletService, - ) {} - - async getUserWallet(userId: number) { - // get user wallet - const userWallet = await this.walletService.fetchWallet(userId) - if(userWallet == null) { - throw new HttpException("Wallet does not exist", HttpStatus.NOT_FOUND); - } - // check user - if(userWallet.type != WalletType.user) { - throw new HttpException("Wallet does not belong to a user", HttpStatus.BAD_REQUEST); - } - return userWallet; - } - - async reduceUserCredits(userId: number, credits: number) { - - // fetch user wallet - let userWallet = await this.getUserWallet(userId); - - // check credits - if(userWallet.credits < credits) { - throw new HttpException("Not enough credits", HttpStatus.BAD_REQUEST); - } - // update user wallet - userWallet = await this.walletService.updateWalletCredits(userId, userWallet.credits - credits); - - return userWallet; - } - - async addCreditsToUser(userId: number, credits: number) { - - // fetch user wallet - let userWallet = await this.getUserWallet(userId); - - // update user wallet - userWallet = await this.walletService.updateWalletCredits(userId, userWallet.credits + credits); - - return userWallet; - } -} diff --git a/src/wallet/dto/wallet.dto.ts b/src/wallet/dto/wallet.dto.ts index 02b3060..e7c7ab2 100644 --- a/src/wallet/dto/wallet.dto.ts +++ b/src/wallet/dto/wallet.dto.ts @@ -1,7 +1,10 @@ import { ApiProperty } from "@nestjs/swagger"; +import { IsInt, Min } from "class-validator"; export class WalletCredits { @ApiProperty() + @IsInt() + @Min(0) credits: number; } \ No newline at end of file diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index 25b7471..ac9a8b0 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -10,7 +10,7 @@ export class WalletService { fetchWallet(userId: number) { return this.prisma.wallets.findUnique({ where: { - userId: userId + userId } }) } @@ -18,7 +18,7 @@ export class WalletService { updateWalletCredits(userId: number, newCreditsAmount: number) { return this.prisma.wallets.update({ where: { - userId: userId + userId }, data: { credits: { From 799953c27c5542303cf2355940ca55773a2995d9 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Tue, 3 Oct 2023 12:31:26 +0530 Subject: [PATCH 05/28] enduser to consumer --- prisma/schema.prisma | 2 +- src/admin/admin.controller.ts | 46 ++++++++--------- src/admin/admin.module.ts | 4 +- src/app.module.ts | 4 +- .../consumer.controller.spec.ts} | 10 ++-- .../consumer.controller.ts} | 50 +++++++++---------- .../consumer.module.ts} | 10 ++-- .../consumer.service.spec.ts} | 10 ++-- src/consumer/consumer.service.ts | 49 ++++++++++++++++++ src/dto/credits.dto.ts | 4 +- src/enduser/enduser.service.ts | 49 ------------------ src/provider/provider.service.ts | 6 +-- src/transactions/transactions.service.ts | 6 +-- 13 files changed, 125 insertions(+), 125 deletions(-) rename src/{enduser/enduser.controller.spec.ts => consumer/consumer.controller.spec.ts} (51%) rename src/{enduser/enduser.controller.ts => consumer/consumer.controller.ts} (60%) rename src/{enduser/enduser.module.ts => consumer/consumer.module.ts} (56%) rename src/{enduser/enduser.service.spec.ts => consumer/consumer.service.spec.ts} (54%) create mode 100644 src/consumer/consumer.service.ts delete mode 100644 src/enduser/enduser.service.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f4a904e..dd04b91 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,7 +13,7 @@ datasource db { enum WalletType { admin provider - enduser + consumer } enum WalletStatus { diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 0714091..f90d3a9 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; import { AdminService } from './admin.service'; import { TransactionService } from 'src/transactions/transactions.service'; -import { EnduserService } from 'src/enduser/enduser.service'; +import { ConsumerService } from 'src/consumer/consumer.service'; import { TransactionType } from '@prisma/client'; import { ProviderService } from 'src/provider/provider.service'; import { CreditsDto } from '../dto/credits.dto'; @@ -15,16 +15,16 @@ import { WalletCredits } from 'src/wallet/dto/wallet.dto'; export class AdminController { constructor( private transactionService: TransactionService, - private enduserService: EnduserService, + private consumerService: ConsumerService, private adminService: AdminService, private providerService: ProviderService ) {} - @ApiOperation({ summary: 'Get All Endusers Transactions' }) + @ApiOperation({ summary: 'Get All Consumers Transactions' }) @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) - @Get("/:adminId/transactions/endusers") - // get all transactions of all endusers - async getAllEndusersTransactions( + @Get("/:adminId/transactions/consumers") + // get all transactions of all consumers + async getAllConsumersTransactions( @Param("adminId", ParseIntPipe) adminId: number, @Res() res ) { @@ -32,7 +32,7 @@ export class AdminController { await this.adminService.getAdminWallet(adminId); // fetch transactions - const transactions = await this.transactionService.fetchAllEndusersTransactions(); + const transactions = await this.transactionService.fetchAllConsumersTransactions(); return res.status(HttpStatus.OK).json({ message: "transactions fetched successfully", @@ -42,23 +42,23 @@ export class AdminController { }) } - @ApiOperation({ summary: 'Get One Enduser Transactions' }) + @ApiOperation({ summary: 'Get One Consumer Transactions' }) @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) - @Get("/:adminId/transactions/endusers/:enduserId") - // get all transactions of a particular enduser - async getEnduserTransactions( + @Get("/:adminId/transactions/consumers/:consumerId") + // get all transactions of a particular consumer + async getConsumerTransactions( @Param("adminId", ParseIntPipe) adminId: number, - @Param("enduserId", ParseIntPipe) enduserId: number, + @Param("consumerId", ParseIntPipe) consumerId: number, @Res() res ) { // check admin await this.adminService.getAdminWallet(adminId); - // check enduser - await this.enduserService.getEnduserWallet(enduserId); + // check consumer + await this.consumerService.getConsumerWallet(consumerId); // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneUser(enduserId); + const transactions = await this.transactionService.fetchTransactionsOfOneUser(consumerId); return res.status(HttpStatus.OK).json({ message: "transactions fetched successfully", data: { @@ -116,7 +116,7 @@ export class AdminController { @ApiOperation({ summary: 'Add Credits' }) @ApiResponse({ status: HttpStatus.OK, description: 'Credits added successfully', type: WalletCredits }) @Post("/:adminId/add-credits") - // add credits to a enduser's wallet + // add credits to a consumer's wallet async addCredits( @Param("adminId", ParseIntPipe) adminId: number, @Body() creditsDto: CreditsDto, @@ -126,15 +126,15 @@ export class AdminController { const adminWallet = await this.adminService.getAdminWallet(adminId); // update wallet - const enduserWallet = await this.enduserService.addCreditsToEnduser(creditsDto.enduserId, creditsDto.credits); + const consumerWallet = await this.consumerService.addCreditsToConsumer(creditsDto.consumerId, creditsDto.credits); // create transaction - await this.transactionService.createTransaction(creditsDto.credits, adminWallet.walletId, enduserWallet.walletId, TransactionType.creditRequest); + await this.transactionService.createTransaction(creditsDto.credits, adminWallet.walletId, consumerWallet.walletId, TransactionType.creditRequest); return res.status(HttpStatus.OK).json({ message: "Credits added successfully", data: { - credits: enduserWallet.credits + credits: consumerWallet.credits } }) } @@ -142,7 +142,7 @@ export class AdminController { @ApiOperation({ summary: 'Reduce Credits' }) @ApiResponse({ status: HttpStatus.OK, description: 'Credits added successfully', type: WalletCredits }) @Post("/:adminId/reduce-credits") - // reduce credits from a enduser's wallet + // reduce credits from a consumer's wallet async reduceCredits( @Param("adminId", ParseIntPipe) adminId: number, @Body() creditsDto: CreditsDto, @@ -152,15 +152,15 @@ export class AdminController { const adminWallet = await this.adminService.getAdminWallet(adminId); // update wallet - const enduserWallet = await this.enduserService.reduceEnduserCredits(creditsDto.enduserId, creditsDto.credits); + const consumerWallet = await this.consumerService.reduceConsumerCredits(creditsDto.consumerId, creditsDto.credits); // create transaction - await this.transactionService.createTransaction(creditsDto.credits, enduserWallet.walletId, adminWallet.walletId, TransactionType.creditRequest); + await this.transactionService.createTransaction(creditsDto.credits, consumerWallet.walletId, adminWallet.walletId, TransactionType.creditRequest); return res.status(HttpStatus.OK).json({ message: "Credits reduced successfully", data: { - credits: enduserWallet.credits + credits: consumerWallet.credits } }) } diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts index efaa0e2..beb704d 100644 --- a/src/admin/admin.module.ts +++ b/src/admin/admin.module.ts @@ -3,13 +3,13 @@ import { AdminController } from './admin.controller'; import { AdminService } from './admin.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { WalletService } from 'src/wallet/wallet.service'; -import { EnduserService } from 'src/enduser/enduser.service'; +import { ConsumerService } from 'src/consumer/consumer.service'; import { ProviderService } from 'src/provider/provider.service'; import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ imports: [PrismaModule], controllers: [AdminController], - providers: [AdminService, TransactionService, WalletService, EnduserService, ProviderService] + providers: [AdminService, TransactionService, WalletService, ConsumerService, ProviderService] }) export class AdminModule {} diff --git a/src/app.module.ts b/src/app.module.ts index 220f824..a8f7f54 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,11 +3,11 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AdminModule } from './admin/admin.module'; import { ProviderModule } from './provider/provider.module'; -import { EnduserModule } from './enduser/enduser.module'; +import { ConsumerModule } from './consumer/consumer.module'; import { PrismaModule } from './prisma/prisma.module'; @Module({ - imports: [AdminModule, ProviderModule, EnduserModule, PrismaModule], + imports: [AdminModule, ProviderModule, ConsumerModule, PrismaModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/enduser/enduser.controller.spec.ts b/src/consumer/consumer.controller.spec.ts similarity index 51% rename from src/enduser/enduser.controller.spec.ts rename to src/consumer/consumer.controller.spec.ts index c6b334d..be9814d 100644 --- a/src/enduser/enduser.controller.spec.ts +++ b/src/consumer/consumer.controller.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { EnduserController } from './enduser.controller'; +import { ConsumerController } from './consumer.controller'; -describe('EnduserController', () => { - let controller: EnduserController; +describe('ConsumerController', () => { + let controller: ConsumerController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - controllers: [EnduserController], + controllers: [ConsumerController], }).compile(); - controller = module.get(EnduserController); + controller = module.get(ConsumerController); }); it('should be defined', () => { diff --git a/src/enduser/enduser.controller.ts b/src/consumer/consumer.controller.ts similarity index 60% rename from src/enduser/enduser.controller.ts rename to src/consumer/consumer.controller.ts index fef0a2e..d7ec747 100644 --- a/src/enduser/enduser.controller.ts +++ b/src/consumer/consumer.controller.ts @@ -1,32 +1,32 @@ import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; import { TransactionType } from '@prisma/client'; import { TransactionService } from 'src/transactions/transactions.service'; -import { EnduserService } from './enduser.service'; +import { ConsumerService } from './consumer.service'; import { ProviderService } from 'src/provider/provider.service'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { WalletCredits } from 'src/wallet/dto/wallet.dto'; import { Transaction } from 'src/transactions/dto/transactions.dto'; import { PurchaseDto } from 'src/dto/credits.dto'; -@ApiTags('endusers') -@Controller('endusers') -export class EnduserController { +@ApiTags('consumers') +@Controller('consumers') +export class ConsumerController { constructor( private transactionService: TransactionService, - private enduserService: EnduserService, + private consumerService: ConsumerService, private providerService: ProviderService ) {} - @ApiOperation({ summary: 'Get Enduser Credits' }) + @ApiOperation({ summary: 'Get Consumer Credits' }) @ApiResponse({ status: HttpStatus.OK, description: 'Credits fetched successfully', type: WalletCredits }) - @Get("/:enduserId/credits") - // get credits of a particular enduser + @Get("/:consumerId/credits") + // get credits of a particular consumer async getCredits( - @Param("enduserId", ParseIntPipe) enduserId: number, + @Param("consumerId", ParseIntPipe) consumerId: number, @Res() res ) { // fetch wallet - const wallet = await this.enduserService.getEnduserWallet(enduserId); + const wallet = await this.consumerService.getConsumerWallet(consumerId); return res.status(HttpStatus.OK).json({ message: "Credits fetched successfully", @@ -36,19 +36,19 @@ export class EnduserController { }) } - @ApiOperation({ summary: 'Get Enduser Transactions' }) + @ApiOperation({ summary: 'Get Consumer Transactions' }) @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) - @Get("/:enduserId/transactions") - // get all transactions of a particular enduser - async getEnduserTransactions( - @Param("enduserId", ParseIntPipe) enduserId: number, + @Get("/:consumerId/transactions") + // get all transactions of a particular consumer + async getConsumerTransactions( + @Param("consumerId", ParseIntPipe) consumerId: number, @Res() res ) { - // check enduser - await this.enduserService.getEnduserWallet(enduserId); + // check consumer + await this.consumerService.getConsumerWallet(consumerId); // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneUser(enduserId); + const transactions = await this.transactionService.fetchTransactionsOfOneUser(consumerId); return res.status(HttpStatus.OK).json({ message: "transactions fetched successfully", data: { @@ -59,23 +59,23 @@ export class EnduserController { @ApiOperation({ summary: 'Handle Purchase' }) @ApiResponse({ status: HttpStatus.OK, description: 'purchase successful', type: Transaction }) - @Post("/:enduserId/purchase") - // transfer credits from endenduser's wallet to provider wallet for purchase + @Post("/:consumerId/purchase") + // transfer credits from endconsumer's wallet to provider wallet for purchase async handlePurchase( - @Param("enduserId", ParseIntPipe) enduserId: number, + @Param("consumerId", ParseIntPipe) consumerId: number, @Body() purchaseDto: PurchaseDto, @Res() res ) { - // update enduser wallet - const enduserWalletPromise = this.enduserService.reduceEnduserCredits(enduserId, purchaseDto.credits); + // update consumer wallet + const consumerWalletPromise = this.consumerService.reduceConsumerCredits(consumerId, purchaseDto.credits); // update provider wallet const providerWalletPromise = this.providerService.addCreditsToProvider(purchaseDto.providerId, purchaseDto.credits); - const [enduserWallet, providerWallet] = await Promise.all([enduserWalletPromise, providerWalletPromise]); + const [consumerWallet, providerWallet] = await Promise.all([consumerWalletPromise, providerWalletPromise]); // create transaction - const transaction = await this.transactionService.createTransaction(purchaseDto.credits, enduserWallet.walletId, providerWallet.walletId, TransactionType.purchase); + const transaction = await this.transactionService.createTransaction(purchaseDto.credits, consumerWallet.walletId, providerWallet.walletId, TransactionType.purchase); return res.status(HttpStatus.OK).json({ message: "purchase successful", diff --git a/src/enduser/enduser.module.ts b/src/consumer/consumer.module.ts similarity index 56% rename from src/enduser/enduser.module.ts rename to src/consumer/consumer.module.ts index d366f02..0cf6f7b 100644 --- a/src/enduser/enduser.module.ts +++ b/src/consumer/consumer.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { EnduserController } from './enduser.controller'; -import { EnduserService } from './enduser.service'; +import { ConsumerController } from './consumer.controller'; +import { ConsumerService } from './consumer.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { WalletService } from 'src/wallet/wallet.service'; import { ProviderService } from 'src/provider/provider.service'; @@ -8,7 +8,7 @@ import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ imports: [PrismaModule], - controllers: [EnduserController], - providers: [EnduserService, TransactionService, WalletService, ProviderService] + controllers: [ConsumerController], + providers: [ConsumerService, TransactionService, WalletService, ProviderService] }) -export class EnduserModule {} +export class ConsumerModule {} diff --git a/src/enduser/enduser.service.spec.ts b/src/consumer/consumer.service.spec.ts similarity index 54% rename from src/enduser/enduser.service.spec.ts rename to src/consumer/consumer.service.spec.ts index ad93eae..40d1db3 100644 --- a/src/enduser/enduser.service.spec.ts +++ b/src/consumer/consumer.service.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { EnduserService } from './enduser.service'; +import { ConsumerService } from './consumer.service'; -describe('EnduserService', () => { - let service: EnduserService; +describe('ConsumerService', () => { + let service: ConsumerService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [EnduserService], + providers: [ConsumerService], }).compile(); - service = module.get(EnduserService); + service = module.get(ConsumerService); }); it('should be defined', () => { diff --git a/src/consumer/consumer.service.ts b/src/consumer/consumer.service.ts new file mode 100644 index 0000000..49012cb --- /dev/null +++ b/src/consumer/consumer.service.ts @@ -0,0 +1,49 @@ +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { WalletType } from '@prisma/client'; +import { WalletService } from 'src/wallet/wallet.service'; + +@Injectable() +export class ConsumerService { + constructor( + private walletService: WalletService, + ) {} + + async getConsumerWallet(consumerId: number) { + // get consumer wallet + const consumerWallet = await this.walletService.fetchWallet(consumerId) + if(consumerWallet == null) { + throw new NotFoundException("Wallet does not exist"); + } + // check consumer + if(consumerWallet.type != WalletType.consumer) { + throw new BadRequestException("Wallet does not belong to a consumer"); + } + return consumerWallet; + } + + async reduceConsumerCredits(consumerId: number, credits: number) { + + // fetch consumer wallet + let consumerWallet = await this.getConsumerWallet(consumerId); + + // check credits + if(consumerWallet.credits < credits) { + throw new BadRequestException("Not enough credits"); + } + // update consumer wallet + consumerWallet = await this.walletService.updateWalletCredits(consumerId, consumerWallet.credits - credits); + + return consumerWallet; + } + + async addCreditsToConsumer(consumerId: number, credits: number) { + + // fetch consumer wallet + let consumerWallet = await this.getConsumerWallet(consumerId); + + // update consumer wallet + consumerWallet = await this.walletService.updateWalletCredits(consumerId, consumerWallet.credits + credits); + + return consumerWallet; + } +} diff --git a/src/dto/credits.dto.ts b/src/dto/credits.dto.ts index 089b074..d5e0ba4 100644 --- a/src/dto/credits.dto.ts +++ b/src/dto/credits.dto.ts @@ -3,12 +3,12 @@ import { IsInt, IsNotEmpty, Min } from "class-validator"; export class CreditsDto { - // enduser ID + // consumer ID @ApiProperty() @IsNotEmpty() @IsInt() @Min(1) - enduserId: number; + consumerId: number; // Number of credits transferred @ApiProperty() diff --git a/src/enduser/enduser.service.ts b/src/enduser/enduser.service.ts deleted file mode 100644 index 42250f3..0000000 --- a/src/enduser/enduser.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; -import { WalletType } from '@prisma/client'; -import { WalletService } from 'src/wallet/wallet.service'; - -@Injectable() -export class EnduserService { - constructor( - private walletService: WalletService, - ) {} - - async getEnduserWallet(enduserId: number) { - // get enduser wallet - const enduserWallet = await this.walletService.fetchWallet(enduserId) - if(enduserWallet == null) { - throw new NotFoundException("Wallet does not exist"); - } - // check enduser - if(enduserWallet.type != WalletType.enduser) { - throw new BadRequestException("Wallet does not belong to a enduser"); - } - return enduserWallet; - } - - async reduceEnduserCredits(enduserId: number, credits: number) { - - // fetch enduser wallet - let enduserWallet = await this.getEnduserWallet(enduserId); - - // check credits - if(enduserWallet.credits < credits) { - throw new BadRequestException("Not enough credits"); - } - // update enduser wallet - enduserWallet = await this.walletService.updateWalletCredits(enduserId, enduserWallet.credits - credits); - - return enduserWallet; - } - - async addCreditsToEnduser(enduserId: number, credits: number) { - - // fetch enduser wallet - let enduserWallet = await this.getEnduserWallet(enduserId); - - // update enduser wallet - enduserWallet = await this.walletService.updateWalletCredits(enduserId, enduserWallet.credits + credits); - - return enduserWallet; - } -} diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index 25a9e2e..0e227c7 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -32,17 +32,17 @@ export class ProviderService { return providerWallet; } - async reduceProviderCredits(enduserId: number, credits: number) { + async reduceProviderCredits(consumerId: number, credits: number) { // fetch wallet - let providerWallet = await this.getProviderWallet(enduserId); + let providerWallet = await this.getProviderWallet(consumerId); // check credits if(providerWallet.credits < credits) { throw new BadRequestException("Not enough credits"); } // update wallet - providerWallet = await this.walletService.updateWalletCredits(enduserId, providerWallet.credits - credits); + providerWallet = await this.walletService.updateWalletCredits(consumerId, providerWallet.credits - credits); return providerWallet; } diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index 1d4f567..c34d05f 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -8,16 +8,16 @@ export class TransactionService { private prisma: PrismaService, ) {} - fetchAllEndusersTransactions() { + fetchAllConsumersTransactions() { return this.prisma.transactions.findMany({ where: { OR: [{ from: { - type: WalletType.enduser + type: WalletType.consumer } }, { to: { - type: WalletType.enduser + type: WalletType.consumer } }] } From 42eed9ad292334751180b988f8607b0331e880c2 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Wed, 11 Oct 2023 10:40:36 +0530 Subject: [PATCH 06/28] readme updated --- README.md | 82 +++++++++---------------------------------------------- 1 file changed, 13 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 00a13b1..dbcab76 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,17 @@ -

- Nest Logo -

+# Wallet Service -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest +[Compass Product Flow](https://miro.com/app/board/uXjVMkv3bh4=/?share_link_id=179469421530) -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Coverage -Discord -Backers on Open Collective -Sponsors on Open Collective - - Support us - -

- +## Use Cases +Admin: + - Add/reduce credits to end user’s wallet + - View admin-user transaction history + - View admin-3CP transaction history -## Description +3CP: + - View transaction history with marketplace -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. - -## Installation - -```bash -$ npm install -``` - -## Running the app - -```bash -# development -$ npm run start - -# watch mode -$ npm run start:dev - -# production mode -$ npm run start:prod -``` - -## Test - -```bash -# unit tests -$ npm run test - -# e2e tests -$ npm run test:e2e - -# test coverage -$ npm run test:cov -``` - -## Support - -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). - -## Stay in touch - -- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) - -## License - -Nest is [MIT licensed](LICENSE). +EndUser: + - View remaining credits + - View transaction history of credits + - Purchase courses with credits \ No newline at end of file From 1087ef9a167eba94657fb6c7f9c7376c56e49e2c Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Fri, 13 Oct 2023 12:48:02 +0530 Subject: [PATCH 07/28] added services diagram --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dbcab76..a3ab31a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Wallet Service [Compass Product Flow](https://miro.com/app/board/uXjVMkv3bh4=/?share_link_id=179469421530) +[Compass Services Diagram](https://app.diagrams.net/#G1ZcWAg558z88DcWNC4b2NKt1Q3MAPHSZu) ## Use Cases Admin: From d07a34c39b253e17224d78d323934854cd834328 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Thu, 9 Nov 2023 23:25:04 +0530 Subject: [PATCH 08/28] changed userId to uuid --- .gitignore | 3 ++- Dockerfile | 2 +- prisma/schema.prisma | 2 +- src/admin/admin.controller.ts | 18 +++++++++--------- src/admin/admin.service.ts | 4 ++-- src/consumer/consumer.controller.ts | 8 ++++---- src/consumer/consumer.service.ts | 6 +++--- src/dto/credits.dto.ts | 17 +++++++---------- src/main.ts | 3 ++- src/provider/provider.controller.ts | 6 +++--- src/provider/provider.service.ts | 6 +++--- src/transactions/transactions.service.ts | 2 +- src/wallet/wallet.service.ts | 4 ++-- 13 files changed, 40 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index a0d218e..d22d5b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist -.env \ No newline at end of file +.env +prisma/migrations \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7b949d3..4a5168d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16 +FROM node:18 WORKDIR /app diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dd04b91..e071c42 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -31,7 +31,7 @@ enum TransactionType { model wallets { walletId Int @id @default(autoincrement()) - userId Int @unique + userId String @unique type WalletType status WalletStatus credits Int diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index f90d3a9..675a716 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; import { AdminService } from './admin.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { ConsumerService } from 'src/consumer/consumer.service'; @@ -25,7 +25,7 @@ export class AdminController { @Get("/:adminId/transactions/consumers") // get all transactions of all consumers async getAllConsumersTransactions( - @Param("adminId", ParseIntPipe) adminId: number, + @Param("adminId", ParseUUIDPipe) adminId: string, @Res() res ) { // check admin @@ -47,8 +47,8 @@ export class AdminController { @Get("/:adminId/transactions/consumers/:consumerId") // get all transactions of a particular consumer async getConsumerTransactions( - @Param("adminId", ParseIntPipe) adminId: number, - @Param("consumerId", ParseIntPipe) consumerId: number, + @Param("adminId", ParseUUIDPipe) adminId: string, + @Param("consumerId", ParseUUIDPipe) consumerId: string, @Res() res ) { // check admin @@ -72,7 +72,7 @@ export class AdminController { @Get("/:adminId/transactions/providers") // get all transactions between all providers and admins async getAllAdminProvidersTransactions( - @Param("adminId", ParseIntPipe) adminId: number, + @Param("adminId", ParseUUIDPipe) adminId: string, @Res() res ) { // check admin @@ -93,8 +93,8 @@ export class AdminController { @Get("/:adminId/transactions/providers/:providerId") // get all transactions of a particular provider async getProviderTransactions( - @Param("adminId", ParseIntPipe) adminId: number, - @Param("providerId", ParseIntPipe) providerId: number, + @Param("adminId", ParseUUIDPipe) adminId: string, + @Param("providerId", ParseUUIDPipe) providerId: string, @Res() res ) { // check admin @@ -118,7 +118,7 @@ export class AdminController { @Post("/:adminId/add-credits") // add credits to a consumer's wallet async addCredits( - @Param("adminId", ParseIntPipe) adminId: number, + @Param("adminId", ParseUUIDPipe) adminId: string, @Body() creditsDto: CreditsDto, @Res() res ) { @@ -144,7 +144,7 @@ export class AdminController { @Post("/:adminId/reduce-credits") // reduce credits from a consumer's wallet async reduceCredits( - @Param("adminId", ParseIntPipe) adminId: number, + @Param("adminId", ParseUUIDPipe) adminId: string, @Body() creditsDto: CreditsDto, @Res() res ) { diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts index 355bced..a96de29 100644 --- a/src/admin/admin.service.ts +++ b/src/admin/admin.service.ts @@ -8,7 +8,7 @@ export class AdminService { private walletService: WalletService, ) {} - async getAdminWallet(adminId: number) { + async getAdminWallet(adminId: string) { // get admin wallet const adminWallet = await this.walletService.fetchWallet(adminId); if(adminWallet == null) { @@ -22,7 +22,7 @@ export class AdminService { return adminWallet; } - async addCreditsToAdmin(adminId: number, credits: number) { + async addCreditsToAdmin(adminId: string, credits: number) { // check admin let adminWallet = await this.getAdminWallet(adminId) diff --git a/src/consumer/consumer.controller.ts b/src/consumer/consumer.controller.ts index d7ec747..155a0c2 100644 --- a/src/consumer/consumer.controller.ts +++ b/src/consumer/consumer.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; import { TransactionType } from '@prisma/client'; import { TransactionService } from 'src/transactions/transactions.service'; import { ConsumerService } from './consumer.service'; @@ -22,7 +22,7 @@ export class ConsumerController { @Get("/:consumerId/credits") // get credits of a particular consumer async getCredits( - @Param("consumerId", ParseIntPipe) consumerId: number, + @Param("consumerId", ParseUUIDPipe) consumerId: string, @Res() res ) { // fetch wallet @@ -41,7 +41,7 @@ export class ConsumerController { @Get("/:consumerId/transactions") // get all transactions of a particular consumer async getConsumerTransactions( - @Param("consumerId", ParseIntPipe) consumerId: number, + @Param("consumerId", ParseUUIDPipe) consumerId: string, @Res() res ) { // check consumer @@ -62,7 +62,7 @@ export class ConsumerController { @Post("/:consumerId/purchase") // transfer credits from endconsumer's wallet to provider wallet for purchase async handlePurchase( - @Param("consumerId", ParseIntPipe) consumerId: number, + @Param("consumerId", ParseUUIDPipe) consumerId: string, @Body() purchaseDto: PurchaseDto, @Res() res ) { diff --git a/src/consumer/consumer.service.ts b/src/consumer/consumer.service.ts index 49012cb..b9bbaa6 100644 --- a/src/consumer/consumer.service.ts +++ b/src/consumer/consumer.service.ts @@ -8,7 +8,7 @@ export class ConsumerService { private walletService: WalletService, ) {} - async getConsumerWallet(consumerId: number) { + async getConsumerWallet(consumerId: string) { // get consumer wallet const consumerWallet = await this.walletService.fetchWallet(consumerId) if(consumerWallet == null) { @@ -21,7 +21,7 @@ export class ConsumerService { return consumerWallet; } - async reduceConsumerCredits(consumerId: number, credits: number) { + async reduceConsumerCredits(consumerId: string, credits: number) { // fetch consumer wallet let consumerWallet = await this.getConsumerWallet(consumerId); @@ -36,7 +36,7 @@ export class ConsumerService { return consumerWallet; } - async addCreditsToConsumer(consumerId: number, credits: number) { + async addCreditsToConsumer(consumerId: string, credits: number) { // fetch consumer wallet let consumerWallet = await this.getConsumerWallet(consumerId); diff --git a/src/dto/credits.dto.ts b/src/dto/credits.dto.ts index d5e0ba4..0fa0a20 100644 --- a/src/dto/credits.dto.ts +++ b/src/dto/credits.dto.ts @@ -1,14 +1,13 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsInt, IsNotEmpty, Min } from "class-validator"; +import { IsInt, IsNotEmpty, IsUUID, Min } from "class-validator"; export class CreditsDto { // consumer ID @ApiProperty() @IsNotEmpty() - @IsInt() - @Min(1) - consumerId: number; + @IsUUID() + consumerId: string; // Number of credits transferred @ApiProperty() @@ -22,9 +21,8 @@ export class PurchaseDto { // provider ID @ApiProperty() @IsNotEmpty() - @IsInt() - @Min(1) - providerId: number; + @IsUUID() + providerId: string; // Number of credits transferred @ApiProperty() @@ -38,9 +36,8 @@ export class SettlementDto { // admin ID @ApiProperty() @IsNotEmpty() - @IsInt() - @Min(1) - adminId: number; + @IsUUID() + adminId: string; // Number of credits transferred @ApiProperty() diff --git a/src/main.ts b/src/main.ts index 0e20ffe..41f2c82 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,7 @@ async function bootstrap() { whitelist: true, }), ); + app.setGlobalPrefix("api") const config = new DocumentBuilder() .setTitle('Wallet Service') @@ -22,6 +23,6 @@ async function bootstrap() { const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); - await app.listen(3000); + await app.listen(process.env.PORT); } bootstrap() \ No newline at end of file diff --git a/src/provider/provider.controller.ts b/src/provider/provider.controller.ts index 567ee26..cf775c5 100644 --- a/src/provider/provider.controller.ts +++ b/src/provider/provider.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseIntPipe, Post, Res } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; import { TransactionService } from 'src/transactions/transactions.service'; import { ProviderService } from './provider.service'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @@ -21,7 +21,7 @@ export class ProviderController { @Get("/:providerId/transactions") // get all transactions of a particular provider async getProviderTransactions( - @Param("providerId", ParseIntPipe) providerId: number, + @Param("providerId", ParseUUIDPipe) providerId: string, @Res() res ) { // check provider @@ -42,7 +42,7 @@ export class ProviderController { @Post("/:providerId/settlement-transaction") // Transfer credits from provider wallet to admin wallet async settleProviderWallet( - @Param("providerId", ParseIntPipe) providerId: number, + @Param("providerId", ParseUUIDPipe) providerId: string, @Body() settlementDto: SettlementDto, @Res() res ) { diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index 0e227c7..1451f5a 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -8,7 +8,7 @@ export class ProviderService { private walletService: WalletService, ) {} - async getProviderWallet(providerId: number) { + async getProviderWallet(providerId: string) { // get provider wallet const providerWallet = await this.walletService.fetchWallet(providerId) if(providerWallet == null) { @@ -22,7 +22,7 @@ export class ProviderService { return providerWallet; } - async addCreditsToProvider(providerId: number, credits: number) { + async addCreditsToProvider(providerId: string, credits: number) { // check provider let providerWallet = await this.getProviderWallet(providerId) @@ -32,7 +32,7 @@ export class ProviderService { return providerWallet; } - async reduceProviderCredits(consumerId: number, credits: number) { + async reduceProviderCredits(consumerId: string, credits: number) { // fetch wallet let providerWallet = await this.getProviderWallet(consumerId); diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index c34d05f..089b1c5 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -24,7 +24,7 @@ export class TransactionService { }); } - fetchTransactionsOfOneUser(userId: number) { + fetchTransactionsOfOneUser(userId: string) { return this.prisma.transactions.findMany({ where: { OR: [{ diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index ac9a8b0..197b941 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -7,7 +7,7 @@ export class WalletService { private prisma: PrismaService, ) {} - fetchWallet(userId: number) { + fetchWallet(userId: string) { return this.prisma.wallets.findUnique({ where: { userId @@ -15,7 +15,7 @@ export class WalletService { }) } - updateWalletCredits(userId: number, newCreditsAmount: number) { + updateWalletCredits(userId: string, newCreditsAmount: number) { return this.prisma.wallets.update({ where: { userId From 6be152663fc922c5c62cb96b4293de5a27eb5bcc Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Fri, 10 Nov 2023 00:58:43 +0530 Subject: [PATCH 09/28] minor debugging --- src/consumer/consumer.controller.ts | 14 ++++++++++---- src/consumer/consumer.service.ts | 10 +++++----- src/provider/provider.service.ts | 7 ++----- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/consumer/consumer.controller.ts b/src/consumer/consumer.controller.ts index 155a0c2..adc8d76 100644 --- a/src/consumer/consumer.controller.ts +++ b/src/consumer/consumer.controller.ts @@ -60,19 +60,25 @@ export class ConsumerController { @ApiOperation({ summary: 'Handle Purchase' }) @ApiResponse({ status: HttpStatus.OK, description: 'purchase successful', type: Transaction }) @Post("/:consumerId/purchase") - // transfer credits from endconsumer's wallet to provider wallet for purchase + // transfer credits from consumer's wallet to provider wallet for purchase async handlePurchase( @Param("consumerId", ParseUUIDPipe) consumerId: string, @Body() purchaseDto: PurchaseDto, @Res() res ) { + // fetch consumer wallet + let consumerWallet = await this.consumerService.getConsumerWallet(consumerId); + + // check provider + let providerWallet = await this.providerService.getProviderWallet(purchaseDto.providerId) + // update consumer wallet - const consumerWalletPromise = this.consumerService.reduceConsumerCredits(consumerId, purchaseDto.credits); + const consumerWalletPromise = this.consumerService.reduceConsumerCredits(consumerId, purchaseDto.credits, consumerWallet); // update provider wallet - const providerWalletPromise = this.providerService.addCreditsToProvider(purchaseDto.providerId, purchaseDto.credits); + const providerWalletPromise = this.providerService.addCreditsToProvider(purchaseDto.providerId, purchaseDto.credits, providerWallet); - const [consumerWallet, providerWallet] = await Promise.all([consumerWalletPromise, providerWalletPromise]); + [consumerWallet, providerWallet] = await Promise.all([consumerWalletPromise, providerWalletPromise]); // create transaction const transaction = await this.transactionService.createTransaction(purchaseDto.credits, consumerWallet.walletId, providerWallet.walletId, TransactionType.purchase); diff --git a/src/consumer/consumer.service.ts b/src/consumer/consumer.service.ts index b9bbaa6..ba168cf 100644 --- a/src/consumer/consumer.service.ts +++ b/src/consumer/consumer.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; -import { WalletType } from '@prisma/client'; +import { WalletType, wallets } from '@prisma/client'; import { WalletService } from 'src/wallet/wallet.service'; @Injectable() @@ -21,11 +21,11 @@ export class ConsumerService { return consumerWallet; } - async reduceConsumerCredits(consumerId: string, credits: number) { - - // fetch consumer wallet - let consumerWallet = await this.getConsumerWallet(consumerId); + async reduceConsumerCredits(consumerId: string, credits: number, consumerWallet?: wallets) { + // fetch consumer wallet if not passed + if(!consumerWallet) + consumerWallet = await this.getConsumerWallet(consumerId); // check credits if(consumerWallet.credits < credits) { throw new BadRequestException("Not enough credits"); diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index 1451f5a..35e8767 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; -import { WalletType } from '@prisma/client'; +import { WalletType, wallets } from '@prisma/client'; import { WalletService } from 'src/wallet/wallet.service'; @Injectable() @@ -22,10 +22,7 @@ export class ProviderService { return providerWallet; } - async addCreditsToProvider(providerId: string, credits: number) { - - // check provider - let providerWallet = await this.getProviderWallet(providerId) + async addCreditsToProvider(providerId: string, credits: number, providerWallet: wallets) { // update provider wallet providerWallet = await this.walletService.updateWalletCredits(providerId, providerWallet.credits + credits); From 77c899226e0607e87f6e667fcbfbfcd599a7c040 Mon Sep 17 00:00:00 2001 From: kh4l1d64 Date: Tue, 14 Nov 2023 09:05:56 +0530 Subject: [PATCH 10/28] Added seed data --- .env | 2 +- prisma/migrations/migration_lock.toml | 3 -- prisma/seed.ts | 64 ++++++++++++++++++++++++--- src/provider/provider.controller.ts | 19 ++++++++ 4 files changed, 78 insertions(+), 10 deletions(-) delete mode 100644 prisma/migrations/migration_lock.toml diff --git a/.env b/.env index 04b587b..2787512 100644 --- a/.env +++ b/.env @@ -4,4 +4,4 @@ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # See the documentation for all the connection string options: https://pris.ly/d/connection-strings -DATABASE_URL="postgresql://username:password@localhost:5434/mydb?schema=public" \ No newline at end of file +DATABASE_URL="postgresql://khalid64:esmagico@123@localhost:5432/mydb?schema=public" \ No newline at end of file diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92..0000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts index 1b9988a..1f6d6fc 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,16 +1,68 @@ import { PrismaClient, TransactionType, WalletStatus, WalletType } from '@prisma/client' const prisma = new PrismaClient() async function main() { - const wallet = await prisma.transactions.create({ + + const wallet1 = await prisma.wallets.create({ + data: { + userId: 1, + type: WalletType.consumer, + status: WalletStatus.active, + credits: 125, + }, + }); + + const wallet2 = await prisma.wallets.create({ + data: { + userId: 2, + type: WalletType.admin, + status: WalletStatus.active, + }, + }); + + const wallet3 = await prisma.wallets.create({ + data: { + userId: 3, + type: WalletType.provider, + status: WalletStatus.active, + } + }); + + const wallet4 = await prisma.wallets.create({ data: { - credits: 15, - fromId: 6, - toId: 10, - type: TransactionType.settlement, + userId: 4, + type: WalletType.consumer, + status: WalletStatus.active, + credits: 300, + }, + }); + const transaction1 = await prisma.transactions.create({ + data: { + credits: 100, + fromId: 2, + toId: 1, + type: TransactionType.creditRequest, + } + }); + + const transaction2 = await prisma.transactions.create({ + data: { + credits: 200, + fromId: 4, + toId: 3, + type: TransactionType.purchase, + } + }); + + const transaction3 = await prisma.transactions.create({ + data: { + credits: 200, + fromId: 3, + toId: 2, + type: TransactionType.settlement } }) - console.log({ wallet }) + console.log({ wallet1, wallet2, wallet3, wallet4, transaction1, transaction2, transaction3 }); } main() .then(async () => { diff --git a/src/provider/provider.controller.ts b/src/provider/provider.controller.ts index cf775c5..8830a90 100644 --- a/src/provider/provider.controller.ts +++ b/src/provider/provider.controller.ts @@ -6,6 +6,7 @@ import { Transaction } from 'src/transactions/dto/transactions.dto'; import { SettlementDto } from 'src/dto/credits.dto'; import { AdminService } from 'src/admin/admin.service'; import { TransactionType } from '@prisma/client'; +import { WalletCredits } from 'src/wallet/dto/wallet.dto'; @ApiTags('providers') @Controller('providers') @@ -16,6 +17,24 @@ export class ProviderController { private adminService: AdminService ) {} + @ApiOperation({ summary: 'Get Provider Credits' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Credits fetched successfully', type: WalletCredits }) + @Get("/:providerId/credits") + async getCredits( + @Param("providerId", ParseUUIDPipe) providerId: string, + @Res() res + ) { + // fetch wallet + const wallet = await this.providerService.getProviderWallet(providerId); + + return res.status(HttpStatus.OK).json({ + message: "Credits fetched successfully", + data: { + credits: wallet.credits + } + }) + } + @ApiOperation({ summary: 'Get Provider Transactions' }) @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:providerId/transactions") From 43232cd6283d02b75c0fb163f08c1b98e4692cc2 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Tue, 14 Nov 2023 14:27:34 +0530 Subject: [PATCH 11/28] logger and error handling --- prisma/schema.prisma | 20 +- prisma/seed.ts | 28 ++- src/admin/admin.controller.ts | 275 ++++++++++++++++------- src/admin/admin.service.ts | 2 +- src/app.module.ts | 3 +- src/consumer/consumer.controller.ts | 133 +++++++---- src/consumer/consumer.service.ts | 2 +- src/provider/provider.controller.ts | 124 +++++++--- src/provider/provider.service.ts | 2 +- src/transactions/transactions.service.ts | 8 +- src/utils/utils.ts | 49 ++++ src/wallet/dto/wallet.dto.ts | 31 ++- src/wallet/wallet.controller.ts | 48 ++++ src/wallet/wallet.module.ts | 11 + src/wallet/wallet.service.ts | 7 + 15 files changed, 555 insertions(+), 188 deletions(-) create mode 100644 src/utils/utils.ts create mode 100644 src/wallet/wallet.controller.ts create mode 100644 src/wallet/wallet.module.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e071c42..73caf1d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -11,21 +11,21 @@ datasource db { } enum WalletType { - admin - provider - consumer + ADMIN + PROVIDER + CONSUMER } enum WalletStatus { - active - inactive - frozen + ACTIVE + INACTIVE + FROZEN } enum TransactionType { - purchase - creditRequest - settlement + PURCHASE + CREDIT_REQUEST + SETTLEMENT } @@ -33,7 +33,7 @@ model wallets { walletId Int @id @default(autoincrement()) userId String @unique type WalletType - status WalletStatus + status WalletStatus @default(ACTIVE) credits Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/prisma/seed.ts b/prisma/seed.ts index 1f6d6fc..7e327a7 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -4,34 +4,32 @@ async function main() { const wallet1 = await prisma.wallets.create({ data: { - userId: 1, - type: WalletType.consumer, - status: WalletStatus.active, + userId: "123e4567-e89b-42d3-a456-556642440000", + type: WalletType.CONSUMER, credits: 125, }, }); const wallet2 = await prisma.wallets.create({ data: { - userId: 2, - type: WalletType.admin, - status: WalletStatus.active, + userId: "123e4567-e89b-42d3-a456-556642440001", + type: WalletType.ADMIN, + credits: 200 }, }); const wallet3 = await prisma.wallets.create({ data: { - userId: 3, - type: WalletType.provider, - status: WalletStatus.active, + userId: "123e4567-e89b-42d3-a456-556642440002", + type: WalletType.PROVIDER, + credits: 100 } }); const wallet4 = await prisma.wallets.create({ data: { - userId: 4, - type: WalletType.consumer, - status: WalletStatus.active, + userId: "123e4567-e89b-42d3-a456-556642440003", + type: WalletType.CONSUMER, credits: 300, }, }); @@ -41,7 +39,7 @@ async function main() { credits: 100, fromId: 2, toId: 1, - type: TransactionType.creditRequest, + type: TransactionType.CREDIT_REQUEST, } }); @@ -50,7 +48,7 @@ async function main() { credits: 200, fromId: 4, toId: 3, - type: TransactionType.purchase, + type: TransactionType.PURCHASE, } }); @@ -59,7 +57,7 @@ async function main() { credits: 200, fromId: 3, toId: 2, - type: TransactionType.settlement + type: TransactionType.SETTLEMENT } }) console.log({ wallet1, wallet2, wallet3, wallet4, transaction1, transaction2, transaction3 }); diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 675a716..25cd822 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Logger, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; import { AdminService } from './admin.service'; import { TransactionService } from 'src/transactions/transactions.service'; import { ConsumerService } from 'src/consumer/consumer.service'; @@ -8,11 +8,15 @@ import { CreditsDto } from '../dto/credits.dto'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Transaction } from 'src/transactions/dto/transactions.dto'; import { WalletCredits } from 'src/wallet/dto/wallet.dto'; +import { getPrismaErrorStatusAndMessage } from 'src/utils/utils'; @ApiTags('admin') @Controller('admin') export class AdminController { + + private readonly logger = new Logger(AdminController.name); + constructor( private transactionService: TransactionService, private consumerService: ConsumerService, @@ -28,18 +32,34 @@ export class AdminController { @Param("adminId", ParseUUIDPipe) adminId: string, @Res() res ) { - // check admin - await this.adminService.getAdminWallet(adminId); - - // fetch transactions - const transactions = await this.transactionService.fetchAllConsumersTransactions(); - - return res.status(HttpStatus.OK).json({ - message: "transactions fetched successfully", - data: { - transactions - } - }) + try { + this.logger.log(`Validating admin`); + + // check admin + await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Getting all consumer transactions`); + + // fetch transactions + const transactions = await this.transactionService.fetchAllConsumersTransactions(); + + this.logger.log(`Successfully retrieved all the transactions`); + + return res.status(HttpStatus.OK).json({ + message: "transactions fetched successfully", + data: { + transactions + } + }); + } catch (err) { + this.logger.error(`Failed to retreive all the transactions`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to fetch all the transactions", + }); + } } @ApiOperation({ summary: 'Get One Consumer Transactions' }) @@ -51,20 +71,39 @@ export class AdminController { @Param("consumerId", ParseUUIDPipe) consumerId: string, @Res() res ) { - // check admin - await this.adminService.getAdminWallet(adminId); - - // check consumer - await this.consumerService.getConsumerWallet(consumerId); - - // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneUser(consumerId); - return res.status(HttpStatus.OK).json({ - message: "transactions fetched successfully", - data: { - transactions - } - }) + try { + this.logger.log(`Validating admin`); + + // check admin + await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Validating Consumer`); + + // check consumer + await this.consumerService.getConsumerWallet(consumerId); + + this.logger.log(`Getting the consumer transactions`); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneUser(consumerId); + + this.logger.log(`Successfully retrieved all the transactions`); + + return res.status(HttpStatus.OK).json({ + message: "transactions fetched successfully", + data: { + transactions + } + }); + } catch (err) { + this.logger.error(`Failed to retreive the transactions`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to fetch the transactions", + }); + } } @ApiOperation({ summary: 'Get All Admin Providers Transactions' }) @@ -75,17 +114,34 @@ export class AdminController { @Param("adminId", ParseUUIDPipe) adminId: string, @Res() res ) { - // check admin - await this.adminService.getAdminWallet(adminId); - - // fetch transactions - const transactions = await this.transactionService.fetchAllAdminProviderTransactions(); - return res.status(HttpStatus.OK).json({ - message: "transactions fetched successfully", - data: { - transactions - } - }) + try { + this.logger.log(`Validating admin`); + + // check admin + await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Getting all providers transactions`); + + // fetch transactions + const transactions = await this.transactionService.fetchAllAdminProviderTransactions(); + + this.logger.log(`Successfully retrieved all the transactions`); + + return res.status(HttpStatus.OK).json({ + message: "transactions fetched successfully", + data: { + transactions + } + }); + } catch (err) { + this.logger.error(`Failed to retreive the transactions`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to fetch the transactions", + }); + } } @ApiOperation({ summary: 'Get One Provider Transactions' }) @@ -97,20 +153,39 @@ export class AdminController { @Param("providerId", ParseUUIDPipe) providerId: string, @Res() res ) { - // check admin - await this.adminService.getAdminWallet(adminId); - - // check provider - await this.providerService.getProviderWallet(providerId); - - // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneUser(providerId); - return res.status(HttpStatus.OK).json({ - message: "transactions fetched successfully", - data: { - transactions - } - }) + try { + this.logger.log(`Validating admin`); + + // check admin + await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Validating Provider`); + + // check provider + await this.providerService.getProviderWallet(providerId); + + this.logger.log(`Getting the providers transactions`); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneUser(providerId); + + this.logger.log(`Successfully retrieved all the transactions`); + + return res.status(HttpStatus.OK).json({ + message: "transactions fetched successfully", + data: { + transactions + } + }); + } catch (err) { + this.logger.error(`Failed to retreive the transactions`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to fetch the transactions", + }); + } } @ApiOperation({ summary: 'Add Credits' }) @@ -122,21 +197,39 @@ export class AdminController { @Body() creditsDto: CreditsDto, @Res() res ) { - // check admin - const adminWallet = await this.adminService.getAdminWallet(adminId); - - // update wallet - const consumerWallet = await this.consumerService.addCreditsToConsumer(creditsDto.consumerId, creditsDto.credits); - - // create transaction - await this.transactionService.createTransaction(creditsDto.credits, adminWallet.walletId, consumerWallet.walletId, TransactionType.creditRequest); - - return res.status(HttpStatus.OK).json({ - message: "Credits added successfully", - data: { - credits: consumerWallet.credits - } - }) + try { + this.logger.log(`Validating admin`); + + // check admin + const adminWallet = await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Updating Consumer wallet`); + + // update wallet + const consumerWallet = await this.consumerService.addCreditsToConsumer(creditsDto.consumerId, creditsDto.credits); + + this.logger.log(`Creating transaction`); + + // create transaction + await this.transactionService.createTransaction(creditsDto.credits, adminWallet.walletId, consumerWallet.walletId, TransactionType.CREDIT_REQUEST); + + this.logger.log(`Successfully added credits`); + + return res.status(HttpStatus.OK).json({ + message: "Credits added successfully", + data: { + credits: consumerWallet.credits + } + }); + } catch (err) { + this.logger.error(`Failed to add credits`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to add credits", + }); + } } @ApiOperation({ summary: 'Reduce Credits' }) @@ -148,20 +241,38 @@ export class AdminController { @Body() creditsDto: CreditsDto, @Res() res ) { - // check admin - const adminWallet = await this.adminService.getAdminWallet(adminId); - - // update wallet - const consumerWallet = await this.consumerService.reduceConsumerCredits(creditsDto.consumerId, creditsDto.credits); - - // create transaction - await this.transactionService.createTransaction(creditsDto.credits, consumerWallet.walletId, adminWallet.walletId, TransactionType.creditRequest); - - return res.status(HttpStatus.OK).json({ - message: "Credits reduced successfully", - data: { - credits: consumerWallet.credits - } - }) + try { + this.logger.log(`Validating admin`); + + // check admin + const adminWallet = await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Updating Consumer wallet`); + + // update wallet + const consumerWallet = await this.consumerService.reduceConsumerCredits(creditsDto.consumerId, creditsDto.credits); + + this.logger.log(`Creating transaction`); + + // create transaction + await this.transactionService.createTransaction(creditsDto.credits, consumerWallet.walletId, adminWallet.walletId, TransactionType.CREDIT_REQUEST); + + this.logger.log(`Successfully reduced credits`); + + return res.status(HttpStatus.OK).json({ + message: "Credits reduced successfully", + data: { + credits: consumerWallet.credits + } + }) + } catch (err) { + this.logger.error(`Failed to reduce credits`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to reduce credits", + }); + } } } diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts index a96de29..73a96f2 100644 --- a/src/admin/admin.service.ts +++ b/src/admin/admin.service.ts @@ -16,7 +16,7 @@ export class AdminService { } // check admin - if(adminWallet.type != WalletType.admin) { + if(adminWallet.type != WalletType.ADMIN) { throw new BadRequestException("Wallet does not belong to admin"); } return adminWallet; diff --git a/src/app.module.ts b/src/app.module.ts index a8f7f54..c9fb139 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,9 +5,10 @@ import { AdminModule } from './admin/admin.module'; import { ProviderModule } from './provider/provider.module'; import { ConsumerModule } from './consumer/consumer.module'; import { PrismaModule } from './prisma/prisma.module'; +import { WalletModule } from './wallet/wallet.module'; @Module({ - imports: [AdminModule, ProviderModule, ConsumerModule, PrismaModule], + imports: [AdminModule, ProviderModule, ConsumerModule, PrismaModule, WalletModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/consumer/consumer.controller.ts b/src/consumer/consumer.controller.ts index adc8d76..a11009f 100644 --- a/src/consumer/consumer.controller.ts +++ b/src/consumer/consumer.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Logger, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; import { TransactionType } from '@prisma/client'; import { TransactionService } from 'src/transactions/transactions.service'; import { ConsumerService } from './consumer.service'; @@ -7,10 +7,14 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { WalletCredits } from 'src/wallet/dto/wallet.dto'; import { Transaction } from 'src/transactions/dto/transactions.dto'; import { PurchaseDto } from 'src/dto/credits.dto'; +import { getPrismaErrorStatusAndMessage } from 'src/utils/utils'; @ApiTags('consumers') @Controller('consumers') export class ConsumerController { + + private readonly logger = new Logger(ConsumerController.name); + constructor( private transactionService: TransactionService, private consumerService: ConsumerService, @@ -25,15 +29,29 @@ export class ConsumerController { @Param("consumerId", ParseUUIDPipe) consumerId: string, @Res() res ) { - // fetch wallet - const wallet = await this.consumerService.getConsumerWallet(consumerId); - - return res.status(HttpStatus.OK).json({ - message: "Credits fetched successfully", - data: { - credits: wallet.credits - } - }) + try { + this.logger.log(`Getting consumer wallet`); + + // fetch wallet + const wallet = await this.consumerService.getConsumerWallet(consumerId); + + this.logger.log(`Successfully retrieved the credits`); + + return res.status(HttpStatus.OK).json({ + message: "Credits fetched successfully", + data: { + credits: wallet.credits + } + }); + } catch (err) { + this.logger.error(`Failed to retreive the credits`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to fetch the credits", + }); + } } @ApiOperation({ summary: 'Get Consumer Transactions' }) @@ -44,17 +62,34 @@ export class ConsumerController { @Param("consumerId", ParseUUIDPipe) consumerId: string, @Res() res ) { - // check consumer - await this.consumerService.getConsumerWallet(consumerId); - - // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneUser(consumerId); - return res.status(HttpStatus.OK).json({ - message: "transactions fetched successfully", - data: { - transactions - } - }) + try { + this.logger.log(`Validating consumer`); + + // check consumer + await this.consumerService.getConsumerWallet(consumerId); + + this.logger.log(`Getting consumer transactions`); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneUser(consumerId); + + this.logger.log(`Successfully retrieved the consumer transactions`); + + return res.status(HttpStatus.OK).json({ + message: "transactions fetched successfully", + data: { + transactions + } + }); + } catch (err) { + this.logger.error(`Failed to retreive the transactions`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to fetch the transactions", + }); + } } @ApiOperation({ summary: 'Handle Purchase' }) @@ -66,28 +101,50 @@ export class ConsumerController { @Body() purchaseDto: PurchaseDto, @Res() res ) { - // fetch consumer wallet - let consumerWallet = await this.consumerService.getConsumerWallet(consumerId); + try { + this.logger.log(`Getting consumer wallet`); + + // fetch consumer wallet + let consumerWallet = await this.consumerService.getConsumerWallet(consumerId); + + this.logger.log(`Validating provider`); + + // check provider + let providerWallet = await this.providerService.getProviderWallet(purchaseDto.providerId) + + this.logger.log(`Updating consumer wallet`); + + // update consumer wallet + const consumerWalletPromise = this.consumerService.reduceConsumerCredits(consumerId, purchaseDto.credits, consumerWallet); + + this.logger.log(`Updating provider wallet`); + + // update provider wallet + const providerWalletPromise = this.providerService.addCreditsToProvider(purchaseDto.providerId, purchaseDto.credits, providerWallet); - // check provider - let providerWallet = await this.providerService.getProviderWallet(purchaseDto.providerId) + [consumerWallet, providerWallet] = await Promise.all([consumerWalletPromise, providerWalletPromise]); - // update consumer wallet - const consumerWalletPromise = this.consumerService.reduceConsumerCredits(consumerId, purchaseDto.credits, consumerWallet); + this.logger.log(`Creating transaction`); - // update provider wallet - const providerWalletPromise = this.providerService.addCreditsToProvider(purchaseDto.providerId, purchaseDto.credits, providerWallet); + // create transaction + const transaction = await this.transactionService.createTransaction(purchaseDto.credits, consumerWallet.walletId, providerWallet.walletId, TransactionType.PURCHASE); - [consumerWallet, providerWallet] = await Promise.all([consumerWalletPromise, providerWalletPromise]); + this.logger.log(`Successfully handled purchase`); - // create transaction - const transaction = await this.transactionService.createTransaction(purchaseDto.credits, consumerWallet.walletId, providerWallet.walletId, TransactionType.purchase); + return res.status(HttpStatus.OK).json({ + message: "purchase successful", + data: { + transaction + } + }); + } catch (err) { + this.logger.error(`Failed to handle purchase`); - return res.status(HttpStatus.OK).json({ - message: "purchase successful", - data: { - transaction - } - }) + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to handle purchase", + }); + } } } diff --git a/src/consumer/consumer.service.ts b/src/consumer/consumer.service.ts index ba168cf..bf952a2 100644 --- a/src/consumer/consumer.service.ts +++ b/src/consumer/consumer.service.ts @@ -15,7 +15,7 @@ export class ConsumerService { throw new NotFoundException("Wallet does not exist"); } // check consumer - if(consumerWallet.type != WalletType.consumer) { + if(consumerWallet.type != WalletType.CONSUMER) { throw new BadRequestException("Wallet does not belong to a consumer"); } return consumerWallet; diff --git a/src/provider/provider.controller.ts b/src/provider/provider.controller.ts index 8830a90..7a49969 100644 --- a/src/provider/provider.controller.ts +++ b/src/provider/provider.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, HttpStatus, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; +import { Body, Controller, Get, HttpStatus, Logger, Param, ParseUUIDPipe, Post, Res } from '@nestjs/common'; import { TransactionService } from 'src/transactions/transactions.service'; import { ProviderService } from './provider.service'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @@ -7,10 +7,14 @@ import { SettlementDto } from 'src/dto/credits.dto'; import { AdminService } from 'src/admin/admin.service'; import { TransactionType } from '@prisma/client'; import { WalletCredits } from 'src/wallet/dto/wallet.dto'; +import { getPrismaErrorStatusAndMessage } from 'src/utils/utils'; @ApiTags('providers') @Controller('providers') export class ProviderController { + + private readonly logger = new Logger(ProviderController.name); + constructor( private transactionService: TransactionService, private providerService: ProviderService, @@ -24,15 +28,29 @@ export class ProviderController { @Param("providerId", ParseUUIDPipe) providerId: string, @Res() res ) { - // fetch wallet - const wallet = await this.providerService.getProviderWallet(providerId); - - return res.status(HttpStatus.OK).json({ - message: "Credits fetched successfully", - data: { - credits: wallet.credits - } - }) + try { + this.logger.log(`Getting provider wallet`); + + // fetch wallet + const wallet = await this.providerService.getProviderWallet(providerId); + + this.logger.log(`Successfully retrieved the credits`); + + return res.status(HttpStatus.OK).json({ + message: "Credits fetched successfully", + data: { + credits: wallet.credits + } + }) + } catch (err) { + this.logger.error(`Failed to retreive the credits`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to retreive the credits", + }); + } } @ApiOperation({ summary: 'Get Provider Transactions' }) @@ -43,17 +61,31 @@ export class ProviderController { @Param("providerId", ParseUUIDPipe) providerId: string, @Res() res ) { - // check provider - await this.providerService.getProviderWallet(providerId); - - // fetch transactions - const transactions = await this.transactionService.fetchTransactionsOfOneUser(providerId); - return res.status(HttpStatus.OK).json({ - message: "transactions fetched successfully", - data: { - transactions - } - }) + try { + this.logger.log(`Validating provider`); + + // check provider + await this.providerService.getProviderWallet(providerId); + + this.logger.log(`Getting all provider's transactions`); + + // fetch transactions + const transactions = await this.transactionService.fetchTransactionsOfOneUser(providerId); + return res.status(HttpStatus.OK).json({ + message: "transactions fetched successfully", + data: { + transactions + } + }) + } catch (err) { + this.logger.error(`Failed to retreive the transactions`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to retreive the transactions", + }); + } } @ApiOperation({ summary: 'Transfer settlement credits and record transaction' }) @@ -65,22 +97,46 @@ export class ProviderController { @Body() settlementDto: SettlementDto, @Res() res ) { - // update provider wallet - const providerWalletPromise = this.providerService.reduceProviderCredits(providerId, settlementDto.credits); + try { + this.logger.log(`Updating provider wallet`); + + // update provider wallet + const providerWalletPromise = this.providerService.reduceProviderCredits(providerId, settlementDto.credits); + + this.logger.log(`Updating admin wallet`); + + // update admin wallet + const adminWalletPromise = this.adminService.addCreditsToAdmin(settlementDto.adminId, settlementDto.credits); + + const [providerWallet, adminWallet] = await Promise.all([providerWalletPromise, adminWalletPromise]); + + this.logger.log(`Creating transaction`); - // update admin wallet - const adminWalletPromise = this.adminService.addCreditsToAdmin(settlementDto.adminId, settlementDto.credits); + // create transaction + const transaction = + await this.transactionService.createTransaction( + settlementDto.credits, + providerWallet.walletId, + adminWallet.walletId, + TransactionType.SETTLEMENT + ); - const [providerWallet, adminWallet] = await Promise.all([providerWalletPromise, adminWalletPromise]); + this.logger.log(`Successfully settled credits`); - // create transaction - const transaction = await this.transactionService.createTransaction(settlementDto.credits, providerWallet.walletId, adminWallet.walletId, TransactionType.settlement); + return res.status(HttpStatus.OK).json({ + message: "credits transferred successfully", + data: { + transaction + } + }) + } catch (err) { + this.logger.error(`Failed to settle the credits`); - return res.status(HttpStatus.OK).json({ - message: "credits transferred successfully", - data: { - transaction - } - }) + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to settle the credits", + }); + } } } diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index 35e8767..d961a88 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -16,7 +16,7 @@ export class ProviderService { } // check provider - if(providerWallet.type != WalletType.provider) { + if(providerWallet.type != WalletType.PROVIDER) { throw new BadRequestException("Wallet does not belong to provider"); } return providerWallet; diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index 089b1c5..1547f27 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -13,11 +13,11 @@ export class TransactionService { where: { OR: [{ from: { - type: WalletType.consumer + type: WalletType.CONSUMER } }, { to: { - type: WalletType.consumer + type: WalletType.CONSUMER } }] } @@ -44,10 +44,10 @@ export class TransactionService { return this.prisma.transactions.findMany({ where: { from: { - type: WalletType.provider + type: WalletType.PROVIDER }, to: { - type: WalletType.admin + type: WalletType.ADMIN } } diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..2a7a5fc --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,49 @@ +import { HttpStatus } from "@nestjs/common"; +import { + PrismaClientKnownRequestError, + PrismaClientValidationError, +} from "@prisma/client/runtime/library"; +import _ from "lodash"; + +export const validationOptions = { + whitelist: true, + transform: true, + forbidNonWhitelisted: true, + transformOptions: { + enableImplicitConversion: true, + }, +}; + +export function getPrismaErrorStatusAndMessage(error: any): { + errorMessage: string | undefined; + statusCode: number; +} { + if ( + error instanceof PrismaClientKnownRequestError || + error instanceof PrismaClientValidationError + ) { + const errorCode = _.get(error, "code", "DEFAULT_ERROR_CODE"); + + const errorCodeMap: Record = { + P2000: HttpStatus.BAD_REQUEST, + P2002: HttpStatus.CONFLICT, + P2003: HttpStatus.CONFLICT, + P2025: HttpStatus.NOT_FOUND, + DEFAULT_ERROR_CODE: HttpStatus.INTERNAL_SERVER_ERROR, + }; + + const statusCode = errorCodeMap[errorCode]; + const errorMessage = error.message.split("\n").pop(); + + return { statusCode, errorMessage }; + } + + const statusCode = + error?.status || + error?.response?.status || + HttpStatus.INTERNAL_SERVER_ERROR; + return { + statusCode, + errorMessage: error.message, + }; +} \ No newline at end of file diff --git a/src/wallet/dto/wallet.dto.ts b/src/wallet/dto/wallet.dto.ts index e7c7ab2..c386d52 100644 --- a/src/wallet/dto/wallet.dto.ts +++ b/src/wallet/dto/wallet.dto.ts @@ -1,5 +1,6 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsInt, Min } from "class-validator"; +import { WalletStatus, WalletType } from "@prisma/client"; +import { IsEnum, IsInt, IsUUID, Min } from "class-validator"; export class WalletCredits { @@ -7,4 +8,32 @@ export class WalletCredits { @IsInt() @Min(0) credits: number; +} + +export class CreateWalletDto { + + // User UUID + @ApiProperty() + @IsUUID() + userId: string; + + // The role of the user wallet belongs to + @ApiProperty() + @IsEnum(WalletType) + @Min(0) + type: WalletType; + + // specifying the number of credits while creating the wallet + @ApiProperty() + @IsInt() + @Min(0) + credits: number; +} + +export class CreateWalletResponse extends CreateWalletDto { + + readonly walletId: number; + readonly status: WalletStatus; + readonly createdAt: Date; + readonly updatedAt: Date; } \ No newline at end of file diff --git a/src/wallet/wallet.controller.ts b/src/wallet/wallet.controller.ts new file mode 100644 index 0000000..5c0045c --- /dev/null +++ b/src/wallet/wallet.controller.ts @@ -0,0 +1,48 @@ +import { Body, Controller, Get, HttpStatus, Logger, Res } from "@nestjs/common"; +import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { WalletService } from "./wallet.service"; +import { CreateWalletDto, CreateWalletResponse } from "./dto/wallet.dto"; +import { getPrismaErrorStatusAndMessage } from "src/utils/utils"; + +@ApiTags('wallet') +@Controller('wallet') +export class WalletController { + + private readonly logger = new Logger(WalletController.name); + + constructor( + private walletService: WalletService + ) {} + + @ApiOperation({ summary: 'Create New Wallet' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Wallet created successfully', type: CreateWalletResponse }) + @Get("/create") + async getCredits( + @Body() createWalletDto: CreateWalletDto, + @Res() res + ) { + try { + this.logger.log(`Fetching wallet`); + + // fetch wallet + const wallet = await this.walletService.createWallet(createWalletDto); + + this.logger.log(`Successfully created wallet`); + + return res.status(HttpStatus.CREATED).json({ + message: "wallet created successfully", + data: { + wallet + } + }) + } catch (err) { + this.logger.error(`Failed to create wallet`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to create wallet", + }); + } + } +} \ No newline at end of file diff --git a/src/wallet/wallet.module.ts b/src/wallet/wallet.module.ts new file mode 100644 index 0000000..97ba5c3 --- /dev/null +++ b/src/wallet/wallet.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { WalletService } from 'src/wallet/wallet.service'; +import { PrismaModule } from 'src/prisma/prisma.module'; +import { WalletController } from './wallet.controller'; + +@Module({ + imports: [PrismaModule], + controllers: [WalletController], + providers: [WalletService] +}) +export class WalletModule {} diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index 197b941..7e3f978 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; +import { CreateWalletDto, CreateWalletResponse } from './dto/wallet.dto'; @Injectable() export class WalletService { @@ -27,4 +28,10 @@ export class WalletService { } }); } + + createWallet(createWalletDto: CreateWalletDto): Promise { + return this.prisma.wallets.create({ + data: createWalletDto + }); + } } \ No newline at end of file From 19e7114a88b213ba9fc0646806937449ce2c4373 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Tue, 14 Nov 2023 19:24:27 +0530 Subject: [PATCH 12/28] request type fix --- src/wallet/wallet.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.controller.ts b/src/wallet/wallet.controller.ts index 5c0045c..53c48e7 100644 --- a/src/wallet/wallet.controller.ts +++ b/src/wallet/wallet.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, HttpStatus, Logger, Res } from "@nestjs/common"; +import { Body, Controller, HttpStatus, Logger, Post, Res } from "@nestjs/common"; import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { WalletService } from "./wallet.service"; import { CreateWalletDto, CreateWalletResponse } from "./dto/wallet.dto"; @@ -16,7 +16,7 @@ export class WalletController { @ApiOperation({ summary: 'Create New Wallet' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Wallet created successfully', type: CreateWalletResponse }) - @Get("/create") + @Post("/create") async getCredits( @Body() createWalletDto: CreateWalletDto, @Res() res From 6a2da7281ca8168f25763b6d04b39d681a9e5ae6 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Thu, 16 Nov 2023 17:09:21 +0530 Subject: [PATCH 13/28] error fixes --- package-lock.json | 8 ++++++++ package.json | 2 ++ src/utils/utils.ts | 5 ++--- src/wallet/dto/wallet.dto.ts | 1 - src/wallet/wallet.controller.ts | 4 ++-- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 239bec9..d17bb59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@prisma/client": "^5.3.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "lodash": "^4.17.21", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, @@ -25,6 +26,7 @@ "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", + "@types/lodash": "^4.14.201", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^6.0.0", @@ -2043,6 +2045,12 @@ "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.201", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.201.tgz", + "integrity": "sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", diff --git a/package.json b/package.json index a459c33..92a7cb3 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@prisma/client": "^5.3.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "lodash": "^4.17.21", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, @@ -39,6 +40,7 @@ "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", + "@types/lodash": "^4.14.201", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^6.0.0", diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 2a7a5fc..73a6ba3 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,7 +3,7 @@ import { PrismaClientKnownRequestError, PrismaClientValidationError, } from "@prisma/client/runtime/library"; -import _ from "lodash"; +import {get} from "lodash"; export const validationOptions = { whitelist: true, @@ -22,8 +22,7 @@ export function getPrismaErrorStatusAndMessage(error: any): { error instanceof PrismaClientKnownRequestError || error instanceof PrismaClientValidationError ) { - const errorCode = _.get(error, "code", "DEFAULT_ERROR_CODE"); - + const errorCode = get(error, "code", "DEFAULT_ERROR_CODE"); const errorCodeMap: Record = { P2000: HttpStatus.BAD_REQUEST, P2002: HttpStatus.CONFLICT, diff --git a/src/wallet/dto/wallet.dto.ts b/src/wallet/dto/wallet.dto.ts index c386d52..e59081c 100644 --- a/src/wallet/dto/wallet.dto.ts +++ b/src/wallet/dto/wallet.dto.ts @@ -20,7 +20,6 @@ export class CreateWalletDto { // The role of the user wallet belongs to @ApiProperty() @IsEnum(WalletType) - @Min(0) type: WalletType; // specifying the number of credits while creating the wallet diff --git a/src/wallet/wallet.controller.ts b/src/wallet/wallet.controller.ts index 53c48e7..9c80af1 100644 --- a/src/wallet/wallet.controller.ts +++ b/src/wallet/wallet.controller.ts @@ -17,12 +17,12 @@ export class WalletController { @ApiOperation({ summary: 'Create New Wallet' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Wallet created successfully', type: CreateWalletResponse }) @Post("/create") - async getCredits( + async createWallet( @Body() createWalletDto: CreateWalletDto, @Res() res ) { try { - this.logger.log(`Fetching wallet`); + this.logger.log(`Creating wallet`); // fetch wallet const wallet = await this.walletService.createWallet(createWalletDto); From bba68cd8208550154549db60df6234aec18b8570 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Mon, 20 Nov 2023 12:44:51 +0530 Subject: [PATCH 14/28] utils change --- src/utils/utils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 73a6ba3..f108b93 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,7 +3,7 @@ import { PrismaClientKnownRequestError, PrismaClientValidationError, } from "@prisma/client/runtime/library"; -import {get} from "lodash"; +import { get } from "lodash"; export const validationOptions = { whitelist: true, @@ -23,6 +23,7 @@ export function getPrismaErrorStatusAndMessage(error: any): { error instanceof PrismaClientValidationError ) { const errorCode = get(error, "code", "DEFAULT_ERROR_CODE"); + const errorCodeMap: Record = { P2000: HttpStatus.BAD_REQUEST, P2002: HttpStatus.CONFLICT, @@ -38,11 +39,12 @@ export function getPrismaErrorStatusAndMessage(error: any): { } const statusCode = + error?.response?.data?.statusCode || error?.status || error?.response?.status || HttpStatus.INTERNAL_SERVER_ERROR; return { statusCode, - errorMessage: error.message, + errorMessage: error?.response?.data?.message || error?.message, }; } \ No newline at end of file From 0e8be470e4bc380b881b8924f7084319cd9e92de Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Mon, 27 Nov 2023 14:04:26 +0530 Subject: [PATCH 15/28] Error message --- src/admin/admin.service.ts | 2 +- src/consumer/consumer.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts index 73a96f2..22b140c 100644 --- a/src/admin/admin.service.ts +++ b/src/admin/admin.service.ts @@ -12,7 +12,7 @@ export class AdminService { // get admin wallet const adminWallet = await this.walletService.fetchWallet(adminId); if(adminWallet == null) { - throw new NotFoundException("Wallet does not exist"); + throw new NotFoundException("Admin Wallet does not exist"); } // check admin diff --git a/src/consumer/consumer.service.ts b/src/consumer/consumer.service.ts index bf952a2..f165e33 100644 --- a/src/consumer/consumer.service.ts +++ b/src/consumer/consumer.service.ts @@ -12,7 +12,7 @@ export class ConsumerService { // get consumer wallet const consumerWallet = await this.walletService.fetchWallet(consumerId) if(consumerWallet == null) { - throw new NotFoundException("Wallet does not exist"); + throw new NotFoundException("Consumer Wallet does not exist"); } // check consumer if(consumerWallet.type != WalletType.CONSUMER) { From 39a0789fbe1ff262ee95bfa481d1d4fa2e0e1c84 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Tue, 28 Nov 2023 14:17:57 +0530 Subject: [PATCH 16/28] get all user credits --- prisma/schema.prisma | 3 +- prisma/seed.ts | 17 +++++----- src/admin/admin.controller.ts | 54 ++++++++++++++++++++++++++++++-- src/consumer/consumer.service.ts | 12 +++++++ src/dto/credits.dto.ts | 8 ++++- src/wallet/wallet.service.ts | 13 ++++++++ 6 files changed, 96 insertions(+), 11 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 73caf1d..09cd379 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,7 +24,8 @@ enum WalletStatus { enum TransactionType { PURCHASE - CREDIT_REQUEST + ADD_CREDITS + REDUCE_CREDITS SETTLEMENT } diff --git a/prisma/seed.ts b/prisma/seed.ts index 7e327a7..dff6b40 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -13,7 +13,7 @@ async function main() { const wallet2 = await prisma.wallets.create({ data: { userId: "123e4567-e89b-42d3-a456-556642440001", - type: WalletType.ADMIN, + type: WalletType.CONSUMER, credits: 200 }, }); @@ -29,7 +29,7 @@ async function main() { const wallet4 = await prisma.wallets.create({ data: { userId: "123e4567-e89b-42d3-a456-556642440003", - type: WalletType.CONSUMER, + type: WalletType.ADMIN, credits: 300, }, }); @@ -37,18 +37,20 @@ async function main() { const transaction1 = await prisma.transactions.create({ data: { credits: 100, - fromId: 2, + fromId: 4, toId: 1, - type: TransactionType.CREDIT_REQUEST, + type: TransactionType.ADD_CREDITS, + description: "Credits added by the admin" } }); const transaction2 = await prisma.transactions.create({ data: { credits: 200, - fromId: 4, + fromId: 1, toId: 3, type: TransactionType.PURCHASE, + description: "Purchased course ABC" } }); @@ -56,8 +58,9 @@ async function main() { data: { credits: 200, fromId: 3, - toId: 2, - type: TransactionType.SETTLEMENT + toId: 4, + type: TransactionType.SETTLEMENT, + description: "Credit balance settled" } }) console.log({ wallet1, wallet2, wallet3, wallet4, transaction1, transaction2, transaction3 }); diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 25cd822..2c31e7f 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -24,6 +24,44 @@ export class AdminController { private providerService: ProviderService ) {} + @ApiOperation({ summary: 'Get All Consumers Credits' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Credits fetched successfully', type: [CreditsDto] }) + @Get("/:adminId/credits/consumers") + // get credits of all consumers + async getAllConsumersCredits( + @Param("adminId", ParseUUIDPipe) adminId: string, + @Res() res + ) { + try { + this.logger.log(`Validating admin`); + + // check admin + await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Getting all consumer credits`); + + // fetch credits + const creditsResponse = await this.consumerService.getAllConsumersCredits(); + + this.logger.log(`Successfully retrieved credits`); + + return res.status(HttpStatus.OK).json({ + message: "credits fetched successfully", + data: { + credits: creditsResponse + } + }); + } catch (err) { + this.logger.error(`Failed to retreive credits`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to fetch credits", + }); + } + } + @ApiOperation({ summary: 'Get All Consumers Transactions' }) @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:adminId/transactions/consumers") @@ -211,7 +249,13 @@ export class AdminController { this.logger.log(`Creating transaction`); // create transaction - await this.transactionService.createTransaction(creditsDto.credits, adminWallet.walletId, consumerWallet.walletId, TransactionType.CREDIT_REQUEST); + await this.transactionService.createTransaction( + creditsDto.credits, + adminWallet.walletId, + consumerWallet.walletId, + TransactionType.ADD_CREDITS, + "Credits added by the admin" + ); this.logger.log(`Successfully added credits`); @@ -255,7 +299,13 @@ export class AdminController { this.logger.log(`Creating transaction`); // create transaction - await this.transactionService.createTransaction(creditsDto.credits, consumerWallet.walletId, adminWallet.walletId, TransactionType.CREDIT_REQUEST); + await this.transactionService.createTransaction( + creditsDto.credits, + consumerWallet.walletId, + adminWallet.walletId, + TransactionType.ADD_CREDITS, + "Credits reduced by the admin" + ); this.logger.log(`Successfully reduced credits`); diff --git a/src/consumer/consumer.service.ts b/src/consumer/consumer.service.ts index f165e33..62361c8 100644 --- a/src/consumer/consumer.service.ts +++ b/src/consumer/consumer.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { WalletType, wallets } from '@prisma/client'; +import { CreditsDto } from 'src/dto/credits.dto'; import { WalletService } from 'src/wallet/wallet.service'; @Injectable() @@ -21,6 +22,17 @@ export class ConsumerService { return consumerWallet; } + async getAllConsumersCredits(): Promise { + const creditsResponse = await this.walletService.getCreditsFromWallets(WalletType.CONSUMER); + + return creditsResponse.map((c) => { + return { + consumerId: c.userId, + credits: c.credits + } + }); + } + async reduceConsumerCredits(consumerId: string, credits: number, consumerWallet?: wallets) { // fetch consumer wallet if not passed diff --git a/src/dto/credits.dto.ts b/src/dto/credits.dto.ts index 0fa0a20..da17719 100644 --- a/src/dto/credits.dto.ts +++ b/src/dto/credits.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsInt, IsNotEmpty, IsUUID, Min } from "class-validator"; +import { IsInt, IsNotEmpty, IsString, IsUUID, Min } from "class-validator"; export class CreditsDto { @@ -29,6 +29,12 @@ export class PurchaseDto { @IsInt() @Min(0) credits: number; + + // purchase description + @ApiProperty() + @IsNotEmpty() + @IsString() + description: string; } export class SettlementDto { diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index 7e3f978..fcb18fe 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; import { CreateWalletDto, CreateWalletResponse } from './dto/wallet.dto'; +import { WalletType } from '@prisma/client'; @Injectable() export class WalletService { @@ -16,6 +17,18 @@ export class WalletService { }) } + getCreditsFromWallets(walletType: WalletType) { + return this.prisma.wallets.findMany({ + where: { + type: walletType + }, + select: { + userId: true, + credits: true + } + }) + } + updateWalletCredits(userId: string, newCreditsAmount: number) { return this.prisma.wallets.update({ where: { From 824ce71d5c281869afc5301e2c38234e2666c5fc Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Wed, 29 Nov 2023 01:23:18 +0530 Subject: [PATCH 17/28] settlement update --- src/admin/admin.controller.ts | 102 +++++++++++++++++++++++++++- src/dto/credits.dto.ts | 8 ++- src/provider/provider.controller.ts | 53 --------------- src/provider/provider.service.ts | 12 ++++ 4 files changed, 119 insertions(+), 56 deletions(-) diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 2c31e7f..8e1db59 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -4,7 +4,7 @@ import { TransactionService } from 'src/transactions/transactions.service'; import { ConsumerService } from 'src/consumer/consumer.service'; import { TransactionType } from '@prisma/client'; import { ProviderService } from 'src/provider/provider.service'; -import { CreditsDto } from '../dto/credits.dto'; +import { CreditsDto, ProviderCreditsDto, SettlementDto } from '../dto/credits.dto'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Transaction } from 'src/transactions/dto/transactions.dto'; import { WalletCredits } from 'src/wallet/dto/wallet.dto'; @@ -144,6 +144,44 @@ export class AdminController { } } + @ApiOperation({ summary: 'Get All Providers Credits' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Credits fetched successfully', type: [ProviderCreditsDto] }) + @Get("/:adminId/credits/providers") + // get credits of all providers + async getAllProvidersCredits( + @Param("adminId", ParseUUIDPipe) adminId: string, + @Res() res + ) { + try { + this.logger.log(`Validating admin`); + + // check admin + await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Getting all provider credits`); + + // fetch credits + const creditsResponse = await this.providerService.getAllProvidersCredits(); + + this.logger.log(`Successfully retrieved credits`); + + return res.status(HttpStatus.OK).json({ + message: "credits fetched successfully", + data: { + credits: creditsResponse + } + }); + } catch (err) { + this.logger.error(`Failed to retreive credits`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to fetch credits", + }); + } + } + @ApiOperation({ summary: 'Get All Admin Providers Transactions' }) @ApiResponse({ status: HttpStatus.OK, description: 'transactions fetched successfully', type: [Transaction] }) @Get("/:adminId/transactions/providers") @@ -303,7 +341,7 @@ export class AdminController { creditsDto.credits, consumerWallet.walletId, adminWallet.walletId, - TransactionType.ADD_CREDITS, + TransactionType.REDUCE_CREDITS, "Credits reduced by the admin" ); @@ -325,4 +363,64 @@ export class AdminController { }); } } + + @ApiOperation({ summary: 'Transfer settlement credits and record transaction' }) + @ApiResponse({ status: HttpStatus.OK, description: 'credits transferred successfully', type: Transaction }) + @Post("/:adminId/providers/:providerId/settle-credits") + // Transfer credits from provider wallet to admin wallet + async settleProviderWallet( + @Param("adminId", ParseUUIDPipe) adminId: string, + @Param("providerId", ParseUUIDPipe) providerId: string, + @Res() res + ) { + try { + this.logger.log(`Validating admin`); + + // check admin + const adminWallet = await this.adminService.getAdminWallet(adminId); + + this.logger.log(`Getting provider wallet`); + + // fetch wallet + const providerWallet = await this.providerService.getProviderWallet(providerId); + + this.logger.log(`Updating admin wallet`); + + // update admin wallet + await this.adminService.addCreditsToAdmin(adminId, providerWallet.credits); + + this.logger.log(`Updating provider wallet`); + + // update provider wallet + await this.providerService.reduceProviderCredits(providerId, providerWallet.credits); + + this.logger.log(`Creating transaction`); + + // create transaction + const transaction = + await this.transactionService.createTransaction( + providerWallet.credits, + providerWallet.walletId, + adminWallet.walletId, + TransactionType.SETTLEMENT, + ); + + this.logger.log(`Successfully settled credits`); + + return res.status(HttpStatus.OK).json({ + message: "credits transferred successfully", + data: { + transaction + } + }) + } catch (err) { + this.logger.error(`Failed to settle the credits`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to settle the credits", + }); + } + } } diff --git a/src/dto/credits.dto.ts b/src/dto/credits.dto.ts index da17719..afcbfa6 100644 --- a/src/dto/credits.dto.ts +++ b/src/dto/credits.dto.ts @@ -16,6 +16,12 @@ export class CreditsDto { credits: number; } +export class ProviderCreditsDto { + + readonly providerId: string; + readonly credits: number; +} + export class PurchaseDto { // provider ID @@ -43,7 +49,7 @@ export class SettlementDto { @ApiProperty() @IsNotEmpty() @IsUUID() - adminId: string; + providerId: string; // Number of credits transferred @ApiProperty() diff --git a/src/provider/provider.controller.ts b/src/provider/provider.controller.ts index 7a49969..c12c241 100644 --- a/src/provider/provider.controller.ts +++ b/src/provider/provider.controller.ts @@ -3,9 +3,7 @@ import { TransactionService } from 'src/transactions/transactions.service'; import { ProviderService } from './provider.service'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Transaction } from 'src/transactions/dto/transactions.dto'; -import { SettlementDto } from 'src/dto/credits.dto'; import { AdminService } from 'src/admin/admin.service'; -import { TransactionType } from '@prisma/client'; import { WalletCredits } from 'src/wallet/dto/wallet.dto'; import { getPrismaErrorStatusAndMessage } from 'src/utils/utils'; @@ -88,55 +86,4 @@ export class ProviderController { } } - @ApiOperation({ summary: 'Transfer settlement credits and record transaction' }) - @ApiResponse({ status: HttpStatus.OK, description: 'credits transferred successfully', type: Transaction }) - @Post("/:providerId/settlement-transaction") - // Transfer credits from provider wallet to admin wallet - async settleProviderWallet( - @Param("providerId", ParseUUIDPipe) providerId: string, - @Body() settlementDto: SettlementDto, - @Res() res - ) { - try { - this.logger.log(`Updating provider wallet`); - - // update provider wallet - const providerWalletPromise = this.providerService.reduceProviderCredits(providerId, settlementDto.credits); - - this.logger.log(`Updating admin wallet`); - - // update admin wallet - const adminWalletPromise = this.adminService.addCreditsToAdmin(settlementDto.adminId, settlementDto.credits); - - const [providerWallet, adminWallet] = await Promise.all([providerWalletPromise, adminWalletPromise]); - - this.logger.log(`Creating transaction`); - - // create transaction - const transaction = - await this.transactionService.createTransaction( - settlementDto.credits, - providerWallet.walletId, - adminWallet.walletId, - TransactionType.SETTLEMENT - ); - - this.logger.log(`Successfully settled credits`); - - return res.status(HttpStatus.OK).json({ - message: "credits transferred successfully", - data: { - transaction - } - }) - } catch (err) { - this.logger.error(`Failed to settle the credits`); - - const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); - res.status(statusCode).json({ - statusCode, - message: errorMessage || "Failed to settle the credits", - }); - } - } } diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index d961a88..79d12b5 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { WalletType, wallets } from '@prisma/client'; +import { ProviderCreditsDto } from 'src/dto/credits.dto'; import { WalletService } from 'src/wallet/wallet.service'; @Injectable() @@ -22,6 +23,17 @@ export class ProviderService { return providerWallet; } + async getAllProvidersCredits(): Promise { + const creditsResponse = await this.walletService.getCreditsFromWallets(WalletType.PROVIDER); + + return creditsResponse.map((c) => { + return { + providerId: c.userId, + credits: c.credits + } + }); + } + async addCreditsToProvider(providerId: string, credits: number, providerWallet: wallets) { // update provider wallet From bd10c0f76973fee23856f187aa34bfd70f56d810 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Wed, 29 Nov 2023 01:35:53 +0530 Subject: [PATCH 18/28] seed update --- prisma/seed.ts | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/prisma/seed.ts b/prisma/seed.ts index dff6b40..798afd1 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -34,6 +34,38 @@ async function main() { }, }); + const wallet5 = await prisma.wallets.create({ + data: { + userId: "123e4567-e89b-42d3-a456-556642440010", + type: WalletType.PROVIDER, + credits: 300, + }, + }); + + const wallet6 = await prisma.wallets.create({ + data: { + userId: "123e4567-e89b-42d3-a456-556642440011", + type: WalletType.PROVIDER, + credits: 300, + }, + }); + + const wallet7 = await prisma.wallets.create({ + data: { + userId: "123e4567-e89b-42d3-a456-556642440012", + type: WalletType.PROVIDER, + credits: 300, + }, + }); + + const wallet8 = await prisma.wallets.create({ + data: { + userId: "123e4567-e89b-42d3-a456-556642440013", + type: WalletType.PROVIDER, + credits: 300, + }, + }); + const transaction1 = await prisma.transactions.create({ data: { credits: 100, @@ -63,7 +95,8 @@ async function main() { description: "Credit balance settled" } }) - console.log({ wallet1, wallet2, wallet3, wallet4, transaction1, transaction2, transaction3 }); + console.log({ wallet1, wallet2, wallet3, wallet4, wallet5, wallet6, wallet7, wallet8, + transaction1, transaction2, transaction3 }); } main() .then(async () => { From 5e79c393b0b1f5c664dbbebf1d0608e83ebcd257 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Fri, 1 Dec 2023 13:33:50 +0530 Subject: [PATCH 19/28] seed update --- prisma/seed.ts | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/prisma/seed.ts b/prisma/seed.ts index 798afd1..b338367 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -21,7 +21,7 @@ async function main() { const wallet3 = await prisma.wallets.create({ data: { userId: "123e4567-e89b-42d3-a456-556642440002", - type: WalletType.PROVIDER, + type: WalletType.CONSUMER, credits: 100 } }); @@ -29,7 +29,7 @@ async function main() { const wallet4 = await prisma.wallets.create({ data: { userId: "123e4567-e89b-42d3-a456-556642440003", - type: WalletType.ADMIN, + type: WalletType.CONSUMER, credits: 300, }, }); @@ -66,10 +66,18 @@ async function main() { }, }); + const wallet9 = await prisma.wallets.create({ + data: { + userId: "123e4567-e89b-42d3-a456-556642440020", + type: WalletType.ADMIN, + credits: 300, + }, + }); + const transaction1 = await prisma.transactions.create({ data: { credits: 100, - fromId: 4, + fromId: 9, toId: 1, type: TransactionType.ADD_CREDITS, description: "Credits added by the admin" @@ -78,9 +86,9 @@ async function main() { const transaction2 = await prisma.transactions.create({ data: { - credits: 200, + credits: 20, fromId: 1, - toId: 3, + toId: 5, type: TransactionType.PURCHASE, description: "Purchased course ABC" } @@ -89,14 +97,24 @@ async function main() { const transaction3 = await prisma.transactions.create({ data: { credits: 200, - fromId: 3, - toId: 4, + fromId: 1, + toId: 5, + type: TransactionType.PURCHASE, + description: "Purchased course XYZ" + } + }); + + const transaction4 = await prisma.transactions.create({ + data: { + credits: 200, + fromId: 5, + toId: 9, type: TransactionType.SETTLEMENT, description: "Credit balance settled" } }) console.log({ wallet1, wallet2, wallet3, wallet4, wallet5, wallet6, wallet7, wallet8, - transaction1, transaction2, transaction3 }); + transaction1, transaction2, transaction3, transaction4 }); } main() .then(async () => { From bd7f7f87253aca258aed1a3d0b8fbb3207f396ab Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Thu, 7 Dec 2023 10:44:57 +0530 Subject: [PATCH 20/28] refund credits for failed purchase --- prisma/schema.prisma | 1 + src/consumer/consumer.controller.ts | 70 ++++++++++++++++++++++++++++- src/consumer/consumer.service.ts | 7 +-- src/provider/provider.service.ts | 5 ++- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 09cd379..2a3c353 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -27,6 +27,7 @@ enum TransactionType { ADD_CREDITS REDUCE_CREDITS SETTLEMENT + REFUND } diff --git a/src/consumer/consumer.controller.ts b/src/consumer/consumer.controller.ts index a11009f..c82f31f 100644 --- a/src/consumer/consumer.controller.ts +++ b/src/consumer/consumer.controller.ts @@ -127,7 +127,13 @@ export class ConsumerController { this.logger.log(`Creating transaction`); // create transaction - const transaction = await this.transactionService.createTransaction(purchaseDto.credits, consumerWallet.walletId, providerWallet.walletId, TransactionType.PURCHASE); + const transaction = await this.transactionService.createTransaction( + purchaseDto.credits, + consumerWallet.walletId, + providerWallet.walletId, + TransactionType.PURCHASE, + purchaseDto.description + ); this.logger.log(`Successfully handled purchase`); @@ -147,4 +153,66 @@ export class ConsumerController { }); } } + + @ApiOperation({ summary: 'Refund failed Purchase' }) + @ApiResponse({ status: HttpStatus.OK, description: 'refund successful', type: Transaction }) + @Post("/:consumerId/refund") + // transfer credits from consumer's wallet to provider wallet for purchase + async refundPurchase( + @Param("consumerId", ParseUUIDPipe) consumerId: string, + @Body() purchaseDto: PurchaseDto, + @Res() res + ) { + try { + this.logger.log(`Getting consumer wallet`); + + // fetch consumer wallet + let consumerWallet = await this.consumerService.getConsumerWallet(consumerId); + + this.logger.log(`Validating provider`); + + // check provider + let providerWallet = await this.providerService.getProviderWallet(purchaseDto.providerId) + + this.logger.log(`Updating consumer wallet`); + + // update consumer wallet + const consumerWalletPromise = this.consumerService.addCreditsToConsumer(consumerId, purchaseDto.credits, consumerWallet); + + this.logger.log(`Updating provider wallet`); + + // update provider wallet + const providerWalletPromise = this.providerService.reduceProviderCredits(purchaseDto.providerId, purchaseDto.credits, providerWallet); + + [consumerWallet, providerWallet] = await Promise.all([consumerWalletPromise, providerWalletPromise]); + + this.logger.log(`Creating transaction`); + + // create transaction + const transaction = await this.transactionService.createTransaction( + purchaseDto.credits, + providerWallet.walletId, + consumerWallet.walletId, + TransactionType.REFUND, + purchaseDto.description + ); + + this.logger.log(`Successfully refunded purchase`); + + return res.status(HttpStatus.OK).json({ + message: "refund successful", + data: { + transaction + } + }); + } catch (err) { + this.logger.error(`Failed to refund purchase`); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to refund purchase", + }); + } + } } diff --git a/src/consumer/consumer.service.ts b/src/consumer/consumer.service.ts index 62361c8..cfff0f1 100644 --- a/src/consumer/consumer.service.ts +++ b/src/consumer/consumer.service.ts @@ -48,10 +48,11 @@ export class ConsumerService { return consumerWallet; } - async addCreditsToConsumer(consumerId: string, credits: number) { + async addCreditsToConsumer(consumerId: string, credits: number, consumerWallet?: wallets) { - // fetch consumer wallet - let consumerWallet = await this.getConsumerWallet(consumerId); + // fetch consumer wallet if not passed + if(!consumerWallet) + consumerWallet = await this.getConsumerWallet(consumerId); // update consumer wallet consumerWallet = await this.walletService.updateWalletCredits(consumerId, consumerWallet.credits + credits); diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index 79d12b5..364459d 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -41,10 +41,11 @@ export class ProviderService { return providerWallet; } - async reduceProviderCredits(consumerId: string, credits: number) { + async reduceProviderCredits(consumerId: string, credits: number, providerWallet?: wallets) { // fetch wallet - let providerWallet = await this.getProviderWallet(consumerId); + if(!providerWallet) + providerWallet = await this.getProviderWallet(consumerId); // check credits if(providerWallet.credits < credits) { From 2bb958976fefb136436944d0a576d97afa830615 Mon Sep 17 00:00:00 2001 From: SanchitEsMagico Date: Tue, 12 Dec 2023 12:48:05 +0530 Subject: [PATCH 21/28] seed update --- prisma/seed.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/prisma/seed.ts b/prisma/seed.ts index b338367..dd173fb 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -4,7 +4,7 @@ async function main() { const wallet1 = await prisma.wallets.create({ data: { - userId: "123e4567-e89b-42d3-a456-556642440000", + userId: "4d45a9e9-4a4d-4c92-aaea-7b5abbd6ff98", type: WalletType.CONSUMER, credits: 125, }, @@ -12,7 +12,7 @@ async function main() { const wallet2 = await prisma.wallets.create({ data: { - userId: "123e4567-e89b-42d3-a456-556642440001", + userId: "abaa7220-5d2e-4e05-842a-95b2c4ce1876", type: WalletType.CONSUMER, credits: 200 }, @@ -20,7 +20,7 @@ async function main() { const wallet3 = await prisma.wallets.create({ data: { - userId: "123e4567-e89b-42d3-a456-556642440002", + userId: "0f5d0b13-8d72-46c9-a7c4-c1f7e5aa1f17", type: WalletType.CONSUMER, credits: 100 } @@ -28,7 +28,7 @@ async function main() { const wallet4 = await prisma.wallets.create({ data: { - userId: "123e4567-e89b-42d3-a456-556642440003", + userId: "bbf1f7cf-4216-458e-8d98-0d9204ae57ef", type: WalletType.CONSUMER, credits: 300, }, From a9323b3a539d87c706eed42607ca3b62df78f075 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Thu, 14 Dec 2023 02:03:30 +0530 Subject: [PATCH 22/28] Updating the docker file for deployment --- Dockerfile | 31 ++++++++++++++++--- docker-compose.yml | 33 +++++--------------- env-sample | 3 ++ package-lock.json | 76 ++++++++++++++++++++++++++++++++++------------ package.json | 6 ++-- 5 files changed, 95 insertions(+), 54 deletions(-) create mode 100644 env-sample diff --git a/Dockerfile b/Dockerfile index 4a5168d..953a06b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,36 @@ -FROM node:18 -WORKDIR /app +# Use the official Node.js 18 image as a base +FROM node:18.16.1-alpine +# Install bash for debugging purposes (optional) +RUN apk add --no-cache bash + +# Install global Node.js packages for NestJS development +RUN npm i -g @nestjs/cli typescript ts-node + +# Set the working directory inside the container +WORKDIR /usr/src/app + +# Copy package.json and package-lock.json to the working directory COPY package*.json ./ +# Copy the Prisma configuration and migration files +# This line copies the "prisma" directory from your project's root into the Docker container's working directory. +COPY prisma ./prisma/ +COPY env-example ./.env +# Install project dependencies RUN npm install +# Copy the rest of the application code to the container COPY . . -RUN npx prisma migrate dev - +# Build your Nest.js application RUN npm run build -CMD [ "npm", "run", "start:dev" ] \ No newline at end of file +# Expose the PORT environment variable (default to 4000 if not provided) +ARG PORT=4000 +ENV PORT=$PORT +EXPOSE $PORT + +# Start the Nest.js application using the start:prod script +CMD ["npm", "run", "start:prod"] diff --git a/docker-compose.yml b/docker-compose.yml index 89dd8fc..d1dc61a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,34 +1,15 @@ version: '3.8' services: - db: - image: postgres:13.5 - container_name: postgres - restart: always - environment: - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_DB: ${DB_NAME} - volumes: - - postgres:/var/lib/postgresql/data - ports: - - '5432:5432' - networks: - - nestjs - app: + wallet: build: context: . dockerfile: Dockerfile container_name: marketplace-walletservice - environment: - - PORT=${PORT} ports: - - '${PORT}:4000' - depends_on: - - db - volumes: - - ./src:/app/src - -volumes: - postgres: + - 4022:4022 + networks: + - compass_samagra + networks: - nestjs: \ No newline at end of file + compass_samagra: + external: true diff --git a/env-sample b/env-sample new file mode 100644 index 0000000..d0ce226 --- /dev/null +++ b/env-sample @@ -0,0 +1,3 @@ +NODE_ENV=production +PORT=4022 +DATABASE_URL="postgresql://postgres:secret@postgres:5432/wallet-db?schema=public" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d17bb59..ca0e666 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.12", - "@prisma/client": "^5.3.1", + "@prisma/client": "^5.7.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "lodash": "^4.17.21", @@ -36,7 +36,7 @@ "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", - "prisma": "^5.3.1", + "prisma": "^5.7.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", @@ -1795,13 +1795,10 @@ } }, "node_modules/@prisma/client": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.3.1.tgz", - "integrity": "sha512-ArOKjHwdFZIe1cGU56oIfy7wRuTn0FfZjGuU/AjgEBOQh+4rDkB6nF+AGHP8KaVpkBIiHGPQh3IpwQ3xDMdO0Q==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.7.0.tgz", + "integrity": "sha512-cZmglCrfNbYpzUtz7HscVHl38e9CrUs31nrVoGUK1nIPXGgt8hT4jj2s657UXcNdQ/jBUxDgGmHyu2Nyrq1txg==", "hasInstallScript": true, - "dependencies": { - "@prisma/engines-version": "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59" - }, "engines": { "node": ">=16.13" }, @@ -1814,17 +1811,56 @@ } } }, + "node_modules/@prisma/debug": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.7.0.tgz", + "integrity": "sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA==", + "devOptional": true + }, "node_modules/@prisma/engines": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.3.1.tgz", - "integrity": "sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.7.0.tgz", + "integrity": "sha512-TkOMgMm60n5YgEKPn9erIvFX2/QuWnl3GBo6yTRyZKk5O5KQertXiNnrYgSLy0SpsKmhovEPQb+D4l0SzyE7XA==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "5.7.0", + "@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "@prisma/fetch-engine": "5.7.0", + "@prisma/get-platform": "5.7.0" + } + }, + "node_modules/@prisma/engines/node_modules/@prisma/engines-version": { + "version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9.tgz", + "integrity": "sha512-V6tgRVi62jRwTm0Hglky3Scwjr/AKFBFtS+MdbsBr7UOuiu1TKLPc6xfPiyEN1+bYqjEtjxwGsHgahcJsd1rNg==", + "devOptional": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.7.0.tgz", + "integrity": "sha512-zIn/qmO+N/3FYe7/L9o+yZseIU8ivh4NdPKSkQRIHfg2QVTVMnbhGoTcecbxfVubeTp+DjcbjS0H9fCuM4W04w==", "devOptional": true, - "hasInstallScript": true + "dependencies": { + "@prisma/debug": "5.7.0", + "@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "@prisma/get-platform": "5.7.0" + } }, - "node_modules/@prisma/engines-version": { - "version": "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59.tgz", - "integrity": "sha512-y5qbUi3ql2Xg7XraqcXEdMHh0MocBfnBzDn5GbV1xk23S3Mq8MGs+VjacTNiBh3dtEdUERCrUUG7Z3QaJ+h79w==" + "node_modules/@prisma/fetch-engine/node_modules/@prisma/engines-version": { + "version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9.tgz", + "integrity": "sha512-V6tgRVi62jRwTm0Hglky3Scwjr/AKFBFtS+MdbsBr7UOuiu1TKLPc6xfPiyEN1+bYqjEtjxwGsHgahcJsd1rNg==", + "devOptional": true + }, + "node_modules/@prisma/get-platform": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.7.0.tgz", + "integrity": "sha512-ZeV/Op4bZsWXuw5Tg05WwRI8BlKiRFhsixPcAM+5BKYSiUZiMKIi713tfT3drBq8+T0E1arNZgYSA9QYcglWNA==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "5.7.0" + } }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -6830,13 +6866,13 @@ } }, "node_modules/prisma": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.3.1.tgz", - "integrity": "sha512-Wp2msQIlMPHe+5k5Od6xnsI/WNG7UJGgFUJgqv/ygc7kOECZapcSz/iU4NIEzISs3H1W9sFLjAPbg/gOqqtB7A==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.7.0.tgz", + "integrity": "sha512-0rcfXO2ErmGAtxnuTNHQT9ztL0zZheQjOI/VNJzdq87C3TlGPQtMqtM+KCwU6XtmkoEr7vbCQqA7HF9IY0ST+Q==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "5.3.1" + "@prisma/engines": "5.7.0" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index 92a7cb3..a9a9fb9 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "node dist/main", + "start:prod": "npx prisma migrate deploy && node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", @@ -27,7 +27,7 @@ "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.12", - "@prisma/client": "^5.3.1", + "@prisma/client": "^5.7.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "lodash": "^4.17.21", @@ -50,7 +50,7 @@ "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", - "prisma": "^5.3.1", + "prisma": "^5.7.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", From d9e513530242008f8b2d7314039d21458266488c Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Fri, 15 Dec 2023 13:33:32 +0530 Subject: [PATCH 23/28] Adding final changes for deployment --- .dockerignore | 4 +- .gitignore | 1 - Dockerfile | 7 ++- docker-compose.yml | 6 +-- package.json | 2 +- .../20231214141343_first/migration.sql | 43 +++++++++++++++++++ prisma/migrations/migration_lock.toml | 3 ++ 7 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 prisma/migrations/20231214141343_first/migration.sql create mode 100644 prisma/migrations/migration_lock.toml diff --git a/.dockerignore b/.dockerignore index 8c6af15..59a3820 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,6 @@ Dockerfile .dockerignore +.env node_modules npm-debug.log -dist -prisma/migrations \ No newline at end of file +dist \ No newline at end of file diff --git a/.gitignore b/.gitignore index d22d5b3..9c97bbd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ node_modules dist .env -prisma/migrations \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 953a06b..f77745c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ COPY package*.json ./ # Copy the Prisma configuration and migration files # This line copies the "prisma" directory from your project's root into the Docker container's working directory. COPY prisma ./prisma/ -COPY env-example ./.env +COPY env-sample ./.env # Install project dependencies RUN npm install @@ -27,9 +27,8 @@ COPY . . # Build your Nest.js application RUN npm run build -# Expose the PORT environment variable (default to 4000 if not provided) -ARG PORT=4000 -ENV PORT=$PORT +# Expose the PORT environment variable (default to 4022 if not provided)\\ +ENV PORT=4022 EXPOSE $PORT # Start the Nest.js application using the start:prod script diff --git a/docker-compose.yml b/docker-compose.yml index d1dc61a..8d6ee52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,12 +4,12 @@ services: build: context: . dockerfile: Dockerfile - container_name: marketplace-walletservice + container_name: wallet-service ports: - 4022:4022 networks: - - compass_samagra + - samagra_compass networks: - compass_samagra: + samagra_compass: external: true diff --git a/package.json b/package.json index a9a9fb9..2f29495 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "npx prisma migrate deploy && node dist/main", + "start:prod": "npx prisma migrate deploy && node dist/src/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", diff --git a/prisma/migrations/20231214141343_first/migration.sql b/prisma/migrations/20231214141343_first/migration.sql new file mode 100644 index 0000000..cb46845 --- /dev/null +++ b/prisma/migrations/20231214141343_first/migration.sql @@ -0,0 +1,43 @@ +-- CreateEnum +CREATE TYPE "WalletType" AS ENUM ('ADMIN', 'PROVIDER', 'CONSUMER'); + +-- CreateEnum +CREATE TYPE "WalletStatus" AS ENUM ('ACTIVE', 'INACTIVE', 'FROZEN'); + +-- CreateEnum +CREATE TYPE "TransactionType" AS ENUM ('PURCHASE', 'ADD_CREDITS', 'REDUCE_CREDITS', 'SETTLEMENT', 'REFUND'); + +-- CreateTable +CREATE TABLE "wallets" ( + "walletId" SERIAL NOT NULL, + "userId" TEXT NOT NULL, + "type" "WalletType" NOT NULL, + "status" "WalletStatus" NOT NULL DEFAULT 'ACTIVE', + "credits" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "wallets_pkey" PRIMARY KEY ("walletId") +); + +-- CreateTable +CREATE TABLE "transactions" ( + "transactionId" SERIAL NOT NULL, + "fromId" INTEGER NOT NULL, + "toId" INTEGER NOT NULL, + "credits" INTEGER NOT NULL, + "type" "TransactionType" NOT NULL, + "description" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "transactions_pkey" PRIMARY KEY ("transactionId") +); + +-- CreateIndex +CREATE UNIQUE INDEX "wallets_userId_key" ON "wallets"("userId"); + +-- AddForeignKey +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_fromId_fkey" FOREIGN KEY ("fromId") REFERENCES "wallets"("walletId") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "transactions" ADD CONSTRAINT "transactions_toId_fkey" FOREIGN KEY ("toId") REFERENCES "wallets"("walletId") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file From b34ebb0de1f625685ba4099c40590ac882539d1b Mon Sep 17 00:00:00 2001 From: kh4l1d64 Date: Wed, 17 Apr 2024 13:06:32 +0530 Subject: [PATCH 24/28] Added logs and updated seed data --- prisma/seed.ts | 2 +- src/admin/admin.controller.ts | 14 +++++++------- src/consumer/consumer.controller.ts | 8 ++++---- src/wallet/wallet.controller.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/prisma/seed.ts b/prisma/seed.ts index dd173fb..c5c83ff 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -12,7 +12,7 @@ async function main() { const wallet2 = await prisma.wallets.create({ data: { - userId: "abaa7220-5d2e-4e05-842a-95b2c4ce1876", + userId: "890f2839-866f-4524-9eac-bebe0d35d607", type: WalletType.CONSUMER, credits: 200 }, diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 8e1db59..3682c9b 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -134,7 +134,7 @@ export class AdminController { } }); } catch (err) { - this.logger.error(`Failed to retreive the transactions`); + this.logger.error(`Failed to retreive the transactions: `, err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -172,7 +172,7 @@ export class AdminController { } }); } catch (err) { - this.logger.error(`Failed to retreive credits`); + this.logger.error(`Failed to retreive credits: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -210,7 +210,7 @@ export class AdminController { } }); } catch (err) { - this.logger.error(`Failed to retreive the transactions`); + this.logger.error(`Failed to retreive the transactions: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -254,7 +254,7 @@ export class AdminController { } }); } catch (err) { - this.logger.error(`Failed to retreive the transactions`); + this.logger.error(`Failed to retreive the transactions: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -304,7 +304,7 @@ export class AdminController { } }); } catch (err) { - this.logger.error(`Failed to add credits`); + this.logger.error(`Failed to add credits: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -354,7 +354,7 @@ export class AdminController { } }) } catch (err) { - this.logger.error(`Failed to reduce credits`); + this.logger.error(`Failed to reduce credits: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -414,7 +414,7 @@ export class AdminController { } }) } catch (err) { - this.logger.error(`Failed to settle the credits`); + this.logger.error(`Failed to settle the credits: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ diff --git a/src/consumer/consumer.controller.ts b/src/consumer/consumer.controller.ts index c82f31f..9880aa3 100644 --- a/src/consumer/consumer.controller.ts +++ b/src/consumer/consumer.controller.ts @@ -44,7 +44,7 @@ export class ConsumerController { } }); } catch (err) { - this.logger.error(`Failed to retreive the credits`); + this.logger.error(`Failed to retreive the credits: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -82,7 +82,7 @@ export class ConsumerController { } }); } catch (err) { - this.logger.error(`Failed to retreive the transactions`); + this.logger.error(`Failed to retreive the transactions: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -144,7 +144,7 @@ export class ConsumerController { } }); } catch (err) { - this.logger.error(`Failed to handle purchase`); + this.logger.error(`Failed to handle purchase: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ @@ -206,7 +206,7 @@ export class ConsumerController { } }); } catch (err) { - this.logger.error(`Failed to refund purchase`); + this.logger.error(`Failed to refund purchase: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ diff --git a/src/wallet/wallet.controller.ts b/src/wallet/wallet.controller.ts index 9c80af1..6c89a14 100644 --- a/src/wallet/wallet.controller.ts +++ b/src/wallet/wallet.controller.ts @@ -36,7 +36,7 @@ export class WalletController { } }) } catch (err) { - this.logger.error(`Failed to create wallet`); + this.logger.error(`Failed to create wallet: `,err.message); const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); res.status(statusCode).json({ From 335d9fd5d7d5e5826e8971fda144929f17a57daa Mon Sep 17 00:00:00 2001 From: kh4l1d64 <144687945+kh4l1d64@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:09:36 +0530 Subject: [PATCH 25/28] Delete .env --- .env | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 2787512..0000000 --- a/.env +++ /dev/null @@ -1,7 +0,0 @@ -# Environment variables declared in this file are automatically made available to Prisma. -# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema - -# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. -# See the documentation for all the connection string options: https://pris.ly/d/connection-strings - -DATABASE_URL="postgresql://khalid64:esmagico@123@localhost:5432/mydb?schema=public" \ No newline at end of file From 743b9f351dd0b5fa7e2f0c616e0aeb5e829fe85d Mon Sep 17 00:00:00 2001 From: kh4l1d64 <144687945+kh4l1d64@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:23:35 +0530 Subject: [PATCH 26/28] Update env-sample --- env-sample | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/env-sample b/env-sample index d0ce226..e41405c 100644 --- a/env-sample +++ b/env-sample @@ -1,3 +1,3 @@ -NODE_ENV=production -PORT=4022 -DATABASE_URL="postgresql://postgres:secret@postgres:5432/wallet-db?schema=public" \ No newline at end of file +NODE_ENV=development +PORT= +DATABASE_URL= From 87baba76aa0913a721a461a9d841053ae788d1c4 Mon Sep 17 00:00:00 2001 From: kh4l1d64 Date: Sun, 21 Apr 2024 23:55:12 +0530 Subject: [PATCH 27/28] Added wallet delete API --- src/wallet/dto/wallet.dto.ts | 6 ++++++ src/wallet/wallet.controller.ts | 34 ++++++++++++++++++++++++++++++++- src/wallet/wallet.service.ts | 20 ++++++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/wallet/dto/wallet.dto.ts b/src/wallet/dto/wallet.dto.ts index e59081c..c60bbb8 100644 --- a/src/wallet/dto/wallet.dto.ts +++ b/src/wallet/dto/wallet.dto.ts @@ -29,6 +29,12 @@ export class CreateWalletDto { credits: number; } +export class DeleteWalletDto { + @ApiProperty() + @IsUUID() + userId: string; +} + export class CreateWalletResponse extends CreateWalletDto { readonly walletId: number; diff --git a/src/wallet/wallet.controller.ts b/src/wallet/wallet.controller.ts index 6c89a14..ded5d07 100644 --- a/src/wallet/wallet.controller.ts +++ b/src/wallet/wallet.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, HttpStatus, Logger, Post, Res } from "@nestjs/common"; import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { WalletService } from "./wallet.service"; -import { CreateWalletDto, CreateWalletResponse } from "./dto/wallet.dto"; +import { CreateWalletDto, CreateWalletResponse, DeleteWalletDto } from "./dto/wallet.dto"; import { getPrismaErrorStatusAndMessage } from "src/utils/utils"; @ApiTags('wallet') @@ -45,4 +45,36 @@ export class WalletController { }); } } + + @ApiOperation({ summary: 'Delete a Wallet' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Wallet created successfully', type: CreateWalletResponse }) + @Post("/delete") + async deleteWallet( + @Body() deleteWalletDto: DeleteWalletDto, + @Res() res + ) { + try { + this.logger.log(`Deleting wallet`); + + // fetch wallet + const wallet = await this.walletService.deleteWallet(deleteWalletDto); + + this.logger.log(`Successfully deleted wallet`); + + return res.status(HttpStatus.CREATED).json({ + message: "wallet deleted successfully", + data: { + wallet + } + }) + } catch (err) { + this.logger.error(`Failed to delete wallet: `,err.message); + + const {errorMessage, statusCode} = getPrismaErrorStatusAndMessage(err); + res.status(statusCode).json({ + statusCode, + message: errorMessage || "Failed to delete wallet", + }); + } + } } \ No newline at end of file diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index fcb18fe..9da23c3 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; -import { CreateWalletDto, CreateWalletResponse } from './dto/wallet.dto'; +import { CreateWalletDto, CreateWalletResponse, DeleteWalletDto } from './dto/wallet.dto'; import { WalletType } from '@prisma/client'; @Injectable() @@ -47,4 +47,22 @@ export class WalletService { data: createWalletDto }); } + + async deleteWallet(deleteWalletDto: DeleteWalletDto) { + const wallet = await this.prisma.wallets.findFirst({ + where: { + userId: deleteWalletDto.userId + } + }) + if(!wallet) { + return; + } + await this.prisma.wallets.delete({ + where: { + userId: deleteWalletDto.userId + } + }) + + return; + } } \ No newline at end of file From 8c5bcd107ae755a230c2313770be6141b1728815 Mon Sep 17 00:00:00 2001 From: VamshiBatta07 Date: Wed, 17 Jul 2024 10:33:37 +0530 Subject: [PATCH 28/28] Creating views for data visualization. --- env-sample | 1 + prisma/scripts/createViewQueries.ts | 22 +++++ prisma/scripts/moveViewsQueries.ts | 51 ++++++++++++ prisma/seed.ts | 120 ++++++++++++++++++++++------ 4 files changed, 168 insertions(+), 26 deletions(-) create mode 100644 prisma/scripts/createViewQueries.ts create mode 100644 prisma/scripts/moveViewsQueries.ts diff --git a/env-sample b/env-sample index e41405c..9589809 100644 --- a/env-sample +++ b/env-sample @@ -1,3 +1,4 @@ NODE_ENV=development PORT= DATABASE_URL= +TELEMETRY_DATABASE_NAME= \ No newline at end of file diff --git a/prisma/scripts/createViewQueries.ts b/prisma/scripts/createViewQueries.ts new file mode 100644 index 0000000..25e390e --- /dev/null +++ b/prisma/scripts/createViewQueries.ts @@ -0,0 +1,22 @@ +export const createViewQueries = [ + ` + DROP VIEW IF EXISTS telemetry_transactions + `, + + ` + CREATE VIEW telemetry_transactions AS + SELECT + wf."userId" AS "fromUserId", + wt."userId" AS "toUserId", + t.credits AS "amount", + t.type, + t.description, + t."createdAt" AS "transactionDate" + FROM + transactions t + JOIN + wallets wf ON t."fromId" = wf."walletId" + JOIN + wallets wt ON t."toId" = wt."walletId" + ` +]; \ No newline at end of file diff --git a/prisma/scripts/moveViewsQueries.ts b/prisma/scripts/moveViewsQueries.ts new file mode 100644 index 0000000..e3397a2 --- /dev/null +++ b/prisma/scripts/moveViewsQueries.ts @@ -0,0 +1,51 @@ +const dbName = process.env.DATABASE_NAME; +const dbUserName = process.env.DATABASE_USERNAME; +const dbPassword = process.env.DATABASE_PASSWORD; +const dbPort = process.env.DATABASE_PORT; +const dbHost = '172.17.0.1'; + +export const copyViewQueries = [ + ` + CREATE EXTENSION IF NOT EXISTS postgres_fdw + `, + ` + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_foreign_server + WHERE srvname = 'wallet_server' + ) THEN + EXECUTE 'CREATE SERVER wallet_server + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (host ''${dbHost}'', dbname ''${dbName}'', port ''${dbPort}'')'; + END IF; + END $$; + `, + ` + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_user_mappings + WHERE srvname = 'wallet_server' AND usename = '${dbUserName}' + ) THEN + EXECUTE 'CREATE USER MAPPING FOR ${dbUserName} + SERVER wallet_server + OPTIONS (user ''${dbUserName}'', password ''${dbPassword}'')'; + END IF; + END $$; + `, + ` + CREATE FOREIGN TABLE telemetry_transactions ( + "fromUserId" text, + "toUserId" text, + "amount" integer, + "type" text, + "description" text, + "transactionDate" timestamp(3) without time zone + ) + SERVER wallet_server + OPTIONS (schema_name 'public', table_name 'telemetry_transactions'); + ` +]; \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts index c5c83ff..5d70c42 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,10 +1,18 @@ -import { PrismaClient, TransactionType, WalletStatus, WalletType } from '@prisma/client' -const prisma = new PrismaClient() -async function main() { +import { Logger } from '@nestjs/common'; +import { Prisma, PrismaClient, TransactionType, WalletStatus, WalletType } from '@prisma/client' +import * as fs from "fs"; +import { createViewQueries } from './scripts/createViewQueries'; +import { copyViewQueries } from './scripts/moveViewsQueries'; +const { Client } = require('pg'); + +const prisma = new PrismaClient(); +const telemetryDbName = process.env.TELEMETRY_DATABASE_NAME; + +async function seed() { const wallet1 = await prisma.wallets.create({ data: { - userId: "4d45a9e9-4a4d-4c92-aaea-7b5abbd6ff98", + userId: "9f4611d4-ab92-4acd-b3ce-13594e362eca", type: WalletType.CONSUMER, credits: 125, }, @@ -12,7 +20,7 @@ async function main() { const wallet2 = await prisma.wallets.create({ data: { - userId: "890f2839-866f-4524-9eac-bebe0d35d607", + userId: "7eddc220-f33d-476c-b204-041e584585c6", type: WalletType.CONSUMER, credits: 200 }, @@ -20,7 +28,7 @@ async function main() { const wallet3 = await prisma.wallets.create({ data: { - userId: "0f5d0b13-8d72-46c9-a7c4-c1f7e5aa1f17", + userId: "836ba369-fc24-4464-95ec-505d61b67ef0", type: WalletType.CONSUMER, credits: 100 } @@ -28,7 +36,7 @@ async function main() { const wallet4 = await prisma.wallets.create({ data: { - userId: "bbf1f7cf-4216-458e-8d98-0d9204ae57ef", + userId: "c8a43816-5a1b-4e29-9e1f-e8ef22efc669", type: WalletType.CONSUMER, credits: 300, }, @@ -68,7 +76,15 @@ async function main() { const wallet9 = await prisma.wallets.create({ data: { - userId: "123e4567-e89b-42d3-a456-556642440020", + userId: "890f2839-866f-4524-9eac-bebe0d35d607", + type: WalletType.ADMIN, + credits: 450, + }, + }); + + const wallet10 = await prisma.wallets.create({ + data: { + userId: "87fd80a9-63e9-4e90-81bb-4b6956c2561b", type: WalletType.ADMIN, credits: 300, }, @@ -77,8 +93,8 @@ async function main() { const transaction1 = await prisma.transactions.create({ data: { credits: 100, - fromId: 9, - toId: 1, + fromId: wallet9.walletId, + toId: wallet1.walletId, type: TransactionType.ADD_CREDITS, description: "Credits added by the admin" } @@ -87,8 +103,8 @@ async function main() { const transaction2 = await prisma.transactions.create({ data: { credits: 20, - fromId: 1, - toId: 5, + fromId: wallet1.walletId, + toId: wallet5.walletId, type: TransactionType.PURCHASE, description: "Purchased course ABC" } @@ -97,8 +113,8 @@ async function main() { const transaction3 = await prisma.transactions.create({ data: { credits: 200, - fromId: 1, - toId: 5, + fromId: wallet1.walletId, + toId: wallet5.walletId, type: TransactionType.PURCHASE, description: "Purchased course XYZ" } @@ -107,21 +123,73 @@ async function main() { const transaction4 = await prisma.transactions.create({ data: { credits: 200, - fromId: 5, - toId: 9, + fromId: wallet5.walletId, + toId: wallet9.walletId, type: TransactionType.SETTLEMENT, description: "Credit balance settled" } }) - console.log({ wallet1, wallet2, wallet3, wallet4, wallet5, wallet6, wallet7, wallet8, + console.log({ wallet1, wallet2, wallet3, wallet4, wallet5, wallet6, wallet7, wallet8, wallet9, wallet10, transaction1, transaction2, transaction3, transaction4 }); } -main() - .then(async () => { - await prisma.$disconnect() - }) - .catch(async (e) => { - console.error(e) - await prisma.$disconnect() - process.exit(1) - }) \ No newline at end of file + +async function createViews() { + let logger = new Logger("CreatingViews"); + logger.log(`Started creating views`); + + for (const sql of createViewQueries) { + logger.log(sql); + await prisma.$executeRaw`${Prisma.raw(sql)}`; + } + + const res:any = await prisma.$queryRaw`${Prisma.raw(`SELECT datname FROM pg_database WHERE datname = '${telemetryDbName}'`)}`; + if (res.length === 0) { + // Create the telemetry-views database if it does not exist + await prisma.$queryRaw`${Prisma.raw(`CREATE DATABASE "${telemetryDbName}"`)}`; + logger.log(`Database "${telemetryDbName}" created.`); + } else { + logger.log(`Database "${telemetryDbName}" already exists.`); + } + + logger.log(`Successfully created views`); +} + +async function moveViews() { + let logger = new Logger("MovingViews"); + + const telemetryClient = new Client({ + user: process.env.DATABASE_USERNAME, + host: '172.17.0.1', + database: process.env.TELEMETRY_DATABASE_NAME, + password: process.env.DATABASE_PASSWORD, + port: 5432, + }); + + await telemetryClient.connect(); + + logger.log(`Started moving views`); + + for (const sql of copyViewQueries) { + logger.log(sql); + await telemetryClient.query(sql); + } + + await telemetryClient.end(); + + logger.log(`Successfully moved views`); +} + +async function main() { + try { + await seed(); + await createViews(); + await moveViews(); + } catch (e) { + console.error(e); + process.exit(1); + } finally { + await prisma.$disconnect(); + } +} + +main(); \ No newline at end of file