Duplicates the team's Google Sheets matchup-matrix template once per
opposing team at a loaded event, pre-filled with names, factions,
career win% (all already computed by this page), and cleaned army
lists. New "Which team is yours?" selector + Generate Matrix button,
team-events only.
- Auth: Google Identity Services token client (drive.file +
spreadsheets scopes) — the viewer signs in with their own Google
account via a real OAuth popup, no backend, no client secret.
- Template layout is located at runtime by searching each tab for its
own placeholder text ("Player N - Faction", "Opponent N") rather
than hardcoded A1 ranges, so it survives the template changing later.
Distinguishes the real input area from the template's own "mobile
matrix" mirror and reference sections (which repeat the same
placeholder text) by checking for literal typed values vs formulas —
CSV export can't tell these apart, only the real Sheets API response
can, which is how this was actually verified before writing this.
Auto-picks between the template's 5-man/8-man tab variants by team
size.
- List cleaning via a vendored copy of desjani's 40k-compactor
(src/vendor/40k-compactor/, see NOTICE.md) — it isn't actually
published to npm despite documenting `npm install 40k-compactor`
(verified: 404 against the registry under every plausible name), so
it can't be a normal dependency; vendored instead, MIT per direct
confirmation from the maintainer. Verified against 40k-compactor's
own sample list fixtures with real Node before committing.
- Generating a matrix needs the same BCP auth token already used for
faction/disposition backfill (list text is a subscriber-gated
endpoint) — reuses the existing token UI/storage as-is; missing
lists are reported, not a hard failure.
- nginx CSP updated (script/connect/frame-src) for Google's identity
script and the Drive/Sheets REST APIs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
54 lines
2.2 KiB
Plaintext
54 lines
2.2 KiB
Plaintext
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name scouting.gateway-gamers.net;
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl;
|
|
listen [::]:443 ssl;
|
|
server_name scouting.gateway-gamers.net;
|
|
|
|
root /var/www/domains/gateway-gamers.net/scouting;
|
|
index index.html;
|
|
|
|
# SSL — managed by Certbot
|
|
ssl_certificate /etc/letsencrypt/live/scouting.gateway-gamers.net/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/scouting.gateway-gamers.net/privkey.pem;
|
|
# include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
|
|
# Hide nginx version
|
|
server_tokens off;
|
|
|
|
# Security headers
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
|
# style-src needs 'unsafe-inline': the app sets bar-chart widths via
|
|
# inline style="width:X%" through innerHTML, which CSP's style-src
|
|
# blocks without it (script-src stays locked down — this only affects
|
|
# CSS, and nothing here renders untrusted external content into a
|
|
# style attribute).
|
|
# script-src/connect-src/frame-src additions are for "Generate Matrix":
|
|
# Google Identity Services (accounts.google.com) for the sign-in popup,
|
|
# and the Drive/Sheets REST APIs the browser calls directly afterward.
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://accounts.google.com; style-src 'self' 'unsafe-inline'; connect-src 'self' https://newprod-api.bestcoastpairings.com https://www.googleapis.com https://sheets.googleapis.com https://accounts.google.com https://oauth2.googleapis.com; frame-src https://accounts.google.com; img-src 'self' data:;" always;
|
|
|
|
location /images/ {
|
|
alias /var/www/domains/gateway-gamers.net/shared/images/;
|
|
}
|
|
|
|
location = /footer.html {
|
|
alias /var/www/domains/gateway-gamers.net/shared/footer.html;
|
|
}
|
|
|
|
location / {
|
|
try_files $uri $uri/ =404;
|
|
}
|
|
}
|
|
|