Skip to content
Open
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
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
coverage
*.log
site/dist
backend/node_modules
site/node_modules
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": false,
"trailingComma": "es5",
"printWidth": 100
}
236 changes: 114 additions & 122 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,158 +8,150 @@ dotenv.config();
const app = express();
const port = 3000;
// 1. Configure Airtable
const base = new Airtable({ apiKey: process.env.AIRTABLE_KEY }).base(
process.env.AIRTABLE_BASE_ID,
);
const base = new Airtable({ apiKey: process.env.AIRTABLE_KEY }).base(process.env.AIRTABLE_BASE_ID);

app.use(cors());

const CACHE_TTL = process.env.NODE_ENV === "production" ? 2 * 60 * 1000 : 0;
const cache = {};

function getCached(key) {
const entry = cache[key];
if (entry && Date.now() - entry.timestamp < CACHE_TTL) {
return entry.data;
}
return null;
const entry = cache[key];
if (entry && Date.now() - entry.timestamp < CACHE_TTL) {
return entry.data;
}
return null;
}

function setCache(key, data) {
cache[key] = { data, timestamp: Date.now() };
cache[key] = { data, timestamp: Date.now() };
}

app.get("/api/stats/alltime", async (req, res) => {
const cached = getCached("alltime");
if (cached) return res.json(cached);
const cached = getCached("alltime");
if (cached) return res.json(cached);

let total_weight = 0;
let total_prints = 0;
let total_printers = 0;
let total_countries = 0;
let total_weight = 0;
let total_prints = 0;
let total_printers = 0;
let total_countries = 0;

// fetch all time prints
try {
const records = await base(process.env.AIRTABLE_PRINT_TABLE_ID)
.select({
fields: ["weight_grams"], // Your actual field names
})
.all();
records.forEach((record) => {
total_prints++;
total_weight += record.get("weight_grams") || 0;
});
total_spools_reimbursed = total_weight / 750; // spools are reimbursed every 750g
} catch (error) {
console.error("Error fetching all-time stats:", error);
res.status(500).json({ error: "Failed to fetch all-time stats" });
}
// fetch all time prints
try {
const records = await base(process.env.AIRTABLE_PRINT_TABLE_ID)
.select({
fields: ["weight_grams"], // Your actual field names
})
.all();
records.forEach((record) => {
total_prints++;
total_weight += record.get("weight_grams") || 0;
});
total_spools_reimbursed = total_weight / 750; // spools are reimbursed every 750g
} catch (error) {
console.error("Error fetching all-time stats:", error);
res.status(500).json({ error: "Failed to fetch all-time stats" });
}

try {
const records = await base(process.env.AIRTABLE_PRINTER_TABLE_ID)
.select({
fields: ["Country"], // Your actual field names
})
.all();
const countriesSet = new Set();
records.forEach((record) => {
total_printers++;
const country = record.get("Country");
if (country) {
countriesSet.add(country);
}
});
total_countries = countriesSet.size;
} catch (error) {
console.error("Error fetching printer stats:", error);
}
const data = { total_prints, total_weight, total_spools_reimbursed, total_countries };
setCache("alltime", data);
res.json(data);
try {
const records = await base(process.env.AIRTABLE_PRINTER_TABLE_ID)
.select({
fields: ["Country"], // Your actual field names
})
.all();
const countriesSet = new Set();
records.forEach((record) => {
total_printers++;
const country = record.get("Country");
if (country) {
countriesSet.add(country);
}
});
total_countries = countriesSet.size;
} catch (error) {
console.error("Error fetching printer stats:", error);
}
const data = { total_prints, total_weight, total_spools_reimbursed, total_countries };
setCache("alltime", data);
res.json(data);
});

