-
Notifications
You must be signed in to change notification settings - Fork 72
feat: add SEP-1 stellar.toml discovery #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
00cd3d8
a230bf9
aebaa00
749bf88
de51449
70075a6
9dabb73
1f8bcc6
926c236
bb8703b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,16 @@ const optionalEnvVars = [ | |
| "JITSI_KID", | ||
| "JITSI_TENANT", | ||
| "STELLAR_NETWORK", | ||
| "STELLAR_PLATFORM_PUBLIC_KEY", | ||
| "ORG_NAME", | ||
| "ORG_URL", | ||
| "ORG_DESCRIPTION", | ||
| "ORG_LOGO", | ||
| "ORG_GITHUB", | ||
| "ORG_TELEGRAM_URL", | ||
| "SIGNING_KEY", | ||
| "WEB_AUTH_ENDPOINT", | ||
| "TRANSFER_SERVER_SEP0024", | ||
| "DONATION_WALLET_PUBLIC_KEY", | ||
| "PLATFORM_FEE_PERCENT", | ||
| "PLATFORM_WALLET_PUBLIC_KEY", | ||
|
|
@@ -28,13 +38,37 @@ const optionalEnvVars = [ | |
| "JOBS_ENABLED", | ||
| "JOBS_DASHBOARD_TOKEN", | ||
| "EMAILJS_RECEIPT_TEMPLATE_ID", | ||
| // Redis configuration (optional - app works without Redis) | ||
| "REDIS_URL", | ||
| "REDIS_HOST", | ||
| "REDIS_PORT", | ||
| "REDIS_USERNAME", | ||
| "REDIS_PASSWORD", | ||
| "HORIZON_URLS", | ||
| "HORIZON_TIMEOUT_MS", | ||
| "HORIZON_MAX_RETRIES", | ||
| "HORIZON_CB_THRESHOLD", | ||
| "HORIZON_CB_COOLDOWN_MS", | ||
| ]; | ||
|
|
||
| export const validateEnv = () => { | ||
| // Default values for TTLs if not provided | ||
| process.env.ACCESS_TOKEN_TTL = process.env.ACCESS_TOKEN_TTL || "15m"; | ||
| process.env.REFRESH_TOKEN_TTL = process.env.REFRESH_TOKEN_TTL || "30d"; | ||
|
|
||
| // Default values for Horizon resilient client if not provided | ||
| const network = process.env.STELLAR_NETWORK || "testnet"; | ||
| if (!process.env.HORIZON_URLS) { | ||
| process.env.HORIZON_URLS = | ||
| network === "mainnet" | ||
| ? "https://horizon.stellar.org" | ||
| : "https://horizon-testnet.stellar.org"; | ||
|
Comment on lines
+64
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Keep Horizon connection strings in environment-backed configuration. These URLs are hardcoded in JavaScript even though As per path instructions: “Flag hardcoded secrets, connection strings, JWT secrets, or wallet keys; all configuration belongs in environment variables documented in .env.example.” 🤖 Prompt for AI AgentsSource: Path instructions |
||
| } | ||
| process.env.HORIZON_TIMEOUT_MS = process.env.HORIZON_TIMEOUT_MS || "10000"; | ||
| process.env.HORIZON_MAX_RETRIES = process.env.HORIZON_MAX_RETRIES || "3"; | ||
| process.env.HORIZON_CB_THRESHOLD = process.env.HORIZON_CB_THRESHOLD || "5"; | ||
| process.env.HORIZON_CB_COOLDOWN_MS = process.env.HORIZON_CB_COOLDOWN_MS || "30000"; | ||
|
Comment on lines
+59
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== Files ==\n'
git ls-files 'src/config/validateEnv.js' 'src/**/horizonClient.js' 'src/**/stellar*.js' 'src/**/stellar*.mjs' 'src/**/stellar*.ts' 'src/**/stellar*.cjs' | sed 's#^`#-` #'
printf '\n== validateEnv.js outline ==\n'
ast-grep outline src/config/validateEnv.js --view expanded || true
printf '\n== horizonClient candidates ==\n'
fd -a 'horizonClient\.(js|mjs|cjs|ts)$' src || true
printf '\n== search for imports/usages ==\n'
rg -n --hidden --glob 'src/**/*.js' 'horizonClient|validateEnv\(' src || true
printf '\n== src/config/validateEnv.js (selected lines) ==\n'
nl -ba src/config/validateEnv.js | sed -n '1,180p'Repository: Deen-Bridge/dnb-backend Length of output: 1509 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== src/config/validateEnv.js ==\n'
sed -n '1,220p' src/config/validateEnv.js
printf '\n== src/services/stellar/horizonClient.js ==\n'
sed -n '1,240p' src/services/stellar/horizonClient.js
printf '\n== app/bootstrap imports ==\n'
rg -n --hidden --glob 'src/**/*.js' 'validateEnv\(\)|from "./config/validateEnv\.js"|from ".*/horizonClient\.js"|from "./services/stellar/stellarService\.js"|from "./services/stellar/horizonClient\.js"' srcRepository: Deen-Bridge/dnb-backend Length of output: 10279 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== root js files ==\n'
git ls-files '*.js' | sed -n '1,200p'
printf '\n== startup-related imports ==\n'
rg -n --hidden --glob 'src/**/*.js' --glob '*.js' \
'validateEnv\(\)|from "\./config/validateEnv\.js"|from "\./src/config/validateEnv\.js"|from ".*/stellarService\.js"|from ".*/horizonClient\.js"|from "\./services/stellar/stellarService\.js"|from "\./services/stellar/horizonClient\.js"' \
. || true
printf '\n== likely entrypoints ==\n'
fd -a '^(app|server|index|main)\.js$' . || true
printf '\n== package scripts ==\n'
sed -n '1,220p' package.json 2>/dev/null || trueRepository: Deen-Bridge/dnb-backend Length of output: 6450 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== app.js ==\n'
sed -n '1,220p' app.js
printf '\n== server.js ==\n'
sed -n '1,220p' server.js
printf '\n== direct stellar imports from app boot path ==\n'
rg -n --hidden --glob 'app.js' --glob 'server.js' --glob 'src/**/*.js' \
'stellarService\.js|horizonClient\.js|validateEnv\.js|routes/stellar|controllers/stellar' \
app.js server.js src || trueRepository: Deen-Bridge/dnb-backend Length of output: 9633 Move env defaults before the Stellar client is instantiated
🤖 Prompt for AI Agents
Comment on lines
+67
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Validate Horizon tuning values before passing them through. Malformed values survive these defaults and are later parsed with 🤖 Prompt for AI Agents |
||
|
|
||
| const missing = []; | ||
|
|
||
| requiredEnvVars.forEach((envVar) => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { jest } from "@jest/globals"; | ||
| import { validateEnv } from "./validateEnv.js"; | ||
|
|
||
| describe("validateEnv", () => { | ||
| const originalEnv = process.env; | ||
|
|
||
| beforeEach(() => { | ||
| jest.resetModules(); | ||
| process.env = { | ||
| ...originalEnv, | ||
| MONGO_URI: "mongodb://localhost:27017/test", | ||
| JWT_SECRET: "test-secret-key-for-ci-minimum-32-chars", | ||
|
Comment on lines
+11
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Move test credentials and connection strings out of JavaScript. These lines embed 🤖 Prompt for AI AgentsSource: Path instructions |
||
| NODE_ENV: "test", | ||
| PORT: "5000", | ||
| }; | ||
| // Ensure new vars are unset | ||
| delete process.env.HORIZON_URLS; | ||
| delete process.env.HORIZON_TIMEOUT_MS; | ||
| delete process.env.HORIZON_MAX_RETRIES; | ||
| delete process.env.HORIZON_CB_THRESHOLD; | ||
| delete process.env.HORIZON_CB_COOLDOWN_MS; | ||
| }); | ||
|
|
||
| afterAll(() => { | ||
| process.env = originalEnv; | ||
| }); | ||
|
|
||
| it("should derive testnet default endpoint when STELLAR_NETWORK is unset or testnet", () => { | ||
| delete process.env.STELLAR_NETWORK; | ||
| validateEnv(); | ||
| expect(process.env.HORIZON_URLS).toBe("https://horizon-testnet.stellar.org"); | ||
| expect(process.env.HORIZON_TIMEOUT_MS).toBe("10000"); | ||
| expect(process.env.HORIZON_MAX_RETRIES).toBe("3"); | ||
| expect(process.env.HORIZON_CB_THRESHOLD).toBe("5"); | ||
| expect(process.env.HORIZON_CB_COOLDOWN_MS).toBe("30000"); | ||
| }); | ||
|
|
||
| it("should derive mainnet default endpoint when STELLAR_NETWORK is mainnet", () => { | ||
| process.env.STELLAR_NETWORK = "mainnet"; | ||
| validateEnv(); | ||
| expect(process.env.HORIZON_URLS).toBe("https://horizon.stellar.org"); | ||
| }); | ||
|
|
||
| it("should preserve explicitly set Horizon values", () => { | ||
| process.env.HORIZON_URLS = "https://custom.stellar.org"; | ||
| process.env.HORIZON_TIMEOUT_MS = "5000"; | ||
| process.env.HORIZON_MAX_RETRIES = "1"; | ||
| process.env.HORIZON_CB_THRESHOLD = "10"; | ||
| process.env.HORIZON_CB_COOLDOWN_MS = "10000"; | ||
| validateEnv(); | ||
| expect(process.env.HORIZON_URLS).toBe("https://custom.stellar.org"); | ||
| expect(process.env.HORIZON_TIMEOUT_MS).toBe("5000"); | ||
| expect(process.env.HORIZON_MAX_RETRIES).toBe("1"); | ||
| expect(process.env.HORIZON_CB_THRESHOLD).toBe("10"); | ||
| expect(process.env.HORIZON_CB_COOLDOWN_MS).toBe("10000"); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,43 +17,76 @@ import { | |||||||||||||||||||||||
| removeBookBookmark, | ||||||||||||||||||||||||
| } from "../../controllers/books/bookmarkBookController.js"; | ||||||||||||||||||||||||
| import { protect } from "../../middlewares/authMiddleware.js"; | ||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||
| cacheMiddleware, | ||||||||||||||||||||||||
| invalidateCacheMiddleware, | ||||||||||||||||||||||||
| } from "../../middlewares/cache.js"; | ||||||||||||||||||||||||
| import { CACHE_TTL, CACHE_KEYS } from "../../utils/cache.js"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const router = express.Router(); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // creating book | ||||||||||||||||||||||||
| // Cache key generators | ||||||||||||||||||||||||
| const booksListCacheKey = () => `${CACHE_KEYS.BOOKS}list`; | ||||||||||||||||||||||||
| const bookDetailCacheKey = (req) => `${CACHE_KEYS.BOOK}${req.params.id}`; | ||||||||||||||||||||||||
| const booksByAuthorCacheKey = (req) => | ||||||||||||||||||||||||
| `${CACHE_KEYS.BOOKS}author:${req.params.authorId}`; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // creating book - invalidates books list cache | ||||||||||||||||||||||||
| router.post( | ||||||||||||||||||||||||
| "/", | ||||||||||||||||||||||||
| protect, | ||||||||||||||||||||||||
| upload.fields([ | ||||||||||||||||||||||||
| { name: "thumbnail", maxCount: 1 }, | ||||||||||||||||||||||||
| { name: "file", maxCount: 1 }, | ||||||||||||||||||||||||
| ]), | ||||||||||||||||||||||||
| invalidateCacheMiddleware([`${CACHE_KEYS.BOOKS}*`]), | ||||||||||||||||||||||||
| createBook | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
Comment on lines
35
to
44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Invalidate personalized recommendations when their source content changes.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| // getting all books | ||||||||||||||||||||||||
| router.get("/", getBooks); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // get recommended books for user | ||||||||||||||||||||||||
| router.get("/recom", fetchRecommendedBooks); | ||||||||||||||||||||||||
| // getting all books - cached for 15 minutes | ||||||||||||||||||||||||
| router.get("/", cacheMiddleware(CACHE_TTL.BOOKS, booksListCacheKey), getBooks); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // get recommended books for user - cached for 5 minutes | ||||||||||||||||||||||||
| router.get( | ||||||||||||||||||||||||
| "/recom", | ||||||||||||||||||||||||
| cacheMiddleware(CACHE_TTL.SHORT, () => `${CACHE_KEYS.BOOKS}recommended`), | ||||||||||||||||||||||||
| fetchRecommendedBooks | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Bookmarks (must come before dynamic :id routes) | ||||||||||||||||||||||||
| router.get("/bookmarks", protect, getBookmarkedBooks); | ||||||||||||||||||||||||
| router.post("/:bookId/bookmark", protect, toggleBookBookmark); | ||||||||||||||||||||||||
| router.get("/:bookId/bookmark/check", protect, checkIfBookBookmarked); | ||||||||||||||||||||||||
| router.delete("/:bookId/bookmark", protect, removeBookBookmark); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| //get books created by the author | ||||||||||||||||||||||||
| router.get("/by-author/:authorId", getBooksByAuthor); | ||||||||||||||||||||||||
| // get books created by the author - cached for 15 minutes | ||||||||||||||||||||||||
| router.get( | ||||||||||||||||||||||||
| "/by-author/:authorId", | ||||||||||||||||||||||||
| cacheMiddleware(CACHE_TTL.BOOKS, booksByAuthorCacheKey), | ||||||||||||||||||||||||
| getBooksByAuthor | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| //get a spefic book | ||||||||||||||||||||||||
| // get a specific book - cached for 15 minutes | ||||||||||||||||||||||||
| router.get("/:id/preview", protect, streamBookPreview); | ||||||||||||||||||||||||
| router.get("/:id", getBook); | ||||||||||||||||||||||||
| router.get( | ||||||||||||||||||||||||
| "/:id", | ||||||||||||||||||||||||
| cacheMiddleware(CACHE_TTL.BOOKS, bookDetailCacheKey), | ||||||||||||||||||||||||
| getBook | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // delete a book | ||||||||||||||||||||||||
| router.delete("/:id", deleteBook); | ||||||||||||||||||||||||
| // delete a book - invalidates book caches | ||||||||||||||||||||||||
| router.delete( | ||||||||||||||||||||||||
| "/:id", | ||||||||||||||||||||||||
| invalidateCacheMiddleware([`${CACHE_KEYS.BOOKS}*`, `${CACHE_KEYS.BOOK}*`]), | ||||||||||||||||||||||||
| deleteBook | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
Comment on lines
+78
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Require authentication before deleting a book.
Proposed fix router.delete(
"/:id",
+ protect,
invalidateCacheMiddleware([`${CACHE_KEYS.BOOKS}*`, `${CACHE_KEYS.BOOK}*`]),
deleteBook
);📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Path instructions |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| //review a book | ||||||||||||||||||||||||
| router.post("/:id/reviews", protect, addBookReview); | ||||||||||||||||||||||||
| // review a book - invalidates specific book cache | ||||||||||||||||||||||||
| router.post( | ||||||||||||||||||||||||
| "/:id/reviews", | ||||||||||||||||||||||||
| protect, | ||||||||||||||||||||||||
| invalidateCacheMiddleware([`${CACHE_KEYS.BOOK}*`]), | ||||||||||||||||||||||||
| addBookReview | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
Comment on lines
+84
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Invalidate every cached view affected by the mutation. These handlers clear only a subset of cached representations, leaving list, profile, or count responses stale until their TTL expires.
📍 Affects 4 files
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export default router; | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Reject unsupported
STELLAR_NETWORKvalues.The current ternary treats every value other than exactly
mainnet—including typos—as testnet. Since this setting controls the Horizon endpoint and Stellar discovery network, fail validation for values other thantestnetormainnetinstead of silently selecting the wrong network.As per path instructions,
STELLAR_NETWORKmust be configured astestnetormainnet.🤖 Prompt for AI Agents
Source: Path instructions