Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
466eeee
[Backend] sorobanService.simulateContractCall: extract STALE_THRESHOL…
EbukaMoses Jun 2, 2026
d0acc9a
Merge branch 'main' into sorobanService.simulate
EbukaMoses Jun 3, 2026
b7fb5c8
fixed bug
EbukaMoses Jun 8, 2026
83ca5cd
fix: use as unknown as Uint8Array for Hash type conversion
EbukaMoses Jun 8, 2026
6132b7a
fix: add Account to imports
EbukaMoses Jun 8, 2026
7073a9e
fix: properly validate JWT signatures in verifyJwt
adityabhatkar23 Jun 13, 2026
45f6739
fix(ci): point Dependabot npm at the workspace root only
ogazboiz Jun 18, 2026
1a92153
Merge branch 'main' into sorobanService.simulate
EbukaMoses Jun 25, 2026
cd1e779
Add stream POST negative tests
Emelie-Dev Jun 30, 2026
e68b9b5
Add stream token address index
chiscookeke11 Jun 30, 2026
1e13610
Fix Soroban worker Prisma type import
chiscookeke11 Jun 30, 2026
ac163b7
Merge pull request #1 from chiscookeke11/codex/add-index-on-stream.to…
chiscookeke11 Jun 30, 2026
320e7f6
Configure Postgres pool limits
chiscookeke11 Jun 30, 2026
a984815
Merge pull request #2 from chiscookeke11/codex/configure-pg.pool-conn…
chiscookeke11 Jun 30, 2026
1cc1e1f
Merge pull request #2 from LabsCrypt/main
Henrichy Jun 30, 2026
aa3bc57
withdraw on a paused stream returns StreamInactive instead of a disti…
Henrichy Jun 30, 2026
4aa21d9
Updated
Henrichy Jun 30, 2026
fa56226
docs: correct replayFromLedger idempotency claims in JSDoc and swagge…
CHKM001 Jun 30, 2026
2b403a6
fix: delegate after streamed error responses
charlesedeh021-cell Jun 29, 2026
0f7b162
fix(security+tests): CSP/COOP/CORP headers, requireAdmin tests, secur…
Drock0 Jun 30, 2026
8aec268
fix(csp): remove unsafe-inline from global CSP; scope to /api-docs only
Drock0 Jun 30, 2026
4702d1d
fix(ci): add prisma schema to Docker runner stage and rate-limit admi…
CHKM001 Jun 30, 2026
6712d8a
Merge remote-tracking branch 'upstream/main' into docs/replay-from-le…
CHKM001 Jun 30, 2026
7659231
fix: resolve TypeScript build error in soroban-event-worker.ts and up…
CHKM001 Jun 30, 2026
d5712a5
fix(ci): copy generated prisma client to dist/generated for correct r…
CHKM001 Jun 30, 2026
d6b848e
fix(ci): remove backend-docker job and frontend Codecov upload
CHKM001 Jun 30, 2026
3fbf6ee
Fixed issues
Emelie-Dev Jun 30, 2026
1a7aa38
Merge branch 'main' into test/815-stream-post-negative-cases
Emelie-Dev Jun 30, 2026
162529b
Fixed issues
Emelie-Dev Jun 30, 2026
73969b6
Fixed issues
Emelie-Dev Jun 30, 2026
e148673
Fixed issues
Emelie-Dev Jun 30, 2026
75f1ceb
Fixed issues
Emelie-Dev Jun 30, 2026
4b81cfc
Fixed issues
Emelie-Dev Jul 1, 2026
cd98dd8
Merge pull request #969 from CHKM001/docs/replay-from-ledger-jsdoc-808
ogazboiz Jul 1, 2026
218b9e6
ci: restore Backend Docker Image CI and fix the runtime image
davidmaronio Jul 1, 2026
2713c60
Merge pull request #974 from LabsCrypt/fix/restore-backend-docker-ci
ogazboiz Jul 1, 2026
f971105
Merge pull request #762 from adityabhatkar23/fix/jwt-signature-verifi…
ogazboiz Jul 1, 2026
9a89583
Merge pull request #740 from EbukaMoses/sorobanService.simulate
ogazboiz Jul 1, 2026
cb53cc6
Merge pull request #772 from LabsCrypt/fix/dependabot-workspace-lockfile
ogazboiz Jul 1, 2026
1bbcbdc
#623 [Frontend] StreamCreationWizard.startPolling hits wrong URL and …
jotel-dev Jun 29, 2026
34d8bc2
fix: resolve parsing and lint errors in page and dashboard views
jotel-dev Jun 29, 2026
3cab838
fix: resolve Skeleton type error in dashboard view
jotel-dev Jun 29, 2026
d6fe402
fix: enforce consistent return type in useIncomingStreams onMutate
jotel-dev Jun 29, 2026
c691811
fix: suppress vite plugin type mismatch in vitest config
jotel-dev Jun 29, 2026
3bbe154
chore: remove tests for deprecated endpoints
jotel-dev Jun 30, 2026
d6b8a09
chore(#621): remove unused API_BASE_URL declaration from dashboard.ts
shogun444 Jun 30, 2026
d88aad0
fix(backend): stabilize getStreamEvents cursor pagination for tied ti…
BernardOnuh Jul 1, 2026
5b301fb
Add stream POST negative tests
Emelie-Dev Jun 30, 2026
0cd1750
Fixed issues
Emelie-Dev Jun 30, 2026
907d3af
Merge branch 'LabsCrypt:main' into issue-832
chiscookeke11 Jul 1, 2026
89bd454
Fixed issues
Emelie-Dev Jun 30, 2026
5d76c89
Fixed issues
Emelie-Dev Jun 30, 2026
8c264f7
Fixed issues
Emelie-Dev Jun 30, 2026
9998661
Merge branch 'test/815-stream-post-negative-cases' of https://github.…
Emelie-Dev Jul 1, 2026
ccd1a05
Fixed Issues
Emelie-Dev Jul 1, 2026
e5ca56d
Merge branch 'main' into main
Henrichy Jul 1, 2026
ce76f9d
chore(#620): drop unused stellar-sdk dependency
Fury03 Jun 30, 2026
966cabf
Merge pull request #963 from shogun444/fix/remove-api-base-url-621
ogazboiz Jul 1, 2026
150f9eb
Merge pull request #962 from Fury03/fix/620-drop-unused-stellar-sdk
ogazboiz Jul 1, 2026
e44acf8
Merge pull request #977 from BernardOnuh/fix/803-stream-events-cursor…
ogazboiz Jul 1, 2026
1229a89
Merge pull request #959 from Henrichy/main
ogazboiz Jul 1, 2026
0d41728
Merge pull request #944 from jotel-dev/fix/623-stream-creation-polling
ogazboiz Jul 1, 2026
f50d60b
Merge pull request #957 from chiscookeke11/issue-832
ogazboiz Jul 1, 2026
09846c4
Add stream POST negative tests
Emelie-Dev Jun 30, 2026
00765e2
Fixed issues
Emelie-Dev Jun 30, 2026
97c9517
Fixed issues
Emelie-Dev Jun 30, 2026
aa93df0
Fixed issues
Emelie-Dev Jun 30, 2026
1113401
Fixed issues
Emelie-Dev Jun 30, 2026
841b885
Fixed issues
Emelie-Dev Jun 30, 2026
820c929
Fixed issues
Emelie-Dev Jun 30, 2026
e984744
Fixed Issues
Emelie-Dev Jul 1, 2026
cad80d7
Merge branch 'test/815-stream-post-negative-cases' of https://github.…
Emelie-Dev Jul 1, 2026
de63644
Fixed issues
Emelie-Dev Jul 1, 2026
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
32 changes: 6 additions & 26 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ version: 2

updates:
# ── npm: root workspace ────────────────────────────────────────────────────
# flowfi is a single npm workspace (frontend + backend hoisted into one root
# package-lock.json). Dependabot must update from the workspace root so the
# root lockfile CI runs `npm ci` against stays in sync. Per-directory entries
# for /frontend and /backend only touched their package.json without updating
# the root lockfile, so every PR they opened failed `npm ci` with
# "lock file's X does not satisfy Y". One root entry covers all workspaces.
- package-ecosystem: "npm"
directory: "/"
schedule:
Expand All @@ -14,32 +20,6 @@ updates:
- "minor"
- "patch"

# ── npm: frontend ──────────────────────────────────────────────────────────
- package-ecosystem: "npm"
directory: "/frontend"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
groups:
minor-and-patch:
update-types:
- "minor"
- "patch"

# ── npm: backend ───────────────────────────────────────────────────────────
- package-ecosystem: "npm"
directory: "/backend"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
groups:
minor-and-patch:
update-types:
- "minor"
- "patch"

# ── Cargo: contracts ───────────────────────────────────────────────────────
- package-ecosystem: "cargo"
directory: "/contracts"
Expand Down
11 changes: 0 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,6 @@ jobs:
run: npm run test:coverage
working-directory: frontend

- name: Upload frontend coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: frontend/coverage/lcov.info
flags: frontend
name: frontend-coverage
fail_ci_if_error: false
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: Build
run: npm run build
working-directory: frontend
Expand Down
10 changes: 10 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Database
DATABASE_URL="postgresql://user:password@localhost:5433/flowfi?schema=public"

# PostgreSQL pool settings
# Maximum database connections per backend process (default: 10)
PG_POOL_MAX=10
# How long an idle connection stays open before being closed (milliseconds, default: 30000)
PG_IDLE_TIMEOUT_MS=30000
# How long to wait when establishing a new database connection (milliseconds, default: 5000)
PG_CONNECTION_TIMEOUT_MS=5000
# Maximum time a PostgreSQL statement may run before cancellation (milliseconds, default: 30000)
PG_STATEMENT_TIMEOUT_MS=30000

# Server
PORT=3001
NODE_ENV=development
Expand Down
7 changes: 5 additions & 2 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ COPY package*.json ./
RUN npm install

COPY tsconfig.json ./
COPY prisma.config.ts ./
COPY src ./src
COPY prisma ./prisma

Expand All @@ -21,8 +22,10 @@ COPY package*.json ./
RUN npm install --omit=dev

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/src/generated ./src/generated
COPY --from=builder /app/src/generated ./dist/generated
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/prisma.config.ts ./prisma.config.ts

EXPOSE 3001

CMD ["npm", "start"]
CMD ["npm", "start"]
1 change: 0 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"express-rate-limit": "^8.5.2",
"ioredis": "^5.11.1",
"pg": "^8.21.0",
"stellar-sdk": "^13.3.0",
"swagger-jsdoc": "^6.3.0",
"swagger-ui-express": "^5.0.1",
"winston": "^3.11.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX IF NOT EXISTS "Stream_tokenAddress_idx" ON "Stream"("tokenAddress");
1 change: 1 addition & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ model Stream {

@@index([sender])
@@index([recipient])
@@index([tokenAddress])
@@index([streamId])
@@index([isActive])
@@index([isPaused])
Expand Down
5 changes: 2 additions & 3 deletions backend/prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import pg from 'pg';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../src/generated/prisma/index.js';
import { createPgPool } from '../src/lib/pg-pool.js';

const connectionString = process.env.DATABASE_URL;
const pool = new pg.Pool({ connectionString });
const pool = createPgPool();
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });

Expand Down
163 changes: 93 additions & 70 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import express, { type Request, type Response, type NextFunction } from 'express';
import cors from 'cors';
import swaggerUi from 'swagger-ui-express';
import { swaggerSpec } from './config/swagger.js';
import { apiVersionMiddleware, type VersionedRequest } from './middleware/api-version.middleware.js';
import { sandboxMiddleware } from './middleware/sandbox.middleware.js';
import { globalRateLimiter } from './middleware/rate-limiter.middleware.js';
import { requestIdMiddleware } from './middleware/requestId.js';
import v1Routes from './routes/v1/index.js';

import healthRoutes from './routes/health.routes.js';
import express, {
type Request,
type Response,
type NextFunction,
} from "express";
import cors from "cors";
import swaggerUi from "swagger-ui-express";
import { swaggerSpec } from "./config/swagger.js";
import {
apiVersionMiddleware,
type VersionedRequest,
} from "./middleware/api-version.middleware.js";
import { sandboxMiddleware } from "./middleware/sandbox.middleware.js";
import { globalRateLimiter } from "./middleware/rate-limiter.middleware.js";
import { requestIdMiddleware } from "./middleware/requestId.js";
import v1Routes from "./routes/v1/index.js";

import healthRoutes from "./routes/health.routes.js";

const app = express();
const isProduction = process.env.NODE_ENV === 'production';
const rawCors = process.env.CORS_ALLOWED_ORIGINS ?? '';
const isProduction = process.env.NODE_ENV === "production";
const rawCors = process.env.CORS_ALLOWED_ORIGINS ?? "";
const allowedOrigins = rawCors
.split(',')
.map((origin) => origin.trim())
.filter(Boolean);
.split(",")
.map((origin) => origin.trim())
.filter(Boolean);

// Default in development to only localhost:3000 (frontend dev server)
if (!process.env.CORS_ALLOWED_ORIGINS && !isProduction) {
allowedOrigins.push('http://localhost:3000');
allowedOrigins.push("http://localhost:3000");
}

// Apply global rate limiter first
Expand All @@ -29,52 +36,60 @@ app.use(globalRateLimiter);
// Request ID tracing
app.use(requestIdMiddleware);

app.disable('x-powered-by');
app.disable("x-powered-by");

// Helmet-equivalent core headers without external dependency.
// Strict CSP applied globally; the /api-docs route overrides it below for Swagger UI.
app.use((req: Request, res: Response, next: NextFunction) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Referrer-Policy', 'no-referrer');
res.setHeader('X-DNS-Prefetch-Control', 'off');
res.setHeader('X-Download-Options', 'noopen');
res.setHeader('X-Permitted-Cross-Domain-Policies', 'none');
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'");
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
if (process.env.NODE_ENV === 'production') {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
next();
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("Referrer-Policy", "no-referrer");
res.setHeader("X-DNS-Prefetch-Control", "off");
res.setHeader("X-Download-Options", "noopen");
res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
res.setHeader(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'",
);
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
if (process.env.NODE_ENV === "production") {
res.setHeader(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains",
);
}
next();
});

app.use(cors({
app.use(
cors({
origin(origin, callback) {
// Allow non-browser clients (no Origin header)
if (!origin) {
callback(null, true);
return;
}

if (allowedOrigins.includes(origin)) {
callback(null, true);
return;
}

// Not allowed
callback(new Error('CORS origin not allowed'));
// Allow non-browser clients (no Origin header)
if (!origin) {
callback(null, true);
return;
}

if (allowedOrigins.includes(origin)) {
callback(null, true);
return;
}

// Not allowed
callback(new Error("CORS origin not allowed"));
},
credentials: true,
}));
}),
);

// Convert CORS errors into 403 responses so callers get a clear status code
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
if (err instanceof Error && err.message === 'CORS origin not allowed') {
res.status(403).json({ error: 'CORS origin not allowed' });
return;
}
next(err);
if (err instanceof Error && err.message === "CORS origin not allowed") {
res.status(403).json({ error: "CORS origin not allowed" });
return;
}
next(err);
});
app.use(express.json());

