1
Fork 0
crypto-ticker/app.js
Leonardo Devai 17a890cc08 Review and modernize all 42 projects to the updated standard
Pinned 2026 toolchains (Go 1.26, Rust 1.98/edition 2024, Python 3.14 + uv, Node 24,
Zig 0.16, NixOS 26.05), postgres 18 / mongo 8, lockfiles built from, non-root
runtimes, .dockerignore, per-project LICENSE, READMEs with the git.devai.io clone
line, checkout@v7 CI. Security fixes in the legacy Rust APIs (any-password login,
self-assigned admin, hard-coded JWT secret), JWT alg/exp/sub enforcement across the
blog series, safe markdown links in the frontends, and many smaller bugs — every
project was built, run and exercised end to end.

Adds scripts/publish.sh + a CI publish job that splits every folder into its own
repo at git.devai.io/templates/<folder>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01128fhuZbgivaSJvtMf4s1G
2026-09-27 21:10:38 +02:00

82 lines
2.7 KiB
JavaScript

const rowsEl = document.getElementById("rows");
const statusEl = document.getElementById("status");
const updatedEl = document.getElementById("updated");
// `id` is the name CoinGecko uses for each coin in its URLs.
const COINS = [
{ id: "bitcoin", name: "Bitcoin", symbol: "BTC" },
{ id: "ethereum", name: "Ethereum", symbol: "ETH" },
{ id: "solana", name: "Solana", symbol: "SOL" },
{ id: "dogecoin", name: "Dogecoin", symbol: "DOGE" },
];
const ids = COINS.map((coin) => coin.id).join(",");
// Cheap coins get more decimals so they don't all read "$0.10".
const money = (n) =>
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: n < 1 ? 4 : 2,
}).format(n);
// POLLING: nothing is pushed to us, so we simply ask again on a timer. Each
// refresh() is one ordinary fetch; setInterval (at the bottom) is the heartbeat.
async function refresh() {
try {
const res = await fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd&include_24hr_change=true`,
);
// 429 = "too many requests": free APIs throttle you if you poll too often.
// Keep the last good prices on screen and try again on the next tick.
if (res.status === 429) {
statusEl.textContent = "Rate limited — showing the last prices. Retrying soon…";
return;
}
if (!res.ok) throw new Error(`CoinGecko returned ${res.status}`);
// { bitcoin: { usd: 84557, usd_24h_change: 0.57 }, ethereum: { … }, … }
const data = await res.json();
render(data);
statusEl.textContent = "";
updatedEl.textContent = "Last updated " + new Date().toLocaleTimeString();
} catch (err) {
statusEl.textContent = "Couldn't reach the price service. Retrying soon…";
console.error(err);
}
}
function el(tag, className, text = "") {
const node = document.createElement(tag);
node.className = className;
node.textContent = text;
return node;
}
function render(data) {
const rows = [];
for (const coin of COINS) {
const info = data[coin.id];
if (typeof info?.usd !== "number") continue; // skip anything the API didn't send
const name = el("div", "coin");
name.append(el("strong", "", coin.name), el("span", "", coin.symbol));
const change = el("div", "change", "—");
if (typeof info.usd_24h_change === "number") {
const up = info.usd_24h_change >= 0;
change.classList.add(up ? "up" : "down");
change.textContent = `${up ? "▲" : "▼"} ${Math.abs(info.usd_24h_change).toFixed(2)}%`;
}
const row = el("div", "row");
row.append(name, el("div", "price", money(info.usd)), change);
rows.push(row);
}
rowsEl.replaceChildren(...rows);
}
refresh();
setInterval(refresh, 30000);