diff --git a/package-lock.json b/package-lock.json index 1e4fa45..00dab3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,15 +10,17 @@ "license": "UNLICENSED", "dependencies": { "@nestjs-modules/mailer": "^2.0.2", - "@nestjs/common": "^11.0.1", + "@nestjs/common": "^11.1.2", "@nestjs/config": "^4.0.1", "@nestjs/core": "^11.0.1", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^11.0.1", - "@nestjs/swagger": "^11.1.5", + "@nestjs/platform-socket.io": "^11.1.3", + "@nestjs/swagger": "^11.2.0", "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", + "@nestjs/websockets": "^11.1.3", "@types/otp-generator": "^4.0.2", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", @@ -29,6 +31,7 @@ "pg": "^8.14.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "socket.io": "^4.8.1", "typeorm": "^0.3.22", "zod": "^3.24.3" }, @@ -2843,6 +2846,25 @@ "@nestjs/core": "^11.0.0" } }, + "node_modules/@nestjs/platform-socket.io": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.3.tgz", + "integrity": "sha512-jQ+ccprmh3kKolBp+bb97zoaS3vKaiyeNqyctGqV4CSG8P6mXSaaUObWxAsw6Jdgn5YQAVEBWJ6FhvF4s6QZbg==", + "license": "MIT", + "dependencies": { + "socket.io": "4.8.1", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "rxjs": "^7.1.0" + } + }, "node_modules/@nestjs/schematics": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.5.tgz", @@ -3026,6 +3048,29 @@ "typeorm": "^0.3.0" } }, + "node_modules/@nestjs/websockets": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.3.tgz", + "integrity": "sha512-IjhWKfRf0D247JxYIEs8USblJJbcxUsKJpzbCPaZ7TrVy4LrpG3IRQDlSTOw599TRIYP5ixyH9C0+v5DyaI9uA==", + "license": "MIT", + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.8.1" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/platform-socket.io": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, "node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -3212,6 +3257,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", @@ -3663,6 +3714,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/cors": { + "version": "2.8.18", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz", + "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/ejs": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", @@ -5585,6 +5645,15 @@ ], "license": "MIT" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -7050,6 +7119,95 @@ "node": ">=8.10.0" } }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -11678,6 +11836,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -13568,6 +13735,141 @@ "node": "*" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", diff --git a/package.json b/package.json index df0553f..e7c06e0 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,17 @@ }, "dependencies": { "@nestjs-modules/mailer": "^2.0.2", - "@nestjs/common": "^11.0.1", + "@nestjs/common": "^11.1.2", "@nestjs/config": "^4.0.1", "@nestjs/core": "^11.0.1", "@nestjs/jwt": "^11.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^11.0.1", - "@nestjs/swagger": "^11.1.5", + "@nestjs/platform-socket.io": "^11.1.3", + "@nestjs/swagger": "^11.2.0", "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", + "@nestjs/websockets": "^11.1.3", "@types/otp-generator": "^4.0.2", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", @@ -45,6 +47,7 @@ "pg": "^8.14.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "socket.io": "^4.8.1", "typeorm": "^0.3.22", "zod": "^3.24.3" }, diff --git a/src/app.module.ts b/src/app.module.ts index 06b6a56..c222a68 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,8 @@ import { DatabaseModule } from './database/database.module'; import { VerificationLogsModule } from './modules/verification-logs/verification-logs.module'; import { CustomMailerModule } from './mailer/mailer.module'; import { MailModule } from './modules/mail/mail.module'; +import { VerifyModule } from './modules/verify/verify.module'; + @Module({ imports: [ ConfigModule.forRoot({ @@ -27,6 +29,7 @@ import { MailModule } from './modules/mail/mail.module'; SupportModule, CustomMailerModule, MailModule, + VerifyModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/database/database.module.ts b/src/database/database.module.ts index c97a7a4..c2be220 100644 --- a/src/database/database.module.ts +++ b/src/database/database.module.ts @@ -6,6 +6,7 @@ import { VerificationLog } from '../entities/verification-log.entity'; import { University } from '../modules/university/entities/university.entity'; import { Admin } from '../modules/auth/entities/admin.entity'; import { OTP } from '../modules/auth/entities/otp.entity'; +import { Verify } from '../modules/verify/entities/verify.entity'; @Module({ imports: [ @@ -19,7 +20,7 @@ import { OTP } from '../modules/auth/entities/otp.entity'; username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_NAME'), - entities: [VerificationLog, University, Admin, OTP], + entities: [VerificationLog, University, Admin, OTP, Verify], synchronize: false, }; diff --git a/src/main.ts b/src/main.ts index cd659f1..8fd0c15 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { IoAdapter } from '@nestjs/platform-socket.io'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -12,6 +13,10 @@ async function bootstrap() { transform: true, }), ); + app.enableCors({ + origin: '*', // or your frontend origin + }); + app.useWebSocketAdapter(new IoAdapter(app)); // Swagger configuration const config = new DocumentBuilder() diff --git a/src/modules/verify/dto/approve-verify.dto.ts b/src/modules/verify/dto/approve-verify.dto.ts new file mode 100644 index 0000000..8d07bb6 --- /dev/null +++ b/src/modules/verify/dto/approve-verify.dto.ts @@ -0,0 +1,9 @@ +import { IsUUID, IsString } from 'class-validator'; + +export class ApproveVerifyDto { + @IsUUID() + verification_id: string; + + @IsString() + approval_key: string; +} diff --git a/src/modules/verify/dto/create-verify.dto.ts b/src/modules/verify/dto/create-verify.dto.ts new file mode 100644 index 0000000..ef0c200 --- /dev/null +++ b/src/modules/verify/dto/create-verify.dto.ts @@ -0,0 +1,6 @@ +import { IsUUID } from 'class-validator'; + +export class CreateVerifyDto { + @IsUUID() + certificate_id: string; +} diff --git a/src/modules/verify/dto/update-verify.dto.ts b/src/modules/verify/dto/update-verify.dto.ts new file mode 100644 index 0000000..bd7e9ce --- /dev/null +++ b/src/modules/verify/dto/update-verify.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateVerifyDto } from './create-verify.dto'; + +export class UpdateVerifyDto extends PartialType(CreateVerifyDto) {} diff --git a/src/modules/verify/entities/verify.entity.ts b/src/modules/verify/entities/verify.entity.ts new file mode 100644 index 0000000..d8dc99a --- /dev/null +++ b/src/modules/verify/entities/verify.entity.ts @@ -0,0 +1,19 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm'; + +@Entity() +export class Verify { + @PrimaryGeneratedColumn('uuid') + verification_id: string; + + @Column() + certificate_id: string; + + @Column({ default: 'pending' }) + status: 'pending' | 'approved'; + + @Column({ nullable: true }) + approval_key: string; + + @CreateDateColumn() + created_at: Date; +} diff --git a/src/modules/verify/verify.controller.spec.ts b/src/modules/verify/verify.controller.spec.ts new file mode 100644 index 0000000..9e747ae --- /dev/null +++ b/src/modules/verify/verify.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VerifyController } from './verify.controller'; +import { VerifyService } from './verify.service'; + +describe('VerifyController', () => { + let controller: VerifyController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VerifyController], + providers: [VerifyService], + }).compile(); + + controller = module.get(VerifyController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/verify/verify.controller.ts b/src/modules/verify/verify.controller.ts new file mode 100644 index 0000000..bd7af8c --- /dev/null +++ b/src/modules/verify/verify.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { VerifyService } from './verify.service'; +import { VerifyGateway } from './verify.gateway'; +import { CreateVerifyDto } from './dto/create-verify.dto'; +import { ApproveVerifyDto } from './dto/approve-verify.dto'; +@Controller('verify') +export class VerifyController { + constructor( + private readonly verifyService: VerifyService, + // private readonly verifyGateway: VerifyGateway, + ) {} + + @Post('initiate') + async initiate(@Body() body: CreateVerifyDto) { + return await this.verifyService.initiateVerification(body.certificate_id); + } + + @Post('approve') + async approve(@Body() body: ApproveVerifyDto) { + const result = await this.verifyService.approveVerification(body.verification_id, body.approval_key); + // this.verifyGateway.notifyEmployer(body.verification_id, body.approval_key); + return { message: 'Verification approved', result }; + } +} diff --git a/src/modules/verify/verify.gateway.ts b/src/modules/verify/verify.gateway.ts new file mode 100644 index 0000000..43927e4 --- /dev/null +++ b/src/modules/verify/verify.gateway.ts @@ -0,0 +1,32 @@ +// verify.gateway.ts +import { + WebSocketGateway, + WebSocketServer, + OnGatewayConnection, + MessageBody, + ConnectedSocket, +} from '@nestjs/websockets'; +import { Server, Socket } from 'socket.io'; + +@WebSocketGateway({ cors: { origin: '*' } }) // or FE origin during deployment +export class VerifyGateway implements OnGatewayConnection { + @WebSocketServer() + server: Server; + + handleConnection(client: Socket) { + const verificationId = client.handshake.query.verificationId as string; + console.log('New connection with verificationId:', verificationId); + + if (!verificationId) { + client.disconnect(); + return; + } + + client.join(verificationId); + console.log(`Client ${client.id} joined room ${verificationId}`); + } + + notifyEmployer(verificationId: string, data: any) { + this.server.to(verificationId).emit('verification_update', data); + } +} diff --git a/src/modules/verify/verify.module.ts b/src/modules/verify/verify.module.ts new file mode 100644 index 0000000..17acb15 --- /dev/null +++ b/src/modules/verify/verify.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { VerifyController } from './verify.controller'; +import { VerifyService } from './verify.service'; +import { VerifyGateway } from './verify.gateway'; +import { Verify } from './entities/verify.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Verify])], + controllers: [VerifyController], + providers: [VerifyService, VerifyGateway], +}) +export class VerifyModule {} diff --git a/src/modules/verify/verify.service.spec.ts b/src/modules/verify/verify.service.spec.ts new file mode 100644 index 0000000..466c935 --- /dev/null +++ b/src/modules/verify/verify.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VerifyService } from './verify.service'; + +describe('VerifyService', () => { + let service: VerifyService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [VerifyService], + }).compile(); + + service = module.get(VerifyService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/verify/verify.service.ts b/src/modules/verify/verify.service.ts new file mode 100644 index 0000000..1237110 --- /dev/null +++ b/src/modules/verify/verify.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Verify } from './entities/verify.entity'; +import { VerifyGateway } from './verify.gateway'; + +@Injectable() +export class VerifyService { + constructor( + @InjectRepository(Verify) + private verifyRepo: Repository, + private verifyGateway: VerifyGateway, + ) {} + + async initiateVerification(certificate_id: string) { + const verification = this.verifyRepo.create({ certificate_id }); + await this.verifyRepo.save(verification); + + return { + verification_id: verification.verification_id, + graduate_link: `https://localhost:3000/verify/${verification.verification_id}`, + }; + } + + async approveVerification(verification_id: string, approval_key: string) { + const verification = await this.verifyRepo.findOneBy({ verification_id }); + if (!verification || verification.status !== 'pending') + throw new Error('Invalid verification'); + + verification.status = 'approved'; + verification.approval_key = approval_key; + await this.verifyRepo.save(verification); + + // 🔔 Notify Employer via WebSocket + this.verifyGateway.notifyEmployer(verification_id, { + status: 'approved', + approval_key, + }); + + return verification; + } +}