Skip to content
Open
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
35 changes: 35 additions & 0 deletions app/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -490,3 +490,38 @@ model IdempotencyKey {
@@index([key])
@@index([expiresAt])
}

enum UploadSessionStatus {
pending
completed
failed
expired
}

model UploadSession {
id String @id @default(cuid())
ownerId String
filename String
contentType String
totalSize Int
status UploadSessionStatus @default(pending)
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

chunks UploadChunk[]

@@index([status, expiresAt])
@@index([ownerId])
}

model UploadChunk {
id String @id @default(cuid())
uploadSessionId String
uploadSession UploadSession @relation(fields: [uploadSessionId], references: [id])
chunkIndex Int
size Int
createdAt DateTime @default(now())

@@unique([uploadSessionId, chunkIndex])
}
60 changes: 12 additions & 48 deletions app/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { AuditModule } from './audit/audit.module';
import { NotificationsModule } from './notifications/notifications.module';
import { JobsModule } from './jobs/jobs.module';
import { RequestCorrelationMiddleware } from './middleware/request-correlation.middleware';
import { SecurityModule } from './common/security/security.module';
import {
SecurityModule,
createRateLimiter,
} from './common/security/security.module';
import { CampaignsModule } from './campaigns/campaigns.module';
import { APP_GUARD } from '@nestjs/core';
import { ApiKeyGuard } from './common/guards/api-key.guard';
Expand All @@ -28,18 +31,10 @@ import { LoggingInterceptor } from './interceptors/logging.interceptor';
import { LoggerService } from './logger/logger.service';
import { AllExceptionsFilter } from './common/filters/http-exception.filter';
import { AnalyticsModule } from './analytics/analytics.module';
import { ThrottlerModule } from '@nestjs/throttler';
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { AidEscrowModule } from './onchain/aid-escrow.module';
import { UploadsModule } from './uploads/uploads.module';
import { ApiKeysModule } from './api-keys/api-keys.module';
import { SessionModule } from './session/session.module';
import { CommonServicesModule } from './common/services/common-services.module';
import { EvidenceModule } from './evidence/evidence.module';
import { RetentionPolicyModule } from './retention-policy/retention-policy.module';
import { InvitesModule } from './orgs/invites.module';
import { AdminSearchModule } from './search/admin-search.module';
import { RedisModule } from '@liaoliaots/nestjs-redis';
import { AdaptiveRateLimitGuard } from './common/guards/adaptive-rate-limit.guard';
import { DeprecationInterceptor } from './common/interceptors/deprecation.interceptor';

