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
4 changes: 2 additions & 2 deletions backend/src/config/env.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,10 @@ const EnvSchema = z
if (isProduction) {
if (!secret || secret.trim() === '') {
issue('JWT_SECRET', 'JWT_SECRET is required in production');
} else if (secret.length < 32) {
issue('JWT_SECRET', 'JWT_SECRET must be at least 32 characters in production');
} else if (PLACEHOLDER_JWT_SECRETS.has(secret.trim().toLowerCase())) {
issue('JWT_SECRET', 'JWT_SECRET must not be a placeholder value in production');
} else if (secret.length < 32) {
issue('JWT_SECRET', 'JWT_SECRET must be at least 32 characters in production');
}
} else {
// Development/test: if a value is explicitly provided it must still meet minimum length
Expand Down
17 changes: 16 additions & 1 deletion backend/src/deliverable/deliverable.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { GigService } from '../gig/gig.service';
import { GigStatus } from '../gig/gig.entity';
import { UploadDeliverableDto } from './deliverable.dto';
import { Deliverable, DeliverableStatus } from './deliverable.entity';
import { PinStatus } from '../ipfs-pinning/ipfs-pinning.types';

const DELIVERABLE_KEY_PREFIX = 'deliverable:';
const DELIVERABLES_BY_GIG_PREFIX = 'deliverables:gig:';
Expand Down Expand Up @@ -49,14 +50,15 @@ export class DeliverableService {
filename: dto.filename,
});

const deliverableStatus = this.mapPinStatusToDeliverableStatus(pinResult.status);
const deliverable: Deliverable = {
id,
gigId: dto.gigId,
freelancer: dto.freelancer,
cid: pinResult.cid,
filename: dto.filename,
size: pinResult.size,
status: DeliverableStatus.PINNED,
status: deliverableStatus,
createdAt: now,
updatedAt: now,
};
Expand Down Expand Up @@ -181,4 +183,17 @@ export class DeliverableService {
private gigKey(gigId: string): string {
return `${DELIVERABLES_BY_GIG_PREFIX}${gigId}`;
}

private mapPinStatusToDeliverableStatus(pinStatus: PinStatus): DeliverableStatus {
switch (pinStatus) {
case PinStatus.HEALTHY:
return DeliverableStatus.PINNED;
case PinStatus.DEGRADED:
return DeliverableStatus.PENDING;
case PinStatus.FAILED:
return DeliverableStatus.FAILED;
default:
return DeliverableStatus.FAILED;
}
}
}
3 changes: 3 additions & 0 deletions backend/src/dispute/dispute-saga.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,9 @@ export class DisputeSagaService implements OnModuleInit {
if (unique.length < 3) {
throw new BadRequestException('At least 3 distinct juror addresses are required');
}
if (unique.length % 2 === 0) {
throw new BadRequestException('Juror count must be odd to prevent vote ties');
}

// Prevent escrow parties from being assigned as jurors to avoid conflicts of interest
const escrow = await this.escrowService.findById(saga.escrowId);
Expand Down
3 changes: 3 additions & 0 deletions backend/src/gig/gig-expiry-worker.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import { DEFAULT_GIG_EXPIRY_SWEEP_INTERVAL_MS } from './gig.entity';
import { DistributedLockService } from '../common/redis/distributed-lock.service';
import { mapWithConcurrency, countRejected } from '../common/concurrency';
import { config } from '../config/env.config';
import { MetricsService } from '../monitoring/metrics.service';
import { SentryService } from '../sentry/sentry.service';

const LOCK_KEY = 'lock:gig-expiry-sweep';
const LOCK_RENEWAL_INTERVAL_MS = 10_000;

/**
* Periodically sweeps the DB for open gig solicitations whose response deadline has
Expand Down