A ranking aggregation and leaderboard system for a daily scheduled post-timing competition game on Misskey
MidNight Network is a comprehensive web application for a game where participants join by posting notes on Misskey at a scheduled daily time. When a user posts a specific string, the system aggregates the post timestamps to rank and visualize results.
- 7-Tier (13 with intermediate) Ranking System — Point-based progression from unranked to the highest tier
- Automated Match Processing — Daily results aggregated from Misskey timeline via cron jobs
- Multiple Leaderboards — Rank points, win rate, average time, and match time
- User Profiles & Analytics — Detailed statistics with charts (earned points, heatmaps, radar, post time trends)
- Streak Tracking — Consecutive participation, within-zone, absence, and flying streaks with bonus points
- Border Protection — Demotion prevention system for players near rank boundaries
- Push Notifications — Match results delivered via Web Push API
- Privacy Controls — Granular visibility settings for leaderboards and profiles
| Layer | Technologies |
|---|---|
| Frontend | SvelteKit (Svelte 5), Vite, TanStack Query, TanStack Table, ECharts, Motion |
| Backend | Cloudflare Workers, Elysia, oRPC |
| Auth | Better Auth + MiAuth (Misskey) |
| Database | Cloudflare D1 (SQLite) + Prisma ORM |
| Shared | oRPC contracts, Zod schema validation |
| Rank Engine | Custom TypeScript calculation module |
| Code Quality | Biome (lint + format), Lefthook (git hooks) |
This is a pnpm workspace monorepo:
packages/
├── backend/ Cloudflare Workers API server (Elysia + Prisma + D1)
├── frontend/ SvelteKit web application (static adapter)
├── shared/ oRPC contracts & shared types
└── rank-calc/ Ranking calculation engine
- Node.js:
24^ - pnpm:
10^ - Cloudflare: Account (for D1, KV, R2, Workers, Queues)
- Misskey: Instance access and API token
git clone https://github.com/Secineralyr/midnight-network.git
cd midnight-networkpnpm install --frozen-lockfileCopy the backend template and fill in the required values:
cp packages/backend/.dev.vars.template packages/backend/.dev.vars| Variable | Description |
|---|---|
MK_HOST |
Misskey instance hostname |
API_TOKEN |
Misskey API token |
WEB_HOST |
Frontend hostname |
BACKEND_HOST |
Backend hostname |
BOT_USER_NAME |
Bot user name on Misskey |
DISABLE_USER_NAME |
User IDs to exclude (JSON array string[]) |
ADMIN_USER_NAME |
Admin user name |
WEBHOOK_SECRET |
Webhook secret key |
VAPID_PRIVATE_KEY |
VAPID private key for push notifications |
BETTER_AUTH_SECRET |
Secret key for auth session encryption (32+ chars) |
Frontend — Copy the template and configure:
cp packages/frontend/template.env packages/frontend/.env| Variable | Description |
|---|---|
VITE_API_ROOT |
Backend API URL (empty for same-origin) |
VITE_MOCK_AUTH |
Enable mock auth for development |
VITE_TARGET_HOUR |
Match target hour in UTC (default: 15) |
VITE_TARGET_MINUTES |
Match target minutes in UTC (default: 0) |
VITE_VAPID_PUBLIC_KEY |
VAPID public key for push notifications |
This step is required even if you are not developing, as it generates the Prisma types.
pnpm --filter @midnight-network/backend migrate:local# Frontend (port 5173)
pnpm front:dev
# Backend (port 8787)
pnpm back:devThe frontend dev server automatically proxies /api requests to the backend.
pnpm -r buildThe following items need to be defined in wrangler. It is recommended to split environments for the modified items.
The following items are primarily required for deployment.
routes: The backend allows api and webhook paths for the hostkv_namespaces: Replaceidwith the ID generated bypnpm wrangler kv namespace create <name>(note that two are required: SESSION and CACHE!)r2_buckets: Replacebucket_namewith the bucket name generated bypnpm wrangler r2 bucket create <bucket_name>queues(producers and consumers): Replacequeuewith the name generated bypnpm wrangler queues create <name>(note that producers and consumers must use the same queue!)d1_databases: Replacedatabase_nameanddatabase_idwith those generated bypnpm wrangler d1 create <name>
Additionally, edit the following items as needed:
| Variable | Description |
|---|---|
ENVIRONMENT |
Current environment; do not set to production outside of the production environment |
GAME_JOIN_POST_TEXT_REGEX |
Regex pattern for matching note text to aggregate |
TARGET_MATCH_HOUR |
Target aggregation hour in UTC |
TARGET_MATCH_MINUTES |
Target aggregation minutes in UTC |
POST_MATCH_RESULT_TITLE |
Title used when posting aggregation results as a note |
POST_MATCH_RESULT_URL |
Frontend URL included in the result note |
POST_MATCH_RESULT_TEMPLATE |
Result note text template (use curly braces for format placeholders: {title}: title / {ranks}: ranking (top 10) / {valid}: valid record count / {flying}: flying record count / {url}: URL) |
DISABLE_POST_MATCH_RESULT |
Disable posting result notes after aggregation |
POST_MATCH_REMIND_TEXT |
Reminder note text posted before the match |
DISABLE_WEBHOOK_BEFORE_TARGET_MATCH |
How long before the aggregation time to ignore webhook responses (ms) |
DISABLE_WEBHOOK_AFTER_TARGET_MATCH |
How long after the aggregation time to re-enable webhook responses (ms) |
VAPID_PUBLIC_KEY |
VAPID public key for push notifications |
DB_QUERY_LOGGING |
Log the executed query (for debug) |
There are also cron execution settings.
These exist because triggers.cron alone cannot control individual processes, so these definitions are used for that control.
Therefore, you must assign the cron trigger string you want to execute to the corresponding process.
| Variable | Description |
|---|---|
CRON_DAILY |
Aggregation process |
CRON_DAILY_REMIND |
Reminder post process |
When deploying the frontend with Cloudflare Workers, the following items need to be defined in wrangler. It is recommended to split environments for the modified items.
routes: Allow any path for the host
A Dev Container configuration is included for VS Code:
- Install the Dev Containers extension
- Open the project and select "Reopen in Container"
- Dependencies are installed automatically on first launch
- Configure
packages/backend/.dev.varsas described above - Start development with
pnpm front:dev/pnpm back:dev
Forwarded Ports:
| Port | Service |
|---|---|
5173 |
Frontend (SvelteKit dev server) |
8787 |
Backend (Wrangler dev server) |
| Command | Description |
|---|---|
pnpm install |
Install all dependencies |
pnpm check |
Run Biome lint + format check |
pnpm check:write |
Auto-fix lint and format issues |
pnpm typecheck |
Type-check all packages |
pnpm build |
Build all packages |
pnpm test |
Run all tests |
pnpm front:dev |
Start frontend dev server |
pnpm back:dev |
Start backend dev server |
| Command | Description |
|---|---|
pnpm --filter @midnight-network/backend dev |
Start Wrangler dev server |
pnpm --filter @midnight-network/backend build |
Build (generate types + compile) |
pnpm --filter @midnight-network/backend migrate:local |
Apply D1 migrations locally |
pnpm --filter @midnight-network/backend migrate:remote |
Apply D1 migrations to production |
pnpm --filter @midnight-network/backend deploy |
Deploy to Cloudflare Workers |
pnpm --filter @midnight-network/backend auth:gen |
Update Prisma definitions when Better Auth definitions change |
pnpm --filter @midnight-network/backend prisma:format |
Format Prisma files |
pnpm --filter @midnight-network/backend prisma:mig |
Write diff from Prisma definitions to migration file (may be inaccurate; manually defining is generally recommended) |
| Command | Description |
|---|---|
pnpm --filter @midnight-network/frontend dev |
Start Vite dev server |
pnpm --filter @midnight-network/frontend build |
Build static site |
pnpm --filter @midnight-network/frontend test |
Run Playwright E2E tests |
Automated CI and deployment workflows are configured in .github/workflows/.
Runs on pull requests:
- Lint & format check (Biome)
- Local D1 migration (for Prisma type generation)
- Build all packages
- Typecheck
- Tests
Runs automatically on push:
- Builds shared packages, backend, and frontend
- Replaces
wrangler.tomlwith the production/staging version stored in GitHub Secrets - Deploys backend and frontend to Cloudflare Workers via
wrangler deploy - Runs remote D1 migrations after backend deployment
- Frontend
VITE_*environment variables are injected at build time
| Secret | Description |
|---|---|
CLOUDFLARE_API_TOKEN |
Wrangler API token (Workers edit permission) |
CLOUDFLARE_ACCOUNT_ID |
Cloudflare account ID |
BACKEND_WRANGLER_TOML |
Full contents of the backend wrangler.toml for this environment |
FRONTEND_WRANGLER_TOML |
Full contents of the frontend wrangler.toml for this environment |
VITE_API_ROOT |
Frontend API root (empty for same-origin) |
VAPID_PUBLIC_KEY |
VAPID public key for push notifications |
D1_DB_NAME |
D1 database name |
Note: Worker Secrets (e.g. API_TOKEN, WEBHOOK_SECRET, BETTER_AUTH_SECRET) must be configured directly on Cloudflare (via Dashboard or wrangler secret put). They are not managed by the deployment workflow.
Deploy as a Cloudflare Worker
pnpm --filter @midnight-network/backend deploy
# Using environment-based deployment is recommended
# pnpm --filter @midnight-network/backend deploy --env <env>Required Cloudflare resources:
- D1 Database: Main data store
- KV Namespaces:
SESSION(auth sessions),CACHE(general cache) - R2 Bucket: Rank status snapshots
- Queue: Rerun job processing
Configure resource bindings in packages/backend/wrangler.toml.
The frontend is built as a static site using @sveltejs/adapter-static:
pnpm --filter @midnight-network/frontend buildOutput is generated in packages/frontend/dist/ and can be deployed to any static hosting service (Cloudflare Workers, Vercel, Netlify, etc.).
Deploying to Cloudflare Workers is generally recommended.
pnpm --filter @midnight-network/frontend deploy
# Using environment-based deployment is recommended
# pnpm --filter @midnight-network/frontend deploy --env <env>Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
The source code of this project is licensed under AGPL-3.0-or-later. See LICENSE for details.
However, images and assets (excluding manifest files) in the packages/frontend/static/ directory are not open source
and are provided under a proprietary license. See packages/frontend/static/LICENSE for details.
If you fork this project, you must replace the proprietary assets with your own.