From 9f6d861ea01bc799c3265963df16e7ac01563c38 Mon Sep 17 00:00:00 2001 From: yosaphatprs Date: Thu, 13 Nov 2025 16:18:29 +0700 Subject: [PATCH] feat: done app layer for external use --- .../api/src/modules/log/dto/store-log.dto.ts | 3 +- .../src/modules/proof/dto/log-proof.dto.ts | 36 +++++++++++ .../modules/proof/dto/request-proof.dto.ts | 8 +++ .../api/src/modules/proof/proof.controller.ts | 11 +++- backend/api/src/modules/proof/proof.module.ts | 3 +- .../api/src/modules/proof/proof.service.ts | 64 +++++++++++++++---- 6 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 backend/api/src/modules/proof/dto/log-proof.dto.ts create mode 100644 backend/api/src/modules/proof/dto/request-proof.dto.ts diff --git a/backend/api/src/modules/log/dto/store-log.dto.ts b/backend/api/src/modules/log/dto/store-log.dto.ts index 26bb787..2bebeb3 100644 --- a/backend/api/src/modules/log/dto/store-log.dto.ts +++ b/backend/api/src/modules/log/dto/store-log.dto.ts @@ -34,8 +34,7 @@ export class StoreLogDto { event: string; @IsNotEmpty({ message: 'User ID wajib diisi' }) - @IsNumber({}, { message: 'User ID harus berupa angka' }) - user_id: number; + user_id: number | string; @IsNotEmpty({ message: 'Payload wajib diisi' }) @IsString({ message: 'Payload harus berupa string' }) diff --git a/backend/api/src/modules/proof/dto/log-proof.dto.ts b/backend/api/src/modules/proof/dto/log-proof.dto.ts new file mode 100644 index 0000000..7cceb63 --- /dev/null +++ b/backend/api/src/modules/proof/dto/log-proof.dto.ts @@ -0,0 +1,36 @@ +import { + ArrayMaxSize, + ArrayMinSize, + IsArray, + IsBoolean, + IsDate, + isDateString, + IsIn, + IsNotEmpty, + IsObject, + IsOptional, + IsString, + Length, + Matches, + ValidateNested, +} from 'class-validator'; +import { Type } from 'class-transformer'; + +export class LogProofDto { + @IsNotEmpty({ message: 'Proof wajib diisi' }) + @IsObject({ message: 'Proof harus berupa objek' }) + proofHash: object; + + @IsNotEmpty({ message: 'ID Visit wajib diisi' }) + @IsString({ message: 'ID Visit harus berupa string' }) + @Length(1, 25, { message: 'ID Visit maksimal 25 karakter' }) + id_visit: string; + + @IsNotEmpty({ message: 'Hasil proof wajib diisi' }) + @IsBoolean({ message: 'Hasil proof harus berupa boolean' }) + proofResult: boolean; + + @IsNotEmpty({ message: 'Timestamp wajib diisi' }) + @IsString({ message: 'Timestamp harus berupa string' }) + timestamp: Date; +} diff --git a/backend/api/src/modules/proof/dto/request-proof.dto.ts b/backend/api/src/modules/proof/dto/request-proof.dto.ts new file mode 100644 index 0000000..579b303 --- /dev/null +++ b/backend/api/src/modules/proof/dto/request-proof.dto.ts @@ -0,0 +1,8 @@ +import { IsNotEmpty, IsString, Length } from 'class-validator'; + +export class RequestProofDto { + @IsNotEmpty({ message: 'ID Visit wajib diisi' }) + @IsString({ message: 'ID Visit harus berupa string' }) + @Length(1, 25, { message: 'ID Visit maksimal 25 karakter' }) + id_visit: string; +} diff --git a/backend/api/src/modules/proof/proof.controller.ts b/backend/api/src/modules/proof/proof.controller.ts index fd93115..63912a9 100644 --- a/backend/api/src/modules/proof/proof.controller.ts +++ b/backend/api/src/modules/proof/proof.controller.ts @@ -1,12 +1,19 @@ import { Body, Controller, Post, UseGuards } from '@nestjs/common'; import { ProofService } from './proof.service'; +import { RequestProofDto } from './dto/request-proof.dto'; +import { LogProofDto } from './dto/log-proof.dto'; @Controller('proof') export class ProofController { constructor(private proofService: ProofService) {} @Post('/request') - requestProof(@Body('id_visit') id_visit: string) { - return this.proofService.getProof(id_visit); + requestProof(@Body() requestProofDto: RequestProofDto) { + return this.proofService.getProof(requestProofDto); + } + + @Post('/log-verification') + logVerification(@Body() logProofDto: LogProofDto) { + return this.proofService.logVerificationProof(logProofDto); } } diff --git a/backend/api/src/modules/proof/proof.module.ts b/backend/api/src/modules/proof/proof.module.ts index 32d7181..f8a526a 100644 --- a/backend/api/src/modules/proof/proof.module.ts +++ b/backend/api/src/modules/proof/proof.module.ts @@ -3,9 +3,10 @@ import { ProofController } from './proof.controller'; import { ProofService } from './proof.service'; import { RekamMedisModule } from '../rekammedis/rekammedis.module'; import { PrismaModule } from '../prisma/prisma.module'; +import { LogModule } from '../log/log.module'; @Module({ - imports: [RekamMedisModule, PrismaModule], + imports: [RekamMedisModule, PrismaModule, LogModule], providers: [ProofService], controllers: [ProofController], }) diff --git a/backend/api/src/modules/proof/proof.service.ts b/backend/api/src/modules/proof/proof.service.ts index cdde8c0..8866035 100644 --- a/backend/api/src/modules/proof/proof.service.ts +++ b/backend/api/src/modules/proof/proof.service.ts @@ -7,12 +7,17 @@ import { PrismaService } from '../prisma/prisma.service'; import { RekammedisService } from '../rekammedis/rekammedis.service'; import { groth16, wtns } from 'snarkjs'; import path from 'path'; +import { RequestProofDto } from './dto/request-proof.dto'; +import { LogProofDto } from './dto/log-proof.dto'; +import { sha256 } from '@api/common/crypto/hash'; +import { LogService } from '../log/log.service'; @Injectable() export class ProofService { constructor( private prismaService: PrismaService, private rekamMedisService: RekammedisService, + private logService: LogService, ) {} buildDir = path.join(__dirname, '../../../', 'dist'); @@ -38,26 +43,63 @@ export class ProofService { return { proof, publicSignals }; } - async getProof(id_visit: any) { - const age = await this.rekamMedisService.getAgeByIdVisit(id_visit); + async getProof(requestProofDto: RequestProofDto) { + const age = await this.rekamMedisService.getAgeByIdVisit( + requestProofDto.id_visit, + ); if (!age) { throw new NotFoundException('ID Visit tidak ditemukan'); } - try { - await this.calculateWitness(age); - } catch (error) { - console.log('Error during witness calculation:', error); - throw new InternalServerErrorException( - "Can't generate proof from input based on constraint. Please check the input data and try again.", - ); - } + // try { + // await this.calculateWitness(age); + // } catch (error) { + // console.log('Error during witness calculation:', error); + // throw new InternalServerErrorException( + // "Can't generate proof from input based on constraint. Please check the input data and try again.", + // ); + // } - const { proof, publicSignals } = await this.generateProof(); + // const { proof, publicSignals } = await this.generateProof(); + + const { proof, publicSignals } = await groth16.fullProve( + { + age: age, + threshold: 18, + }, + this.wasmPath, + this.zkeyPath, + ); return { proof: proof, publicSignals: publicSignals, }; } + + async logVerificationProof(logProofDto: LogProofDto) { + const payload = { + id_visit: logProofDto.id_visit || null, + proofHash: logProofDto.proofHash || null, + proofResult: logProofDto.proofResult || null, + }; + + const payloadHash = sha256(JSON.stringify(payload)); + + const response = { + id: `PROOF_${payload.id_visit}`, + event: 'proof_verification_logged', + user_id: 'External', + payload: payloadHash, + }; + + // const response = await this.logService.storeLog({ + // id: `PROOF_${payload.id_visit}`, + // event: 'proof_verification_logged', + // user_id: 'External', + // payload: payloadHash, + // }); + + return response; + } }