diff --git a/prisma/migrations/20240220083932_2_20/migration.sql b/prisma/migrations/20240220083932_2_20/migration.sql new file mode 100644 index 0000000..4f83587 --- /dev/null +++ b/prisma/migrations/20240220083932_2_20/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Contribution" ADD COLUMN "endDate" TIMESTAMP(3), +ADD COLUMN "startDate" TIMESTAMP(3); diff --git a/prisma/migrations/20240220093751_payment/migration.sql b/prisma/migrations/20240220093751_payment/migration.sql new file mode 100644 index 0000000..80a9a16 --- /dev/null +++ b/prisma/migrations/20240220093751_payment/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "Payment" ( + "id" TEXT NOT NULL, + "purpose" TEXT NOT NULL, + "counterparties" TEXT[], + "category" TEXT NOT NULL, + "chainId" TEXT NOT NULL, + "allocate" TEXT NOT NULL, + "amount" TEXT NOT NULL, + "createAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deleted" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "Payment_pkey" PRIMARY KEY ("id") +); diff --git a/prisma/migrations/20240220093844_payment1/migration.sql b/prisma/migrations/20240220093844_payment1/migration.sql new file mode 100644 index 0000000..31225e6 --- /dev/null +++ b/prisma/migrations/20240220093844_payment1/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - Added the required column `projectId` to the `Payment` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Payment" ADD COLUMN "projectId" TEXT NOT NULL; + +-- AddForeignKey +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20240220095207_payment2/migration.sql b/prisma/migrations/20240220095207_payment2/migration.sql new file mode 100644 index 0000000..e367f00 --- /dev/null +++ b/prisma/migrations/20240220095207_payment2/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `symbol` to the `Payment` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Payment" ADD COLUMN "symbol" TEXT NOT NULL; diff --git a/prisma/migrations/20240220100436_payment3/migration.sql b/prisma/migrations/20240220100436_payment3/migration.sql new file mode 100644 index 0000000..185f367 --- /dev/null +++ b/prisma/migrations/20240220100436_payment3/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `crateor` to the `Payment` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Payment" ADD COLUMN "crateor" TEXT NOT NULL; diff --git a/prisma/migrations/20240220100530_payment4/migration.sql b/prisma/migrations/20240220100530_payment4/migration.sql new file mode 100644 index 0000000..9e4acac --- /dev/null +++ b/prisma/migrations/20240220100530_payment4/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `crateor` on the `Payment` table. All the data in the column will be lost. + - Added the required column `createor` to the `Payment` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Payment" DROP COLUMN "crateor", +ADD COLUMN "createor" TEXT NOT NULL; diff --git a/prisma/migrations/20240220100709_payment5/migration.sql b/prisma/migrations/20240220100709_payment5/migration.sql new file mode 100644 index 0000000..8db964d --- /dev/null +++ b/prisma/migrations/20240220100709_payment5/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `createor` on the `Payment` table. All the data in the column will be lost. + - Added the required column `creator` to the `Payment` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Payment" DROP COLUMN "createor", +ADD COLUMN "creator" TEXT NOT NULL; diff --git a/prisma/migrations/20240220101214_payment6/migration.sql b/prisma/migrations/20240220101214_payment6/migration.sql new file mode 100644 index 0000000..b2d60cf --- /dev/null +++ b/prisma/migrations/20240220101214_payment6/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `txHash` to the `Payment` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Payment" ADD COLUMN "txHash" TEXT NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1e026ce..8cba4c0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -42,6 +42,7 @@ model Project { contributors Contributor[] MintReocrd MintReocrd[] ContributionType ContributionType[] + Payment Payment[] } model User { @@ -73,6 +74,8 @@ model Contribution { projectId String type String[] contributionDate String? + startDate DateTime? + endDate DateTime? createAt DateTime @default(now()) updatedAt DateTime @updatedAt deleted Boolean @default(false) @@ -118,3 +121,21 @@ model MintReocrd { updatedAt DateTime @updatedAt deleted Boolean @default(false) } + +model Payment { + id String @id @default(uuid()) + purpose String + counterparties String[] + category String + chainId String + allocate String + symbol String + amount String + project Project @relation(fields: [projectId], references: [id]) + projectId String + creator String + txHash String + createAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deleted Boolean @default(false) +} diff --git a/src/app.module.ts b/src/app.module.ts index 9a63abf..e51d158 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,6 +3,7 @@ import LogsMiddleware from '@/src/middleware/logs'; import { ContributionModule } from '@/src/module/contribution.module'; import { ContributorModule } from '@/src/module/contributor.module'; import { EasModule } from '@/src/module/eas.module'; +import { PaymentModule } from '@/src/module/payment.module'; import { UserModule } from '@/src/module/user.module'; import { Logger, MiddlewareConsumer, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -31,6 +32,7 @@ import { ProjectModule } from './module/project.module'; UserModule, ContributionModule, EasModule, + PaymentModule, ], providers: [ { diff --git a/src/controller/contribution.controller.ts b/src/controller/contribution.controller.ts index d7eec92..c4c387b 100644 --- a/src/controller/contribution.controller.ts +++ b/src/controller/contribution.controller.ts @@ -3,6 +3,7 @@ import { ContributionListQuery, CreateContributionBody, DeleteContributionBody, + GetAllocationDetailsQuery, PrepareClaimBody, UpdateContributionStateBody, } from '@core/type/doc/contribution'; @@ -64,4 +65,10 @@ export class ContributionController { await this.contributionService.deleteContribution(contributionId, body); return CoreApiResponse.success(); } + + @Get('allocationDetails') + async getAllocationDetails(@Query() query: GetAllocationDetailsQuery) { + const data = await this.contributionService.getAllocationDetails(query); + return CoreApiResponse.success(data); + } } diff --git a/src/controller/payment.controller.ts b/src/controller/payment.controller.ts new file mode 100644 index 0000000..7f6bd6e --- /dev/null +++ b/src/controller/payment.controller.ts @@ -0,0 +1,24 @@ +import { CoreApiResponse } from '@core/api/coreApiResponse'; +import { CreatePaymentBody, PaymentListQuery } from '@core/type/doc/payment'; +import { Body, Controller, Get, Inject, Post, Query } from '@nestjs/common'; +import { PaymentService } from '@service/payment.service'; + +@Controller('payment') +export class PaymentController { + constructor( + @Inject(PaymentService) + private readonly paymentService: PaymentService, + ) {} + + @Get('list') + async getPaymentList(@Query() data: PaymentListQuery) { + const user = await this.paymentService.getPaymentList(data); + return CoreApiResponse.success(user); + } + + @Post('create') + async createPayment(@Body() body: CreatePaymentBody) { + const data = await this.paymentService.createPayment(body); + return CoreApiResponse.success(data); + } +} diff --git a/src/core/service/contribution.service.ts b/src/core/service/contribution.service.ts index f4512a5..65ef536 100644 --- a/src/core/service/contribution.service.ts +++ b/src/core/service/contribution.service.ts @@ -3,6 +3,7 @@ import { ContributionListQuery, CreateContributionBody, DeleteContributionBody, + GetAllocationDetailsQuery, PrepareClaimBody, UpdateContributionStateBody, } from '@core/type/doc/contribution'; @@ -162,7 +163,8 @@ export class ContributionService { credit, operatorId, type, - contributionDate, + startDate, + endDate, } = body; const project = await this.prisma.project.findFirst({ where: { @@ -194,7 +196,8 @@ export class ContributionService { credit, projectId, type, - contributionDate, + startDate: new Date(startDate), + endDate: new Date(endDate), ownerId: operatorId, id, }, @@ -290,4 +293,40 @@ export class ContributionService { }, }); } + + async getAllocationDetails(query: GetAllocationDetailsQuery) { + const { endDateFrom, endDateTo, projectId } = query; + const project = await this.prisma.project.findFirst({ + where: { + id: projectId, + deleted: false, + }, + }); + if (!project) { + throw new HttpException( + Code.NOT_FOUND_ERROR.message, + Code.NOT_FOUND_ERROR.code, + ); + } + const contributions = await this.prisma.contribution.findMany({ + where: { + projectId, + deleted: false, + status: Status.CLAIM, + endDate: { + gte: new Date(endDateFrom), + lte: new Date(endDateTo), + }, + }, + }); + const data: Record<string, number> = {}; + contributions.forEach((item) => { + const contributorId = item.toIds[0]; + if (!data[contributorId]) { + data[contributorId] = 0; + } + data[contributorId] += item.credit; + }); + return data; + } } diff --git a/src/core/service/payment.service.ts b/src/core/service/payment.service.ts new file mode 100644 index 0000000..098fd13 --- /dev/null +++ b/src/core/service/payment.service.ts @@ -0,0 +1,59 @@ +import { Code } from '@core/code'; +import { CreatePaymentBody, PaymentListQuery } from '@core/type/doc/payment'; +import { paginate } from '@core/utils/paginator'; +import { HttpException, Injectable } from '@nestjs/common'; +import { PrismaService } from 'nestjs-prisma'; + +@Injectable() +export class PaymentService { + constructor(private prisma: PrismaService) {} + + async getPaymentList(data: PaymentListQuery) { + const { pageSize, currentPage, projectId } = data; + return paginate( + this.prisma.payment, + { + where: { + deleted: false, + projectId, + }, + }, + { + pageSize, + currentPage, + }, + ); + } + + async createPayment(body: CreatePaymentBody) { + const { projectId, wallet, ...data } = body; + const project = await this.prisma.project.findFirst({ + where: { + id: projectId, + deleted: false, + }, + }); + if (!project) { + throw new HttpException( + Code.NOT_FOUND_ERROR.message, + Code.NOT_FOUND_ERROR.code, + ); + } + const user = await this.prisma.contributor.findFirst({ + where: { + projectId, + wallet, + }, + }); + if (!user) { + throw new HttpException(Code.NO_AUTH.message, Code.NO_AUTH.code); + } + return this.prisma.payment.create({ + data: { + ...data, + creator: wallet, + projectId, + }, + }); + } +} diff --git a/src/core/service/project.service.ts b/src/core/service/project.service.ts index 4e283cd..6962487 100644 --- a/src/core/service/project.service.ts +++ b/src/core/service/project.service.ts @@ -169,7 +169,6 @@ export class ProjectService { voteApprove, voteSystem, voteThreshold, - rule, operatorId, } = body; await this.getProject(projectId, true); @@ -178,7 +177,7 @@ export class ProjectService { id: operatorId, }, }); - if (user?.permission === Permission.Contributor) { + if (user?.permission !== Permission.Admin) { throw new HttpException(Code.NO_AUTH.message, Code.NO_AUTH.code); } this.checkVoteThreshold(voteThreshold); @@ -194,7 +193,6 @@ export class ProjectService { voteApprove, voteSystem, voteThreshold, - rule, }, }); } diff --git a/src/core/type/doc/contribution.ts b/src/core/type/doc/contribution.ts index 593877b..4bd4def 100644 --- a/src/core/type/doc/contribution.ts +++ b/src/core/type/doc/contribution.ts @@ -67,9 +67,16 @@ export class CreateContributionBody extends AuthBody { type: string[]; @IsNotEmpty() - @IsString() - @ApiProperty({ type: 'string' }) - contributionDate: string; + @IsNumber() + @Type(() => Number) + @ApiProperty({ type: 'number' }) + startDate: number; + + @IsNotEmpty() + @IsNumber() + @Type(() => Number) + @ApiProperty({ type: 'number' }) + endDate: number; } export class PrepareClaimBody { @@ -95,3 +102,22 @@ export class PrepareClaimBody { } export class DeleteContributionBody extends AuthBody {} + +export class GetAllocationDetailsQuery { + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + projectId: string; + + @IsNotEmpty() + @IsNumber() + @Type(() => Number) + @ApiProperty({ type: 'number' }) + endDateFrom: number; + + @IsNotEmpty() + @IsNumber() + @Type(() => Number) + @ApiProperty({ type: 'number' }) + endDateTo: number; +} diff --git a/src/core/type/doc/payment.ts b/src/core/type/doc/payment.ts new file mode 100644 index 0000000..bb5eac7 --- /dev/null +++ b/src/core/type/doc/payment.ts @@ -0,0 +1,58 @@ +import { AuthBody } from '@core/type/doc/auth'; +import { PaginateQuery } from '@core/type/doc/common'; +import { ApiProperty } from '@nestjs/swagger'; +import { ArrayNotEmpty, IsArray, IsNotEmpty, IsString } from 'class-validator'; + +export class CreatePaymentBody extends AuthBody { + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + purpose: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + category: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + chainId: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + allocate: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + projectId: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + symbol: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + amount: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + txHash: string; + + @ArrayNotEmpty() + @IsArray() + @ApiProperty({ isArray: true }) + counterparties: string[]; +} + +export class PaymentListQuery extends PaginateQuery { + @IsNotEmpty() + @IsString() + @ApiProperty({ type: 'string' }) + projectId: string; +} diff --git a/src/core/type/doc/project.ts b/src/core/type/doc/project.ts index a859205..a9843d6 100644 --- a/src/core/type/doc/project.ts +++ b/src/core/type/doc/project.ts @@ -132,11 +132,6 @@ export class UpdateProjectBody extends AuthBody { @Type(() => Number) @ApiProperty({ type: 'number' }) voteThreshold: number; - - @IsNotEmpty() - @IsString() - @ApiProperty({ type: 'string' }) - rule: string; } export class ProjectListQuery extends PaginateQuery { diff --git a/src/module/payment.module.ts b/src/module/payment.module.ts new file mode 100644 index 0000000..7ca442f --- /dev/null +++ b/src/module/payment.module.ts @@ -0,0 +1,9 @@ +import { PaymentController } from '@controller/payment.controller'; +import { Module } from '@nestjs/common'; +import { PaymentService } from '@service/payment.service'; + +@Module({ + controllers: [PaymentController], + providers: [PaymentService], +}) +export class PaymentModule {}