Skip to content
Merged
Show file tree
Hide file tree
Changes from 78 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
62fd037
feat: complete submission + admin review + OAuth + CI/CD
lxgy1024 May 29, 2026
adb5506
fix: set packageManager to npm in wrangler-action to avoid pnpm compa…
lxgy1024 May 30, 2026
471f76b
fix: add wrangler as devDependency so wrangler-action finds it pre-in…
lxgy1024 May 30, 2026
e3c5e84
fix: align API with apidocs.md contract + resolve all 20 code review …
lxgy1024 May 30, 2026
697d16c
docs: add Cloudflare API Token setup guide to DEPLOY.md
lxgy1024 May 31, 2026
c723fbc
fix: remove deprecated functions/, fix OAuth flow, UI accessibility, …
lxgy1024 May 31, 2026
96e1e07
chore: remove D1 bindings + add API_BASE env routing
lxgy1024 May 31, 2026
22a22e1
feat: Express+MySQL backend + fix OAuth state IP/UA binding (#2)
lxgy1024 Jun 1, 2026
1e06965
:recycle: refactor(code-review): fix Admin.tsx React hooks violation …
lxgy1024 Jun 1, 2026
2ba659f
:memo: chore(config): rename to config.toml.example, clean defaults
lxgy1024 Jun 1, 2026
9348326
:recycle: refactor: clean up types, eslint directives, rm CF leftovers
lxgy1024 Jun 2, 2026
a165f85
:fire: chore: remove unused @cloudflare/workers-types and lucide-react
lxgy1024 Jun 2, 2026
06b6464
:recycle: refactor: align with api.md spec — error codes, JWT, schema
lxgy1024 Jun 2, 2026
8d86cfd
:recycle: refactor(api): Phase 1 api.md compliance fixes
lxgy1024 Jun 2, 2026
a36eed9
:sparkles: feat(api): Phase 2-5 — missing endpoints + security
lxgy1024 Jun 2, 2026
ad6dc94
:sparkles: feat(ui): Phase 6 — MFA/step-up/redirectAfter for frontend
lxgy1024 Jun 2, 2026
8a7735b
:sparkles: feat(api): passkey, images, edit-requests, admin-mgmt
lxgy1024 Jun 7, 2026
a8d650f
:fire: chore: remove dev artifacts
lxgy1024 Jun 7, 2026
10ac733
:bug: fix(api): guard idempotency-key dup with 409 and rm dead commit
lxgy1024 Jun 8, 2026
893521e
:wrench: chore: fix typecheck script to actually check files
lxgy1024 Jun 11, 2026
cba323e
:bug: fix(webauthn): fix type assertions and getClientExtensionResults
lxgy1024 Jun 11, 2026
8fd2e10
:bug: fix(security): resolve TS errors in SettingsSecurity
lxgy1024 Jun 11, 2026
cbb0a33
:bug: fix(admin): extend Status type and fix client assertions
lxgy1024 Jun 11, 2026
c6123a5
:bug: fix(detail): add version fallback in setState callbacks
lxgy1024 Jun 11, 2026
63e883e
:bug: fix: add required reason/expectedVersion to admin write ops
lxgy1024 Jun 11, 2026
219cb50
:bug: fix(admin-edit-requests): adapt api.md 10.5 nested response
lxgy1024 Jun 11, 2026
8d8443b
:bug: fix(auth): distinguish admin vs reviewer roles in gating and nav
lxgy1024 Jun 11, 2026
979c3bc
:bug: fix(mfa): support recovery code input (14-char with dashes)
lxgy1024 Jun 11, 2026
64db6f7
:bug: fix(logout-all): use AuthContext.logoutAll with correct semantics
lxgy1024 Jun 11, 2026
e195bbd
:bug: fix: correct edit-request link path and wire Home search
lxgy1024 Jun 11, 2026
8c8d418
:bug: fix(auth): add re-entrancy guards for one-time token effects
lxgy1024 Jun 11, 2026
9cdf65a
:bug: fix(oauth): wire redirectAfter and expose registration error codes
lxgy1024 Jun 11, 2026
4de6524
:sparkles: feat(mfa): respect availableMethods in login flow
lxgy1024 Jun 11, 2026
5f36bbd
:bug: fix(oauthbinding): add provider to complete-binding, handle errors
lxgy1024 Jun 11, 2026
2d54b3f
:bug: fix(stepup-dialog): reset passkey ref on cancel, add retry btn
lxgy1024 Jun 11, 2026
22eb33c
:bug: fix(auth): add guards to admin sub-pages and fix skipRefresh
lxgy1024 Jun 11, 2026
1182feb
:sparkles: feat(navbar): add useAuth links and logout
lxgy1024 Jun 11, 2026
e5c6b25
:bug: fix: in_review withdraw, uploader try/catch, i18n keys
lxgy1024 Jun 11, 2026
efeedf1
:bug: fix: race condition guards in paginated fetches and idempotency…
lxgy1024 Jun 11, 2026
0f6f49e
:bug: fix(phase3): apply low-severity fixes L1-L18
lxgy1024 Jun 11, 2026
b355d08
:bug: fix(frontend): Register labels, Unicode, review, busy, file reset
lxgy1024 Jun 11, 2026
308d1f1
:sparkles: feat(sessions): add load-more pagination for sessions list
lxgy1024 Jun 11, 2026
7f1a7a8
:bug: fix: map VALIDATION_ERROR details and add toast banner
lxgy1024 Jun 11, 2026
b9aa8d2
:bug: fix(story): wire to real API, escape quotes, guard formatDate
lxgy1024 Jun 11, 2026
aea3405
:memo: docs: CSRF channel analysis + idempotency-key plan
lxgy1024 Jun 11, 2026
a794ed8
:bug: fix(api): extract CSRF and lift idempotency-key to intent layer
lxgy1024 Jun 11, 2026
476908e
:bug: fix(auth): clear CSRF on login, guard reason, fix effect deps
lxgy1024 Jun 11, 2026
2649ffc
:bug: fix(review): M8 skipRefresh, M7 reset, H2 delete btn, L18 i18n
lxgy1024 Jun 11, 2026
f8b09f4
:globe_with_meridians: build(same-origin): serve frontend from Express
lxgy1024 Jun 12, 2026
73abb79
:bug: fix(mail): use localhost URL in dev mode
lxgy1024 Jun 12, 2026
b224bc6
:wrench: chore: ignore config copy, clean up git
lxgy1024 Jun 12, 2026
508947c
:bug: fix(db): use query() over execute() for MySQL 8.4 LIMIT compat
lxgy1024 Jun 12, 2026
5ef02ad
:bug: fix(admin): admin role gates, boundAt column, self-grant guard
lxgy1024 Jun 12, 2026
828d01c
:bug: fix(auth): temp admin token, i18n msg, schema index
lxgy1024 Jun 12, 2026
d92f858
:bug: fix(auth): ensure temp admin user exists in DB for FK refs
lxgy1024 Jun 12, 2026
4a9648d
:bug: fix(admin): allow deleting published contributions
lxgy1024 Jun 12, 2026
183e88e
:bug: fix(admin): store and display hide/delete reason
lxgy1024 Jun 12, 2026
a05a972
:bug: fix(validation): accept null for optional review fields
lxgy1024 Jun 12, 2026
9f33d22
:bug: fix(admin): prevent self-revoke of roles
lxgy1024 Jun 12, 2026
5679eb5
:green_heart: ci: fix workflow secrets context error
lxgy1024 Jun 13, 2026
8fc243e
:green_heart: ci: fix pnpm version mismatch (9→11)
lxgy1024 Jun 13, 2026
c68e4a0
:green_heart: ci: remove pnpm version key, let action auto-detect
lxgy1024 Jun 13, 2026
12af31d
:green_heart: ci: replace pnpm action with corepack
lxgy1024 Jun 13, 2026
75c683f
:green_heart: ci: use npm install pnpm instead of corepack
lxgy1024 Jun 13, 2026
be41cd2
:green_heart: ci: fix step order — setup-node before pnpm
lxgy1024 Jun 13, 2026
0d48223
:green_heart: ci: restore pnpm action without version key
lxgy1024 Jun 13, 2026
9d921d5
:green_heart: ci: use pnpm action with exact version 11.4.0
lxgy1024 Jun 13, 2026
89246aa
:green_heart: ci: remove functions from root tsconfig refs
lxgy1024 Jun 13, 2026
f1f3ae6
:bug: fix(review4): issues 1-6, 10-12 from report #4
lxgy1024 Jun 14, 2026
22174f7
:bug: fix(review4): items 7-9 — votes display, story, zh-TW
lxgy1024 Jun 14, 2026
becb805
:bug: fix(review4): item 13 + VERSION_CONFLICT recovery
lxgy1024 Jun 14, 2026
c065fd4
:bug: fix(review4): L1 L7 L8 L14 — rate limit, unicode, details, signal
lxgy1024 Jun 14, 2026
3b38520
:bug: fix(risk): navbar anchors, i18n auto-detect, css fallback
lxgy1024 Jun 14, 2026
53a9731
:bug: fix: resolve report4 issues
lxgy1024 Jun 14, 2026
06f2655
:bug: fix(frontend): resolve 6 issues from issue-report-4
lxgy1024 Jun 15, 2026
f225e78
:bug: fix(frontend): fix passkey id, lang switcher, rate-limit toast
lxgy1024 Jun 16, 2026
e8b36e7
:bug: fix(frontend): resolve all issues from problem report #5
lxgy1024 Jun 17, 2026
bc7a41b
:bug: fix(frontend): race in search expand, TOTP guard, XSS sanitizer
lxgy1024 Jun 18, 2026
29391c7
:bug: fix(frontend): resolve issues from problem report #7
lxgy1024 Jun 18, 2026
4cde65d
:shield: fix(frontend): add permission gating and Unicode truncation
lxgy1024 Jun 18, 2026
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
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
106 changes: 106 additions & 0 deletions .git-hooks/commit-msg.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env node

/**
* commit-msg hook: validates commit message against project convention.
*
* Format: `:<emoji>: <type>(<scope>): <subject>`
* Example: `:sparkles: feat(auth): add OAuth login support`
*
* Rules:
* - Must start with :<emoji-name>:
* - Must contain <type>(<scope>): <subject>
* - type must be one of the known types
* - subject must be lowercase, no period at end
* - First line max 72 characters
* - No Chinese characters in the first line
*/

import { readFileSync } from 'node:fs';

const COMMIT_TYPES = [
'feat', 'fix', 'docs', 'style', 'refactor', 'perf',
'test', 'build', 'ci', 'chore', 'revert',
'security', 'a11y', 'i18n', 'release', 'hotfix',
];

const EMOJI_PATTERN = /^:[a-z0-9_+-]+:\s+/;
const FORMAT_PATTERN = /^:[a-z0-9_+-]+:\s+([a-z]+)(\([a-z0-9_-]+\))?(!)?:\s+.+/;

function validate(filePath) {
const content = readFileSync(filePath, 'utf-8');
const lines = content.split('\n').filter(Boolean);

if (lines.length === 0) {
error('提交信息不能为空');
}

const firstLine = lines[0];
const restLines = lines.slice(1);

// Check first line length
if (firstLine.length > 72) {
error(`提交信息第一行超过 72 个字符(当前 ${firstLine.length})`);
}

// Check emoji prefix
if (!EMOJI_PATTERN.test(firstLine)) {
error(`提交信息必须以 :emoji: 开头,如 ':sparkles: feat(auth): xxx'`);
}

// Check format
const match = firstLine.match(FORMAT_PATTERN);
if (!match) {
error(
`提交信息格式错误。\n` +
`格式: :<emoji>: <type>(<scope>): <subject>\n` +
`示例: :sparkles: feat(auth): add OAuth login support`,
);
}

if (match) {
const type = match[1];
if (!COMMIT_TYPES.includes(type)) {
error(
`未知的 type "${type}"。\n` +
`允许的值: ${COMMIT_TYPES.join(', ')}`,
);
}
}

// Check no Chinese characters in first line
const chinesePattern = /[一-鿿㐀-䶿]/;
if (chinesePattern.test(firstLine)) {
error('提交信息第一行不能包含中文字符,请使用英文');
}

// Check no trailing period on first line
if (firstLine.endsWith('.')) {
error('提交信息第一行不能以句号结尾');
}

// body lines should be wrapped at 72 characters
for (let i = 0; i < restLines.length; i++) {
const line = restLines[i];
// Skip blank lines and comment lines
if (line === '' || line.startsWith('#')) continue;
if (line.length > 72) {
error(`第 ${i + 2} 行超过 72 个字符(当前 ${line.length})`);
}
}

process.exit(0);
}

function error(msg) {
console.error(`\n❌ 提交信息格式检查未通过:\n ${msg}\n`);
console.error(
'正确的格式: :<emoji>: <type>(<scope>): <subject>\n' +
'示例:\n' +
' :sparkles: feat(auth): add OAuth login support\n' +
' :bug: fix(navbar): resolve mobile overflow\n' +
' :recycle: refactor(utils): extract date formatting\n',
);
process.exit(1);
}

validate(process.argv[2]);
135 changes: 135 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: Deploy Frontend + Backend

on:
push:
branches: [main]

concurrency:
group: deploy-${{ github.ref_name }}
cancel-in-progress: true

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/transcircle-backend

jobs:
frontend:
name: Build & Deploy Frontend
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 11.4.0

- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'

- run: pnpm install --frozen-lockfile

- run: pnpm run build

- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=transcircle-submit --branch=${{ github.ref_name }}

backend:
name: Build & Push Backend Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 11.4.0

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Enable pnpm via corepack
run: |
corepack enable
corepack prepare pnpm@11.4.0 --activate

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install & build backend
working-directory: ./functions
run: |
pnpm install
pnpm run build

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix={{branch}}-

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: ./functions
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

db-migrate:
name: Database Migration
runs-on: ubuntu-latest
needs: [backend]

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Enable pnpm via corepack
run: |
corepack enable
corepack prepare pnpm@11.4.0 --activate

- name: Run database setup
working-directory: ./functions
env:
DB_CONFIG__DB_HOSTNAME: ${{ secrets.DB_HOST }}
DB_CONFIG__DB_PORT: ${{ secrets.DB_PORT || '3306' }}
DB_CONFIG__DB_USERNAME: ${{ secrets.DB_USER }}
DB_CONFIG__DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_CONFIG__DB_DATABASE: ${{ secrets.DB_NAME || 'transcircle' }}
run: |
pnpm install
pnpm run db:setup
44 changes: 44 additions & 0 deletions .github/workflows/story-rebuild.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Rebuild Story Site

on:
repository_dispatch:
types: [story-rebuild]
# Allow manual trigger for testing
workflow_dispatch:
inputs:
api_base:
description: 'API base URL to fetch published stories'
required: true
default: 'https://api.transcircle.org'

env:
# Default API base — overridden by dispatch payload or workflow_dispatch input
API_BASE: ${{ github.event.client_payload.api_base || github.event.inputs.api_base || 'https://api.transcircle.org' }}

jobs:
build-and-deploy:
name: Build & Deploy Story Site
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write

steps:
- uses: actions/checkout@v4

# ── Fetch published submissions from the API ──
- name: Fetch published stories
run: |
mkdir -p story/data
# API returns { data: [...], requestId: "..." } — extract the array
RESPONSE=$(curl -sSf "${{ env.API_BASE }}/v1/stories/published" -H "Accept: application/json" 2>/dev/null || echo '{"data":[]}')
echo "$RESPONSE" | jq '.data // []' > story/data/submissions.json
echo "Story entries: $(jq 'length' story/data/submissions.json)"

# ── Deploy to Cloudflare Pages (separate project for story site) ──
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy story --project-name=transcircle-story --branch=${{ github.ref_name }}
28 changes: 28 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
node_modules/
dist/
.wrangler/
.dev.vars
story/.wrangler/

# build outputs
*.tsbuildinfo
functions/dist/

# logs
functions/logs/

# local config
functions/config.toml
config.toml

# local env
.env
.env.local

# generated lockfile (functions is standalone, lockfile created by CI)
functions/pnpm-lock.yaml

# dev/plan documents
docs/superpowers/
*.toml
.jwt-keys/
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
prefer-offline=true
registry=https://registry.npmmirror.com/
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"tabWidth": 2,
"arrowParens": "always"
}
Loading