@Module({
imports: [
Expand All @@ -64,21 +59,6 @@ import { DeprecationInterceptor } from './common/interceptors/deprecation.interc
host: configService.get<string>('REDIS_HOST') ?? 'localhost',
port: parseInt(configService.get<string>('REDIS_PORT') ?? '6379', 10),
},
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 5000,
},
removeOnComplete: {
age: 3600, // keep for 1 hour
count: 1000,
},
removeOnFail: {
age: 24 * 3600, // keep for 24 hours
count: 5000,
},
},
}),
inject: [ConfigService],
}),
Expand All @@ -98,23 +78,8 @@ import { DeprecationInterceptor } from './common/interceptors/deprecation.interc
JobsModule,
AnalyticsModule,
AidEscrowModule,
UploadsModule,
ApiKeysModule,
SessionModule,
CommonServicesModule,
EvidenceModule,
RetentionPolicyModule,
InvitesModule,
AdminSearchModule,
RedisModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
config: {
host: configService.get<string>('REDIS_HOST') ?? 'localhost',
port: parseInt(configService.get<string>('REDIS_PORT') ?? '6379', 10),
},
}),
inject: [ConfigService],
}),
ThrottlerModule.forRoot([
{
ttl: 60000, // 60 seconds window
Expand All @@ -138,17 +103,13 @@ import { DeprecationInterceptor } from './common/interceptors/deprecation.interc
provide: APP_GUARD,
useClass: RolesGuard, // runs second — checks request.user.role against @Roles()
},
{
provide: APP_GUARD,
useClass: AdaptiveRateLimitGuard, // Adaptive rate limiting using Redis
},
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: DeprecationInterceptor,
provide: APP_GUARD,
useClass: ThrottlerGuard, // rate-limiting guard runs after auth and role checks to avoid unnecessary counting of unauthenticated/unauthorized requests
},
],
})
Expand All @@ -162,6 +123,9 @@ export class AppModule implements NestModule {
// Request correlation middleware
consumer.apply(RequestCorrelationMiddleware).forRoutes('*');

// Rate limiter middleware
consumer.apply(createRateLimiter(this.configService)).forRoutes('*');

// Startup log
this.loggerService.log(
'AppModule initialized with structured logging, correlation IDs, and rate limiting',
Expand Down
44 changes: 20 additions & 24 deletions app/backend/src/onchain/soroban.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ import {
} from './onchain.adapter';
import { SorobanErrorMapper } from './utils/soroban-error.mapper';

interface SorobanSDK {
SorobanRpc: {
Server: new (url: string, options: { allowHttp: boolean }) => any;
};
[key: string]: any;
}

/**
* Soroban adapter implementation for AidEscrow contract
* Handles all interactions with the Soroban AidEscrow contract via RPC
Expand All @@ -41,7 +48,7 @@ export class SorobanAdapter implements OnchainAdapter {

// Note: The actual Soroban SDK will be lazily imported when needed
// to avoid bundle size issues in development builds
private sorobanLib: Record<string, any> | null = null;
private sorobanLib: SorobanSDK | null = null;

constructor(private configService: ConfigService) {
this.contractId = this.configService.get<string>('SOROBAN_CONTRACT_ID', '');
Expand All @@ -62,22 +69,20 @@ export class SorobanAdapter implements OnchainAdapter {
}
}

private async loadSorobanSDK() {
private async loadSorobanSDK(): Promise<SorobanSDK> {
if (this.sorobanLib) {
return this.sorobanLib;
}

try {
// Dynamically import stellar/cli SDK
// @ts-expect-error - stellar is optional, only required in production
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const mod = await import('stellar');
const mod = (await import('stellar')) as unknown as SorobanSDK;
this.sorobanLib = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
rpc: mod,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
api: mod,
...(mod as Record<string, any>),
SorobanRpc: mod.SorobanRpc,
...mod,
};
return this.sorobanLib;
} catch (error) {
Expand All @@ -93,7 +98,6 @@ export class SorobanAdapter implements OnchainAdapter {
*/
private async getRpcClient() {
const sdk = await this.loadSorobanSDK();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
return new sdk.SorobanRpc.Server(this.rpcUrl, {
allowHttp: this.rpcUrl.startsWith('http://'),
});
Expand All @@ -116,8 +120,7 @@ export class SorobanAdapter implements OnchainAdapter {

try {
const _sdk = await this.loadSorobanSDK();

const _client = await this.getRpcClient(); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
const _client = await this.getRpcClient();

// Note: Actual implementation would require signing the transaction
// with the contract owner's keypair and submitting to the network.
Expand Down Expand Up @@ -156,8 +159,7 @@ export class SorobanAdapter implements OnchainAdapter {

try {
const _sdk = await this.loadSorobanSDK();

const _client = await this.getRpcClient(); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
const _client = await this.getRpcClient();

// Implementation would call contract's create_package method
// This is a placeholder showing the expected response
Expand Down Expand Up @@ -194,8 +196,7 @@ export class SorobanAdapter implements OnchainAdapter {

try {
const _sdk = await this.loadSorobanSDK();

const _client = await this.getRpcClient(); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
const _client = await this.getRpcClient();

// Implementation would call contract's batch_create_packages method
const packageIds = params.recipientAddresses.map((_, index) =>
Expand Down Expand Up @@ -231,8 +232,7 @@ export class SorobanAdapter implements OnchainAdapter {

try {
const _sdk = await this.loadSorobanSDK();

const _client = await this.getRpcClient(); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
const _client = await this.getRpcClient();

// Implementation would call contract's claim method
const transactionHash = this.generateMockHash(
Expand Down Expand Up @@ -268,8 +268,7 @@ export class SorobanAdapter implements OnchainAdapter {

try {
const _sdk = await this.loadSorobanSDK();

const _client = await this.getRpcClient(); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
const _client = await this.getRpcClient();

// Implementation would call contract's disburse method
const transactionHash = this.generateMockHash(
Expand Down Expand Up @@ -302,8 +301,7 @@ export class SorobanAdapter implements OnchainAdapter {

try {
const _sdk = await this.loadSorobanSDK();

const _client = await this.getRpcClient(); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
const _client = await this.getRpcClient();

// Implementation would call contract's get_package method
// For now, returning a mock response structure
Expand Down Expand Up @@ -339,8 +337,7 @@ export class SorobanAdapter implements OnchainAdapter {

try {
const _sdk = await this.loadSorobanSDK();

const _client = await this.getRpcClient(); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
const _client = await this.getRpcClient();

// Implementation would call contract's get_aggregates method
// Returns aggregates for the specified token
Expand Down Expand Up @@ -370,8 +367,7 @@ export class SorobanAdapter implements OnchainAdapter {

try {
const _sdk = await this.loadSorobanSDK();

const _client = await this.getRpcClient(); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
const _client = await this.getRpcClient();

// Implementation would call token contract's balance method
// This is a placeholder showing the expected response
Expand Down
Loading
Loading