Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
98006ab
feat: dynamic idle backoff for indexer poll interval
Osuochasam Aug 25, 2026
253c426
test: verify duplicate_prevention lookups already use the UNIQUE index
Osuochasam Aug 25, 2026
baac21f
feat: dynamic start/end ledger historical import for event_type_filter
Osuochasam Aug 25, 2026
cdc0a09
feat: high-frequency debug diagnostics (poll speed + payload sizes) f…
Osuochasam Aug 25, 2026
4f1ee39
Merge branch 'main' into feat/indexer-polling-and-event-filter-enhanc…
Osuochasam Aug 26, 2026
5fa9122
fix(event_type_filter): wrap operations in SQLite transaction for iso…
benedictworks-home Aug 26, 2026
61b20b4
fix(database_writer_pool): add exponential backoff retry for RPC time…
benedictworks-home Aug 26, 2026
d0aecdb
Add sqlite_schema_manager consecutive-failure and stall alerting.
Aug 27, 2026
ebbd985
Merge pull request #8 from ademolacomfort/issue-243-custom-rate-limit
ademolacomfort Aug 27, 2026
a510826
feat(indexer): add SQLite index structures for indexer_runner (#250)
zeemscript Aug 27, 2026
250516a
Merge branch 'main' into fix/188-event-type-filter-transaction-isolation
godamongstmen897 Aug 28, 2026
6d69ffc
Merge branch 'main' into fix/262-implement-alerting-notifications-on-…
godamongstmen897 Aug 28, 2026
0d8965e
Merge branch 'main' into fix/issue-250
godamongstmen897 Aug 28, 2026
d4a25df
feat(indexer): add event_type_filter with alerting, queue locks and d…
Ike-Nathan Aug 28, 2026
88ec873
Merge branch 'main' into main
ademolacomfort Aug 28, 2026
759ce88
feat: reusable Zod validation middleware + indexer RPC backoff retry
walexjnr Aug 28, 2026
ba62716
fix: repair pre-existing CI failures from merged PRs
Aug 29, 2026
368092c
feat(indexer_runner): dynamic poller throttling parameters (#256)
Aug 29, 2026
5018a7a
fix: repair pre-existing CI failures from merged PRs
Aug 29, 2026
3d15ca6
feat(indexer_metrics_collector): dynamic poll throttling parameters (…
Aug 29, 2026
85d2519
fix: repair pre-existing CI failures from merged PRs
Aug 29, 2026
5a7581f
feat(sqlite_vacuum_cleaner): configure SQLite index structures (#344)
Aug 29, 2026
3419979
fix: repair pre-existing CI failures from merged PRs
Aug 29, 2026
3cbe10d
feat(sqlite_vacuum_cleaner): add polling diagnostics logs (#346)
Aug 29, 2026
1c641a8
fix(indexer): restore schema test contracts
zeemscript Aug 29, 2026
09cc50a
feat: implement in-memory cache for partial-release endpoint (Issue #…
Davoski1 Aug 29, 2026
aae55ff
fix(security): configure CORS and security headers for build-tx route…
Davoski1 Aug 29, 2026
245b4d5
Prevent duplicate event inserts under concurrent writer-pool notifica…
Goodnessukaigwe Aug 29, 2026
8b6cdc6
feat(database_writer_pool): alert on consecutive write failures and s…
Goodnessukaigwe Aug 29, 2026
5d6161b
feat(indexer): add SQLite write-path indexes for database_writer_pool
Goodnessukaigwe Aug 29, 2026
ff8d8ed
fix: unblock tsc and jest after main merge damage
Goodnessukaigwe Aug 29, 2026
f3b10a7
feat(indexer_metrics_collector): add migration verification hooks (cl…
Aug 30, 2026
c413f43
feat(vacuum): implement exponential backoff retry for vacuum connecti…
Aug 30, 2026
086fa57
fix(sqlite_schema_manager): add memory queue locks for concurrent eve…
solaawojobi00-bit Aug 29, 2026
72e6671
feat(sqlite_schema_manager): add poll diagnostics logging
solaawojobi00-bit Aug 28, 2026
ca766f9
fix(tests): repair pre-existing CI type-check and runtime failures
solaawojobi00-bit Aug 28, 2026
9290796
feat(sqlite_schema_manager): dynamic historical sync ranges for custo…
solaawojobi00-bit Aug 28, 2026
7d85833
fix(tests): repair pre-existing schema-manager test bugs after rebase
solaawojobi00-bit Aug 30, 2026
693fed6
feat(sqlite_vacuum_cleaner): threshold alerting for consecutive and s…
solaawojobi00-bit Aug 29, 2026
21834a6
fix(tests): repair pre-existing schema-manager test bugs after rebase
solaawojobi00-bit Aug 30, 2026
0a57d1d
Merge branch 'main' into fix/issues-235-249
oladev2026-tech Aug 30, 2026
7d5d87c
Merge remote-tracking branch 'pr/307' into integrate
godamongstmen897 Aug 30, 2026
ddd70f3
Merge remote-tracking branch 'pr/311' into integrate
godamongstmen897 Aug 30, 2026
2fb484b
Merge remote-tracking branch 'pr/316' into integrate
godamongstmen897 Aug 30, 2026
f412d44
Merge remote-tracking branch 'pr/317' into integrate
godamongstmen897 Aug 30, 2026
f67a447
Merge remote-tracking branch 'pr/321' into integrate
godamongstmen897 Aug 30, 2026
8e3bcb3
Merge remote-tracking branch 'pr/322' into integrate
godamongstmen897 Aug 30, 2026
dc6d3e6
Merge remote-tracking branch 'pr/359' into integrate
godamongstmen897 Aug 30, 2026
2b1c8fe
Merge remote-tracking branch 'pr/370' into integrate
godamongstmen897 Aug 30, 2026
8f591ca
Merge remote-tracking branch 'pr/371' into integrate
godamongstmen897 Aug 30, 2026
a6d162b
Merge remote-tracking branch 'pr/372' into integrate
godamongstmen897 Aug 30, 2026
4986fd7
Merge remote-tracking branch 'pr/373' into integrate
godamongstmen897 Aug 30, 2026
e9fb174
Merge remote-tracking branch 'pr/375' into integrate
godamongstmen897 Aug 30, 2026
d130389
Merge remote-tracking branch 'pr/376' into integrate
godamongstmen897 Aug 30, 2026
214c99a
Merge remote-tracking branch 'pr/377' into integrate
godamongstmen897 Aug 30, 2026
e7e4e1e
Merge remote-tracking branch 'pr/378' into integrate
godamongstmen897 Aug 30, 2026
b937167
Merge remote-tracking branch 'pr/379' into integrate
godamongstmen897 Aug 30, 2026
218a499
Merge remote-tracking branch 'pr/380' into integrate
godamongstmen897 Aug 30, 2026
6c5a348
Merge remote-tracking branch 'pr/384' into integrate
godamongstmen897 Aug 30, 2026
fcd2065
Merge remote-tracking branch 'pr/385' into integrate
godamongstmen897 Aug 30, 2026
63883d1
Merge remote-tracking branch 'pr/388' into integrate
godamongstmen897 Aug 30, 2026
1010107
fix: resolve #385 CORS conflict keeping both whitelist and build-tx g…
godamongstmen897 Aug 30, 2026
b211250
Merge remote-tracking branch 'pr/389' into integrate
godamongstmen897 Aug 30, 2026
236a1dd
Merge remote-tracking branch 'pr/390' into integrate
godamongstmen897 Aug 30, 2026
6350c60
Merge remote-tracking branch 'pr/392' into integrate
godamongstmen897 Aug 30, 2026
c0e1916
Merge remote-tracking branch 'pr/393' into integrate
godamongstmen897 Aug 30, 2026
cffcd48
Merge remote-tracking branch 'pr/394' into integrate
godamongstmen897 Aug 30, 2026
b9a3078
fix: union build-tx logger-trace and CORS suites; fix cp1252 en dash
godamongstmen897 Aug 30, 2026
e6eb673
fix: repair merge-spliced syntax in event_type_filter and vacuum cleaner
godamongstmen897 Aug 30, 2026
4a1383b
Merge branch 'main' into integrate-25-prs
godamongstmen897 Aug 30, 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
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ RATE_LIMIT_MAX_STRICT=10
# LEDGER_RANGE_FAILURE_THRESHOLD=3
# Stall window in ms since last successful tracker operation (default: 120000)
# LEDGER_RANGE_STALL_THRESHOLD_MS=120000

# Indexer RPC exponential backoff retry (#249)
# Retry transient RPC failures (timeouts, connection resets, rate limits, 5xx)
# with a doubling backoff up to the configured max. Resets on success.
INDEXER_RPC_MAX_RETRIES=5
INDEXER_RPC_INITIAL_BACKOFF_MS=1000
INDEXER_RPC_BACKOFF_MULTIPLIER=2
INDEXER_RPC_MAX_BACKOFF_MS=30000
34 changes: 34 additions & 0 deletions __tests__/build-tx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,3 +646,37 @@ describe("POST /api/jobs/build-tx — Winston traces (#109)", () => {
expect(meta.contractId).toBe(VALID_BODY.contractId);
});
});
describe('POST /api/jobs/build-tx – CORS and security headers', () => {
it('rejects requests from unauthorized origins', async () => {
const res = await request(app)
.post('/api/jobs/build-tx')
.set('Origin', 'http://malicious.com')
.send({})
.expect(403);
expect(res.body).toEqual({
success: false,
error: 'Origin not allowed by CORS policy',
});
});

it('allows trusted origins and sets CORS response headers', async () => {
const res = await request(app)
.post('/api/jobs/build-tx')
.set('Origin', 'http://localhost:3000')
.send({}) // send empty body to trigger 400 validation error, but cors should pass first
.expect(400);
expect(res.headers['access-control-allow-origin']).toBe('http://localhost:3000');
expect(res.headers['access-control-allow-methods']).toContain('POST');
});

it('sets security headers', async () => {
const res = await request(app)
.post('/api/jobs/build-tx')
.set('Origin', 'http://localhost:3000')
.send({})
.expect(400);
expect(res.headers['x-frame-options']).toBe('DENY');
expect(res.headers['x-content-type-options']).toBe('nosniff');
});
});

148 changes: 148 additions & 0 deletions __tests__/create-job-draft-validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import request from "supertest";
import express from "express";
import { createJobDraftValidation } from "../src/middleware/create-job-draft-validation.js";

const VALID_CLIENT =
"GAODBHVR63Z56MVQRBEJSYM2H5423LJ4WAPUUBOFG4JYY72S6ROKVZRX";
const VALID_FREELANCER =
"GB5CRPXUGXZCG6BESL4CM4F3VUAGQGFNYNBHPBRJAGLXXSRYJSEGZHUV";
const VALID_ARBITER =
"GABNCQRZNTG6MMITD33VHFITKJZ5PSYW2XVEXMP52BSMTPLU7WORDQNT";
const VALID_TOKEN =
"CDD5WKK3WT3QVKXMXTJNDIXE4T73FK6GGXDSD6UTJAH6YYZU52SQ4MUH";

const VALID_BODY = {
client: VALID_CLIENT,
freelancer: VALID_FREELANCER,
arbiter: VALID_ARBITER,
token: VALID_TOKEN,
autoReleaseDays: 7,
milestones: [{ amount: "10000000" }],
};

const VALID_LEGACY_BODY = {
clientAddress: VALID_CLIENT,
freelancerAddress: VALID_FREELANCER,
arbiterAddress: VALID_ARBITER,
tokenAddress: VALID_TOKEN,
milestones: [{ amount: "10000000" }],
};

function buildApp() {
const app = express();
app.use(express.json());
app.post("/create-job-draft", createJobDraftValidation, (req, res) => {
res.status(200).json({ success: true, data: req.body });
});
return app;
}

describe("createJobDraftValidation middleware – reusable Zod handler", () => {
it("passes a valid modern-naming body through to the handler", async () => {
const res = await request(buildApp())
.post("/create-job-draft")
.send(VALID_BODY)
.expect(200);

expect(res.body.success).toBe(true);
expect(res.body.data.freelancer).toBe(VALID_FREELANCER);
});

it("passes a valid legacy *Address body through to the handler", async () => {
const res = await request(buildApp())
.post("/create-job-draft")
.send(VALID_LEGACY_BODY)
.expect(200);

expect(res.body.success).toBe(true);
expect(res.body.data.freelancerAddress).toBe(VALID_FREELANCER);
});

it("rejects a missing freelancer with a field validation error", async () => {
const { freelancer: _f, ...rest } = VALID_BODY;
const res = await request(buildApp())
.post("/create-job-draft")
.send(rest)
.expect(400);

expect(res.body.success).toBe(false);
expect(res.body.error).toBe("ValidationError");
expect(res.body.details).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "freelancer",
message: expect.stringMatching(/freelancer/i),
}),
]),
);
});

it("rejects a malformed milestone amount as a field validation error", async () => {
const res = await request(buildApp())
.post("/create-job-draft")
.send({ ...VALID_BODY, milestones: [{ amount: "not-a-number" }] })
.expect(400);

expect(res.body.success).toBe(false);
expect(res.body.error).toBe("ValidationError");
expect(res.body.details).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "milestones",
message: expect.stringMatching(/amount/i),
}),
]),
);
});

it("rejects an invalid token contract address in the modern variant", async () => {
const res = await request(buildApp())
.post("/create-job-draft")
.send({ ...VALID_BODY, token: "not-a-contract-id" })
.expect(400);

expect(res.body.details).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "token",
message: expect.stringMatching(/contract address/i),
}),
]),
);
});

it("rejects an invalid arbiter address in the legacy variant", async () => {
const res = await request(buildApp())
.post("/create-job-draft")
.send({ ...VALID_LEGACY_BODY, arbiterAddress: "GSHORT" })
.expect(400);

expect(res.body.success).toBe(false);
expect(res.body.error).toBe("ValidationError");
expect(res.body.details).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "arbiterAddress",
message: expect.stringMatching(/valid Stellar account address/i),
}),
]),
);
});

it("rejects a missing tokenAddress in the legacy variant", async () => {
const { tokenAddress: _t, ...rest } = VALID_LEGACY_BODY;
const res = await request(buildApp())
.post("/create-job-draft")
.send(rest)
.expect(400);

expect(res.body.details).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "tokenAddress",
message: expect.stringMatching(/tokenAddress/i),
}),
]),
);
});
});
Loading
Loading