Expand All @@ -83,18 +98,26 @@ app.use(sandboxMiddleware);

// Swagger UI setup
// Override CSP for /api-docs only: Swagger UI requires inline scripts/styles.
app.use('/api-docs', (req: Request, res: Response, next: NextFunction) => {
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'");
app.use(
"/api-docs",
(req: Request, res: Response, next: NextFunction) => {
res.setHeader(
"Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'",
);
next();
}, swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: 'FlowFi API Documentation',
}));
},
swaggerUi.serve,
swaggerUi.setup(swaggerSpec, {
customCss: ".swagger-ui .topbar { display: none }",
customSiteTitle: "FlowFi API Documentation",
}),
);

// Serve raw OpenAPI spec as JSON
app.get('/api-docs.json', (req: Request, res: Response) => {
res.setHeader('Content-Type', 'application/json');
res.send(swaggerSpec);
app.get("/api-docs.json", (req: Request, res: Response) => {
res.setHeader("Content-Type", "application/json");
res.send(swaggerSpec);
});

// API Versioning
Expand All @@ -105,16 +128,16 @@ app.use(apiVersionMiddleware);
// After versioning middleware, /v1/streams becomes /streams, so we mount v1Routes at root
// But only handle requests that had a version prefix (apiVersion is set)
app.use((req: Request, res: Response, next: NextFunction) => {
const versionedReq = req as VersionedRequest;
if (versionedReq.apiVersion) {
// This was a versioned request, route to v1 handlers
return v1Routes(req, res, next);
}
return next(); // Not versioned, continue to deprecated handlers
const versionedReq = req as VersionedRequest;
if (versionedReq.apiVersion) {
// This was a versioned request, route to v1 handlers
return v1Routes(req, res, next);
}
return next(); // Not versioned, continue to deprecated handlers
});

// Health check routes
app.use('/health', healthRoutes);
app.use("/health", healthRoutes);

/**
* @openapi
Expand All @@ -128,11 +151,11 @@ app.use('/health', healthRoutes);
* 200:
* description: API is running successfully
*/
app.get('/', (req: Request, res: Response) => {
res.send('FlowFi Backend is running');
app.get("/", (req: Request, res: Response) => {
res.send("FlowFi Backend is running");
});

import { errorHandler } from './middleware/error.middleware.js';
import { errorHandler } from "./middleware/error.middleware.js";

app.use(errorHandler);

Expand Down
Loading
Loading