app.get("/api/stats/leaderboard", async (req, res) => {
const cached = getCached("leaderboard");
if (cached) return res.json(cached);
const cached = getCached("leaderboard");
if (cached) return res.json(cached);

let leaderboard_position = 1;
try {
const records = await base(process.env.AIRTABLE_PRINTER_TABLE_ID)
.select({
fields: [
"slack_id",
"Display Name",
"total_grams",
"Profile Picture",
"total_prints",
],
view: "Leaderboard",
})
.all();
const leaderboard = records.map((record) => ({
position: leaderboard_position++,
slack_id: record.get("slack_id"),
nickname: record.get("Display Name"),
total_grams: record.get("total_grams") || 0,
total_prints: record.get("total_prints") || 0,
profile_pic: record.get("Profile Picture")[0]?.url,
}));
setCache("leaderboard", leaderboard);
res.json(leaderboard);
} catch (error) {
console.error("Error fetching leaderboard stats:", error);
res.status(500).json({ error: "Failed to fetch leaderboard stats" });
}
let leaderboard_position = 1;
try {
const records = await base(process.env.AIRTABLE_PRINTER_TABLE_ID)
.select({
fields: ["slack_id", "Display Name", "total_grams", "Profile Picture", "total_prints"],
view: "Leaderboard",
})
.all();
const leaderboard = records.map((record) => ({
position: leaderboard_position++,
slack_id: record.get("slack_id"),
nickname: record.get("Display Name"),
total_grams: record.get("total_grams") || 0,
total_prints: record.get("total_prints") || 0,
profile_pic: record.get("Profile Picture")[0]?.url,
}));
setCache("leaderboard", leaderboard);
res.json(leaderboard);
} catch (error) {
console.error("Error fetching leaderboard stats:", error);
res.status(500).json({ error: "Failed to fetch leaderboard stats" });
}
});

app.get("/api/printers", async (req, res) => {
const cached = getCached("printers");
if (cached) return res.json(cached);
const cached = getCached("printers");
if (cached) return res.json(cached);

try {
const records = await base(process.env.AIRTABLE_PRINTER_TABLE_ID)
.select({
fields: [
"slack_id",
"Display Name",
"Profile Picture",
"website",
"Bio",
"Country",
"total_prints",
"total_grams",
],
view: "Leaderboard_inclusive",
})
.all();
const printers = records.map((record) => ({
slack_id: record.get("slack_id"),
nickname: record.get("Display Name"),
profile_pic: record.get("Profile Picture")[0]?.url, // First attachment URL
website: record.get("website"),
bio: record.get("Bio"),
country: record.get("Country"),
total_prints: record.get("total_prints") || 0,
total_grams: record.get("total_grams") || 0,
}));
setCache("printers", printers);
res.json(printers);
} catch (error) {
console.error("Error fetching printers:", error);
res.status(500).json({ error: "Failed to fetch printers" });
}
try {
const records = await base(process.env.AIRTABLE_PRINTER_TABLE_ID)
.select({
fields: [
"slack_id",
"Display Name",
"Profile Picture",
"website",
"Bio",
"Country",
"total_prints",
"total_grams",
],
view: "Leaderboard_inclusive",
})
.all();
const printers = records.map((record) => ({
slack_id: record.get("slack_id"),
nickname: record.get("Display Name"),
profile_pic: record.get("Profile Picture")[0]?.url, // First attachment URL
website: record.get("website"),
bio: record.get("Bio"),
country: record.get("Country"),
total_prints: record.get("total_prints") || 0,
total_grams: record.get("total_grams") || 0,
}));
setCache("printers", printers);
res.json(printers);
} catch (error) {
console.error("Error fetching printers:", error);
res.status(500).json({ error: "Failed to fetch printers" });
}
});

// Serve the frontend from the site/dist directory
app.use(express.static(path.resolve(__dirname, "../site/dist")));
app.get("/{*any}", (req, res, next) => {
// Only serve index.html for non-API, non-static requests
if (req.path.startsWith("/api/")) return next();
// Prevent directory traversal attacks
if (req.path.includes("..")) return res.status(400).send("Bad Request");
res.sendFile(path.resolve(__dirname, "../site/dist", "index.html"));
// Only serve index.html for non-API, non-static requests
if (req.path.startsWith("/api/")) return next();
// Prevent directory traversal attacks
if (req.path.includes("..")) return res.status(400).send("Bad Request");
res.sendFile(path.resolve(__dirname, "../site/dist", "index.html"));
});

app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
console.log(`Example app listening on port ${port}`);
});
7 changes: 6 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon index.js"
"dev": "nodemon index.js",
"format": "prettier . --write",
"check-format": "prettier . --check"
},
"keywords": [],
"author": "",
Expand All @@ -18,5 +20,8 @@
"express": "^5.1.0",
"nodemon": "^3.1.10",
"path": "^0.12.7"
},
"devDependencies": {
"prettier": "^3.9.4"
}
}
Loading