Skip to content

Commit eab2c91

Browse files
committed
chore: fix code rabbit suggestions
1 parent 1ee45c3 commit eab2c91

11 files changed

Lines changed: 595 additions & 393 deletions

File tree

platforms/eReputation-api/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
},
1515
"dependencies": {
1616
"axios": "^1.6.7",
17+
"bullmq": "^5.3.0",
1718
"cors": "^2.8.5",
1819
"dotenv": "^16.4.5",
1920
"express": "^4.18.2",
21+
"ioredis": "^5.3.2",
2022
"jsonwebtoken": "^9.0.2",
2123
"openai": "^4.20.1",
2224
"pg": "^8.11.3",

platforms/eReputation-api/src/controllers/WebhookController.ts

Lines changed: 26 additions & 364 deletions
Large diffs are not rendered by default.

platforms/eReputation-api/src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { GroupController } from "./controllers/GroupController";
1515
import { DashboardController } from "./controllers/DashboardController";
1616
import { authMiddleware, authGuard } from "./middleware/auth";
1717
import { adapter } from "./web3adapter/watchers/subscriber";
18+
import { JobQueueService } from "./services/JobQueueService";
19+
import { PollReputationWorker } from "./workers/PollReputationWorker";
1820

1921
config({ path: path.resolve(__dirname, "../../../.env") });
2022

@@ -49,10 +51,14 @@ app.use(
4951
app.use(express.json({ limit: "50mb" }));
5052
app.use(express.urlencoded({ limit: "50mb", extended: true }));
5153

54+
// Initialize job queue and worker
55+
const jobQueueService = new JobQueueService();
56+
const pollReputationWorker = new PollReputationWorker(jobQueueService);
57+
5258
// Controllers
5359
const userController = new UserController();
5460
const authController = new AuthController();
55-
const webhookController = new WebhookController();
61+
const webhookController = new WebhookController(jobQueueService);
5662
const referenceController = new ReferenceController();
5763
const calculationController = new CalculationController();
5864
const platformController = new PlatformController();

platforms/eReputation-api/src/middleware/auth.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { NextFunction, Request, Response } from "express";
22
import { AppDataSource } from "../database/data-source";
33
import { User } from "../database/entities/User";
4-
import { verifyToken } from "../utils/jwt";
4+
import { verifyToken, AuthTokenPayload } from "../utils/jwt";
55

66
export const authMiddleware = async (
77
req: Request,
@@ -16,11 +16,7 @@ export const authMiddleware = async (
1616
}
1717

1818
const token = authHeader.split(" ")[1];
19-
const decoded = verifyToken(token) as { userId: string };
20-
21-
if (!decoded?.userId) {
22-
return res.status(401).json({ error: "Invalid token" });
23-
}
19+
const decoded: AuthTokenPayload = verifyToken(token);
2420

2521
const userRepository = AppDataSource.getRepository(User);
2622
const user = await userRepository.findOneBy({ id: decoded.userId });

platforms/eReputation-api/src/services/GroupService.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Repository } from "typeorm";
1+
import { Repository, In } from "typeorm";
22
import { AppDataSource } from "../database/data-source";
33
import { Group } from "../database/entities/Group";
44
import { User } from "../database/entities/User";
@@ -23,8 +23,8 @@ export class GroupService {
2323
name: string,
2424
description: string,
2525
owner: string,
26-
adminIds: string[],
27-
participantIds: string[],
26+
adminIds: string[] = [],
27+
participantIds: string[] = [],
2828
charter?: string
2929
): Promise<Group> {
3030
const group = this.groupRepository.create({
@@ -36,13 +36,13 @@ export class GroupService {
3636

3737
// Add admins
3838
if (adminIds.length > 0) {
39-
const admins = await this.userRepository.findByIds(adminIds);
39+
const admins = await this.userRepository.findBy({ id: In(adminIds) });
4040
group.admins = admins;
4141
}
4242

4343
// Add participants
4444
if (participantIds.length > 0) {
45-
const participants = await this.userRepository.findByIds(participantIds);
45+
const participants = await this.userRepository.findBy({ id: In(participantIds) });
4646
group.participants = participants;
4747
}
4848

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { Queue, QueueOptions } from "bullmq";
2+
import Redis from "ioredis";
3+
4+
export interface PollReputationJobData {
5+
pollId: string;
6+
eventId: string; // For idempotency
7+
groupId: string;
8+
}
9+
10+
export class JobQueueService {
11+
private queue: Queue<PollReputationJobData>;
12+
private redis: Redis;
13+
14+
constructor() {
15+
// Create Redis connection
16+
this.redis = new Redis({
17+
host: process.env.REDIS_HOST || "localhost",
18+
port: parseInt(process.env.REDIS_PORT || "6379", 10),
19+
maxRetriesPerRequest: null,
20+
});
21+
22+
// Create BullMQ queue
23+
const queueOptions: QueueOptions = {
24+
connection: {
25+
host: process.env.REDIS_HOST || "localhost",
26+
port: parseInt(process.env.REDIS_PORT || "6379", 10),
27+
},
28+
defaultJobOptions: {
29+
attempts: 3,
30+
backoff: {
31+
type: "exponential",
32+
delay: 2000,
33+
},
34+
removeOnComplete: {
35+
age: 3600, // Keep completed jobs for 1 hour
36+
count: 1000,
37+
},
38+
removeOnFail: {
39+
age: 86400, // Keep failed jobs for 24 hours
40+
},
41+
},
42+
};
43+
44+
this.queue = new Queue<PollReputationJobData>("poll-reputation-calculation", queueOptions);
45+
}
46+
47+
/**
48+
* Enqueue a poll reputation calculation job with deduplication
49+
*/
50+
async enqueuePollReputationJob(
51+
pollId: string,
52+
groupId: string,
53+
eventId: string
54+
): Promise<void> {
55+
try {
56+
// Use pollId as the job ID for deduplication (same poll = same job)
57+
const jobId = `poll-reputation:${pollId}`;
58+
59+
// Check if job already exists or was recently processed
60+
const existingJob = await this.queue.getJob(jobId);
61+
if (existingJob) {
62+
const state = await existingJob.getState();
63+
if (state === "active" || state === "waiting" || state === "delayed") {
64+
// Job already queued, skip
65+
return;
66+
}
67+
}
68+
69+
// Add job with deduplication
70+
await this.queue.add(
71+
"calculate-poll-reputation",
72+
{
73+
pollId,
74+
groupId,
75+
eventId,
76+
},
77+
{
78+
jobId, // Use pollId as job ID for deduplication
79+
removeOnComplete: true,
80+
removeOnFail: false,
81+
}
82+
);
83+
} catch (error) {
84+
throw error;
85+
}
86+
}
87+
88+
/**
89+
* Close the queue connection
90+
*/
91+
async close(): Promise<void> {
92+
await this.queue.close();
93+
await this.redis.quit();
94+
}
95+
96+
getQueue(): Queue<PollReputationJobData> {
97+
return this.queue;
98+
}
99+
}
100+

platforms/eReputation-api/src/services/VotingReputationService.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,21 @@ export class VotingReputationService {
221221
ename = member?.ename || null;
222222
}
223223

224-
if (!ename || !item.score || typeof item.score !== 'number' || !item.justification || typeof item.justification !== 'string') {
225-
console.error(` ❌ Invalid result at index ${index}:`, item);
224+
// Explicit validation: ensure ename is a non-empty string
225+
if (typeof ename !== 'string' || ename.trim() === '') {
226+
console.error(` ❌ Invalid result at index ${index}: ename is not a non-empty string`, item);
227+
return;
228+
}
229+
230+
// Explicit validation: ensure score is a finite number
231+
if (typeof item.score !== 'number' || !Number.isFinite(item.score)) {
232+
console.error(` ❌ Invalid result at index ${index}: score is not a finite number`, item);
233+
return;
234+
}
235+
236+
// Explicit validation: ensure justification is a string
237+
if (typeof item.justification !== 'string') {
238+
console.error(` ❌ Invalid result at index ${index}: justification is not a string`, item);
226239
return;
227240
}
228241

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
1-
import jwt from "jsonwebtoken";
1+
import jwt, { JwtPayload } from "jsonwebtoken";
22

3-
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
3+
// Fail fast if JWT_SECRET is missing
4+
if (!process.env.JWT_SECRET) {
5+
throw new Error("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
6+
}
47

5-
export const signToken = (payload: { userId: string }): string => {
8+
const JWT_SECRET = process.env.JWT_SECRET;
9+
10+
export interface AuthTokenPayload {
11+
userId: string;
12+
}
13+
14+
export const signToken = (payload: AuthTokenPayload): string => {
615
return jwt.sign(payload, JWT_SECRET, { expiresIn: "7d" });
716
};
817

9-
export const verifyToken = (token: string): any => {
10-
return jwt.verify(token, JWT_SECRET);
18+
export const verifyToken = (token: string): AuthTokenPayload => {
19+
const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload & AuthTokenPayload;
20+
21+
// Validate that the decoded token has the required userId field
22+
if (!decoded.userId || typeof decoded.userId !== 'string') {
23+
throw new Error("Invalid token: missing or invalid userId");
24+
}
25+
26+
return {
27+
userId: decoded.userId
28+
};
1129
};

0 commit comments

Comments
 (0)