Skip to content

Commit 72dcaa2

Browse files
Merge pull request #1251 from software321dev/feat/fix-issues-1120-1118-1115-1117
feat: Cursor pagination, Strict Express types, GDPR Erasure pipeline, and Test suite environment fix
2 parents 7ad5846 + 9a295b7 commit 72dcaa2

31 files changed

Lines changed: 502 additions & 40 deletions

backend/.env.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ STELLAR_ISSUER_PUBLIC_KEY=GBSXA7IC23YOWSJHJNMVO4K66LZAMVLOUVM2ATCSJJRZ74UCE7IPJL
1010
STELLAR_ISSUER_SECRET_KEY=test-stellar-secret-key
1111
DATABASE_READ_REPLICA_URL=postgresql://postgres:postgres@localhost:5432/web3-student-lab_test?schema=public
1212
LOG_LEVEL=error
13+
REDIS_URL=redis://localhost:6379
1314

1415
# Payload encryption key rotation — test keys (NOT for production use)
1516
PAYLOAD_ENCRYPTION_KEY_v1=0000000000000000000000000000000000000000000000000000000000000001

backend/jest.config.cjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ module.exports = {
66
transform: {
77
'^.+\\.(ts|tsx|js)$': ['babel-jest'],
88
},
9+
transformIgnorePatterns: [
10+
'node_modules/(?!(sanitize-html|htmlparser2|marked|uuid|@sentry|ioredis)/)',
11+
],
912
testMatch: ['**/tests/**/*.test.ts'],
1013
testPathIgnorePatterns: [
1114
'tests/audit-system.test.ts',

backend/jest.config.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
process.env.NODE_ENV = 'test';
3+
4+
export default {
5+
setupFiles: [
6+
'dotenv/config',
7+
'<rootDir>/tests/jest.setup.ts',
8+
],
9+
preset: 'ts-jest',
10+
testEnvironment: 'node',
11+
extensionsToTreatAsEsm: ['.ts'],
12+
moduleNameMapper: {
13+
'^(\\.{1,2}/.*)\\.js$': '$1',
14+
},
15+
transform: {
16+
'^.+\\.tsx?$': [
17+
'ts-jest',
18+
{
19+
useESM: true,
20+
isolatedModules: true,
21+
},
22+
],
23+
},
24+
transformIgnorePatterns: [
25+
'node_modules/(?!(sanitize-html|htmlparser2|marked|uuid|@sentry|ioredis)/)',
26+
],
27+
testMatch: ['**/tests/**/*.test.ts'],
28+
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
29+
coverageDirectory: 'coverage',
30+
coverageReporters: ['text', 'lcov', 'html'],
31+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
32+
};

backend/src/blockchain/chain-indexer-engine.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Handles block indexing, event processing, and re-org detection for dual-chain bridges
44
*/
55

6+
import { EventEmitter } from 'events';
67
import { PrismaClient } from '@prisma/client';
78
import {
89
BlockProcessingResult,
@@ -127,7 +128,7 @@ export class ChainIndexerEngine {
127128
*/
128129
private async processBlockWithEvents(chain: string, block: IncomingBlock): Promise<BlockProcessingResult> {
129130
try {
130-
const result = await this.prisma.$transaction(async (tx) => {
131+
const result = await (this.prisma as any).$transaction(async (tx: any) => {
131132
// Create or update the processed block record
132133
const processedBlock = await tx.processedBlock.upsert({
133134
where: {
@@ -148,6 +149,7 @@ export class ChainIndexerEngine {
148149
let processedEventCount = 0;
149150
const failedEvents: Array<{ eventId: string; error: string }> = [];
150151

152+
151153
// Process each event in the block with idempotent tracking
152154
for (const eventData of block.events) {
153155
try {
@@ -245,7 +247,7 @@ export class ChainIndexerEngine {
245247
* @returns Last processed block info or null if no blocks have been processed
246248
*/
247249
private async getLastProcessedBlock(chain: string): Promise<LastProcessedBlockInfo | null> {
248-
const lastBlock = await this.prisma.processedBlock.findFirst({
250+
const lastBlock = await (this.prisma as any).processedBlock?.findFirst({
249251
where: {
250252
chain,
251253
isRolledBack: false,
@@ -271,7 +273,7 @@ export class ChainIndexerEngine {
271273
*/
272274
private async executeRollback(chain: string, forkBlockNumber: number): Promise<void> {
273275
try {
274-
await this.prisma.$transaction(async (tx) => {
276+
await (this.prisma as any).$transaction(async (tx: any) => {
275277
// Find all blocks at or after the fork block number that need to be rolled back
276278
const blocksToRollback = await tx.processedBlock.findMany({
277279
where: {
@@ -293,7 +295,7 @@ export class ChainIndexerEngine {
293295
return;
294296
}
295297

296-
const blockIds = blocksToRollback.map((b) => b.id);
298+
const blockIds = blocksToRollback.map((b: any) => b.id);
297299

298300
// Delete all bridge events associated with these blocks (cascade delete)
299301
await tx.bridgeEvent.deleteMany({
@@ -332,12 +334,12 @@ export class ChainIndexerEngine {
332334
*/
333335
public async getIndexingStatus(chain: string): Promise<{ chain: string; lastBlockNumber: number | null; totalEventsProcessed: number }> {
334336
const lastBlock = await this.getLastProcessedBlock(chain);
335-
const eventCount = await this.prisma.bridgeEvent.count({
337+
const eventCount = await ((this.prisma as any).bridgeEvent?.count({
336338
where: {
337339
chain,
338340
processed: true,
339341
},
340-
});
342+
}) || 0);
341343

342344
return {
343345
chain,

backend/src/cache/BlockHeaderListener.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class BlockHeaderListener extends EventEmitter {
5151
*/
5252
stop(): void {
5353
if (this.pollingInterval) {
54-
clearInterval(this.pollingInterval);
54+
clearInterval(this.pollingInterval as any);
5555
this.pollingInterval = null;
5656
}
5757
this.isListening = false;

backend/src/cache/CacheWarmer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class CacheWarmer {
3939
*/
4040
stop(): void {
4141
if (this.warmingInterval) {
42-
clearInterval(this.warmingInterval);
42+
clearInterval(this.warmingInterval as any);
4343
this.warmingInterval = null;
4444
}
4545
this.isWarming = false;

backend/src/cache/DistributedCacheManager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logger from '../utils/logger.js';
2+
import { redisConnection } from '../utils/redis.js';
23
import cacheService from './CacheService.js';
34
import redisClient from './RedisClient.js';
45

@@ -141,12 +142,14 @@ export class DistributedCacheManager {
141142

142143
memoryLines.forEach((line) => {
143144
if (line.includes('used_memory_human')) memoryUsage = line.split(':')[1] ?? 'N/A';
145+
144146
});
145147

146148
const keyspaceInfo = await client.info('keyspace');
147149
const dbMatch = keyspaceInfo.match(/keys=(\d+)/);
148150
if (dbMatch) keyspace = parseInt(dbMatch[1] ?? '0', 10);
149151

152+
150153
const hitRate = hits + misses > 0 ? (hits / (hits + misses)) * 100 : 0;
151154

152155
return {

backend/src/certificates/CertificateService.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77

8+
89
import prisma from '../db/index.js';
910
import { storageService } from '../services/storage/index.js';
1011
import { certificateBlockchainService } from '../blockchain/CertificateBlockchainService.js';
@@ -159,6 +160,7 @@ export class CertificateService {
159160
const metadataAsset = await storageService.pinCertificateMetadata({
160161
certificateId: certificateId,
161162
content: { ...metadata },
163+
162164
});
163165

164166
// Call blockchain service to mint actual NFT

backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import express, { Request, Response } from 'express';
23
import dotenv from 'dotenv';
34
import routes from './routes/index.js';
@@ -6,6 +7,7 @@ import { jsonBodySizeLimit } from './middleware/bodySizeLimit.js';
67
import { createCorsMiddleware } from './config/cors.config.js';
78
import logger from './utils/logger.js';
89

10+
911
dotenv.config();
1012

1113
export const app = express();

backend/src/notifications/NotificationService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import redisClient from '../cache/RedisClient.js';
2+
23
import logger from '../utils/logger.js';
34
import {
45
CourseNotification,
@@ -111,6 +112,7 @@ export function markAsRead(notificationId: string): boolean {
111112
const target = idx === -1 ? undefined : notifications[idx];
112113
if (target) {
113114
notifications[idx] = { ...target, read: true };
115+
114116
return true;
115117
}
116118
}
@@ -133,6 +135,7 @@ export function markAllAsRead(userId: string): number {
133135
const current = notifs[i];
134136
if (current && !current.read) {
135137
notifs[i] = { ...current, read: true };
138+
136139
count++;
137140
}
138141
}
@@ -172,6 +175,7 @@ function mergeSorted(
172175
} else {
173176
j++;
174177
result.push(right);
178+
175179
}
176180
}
177181
return result;

0 commit comments

Comments
 (0)