BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2 或任何藍牙控制器,搭配 MyWhoosh、Rouvy、Zwift 等。iOS、Android、Windows、macOS 均可免費試用。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
Production preparation and recovery procedures are in Launch readiness, Render deployment, and Operator runbook. The launch document records which requirements (hosting, SMTP, backups, monitoring) are in use and which still need verification.
Live at https://playpass.gotchadinkclub.com (pilot started October 2026).查看完整 README ↗Production preparation and recovery procedures are in Launch readiness, Render deployment, and Operator runbook. The launch document records which requirements (hosting, SMTP, backups, monitoring) are in use and which still need verification.
Live at https://playpass.gotchadinkclub.com (pilot started October 2026).
Server-rendered Django loyalty application for Got Cha Dink Club OpenPlay sessions. Members sign up, manage their profile and registrations, submit attendance for admin review, and view loyalty progress, rewards, streaks, and badges. Staff can run rosters, assist signed-up members, complete or cancel sessions, and redeem rewards. Admins approve or decline submitted attendance. The attendance ledger and loyalty engine remain the source of truth.
Phase 6 adds password recovery, timezone safeguards, role-change audit in Django admin, production configuration checks, and an operator runbook. Hosting, SMTP, monitoring, and backups are set up on the hosting providers, not in this repository; see the launch document for their current status.
uv.lockThe project was verified on Windows 10 with Python 3.14.7, Django 6.1.1, psycopg 3.3.6, and PostgreSQL 18.6. Use uv sync --locked to reproduce the Python dependencies. PostgreSQL runs separately and is never replaced with SQLite.
Install PostgreSQL 18 or download the Windows binary archive linked from the PostgreSQL Windows downloads page. A user-owned archive extraction works without a system service. The commands below assume its pgsql folder is at %LOCALAPPDATA%\gcdc-postgres\pgsql. Run these initialization commands once in PowerShell; initdb prompts for the local postgres password:
$pgRoot = Join-Path $env:LOCALAPPDATA 'gcdc-postgres'
& "$pgRoot\pgsql\bin\initdb.exe" -D "$pgRoot\data" -U postgres -A scram-sha-256 -W -E UTF8
& "$pgRoot\pgsql\bin\pg_ctl.exe" -D "$pgRoot\data" -l "$pgRoot\server.log" -o '-h 127.0.0.1 -p 55432' start
& "$pgRoot\pgsql\bin\createuser.exe" -h 127.0.0.1 -p 55432 -U postgres --pwprompt --createdb gcdc_app
& "$pgRoot\pgsql\bin\createdb.exe" -h 127.0.0.1 -p 55432 -U postgres -O gcdc_app gcdc_dev
The development role needs CREATEDB because Django creates an isolated test_gcdc_dev database for tests. A production application role should not have that privilege. On later local sessions, start the server with the pg_ctl ... start command; stop it with pg_ctl -D "$pgRoot\data" stop. An existing PostgreSQL installation is equally suitable: create a role and database, then put its connection details in .env.
From the repository root:
uv sync --locked
Copy-Item .env.example .env
Fill in .env with local values. Generate DJANGO_SECRET_KEY rather than copying a shared value:
python -c "import secrets; print(secrets.token_urlsafe(64))"
Required development variables are DJANGO_SECRET_KEY, DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, and DB_PORT. For the PostgreSQL example above, use database gcdc_dev, user gcdc_app, host 127.0.0.1, and port 55432, with the password chosen during createuser. DB_SSLMODE=disable is suitable for that loopback-only server. Development defaults to UTC if DJANGO_TIME_ZONE is unset; set it to the owner-confirmed club timezone, Asia/Manila, when checking club-local dates and times. The .env file is ignored by Git; .env.example contains names only.
The application reads process environment variables. uv run --env-file .env loads the local file without an extra application dependency. Direct python manage.py commands work if the same variables are already present in the shell.
uv run --locked --env-file .env python manage.py check
uv run --locked --env-file .env python manage.py migrate
uv run --locked --env-file .env python manage.py createsuperuser
uv run --locked --env-file .env python manage.py runserver
createsuperuser asks for an email and password. There is no username field. The Django admin at /admin/ remains for User and Group administration. Assign staff to the ORGANIZER or ADMIN Group there; is_staff alone does not grant organizer access. Members begin at /signup/ with their email, open the 30-minute verification link, then enter full name, contact number, ReClub name and profile link, and a password. Passwords need at least six letters or numbers, including at least one of each; symbols are allowed, while common and email-similar passwords remain blocked. No account is created until that form is completed. Local development writes verification email to ignored .local-emails/; production needs working SMTP. Members sign in at /login/. Staff use /organizer/; members use /player/. Signup never claims an existing walk-in record automatically; an admin must verify identity and link it. Existing sessions can be scheduled at /organizer/schedule/openplays/new/.
The health endpoint is GET /health/. It returns HTTP 200 with {"status":"ok"} when Django can query PostgreSQL and HTTP 503 with {"status":"unhealthy"} when the database query fails. It does not expose database connection details.
uv run --locked --env-file .env python manage.py test --settings=config.settings.test
uv run --locked --env-file .env python manage.py makemigrations --check --dry-run
uv run --locked --env-file .env python manage.py migrate --check
config.settings.test is development settings with a fast password hasher (the full suite then takes seconds instead of minutes); it is for tests only. The production-settings smoke test must still run on production settings so the real PBKDF2 login path stays covered (see the launch checklist). Tests create a temporary PostgreSQL database; the test suite asserts that it is connected to PostgreSQL. Project migrations create the custom User, Phase 2 domain models, and Phase 3 loyalty models. Data migrations seed the ORGANIZER and ADMIN groups and the initial loyalty configuration. Django's built-in applications supply their own migrations.
The CI release check runs on pull requests, pushes to main, and manual dispatch. It uses Python from .python-version, PostgreSQL 18, and synthetic settings to check uv.lock, install locked dependencies, verify and apply migrations, run the full suite, collect static assets, and run the production-settings smoke tests. The workflow has read-only repository permission and contains no deployment step. Its command sequence and YAML lint passed locally; the first GitHub-hosted run remains to be verified after an authorized push.
The club.services module is the supported entry point for creating Players and sessions, registrations, attendance submissions and reviews, session completion/cancellation, and ADMIN corrections. find_players() searches candidates but never links identities automatically. link_player_account() is an explicit ADMIN operation. An active account may access its own Player record; ORGANIZER and ADMIN are Django Groups seeded by migration.
Players submit attendance after an OpenPlay starts. The session choice shows its club-local date, Morning/Afternoon/Evening period, exact time, and venue so multiple sessions on one day remain distinct. A submission is PENDING until an ADMIN approves it; approval creates VALID Attendance. Declined submissions may be corrected and resubmitted. The current OpenPlays Attended count comes from openplays_attended(player): it counts VALID Attendance linked to a non-CANCELLED OpenPlay. Registration and pending submissions never count. Historical rows are retained, and important service mutations write AuditEvent records in the same transaction.
club.loyalty provides get_openplays_attended, get_loyalty_level, get_eligible_milestones, get_loyalty_summary, reconcile_player_loyalty, and redeem_reward. Levels use the OpenPlays Attended count after attendance approval. Reward eligibility uses only valid attendance on COMPLETED sessions. Approval on a completed session reconciles rewards immediately; approval before completion updates attendance progress, then completion evaluates rewards.
The seeded levels begin at 0/5/10/20/30. The seeded milestones are Dinker at 10 (₱50 discount), Dink Addict at 20 (₱100 discount), Court Regular at 30 (free OpenPlay), Got Cha Loyalist at 50 (free OpenPlay and recognition), Club Veteran at 75 (free OpenPlay and club perk), and Century Club at 100 (recognition and premium reward). Milestone benefits are configuration data; issuance captures a terms snapshot. Each milestone creates one bundled entitlement per Player. Redemption consumes that entitlement once; staff apply any discount, club item, or recognition operationally.
Completion, cancellation, and ADMIN attendance additions/voids/restorations reconcile affected Players in their transactions. Available rewards become REVOKED if completed attendance falls below the snapshotted threshold and return to AVAILABLE when it is restored. REDEEMED rewards remain redeemed; an eligibility discrepancy timestamp and audit event record a later shortfall. redeem_reward requires an ORGANIZER or ADMIN and records the OpenPlay selected by staff. No payment processing exists.
At /organizer/, open today's session. The roster shows registrations, pending submissions, and approved attendance. Staff may submit for an existing player only after the player has a linked account. Ask a new player to sign up at /signup/ first; a legacy unlinked walk-in needs admin identity verification and account linking. Submissions remain pending. ADMIN reviews them at /organizer/attendance-submissions/ using All, Pending approval, Approved, and Declined views; verify actual attendance before approving. A declined claim needs a reason. VOID attendance still needs an ADMIN correction.
After the scheduled end, review counts on the completion confirmation page and explicitly complete the session. This calls the existing transactional domain service and reconciles rewards. Session cancellation requires a reason. The player lookup shows derived attendance, level, reward status, and recent history. Staff redeem an AVAILABLE reward against a selected OpenPlay; ADMIN users can void or restore historical attendance with an audit reason.
Set DJANGO_TIME_ZONE=Asia/Manila before using the organizer screens: today's list, displayed session periods, and submission times depend on it. Development still defaults to UTC when this variable is unset. The interface is server-rendered with responsive CSS and works without JavaScript.
Members use /player/ for OpenPlays Attended, next configured milestone, current level, available rewards, current and best streak, and the First Rally and On a Roll badges. /player/openplays/ lists upcoming sessions and provides register/cancel actions before the scheduled start. A new player must sign up before submitting attendance. At /player/attendance/submit/, signed-in members choose a session; their saved ReClub details are included without reentry. /player/attendance/ shows pending, approved, declined, void, and cancelled history. /player/profile/ permits full name, phone, and ReClub edits; the login email stays on User.
Approval increases OpenPlays Attended; milestone rewards become available only after both approval and session completion. Current and best streak are derived from completed sessions: approved attendance extends the run, an active registration without valid attendance breaks it, and cancelled registrations, unregistered sessions, cancelled sessions, and incomplete sessions are neutral. Approved walk-in attendance counts. The two badges are derived from completed attendance and best streak, with no stored badge or revocation state; a historical correction can change current qualification.
Set DJANGO_SETTINGS_MODULE=config.settings.production in the hosting environment. Production requires explicit DJANGO_SECRET_KEY, database credentials and DB_SSLMODE, DJANGO_ALLOWED_HOSTS, HTTPS DJANGO_CSRF_TRUSTED_ORIGINS, a valid IANA DJANGO_TIME_ZONE, and SMTP settings. It rejects missing values and DJANGO_DEBUG=true; it forces HTTPS redirect and secure session/CSRF cookies. Set DJANGO_TRUST_PROXY_SSL=true only after verifying that the trusted proxy overwrites X-Forwarded-Proto. HSTS remains off until the real domain and every relevant subdomain have verified HTTPS. See the authoritative launch checklist and HSTS sequence.
The approved target is Render: one Python 3.14 WSGI web service and one paid Render PostgreSQL instance per environment, with separate staging and production resources in Singapore. Gunicorn and WhiteNoise are locked dependencies. Render's build collects files into staticfiles/; WhiteNoise serves /static/ from the WSGI service. The exact build, pre-deploy migration, start, health, and environment settings are in the Render deployment guide. Exact paid plans, production domain, staging sender address, and monitoring alert recipient still require explicit decisions. The approved club timezone is Asia/Manila, but it has not been configured on a hosted service. No Render service has been provisioned.
Local reset emails are written to ignored .local-emails/; production uses Resend SMTP after sender verification. A repeatable local restore check is uv run --locked --env-file .env python scripts/verify_local_restore.py. Render PITR, offsite logical backups, Better Stack alerts, and real SMTP delivery are not configured yet.
scripts/offsite_backup.py backup takes the same encrypted dump and uploads it to an S3-compatible bucket (Cloudflare R2) as daily/<file> and, on the 1st of the month, also monthly/<file>. It reads each upload back and compares the size, exits non-zero on any failure, and then pings BACKUP_HEARTBEAT_URL (a Better Stack heartbeat) so a missed or failed run raises an alert. Only the encrypted file leaves the host. It signs requests with AWS Signature V4 using the standard library (no extra dependency) and does not import Django, so the cron job needs only the variables below.
Variables (names only; values go in the host secret manager): DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_SSLMODE, BACKUP_ENCRYPTION_KEY, R2_ACCOUNT_ID, R2_BUCKET, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, BACKUP_HEARTBEAT_URL.
One-time Cloudflare setup: create a private R2 bucket; add lifecycle rules daily/ delete after 30 days and monthly/ delete after 365 days (this is where retention is enforced); create an R2 API token with Object Read & Write limited to that bucket. Keep BACKUP_ENCRYPTION_KEY in a password manager as well as the host: without it no backup can be restored.
Render Cron Job (same repository, branch, region, and environment as the database): build uv sync --locked --no-dev, command uv run --locked --no-sync python scripts/offsite_backup.py backup, schedule 0 18 * * * (02:00 Asia/Manila). Whether pg_dump 18 is available to the cron runtime must be confirmed with a manual run.
Restore drill (needs the same R2 variables and key): python scripts/offsite_backup.py fetch daily/<file>.dump.enc <new-local-file> then python scripts/verify_local_restore.py <new-local-file>. fetch refuses to overwrite a file or to take anything but a daily/ or monthly/ key.
scripts/backup_database.py runs pg_dump -Fc with the DB_* variables, encrypts the dump with Fernet (AES-128-CBC plus HMAC-SHA256, from the locked cryptography package), and writes backups/gcdc_<DB_NAME>_<UTC YYYYMMDDTHHMMSSZ>.dump.enc. No plaintext dump touches disk, an existing file is never overwritten, and a failed dump or encryption leaves no file. The whole dump is held in memory (fine at club scale). It needs pg_dump on PATH or PG_BIN, and a key that you keep outside the repository and the database host; without the key a backup cannot be restored. Generate it once with python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())", store it in a password manager, and set it as BACKUP_ENCRYPTION_KEY.
uv run --locked --env-file .env python scripts/backup_database.py # writes ./backups
uv run --locked --env-file .env python scripts/verify_local_restore.py backups\gcdc_gcdc_dev_20261008T030405Z.dump.enc
The second command decrypts the file, restores it into a new scratch database, runs check, migrate --check, and /health/, prints row counts to compare with the source, and drops only that scratch database. verify_local_restore.py with no argument runs the same path on synthetic data. This is not scheduled, off-site, or retained: see the launch checklist.