Route Prefix: /admin
Access: Cloudflare Access (prod), token (dev)
Core flows
Dashboard
- Quick links to Events, Results, Import, Settings
- At-a-glance: next events, top floors, last import
Events management
- Search + filter by day/sport
- Create/edit: title, sport, description, location, start/end, capacity, form_url, source_label
- Links: “View public page” + “Open Google Form”
Results entry
- Pick event → enter floor rows
- Medal buttons auto-apply placement + points
- Confirmation before save
- Undo last save
CSV import
- Upload Google Forms CSV export
- Preview first rows before commit
- Store raw submissions + import run stats
- Optional delta mode:
/api/admin/import/csv?delta=1only inserts rows newer than the latest stored response timestamp - Cloudflare Access service tokens can be used for automated imports (Apps Script)
- Imports overwrite older submissions for the same room (latest wins).
Automated import (Apps Script)
const API_URL = "https://api.games.ihnyc-rc.org/api/admin/import/csv?delta=1";
const CF_ACCESS_CLIENT_ID = "YOUR_CF_ACCESS_CLIENT_ID";
const CF_ACCESS_CLIENT_SECRET = "YOUR_CF_ACCESS_CLIENT_SECRET";
const SHEET_ID = "YOUR_SHEET_ID";
const SHEET_NAME = "Form Responses 1";
function uploadResponses() {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
const values = sheet.getDataRange().getValues();
const csv = values
.map(row =>
row.map(cell => {
const s = String(cell ?? "");
if (s.includes('"') || s.includes(",") || s.includes("\n")) {
return `"${s.replace(/"/g, '""')}"`;
}
return s;
}).join(",")
)
.join("\n");
const res = UrlFetchApp.fetch(API_URL, {
method: "post",
contentType: "text/csv",
payload: csv,
headers: {
"CF-Access-Client-Id": CF_ACCESS_CLIENT_ID,
"CF-Access-Client-Secret": CF_ACCESS_CLIENT_SECRET,
},
muteHttpExceptions: true,
});
Logger.log(res.getResponseCode());
Logger.log(res.getContentText());
}
function createFiveMinuteTrigger() {
ScriptApp.newTrigger("uploadResponses")
.timeBased()
.everyMinutes(5)
.create();
}Submissions review
- Search by name/email/room
- Delete invalid rows
Admin protection
- Protect
/admin*with Cloudflare Access (email allowlist). - Protect
/api/admin*with Access and/orX-Admin-Token.