diff --git a/package-lock.json b/package-lock.json index c90ec0d..093d01b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "bcryptjs": "^2.4.3", "cors": "^2.8.5", + "decimal.js": "^10.6.0", "express": "^4.21.1", "express-rate-limit": "^7.5.0", "helmet": "^7.2.0", @@ -2141,6 +2142,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", diff --git a/package.json b/package.json index 2fb7010..dcef087 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "dependencies": { "bcryptjs": "^2.4.3", "cors": "^2.8.5", + "decimal.js": "^10.6.0", "express": "^4.21.1", "express-rate-limit": "^7.5.0", "helmet": "^7.2.0", diff --git a/src/components/v1/distribution/distribution.controller.ts b/src/components/v1/distribution/distribution.controller.ts index a044140..f365bdf 100644 --- a/src/components/v1/distribution/distribution.controller.ts +++ b/src/components/v1/distribution/distribution.controller.ts @@ -2,7 +2,7 @@ import type { Request, Response } from "express" import AppDataSource from "../../../config/persistence/data-source" import { DistributionEntity } from "./distribution.entity" import { DistributionService } from "./distribution.service" -import type { ApiResponse, DistributionResponseDto, CreateDistributionDto } from "./distribution.dto" +import type { ApiResponse, DistributionResponseDto, CreateDistributionDto, UpdateDistributionDto } from "./distribution.dto" const getDistributionService = () => { if (!AppDataSource.isInitialized) { @@ -41,6 +41,35 @@ export const createDistribution = async (req: Request, res: Response): Promise => { + try { + const distributionService = getDistributionService() + const { id } = req.params + const validatedData = req.body as UpdateDistributionDto + + const distribution = await distributionService.updateDistribution(id, validatedData) + + const response: ApiResponse = { + data: distribution, + success: true, + message: "Distribution updated successfully", + } + + res.status(200).json(response) + } catch (error) { + console.error("Error in updateDistribution:", error) + + const isNotFound = error instanceof Error && error.message === "Distribution not found" + const status = isNotFound ? 404 : 500 + const errorResponse: ApiResponse = { + data: null, + success: false, + message: isNotFound && error instanceof Error ? error.message : "Internal server error", + } + res.status(status).json(errorResponse) + } +} + export const listDistributions = async (_req: Request, res: Response): Promise => { try { const distributionService = getDistributionService() diff --git a/src/components/v1/distribution/distribution.dto.ts b/src/components/v1/distribution/distribution.dto.ts index b86e100..edf3b4e 100644 --- a/src/components/v1/distribution/distribution.dto.ts +++ b/src/components/v1/distribution/distribution.dto.ts @@ -1,7 +1,8 @@ import type { DistributionType, Network } from "../../../types/enums" -import type { CreateDistributionInput } from "./distribution.validation" +import type { CreateDistributionInput, UpdateDistributionInput } from "./distribution.validation" export type CreateDistributionDto = CreateDistributionInput +export type UpdateDistributionDto = UpdateDistributionInput export interface DistributionResponseDto { id: string diff --git a/src/components/v1/distribution/distribution.service.ts b/src/components/v1/distribution/distribution.service.ts index 5aaecd1..c4f4a06 100644 --- a/src/components/v1/distribution/distribution.service.ts +++ b/src/components/v1/distribution/distribution.service.ts @@ -1,6 +1,7 @@ +import { Decimal } from "decimal.js" import type { Repository } from "typeorm" import type { DistributionEntity } from "./distribution.entity" -import type { CreateDistributionDto, DistributionResponseDto } from "./distribution.dto" +import type { CreateDistributionDto, DistributionResponseDto, UpdateDistributionDto } from "./distribution.dto" import { DistributionStatus, Network } from "../../../types/enums" export class DistributionService { @@ -20,6 +21,47 @@ export class DistributionService { } } + async updateDistribution(id: string, updateData: UpdateDistributionDto): Promise { + try { + const distribution = await this.distributionRepository.findOne({ where: { id } }) + if (!distribution) { + throw new Error("Distribution not found") + } + + const updatedFields: Partial = { ...updateData } + + if (updateData.userAddress !== undefined) { + updatedFields.userAddress = updateData.userAddress.toLowerCase() + } + if (updateData.tokenAddress !== undefined) { + updatedFields.tokenAddress = updateData.tokenAddress.toLowerCase() + } + if (updateData.tokenSymbol !== undefined) { + updatedFields.tokenSymbol = updateData.tokenSymbol.toUpperCase() + } + + if (updateData.totalAmount || updateData.usdRate) { + const totalAmount = updateData.totalAmount ?? distribution.totalAmount + const usdRate = updateData.usdRate ?? distribution.usdRate + updatedFields.totalUsdAmount = this.calculateTotalUsdAmount(totalAmount, usdRate) + } + + if (updateData.metadata === null) { + updatedFields.metadata = null + } else if (updateData.metadata !== undefined) { + updatedFields.metadata = this.processMetadata(updateData.metadata) + } + + Object.assign(distribution, updatedFields) + const savedDistribution = await this.distributionRepository.save(distribution) + + return this.formatDistributionResponse(savedDistribution) + } catch (error) { + console.error("Error updating distribution:", error) + throw error instanceof Error ? error : new Error("Failed to update distribution") + } + } + async listDistributions(limit = 50): Promise { try { const distributions = await this.distributionRepository.find({ @@ -69,10 +111,9 @@ export class DistributionService { private calculateTotalUsdAmount(totalAmount: string, usdRate: string): string { try { - const amount = Number.parseFloat(totalAmount) - const rate = Number.parseFloat(usdRate) - const totalUsd = amount * rate - return totalUsd.toString() + const amount = new Decimal(totalAmount) + const rate = new Decimal(usdRate) + return amount.mul(rate).toString() } catch (error) { console.warn("Error calculating total USD amount:", error) return "0" diff --git a/src/components/v1/distribution/distribution.validation.ts b/src/components/v1/distribution/distribution.validation.ts index 9e17b4f..de8f014 100644 --- a/src/components/v1/distribution/distribution.validation.ts +++ b/src/components/v1/distribution/distribution.validation.ts @@ -1,5 +1,5 @@ import { z } from "zod" -import { DistributionType, Network } from "../../../types/enums" +import { DistributionType, DistributionStatus, Network } from "../../../types/enums" export class ValidationError extends Error { constructor(message: string) { @@ -46,7 +46,18 @@ export const createDistributionSchema = z.object({ }) .optional(), - metadata: z.record(z.any()).optional(), + metadata: z.record(z.any()).nullable().optional(), +}) + +export const updateDistributionSchema = createDistributionSchema.partial().extend({ + status: z.nativeEnum(DistributionStatus, { + errorMap: () => ({ message: "status must be a valid DistributionStatus" }), + }).optional(), +}) + +export const updateDistributionParamsSchema = z.object({ + id: z.string().uuid("id must be a valid UUID"), }) export type CreateDistributionInput = z.infer +export type UpdateDistributionInput = z.infer diff --git a/src/components/v1/distribution/distrubtion.routes.ts b/src/components/v1/distribution/distrubtion.routes.ts index a9a26e3..f9f8ed3 100644 --- a/src/components/v1/distribution/distrubtion.routes.ts +++ b/src/components/v1/distribution/distrubtion.routes.ts @@ -1,8 +1,13 @@ import EnhancedRouter from "../../../utils/enhancedRouter" import policyMiddleware from "../../../appMiddlewares/policy.middleware" -import { createDistributionSchema } from "./distribution.validation" +import { + createDistributionSchema, + updateDistributionSchema, + updateDistributionParamsSchema, +} from "./distribution.validation" import { createDistribution, + updateDistribution, listDistributions, } from "./distribution.controller" @@ -10,5 +15,11 @@ const distributionRouter = new EnhancedRouter() distributionRouter.get("/", listDistributions) distributionRouter.post("/", policyMiddleware(createDistributionSchema), createDistribution) +distributionRouter.patch( + "/:id", + policyMiddleware(updateDistributionParamsSchema, "params"), + policyMiddleware(updateDistributionSchema), + updateDistribution, +) export default distributionRouter.getRouter()