Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,16 @@ frontend/tsc_errors.log
frontend/tsc_errors_new.log
frontend/tsc_errors_new2.log
contracts/test_output_activity.txt
contracts/test_output*.txt
contracts/check_output*.json
contracts/check_errors.txt
contracts/errors*.json

# test snapshots
**/__snapshots__/
*.snap

# editor/OS junk
.vscode/
.idea/
*.swp
7 changes: 7 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ node_modules
.env

/src/generated/prismalogs/

# test snapshots and coverage output
**/__snapshots__/
*.snap
coverage/
dist/
*.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- CreateTable: ProcessedBlock and BridgeEvent for the cross-chain bridge indexer
CREATE TABLE "processed_blocks" (
"id" TEXT NOT NULL,
"chain" TEXT NOT NULL,
"blockNumber" INTEGER NOT NULL,
"blockHash" TEXT NOT NULL,
"parentHash" TEXT NOT NULL,
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"processedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isRolledBack" BOOLEAN NOT NULL DEFAULT false,
"rolledBackAt" TIMESTAMP(3),

CONSTRAINT "processed_blocks_pkey" PRIMARY KEY ("id")
);

CREATE TABLE "bridge_events" (
"id" TEXT NOT NULL,
"chain" TEXT NOT NULL,
"blockNumber" INTEGER NOT NULL,
"blockHash" TEXT NOT NULL,
"processedBlockId" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"eventType" TEXT NOT NULL,
"sourceChain" TEXT NOT NULL,
"targetChain" TEXT NOT NULL,
"data" JSONB NOT NULL,
"transactionHash" TEXT,
"logIndex" INTEGER,
"processed" BOOLEAN NOT NULL DEFAULT false,
"processedAt" TIMESTAMP(3),

CONSTRAINT "bridge_events_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "processed_blocks_blockHash_key" ON "processed_blocks"("blockHash");
CREATE INDEX "processed_blocks_chain_blockNumber_idx" ON "processed_blocks"("chain", "blockNumber");
CREATE UNIQUE INDEX "bridge_events_chain_eventId_key" ON "bridge_events"("chain", "eventId");
CREATE INDEX "bridge_events_processedBlockId_idx" ON "bridge_events"("processedBlockId");

-- AddForeignKey
ALTER TABLE "bridge_events" ADD CONSTRAINT "bridge_events_processedBlockId_fkey" FOREIGN KEY ("processedBlockId") REFERENCES "processed_blocks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
40 changes: 40 additions & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,43 @@ model P2PMessage {
@@index([receiverId])
@@map("p2p_messages")
}

model ProcessedBlock {
id String @id @default(cuid())
chain String
blockNumber Int
blockHash String @unique
parentHash String
timestamp DateTime @default(now())
processedAt DateTime @default(now())
isRolledBack Boolean @default(false)
rolledBackAt DateTime?

bridgeEvents BridgeEvent[]

@@index([chain, blockNumber])
@@map("processed_blocks")
}

model BridgeEvent {
id String @id @default(cuid())
chain String
blockNumber Int
blockHash String
processedBlockId String
eventId String
eventType String
sourceChain String
targetChain String
data Json
transactionHash String?
logIndex Int?
processed Boolean @default(false)
processedAt DateTime?

processedBlock ProcessedBlock @relation(fields: [processedBlockId], references: [id], onDelete: Cascade)

@@unique([chain, eventId])
@@index([processedBlockId])
@@map("bridge_events")
}
1 change: 0 additions & 1 deletion backend/src/blockchain/chain-indexer-engine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
/**
* Cross-Chain Bridge Event Indexer Engine
* Handles block indexing, event processing, and re-org detection for dual-chain bridges
Expand Down
3 changes: 1 addition & 2 deletions backend/src/cache/BlockHeaderListener.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { EventEmitter } from 'events';
import logger from '../utils/logger.js';
import cacheService from './CacheService.js';
Expand All @@ -18,7 +17,7 @@ export interface BlockHeader {
export class BlockHeaderListener extends EventEmitter {
private isListening = false;
private lastBlockHeight = 0;
private pollingInterval: NodeJS.Timer | null = null;
private pollingInterval: NodeJS.Timeout | null = null;
private readonly POLL_INTERVAL = parseInt(process.env.BLOCK_POLL_INTERVAL || '10000', 10); // 10 seconds default

/**
Expand Down
3 changes: 1 addition & 2 deletions backend/src/cache/CacheWarmer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { cacheTTL } from '../config/redis.config.js';
import logger from '../utils/logger.js';
import cacheService, { CACHE_KEYS } from './CacheService.js';
Expand All @@ -9,7 +8,7 @@ import cacheService, { CACHE_KEYS } from './CacheService.js';
*/
export class CacheWarmer {
private isWarming = false;
private warmingInterval: NodeJS.Timer | null = null;
private warmingInterval: NodeJS.Timeout | null = null;
private readonly WARMING_INTERVAL = parseInt(process.env.CACHE_WARMING_INTERVAL || '300000', 10); // 5 minutes

/**
Expand Down
11 changes: 5 additions & 6 deletions backend/src/cache/DistributedCacheManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import logger from '../utils/logger.js';
import cacheService from './CacheService.js';
import redisClient from './RedisClient.js';
Expand Down Expand Up @@ -135,18 +134,18 @@ export class DistributedCacheManager {
let memoryUsage = 'N/A';

lines.forEach((line) => {
if (line.includes('keyspace_hits')) hits = parseInt(line.split(':')[1]);
if (line.includes('keyspace_misses')) misses = parseInt(line.split(':')[1]);
if (line.includes('evicted_keys')) evictions = parseInt(line.split(':')[1]);
if (line.includes('keyspace_hits')) hits = parseInt(line.split(':')[1] ?? '0', 10);
if (line.includes('keyspace_misses')) misses = parseInt(line.split(':')[1] ?? '0', 10);
if (line.includes('evicted_keys')) evictions = parseInt(line.split(':')[1] ?? '0', 10);
});

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

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

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

Expand Down
1 change: 0 additions & 1 deletion backend/src/certificates/CertificateService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import prisma from '../db/index.js';
import {
Certificate,
Expand Down
4 changes: 2 additions & 2 deletions backend/src/config/region.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
/**
* Multi-region Redis replication configuration.
*
Expand Down Expand Up @@ -68,7 +67,8 @@ export function resolveActiveRegionName(
if (requested && regions.some((r) => r.name === requested)) {
return requested;
}
return regions[0].name;
// Length checked above, so the first element is guaranteed to exist.
return regions[0]!.name;
}

/**
Expand Down
1 change: 0 additions & 1 deletion backend/src/dashboard/hash.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { Request, Response } from 'express';
import { HashService } from './hash.service.js';

Expand Down
1 change: 0 additions & 1 deletion backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import cors from 'cors';
import express, { Request, Response } from 'express';
import { createServer } from 'http';
Expand Down
1 change: 0 additions & 1 deletion backend/src/infrastructure/p2p.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { Request, Response } from 'express';
import { P2PService } from './p2p.service.js';

Expand Down
1 change: 0 additions & 1 deletion backend/src/licenses/license.routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
/**
* Open Source License Guide - Express Routes
*
Expand Down
17 changes: 9 additions & 8 deletions backend/src/notifications/NotificationService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-nocheck
import logger from '../utils/logger.js';
import redisClient from '../cache/RedisClient.js';
import {
CourseNotification,
CreateCourseNotificationDto,
Expand Down Expand Up @@ -68,7 +68,8 @@ export async function createNotification(

// Broadcast so all server instances & connected WebSocket clients receive it
try {
await pubClient.publish('course_notifications', JSON.stringify(notification));
const pubClient = redisClient.getPubClient();
await pubClient?.publish('course_notifications', JSON.stringify(notification));
} catch (err) {
logger.warn('Failed to publish course_notification to Redis:', err);
}
Expand Down Expand Up @@ -105,7 +106,7 @@ export function markAsRead(notificationId: string): boolean {
for (const [, notifications] of store.entries()) {
const idx = notifications.findIndex((n) => n.id === notificationId);
if (idx !== -1) {
notifications[idx] = { ...notifications[idx], read: true };
notifications[idx] = { ...notifications[idx]!, read: true };
return true;
}
}
Expand All @@ -125,8 +126,8 @@ export function markAllAsRead(userId: string): number {
const notifs = store.get(key);
if (notifs) {
for (let i = 0; i < notifs.length; i++) {
if (!notifs[i].read) {
notifs[i] = { ...notifs[i], read: true };
if (!notifs[i]!.read) {
notifs[i] = { ...notifs[i]!, read: true };
count++;
}
}
Expand All @@ -152,11 +153,11 @@ function mergeSorted(
let j = 0;
while (result.length < max && (i < a.length || j < b.length)) {
if (i >= a.length) {
result.push(b[j++]);
result.push(b[j++]!);
} else if (j >= b.length) {
result.push(a[i++]);
result.push(a[i++]!);
} else {
result.push(a[i].createdAt >= b[j].createdAt ? a[i++] : b[j++]);
result.push(a[i]!.createdAt >= b[j]!.createdAt ? a[i++]! : b[j++]!);
}
}
return result;
Expand Down
1 change: 0 additions & 1 deletion backend/src/notifications/notification.routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { Router, Request, Response } from 'express';
import {
getNotifications,
Expand Down
17 changes: 8 additions & 9 deletions backend/src/routes/contracts.validation.schemas.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
// @ts-nocheck
import { z } from 'zod';

export const contractCompileSchema = z.object({
sourceCode: z
.string()
.min(32, { message: 'Contract source must contain at least 32 characters.' })
.max(15000, { message: 'Contract source must not exceed 15,000 characters.' }),
.min(32, 'Contract source must contain at least 32 characters.')
.max(15000, 'Contract source must not exceed 15,000 characters.'),
compilerVersion: z
.string()
.regex(/^\d+\.\d+\.\d+$/, { message: 'Compiler version must follow semantic versioning, e.g. 0.8.10' }),
.regex(/^\d+\.\d+\.\d+$/, 'Compiler version must follow semantic versioning, e.g. 0.8.10'),
optimization: z.boolean().default(false),
target: z.enum(['solidity', 'evm', 'soroban', 'wasm']),
entryPoint: z.string().max(128).optional(),
});

export const contractExecutionSchema = z.object({
contractAddress: z.string().min(32, { message: 'Contract address is required.' }),
functionName: z.string().min(1, { message: 'Function name is required.' }),
contractAddress: z.string().min(32, 'Contract address is required.'),
functionName: z.string().min(1, 'Function name is required.'),
parameters: z
.array(z.union([z.string(), z.number(), z.boolean(), z.null()]), { invalid_type_error: 'Parameter values must be primitive types.' })
.max(50, { message: 'Maximum of 50 parameters allowed.' })
.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))
.max(50, 'Maximum of 50 parameters allowed.')
.optional(),
gasLimit: z.number().int().positive().max(10_000_000, { message: 'Gas limit must be positive and no more than 10,000,000.' }),
gasLimit: z.number().int().positive().max(10_000_000, 'Gas limit must be positive and no more than 10,000,000.'),
caller: z.string().max(128).optional(),
});
1 change: 0 additions & 1 deletion backend/src/routes/courses.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { Router } from 'express';
import { cacheMiddleware } from '../cache/CacheMiddleware.js';
import { invalidateAllCourses, invalidateCourseCache } from '../cache/CacheInvalidation.js';
Expand Down
17 changes: 7 additions & 10 deletions backend/src/routes/dependencies.validation.schemas.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
// @ts-nocheck
import { z } from 'zod';

export const dependencyCheckSchema = z.object({
cargoToml: z
.string()
.min(1, { message: 'Cargo.toml content is required.' })
.max(50_000, { message: 'Cargo.toml must not exceed 50,000 characters.' }),
.min(1, 'Cargo.toml content is required.')
.max(50_000, 'Cargo.toml must not exceed 50,000 characters.'),
});

export const dependencyUpdateSchema = z.object({
cargoToml: z
.string()
.min(1, { message: 'Cargo.toml content is required.' })
.max(50_000, { message: 'Cargo.toml must not exceed 50,000 characters.' }),
.min(1, 'Cargo.toml content is required.')
.max(50_000, 'Cargo.toml must not exceed 50,000 characters.'),
dependencies: z
.array(z.string().min(1).max(128), {
invalid_type_error: 'Dependencies must be an array of strings.',
})
.min(1, { message: 'At least one dependency name is required.' })
.max(100, { message: 'Cannot update more than 100 dependencies at once.' }),
.array(z.string().min(1).max(128))
.min(1, 'At least one dependency name is required.')
.max(100, 'Cannot update more than 100 dependencies at once.'),
});
1 change: 0 additions & 1 deletion backend/src/routes/generator/explorer.routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { Router, Request, Response } from 'express';
import {
filterTransactions,
Expand Down
1 change: 0 additions & 1 deletion backend/src/routes/generator/generator.routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { randomUUID } from 'crypto';
import { Request, Response, Router } from 'express';
import { GeneratorService, InvalidGeneratedIdeaError } from '../../generator/generator.service.js';
Expand Down
9 changes: 4 additions & 5 deletions backend/src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { Router } from 'express';
import dashboardRoutes from '../dashboard/dashboard.routes.js';
import activityLogRouter from '../dashboard/activityLog.routes.js';
Expand Down Expand Up @@ -27,15 +26,15 @@ import studentsRouter from './students.js';
import simulatorErrorsRouter from './simulatorErrors.routes.js';
import termsOfServiceRouter from './termsOfService.routes.js';
import privacyPolicyRouter from './privacyPolicy.routes.js';
import playgroundRouter from './playground.routes.js';
import playgroundValidateRouter from './playground.routes.js';
import oauthRouter from './oauth.routes.js';

import notificationRouter from '../notifications/notification.routes.js';
import notificationPreferencesRouter from '../notifications/preferences.routes.js';
import metricsRouter from './metrics.routes.js';
import dependenciesRouter from './dependencies.routes.js';
import infrastructureRouter from '../infrastructure/infrastructure.routes.js';
import simulatorRouter from '../simulator/simulator.routes.js';
import simulatorIdeasRouter from '../simulator/simulator.routes.js';

import webhooksRouter from './webhooks.js';
import adminDLQRouter from './admin/dlq.routes.js';
Expand Down Expand Up @@ -73,10 +72,10 @@ router.use('/user', userRouter);
router.use('/metrics', metricsRouter);
router.use('/dependencies', dependenciesRouter);
router.use('/infrastructure', infrastructureRouter);
router.use('/simulator', simulatorRouter);
router.use('/simulator', simulatorIdeasRouter);
router.use('/simulator/errors', simulatorErrorsRouter);
router.use('/roadmap/tos', termsOfServiceRouter);
router.use('/playground', playgroundRouter);
router.use('/playground', playgroundValidateRouter);
router.use('/playground/privacy-policy', privacyPolicyRouter);
router.use('/oauth', oauthRouter);

Expand Down
1 change: 0 additions & 1 deletion backend/src/routes/playground/playground.routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { Router, Request, Response } from 'express';
import {
TRIAGE_SCENARIOS,
Expand Down
1 change: 0 additions & 1 deletion backend/src/routes/simulatorErrors.routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { Request, Response, Router } from 'express';
import { authenticate } from '../auth/auth.middleware.js';
import logger from '../utils/logger.js';
Expand Down
Loading