diff --git a/.env.example b/.env.example index e9495bd..c96cd16 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,5 @@ VITE_SUPABASE_ANON_KEY=your-anon-key-here # --- Cloudflare Pages Functions only (never expose in client code) --- SUPABASE_URL=https://YOUR_PROJECT.supabase.co +SUPABASE_ANON_KEY=your-anon-key-here SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..914f28c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: Pull Request CI + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Check text health + run: npm run check:text + + - name: Lint + run: npm run lint + + - name: Test + run: npm test + + - name: Build + run: npm run build + + - name: Verify build output + run: npm run check:dist diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d30fa17..e982c53 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -45,6 +45,9 @@ jobs: - name: Build run: npm run build + - name: Verify build output + run: npm run check:dist + - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 32e91a2..eb3f1ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added +- Pull request CI for text health, lint, tests, production build, and required-page verification +- Production-path API handler tests and authenticated community signal contract coverage +- Dashboard production build entry and explicit missing-auth-configuration state +- `npm run check:dist` verification for all six public pages - Text health check script (`npm run check:text`) to catch mojibake regressions - Rate limiting to API (60 req/min/IP, Cloudflare-aware) - GET /api/vaults/:id - single node detail endpoint @@ -15,6 +19,9 @@ All notable changes to this project will be documented in this file. - Enhanced .gitignore (OS files, .vite/, supabase local) ### Changed +- Community signals for real nodes now require a verified Supabase user session and execute under RLS +- Demo community signals remain non-persisted and no longer attempt database writes +- API helper tests import the production implementation instead of copied functions - lib/auth.ts: fixed NEXT_PUBLIC_* env vars to VITE_* - API jsonResponse now accepts optional extraHeaders @@ -22,7 +29,7 @@ All notable changes to this project will be documented in this file. - pgvector semantic search integration - Frontend React migration from vanilla JS pages - WebSocket for real-time community signals -- CI/CD pipeline beyond GitHub Pages deploy +- Production data seeding and Cloudflare-native rate limiting ## [1.0.0] - 2026-06-10 diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md index a2f87f1..a8aef69 100644 --- a/DEPLOYMENT_GUIDE.md +++ b/DEPLOYMENT_GUIDE.md @@ -82,6 +82,7 @@ - Cloudflare Dashboard → Pages → `aiknowledgebank` → Settings → Environment variables - 添加: - `SUPABASE_URL` + - `SUPABASE_ANON_KEY` - `SUPABASE_SERVICE_ROLE_KEY` - `VITE_SUPABASE_URL` (如需浏览器侧登录) - `VITE_SUPABASE_ANON_KEY` (如需浏览器侧登录) @@ -137,6 +138,7 @@ ```env SUPABASE_URL=https://YOUR_PROJECT.supabase.co +SUPABASE_ANON_KEY=YOUR_ANON_KEY SUPABASE_SERVICE_ROLE_KEY=YOUR_SERVICE_ROLE_KEY VITE_SUPABASE_URL=https://YOUR_PROJECT.supabase.co VITE_SUPABASE_ANON_KEY=YOUR_ANON_KEY diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index a8e82d9..a6e3c70 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -19,7 +19,9 @@ Run these before committing user-facing or runtime changes: ```bash npm run check:text npm run lint +npm test npm run build +npm run check:dist ``` Use `npm run preview` after `npm run build` when visual inspection is needed. @@ -45,6 +47,7 @@ AI Knowledge Bank |-- tools.html # Tools Vault page |-- cases.html # Cases Vault page |-- community.html # Community validation page +|-- dashboard.html # Authentication, profile, and contribution dashboard |-- assets/ # Shared CSS, JS, and visual assets |-- functions/api/ # Cloudflare Pages Functions backend |-- lib/ # TypeScript utilities and workflow code @@ -61,20 +64,21 @@ AI Knowledge Bank - Phase 2: improved dedicated vault pages with readable fallbacks, filtering, node details, and community signals. - Phase 3: added Cross-Vault Dispatch panels to all dedicated vault pages. - Phase 4: added text health verification and documented the guardrail. +- Phase 5: restored the Dashboard build, secured authenticated community writes, and added production-path API and PR checks. ## Backlog High priority: -- Add focused API contract tests for `/api/search`, `/api/vaults`, and `/api/community-signals`. - Add a contribution flow for submitting knowledge, tool, and case candidates. - Add UI smoke checks for the dedicated vault pages. +- Publish the existing seed corpus so production vault reads no longer use fallback data. Medium priority: - Add a lightweight analytics dashboard for evolution signals. - Improve Supabase Realtime integration for community signals. -- Add authenticated authoring flows when credentials are available. +- Add authenticated authoring flows beyond community validation. Future: @@ -85,5 +89,5 @@ Future: ## Commit Expectations - Keep commits scoped to one phase or one bugfix. -- Run `npm run check:text`, `npm run lint`, and `npm run build` before pushing. +- Run `npm run check:text`, `npm run lint`, `npm test`, `npm run build`, and `npm run check:dist` before pushing. - Update `CHANGELOG.md` under `[Unreleased]` when adding visible behavior, scripts, docs, or deployment changes. diff --git a/README.md b/README.md index a98f75a..61f5d0b 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,9 @@ Build and verify / 构建与验证: ```bash npm run check:text npm run lint +npm test npm run build +npm run check:dist npm run preview ``` @@ -144,10 +146,11 @@ Then configure / 然后配置: VITE_SUPABASE_URL=your-project-url VITE_SUPABASE_ANON_KEY=your-anon-key SUPABASE_URL=your-project-url +SUPABASE_ANON_KEY=your-anon-key SUPABASE_SERVICE_ROLE_KEY=your-service-role-key ``` -`VITE_*` keys are for browser-side auth flows. `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` are Cloudflare Pages Function variables and must not be exposed in client code. +`VITE_*` keys are for browser-side auth flows. `SUPABASE_ANON_KEY` lets Pages Functions perform authenticated, user-scoped writes that respect RLS. `SUPABASE_SERVICE_ROLE_KEY` is reserved for trusted server-side reads and must never be exposed in client code. `VITE_*` 用于浏览器侧登录流程;`SUPABASE_URL` 和 `SUPABASE_SERVICE_ROLE_KEY` 用于 Cloudflare Pages Functions,不能写入前端代码。 @@ -163,7 +166,7 @@ Cloudflare Pages 通过 `functions/api/[[path]].js` 提供真实后端接口。G | `GET /api/vaults` | Three-vault grouped node data and stats | | `GET /api/vaults/:id` | Single node detail endpoint | | `GET /api/search?q=&locale=&limit=` | Cross-Vault RAG search contract returning knowledge, tools, and cases | -| `POST /api/community-signals` | Community validation, usage, fork, merge, comment, and dispute signals | +| `POST /api/community-signals` | Authenticated community signals for real nodes; node-less requests remain non-persisted demos | | `GET /api/leaderboard` | User ranking endpoint | Related docs / 相关文档: diff --git a/assets/vault-page.js b/assets/vault-page.js index 3f31095..c8babfd 100644 --- a/assets/vault-page.js +++ b/assets/vault-page.js @@ -1,3 +1,5 @@ +import { buildCommunitySignalRequest, fetchApi } from '../lib/shared/api.js'; + const pageType = document.body.dataset.page || 'knowledge'; const vaultKeyByPage = { knowledge: 'knowledge', @@ -91,6 +93,7 @@ const dictionaries = { validationPersisted: '验证信号已写入后端,节点演化权重已更新', validationQueued: '验证信号已进入演示队列', validationFailed: '后端暂不可用,已保留为本地演示状态', + validationAuthRequired: '请先在控制台登录,再提交真实社区验证。', panelTitleCommunity: '实时验证信号', panelCopyCommunity: '点击后会向现有 Cloudflare API 提交一次社区验证信号,用来模拟内容从个人经验进入公共演化循环。', footer: 'AI Knowledge Bank:AI 时代的公共知识银行。' @@ -180,6 +183,7 @@ const dictionaries = { validationPersisted: 'Validation signal written to backend; node evolution weight updated', validationQueued: 'Validation signal entered the demo queue', validationFailed: 'Backend unavailable; kept as local demo state', + validationAuthRequired: 'Sign in from the dashboard before submitting a real community validation.', panelTitleCommunity: 'Live Validation Signal', panelCopyCommunity: 'This sends one community validation signal to the existing Cloudflare API and simulates how personal practice enters the shared evolution loop.', footer: 'AI Knowledge Bank: Where AI experience grows together.' @@ -321,29 +325,6 @@ const dispatchRunButton = document.getElementById('dispatch-run'); const dispatchStatus = document.getElementById('dispatch-status'); const dispatchResults = document.getElementById('dispatch-results'); -function resolveApiBase() { - if (window.location.hostname.endsWith('github.io') || window.location.protocol === 'file:') { - return 'https://aiknowledgebank.pages.dev/api'; - } - - return `${window.location.origin}/api`; -} - -const apiBase = resolveApiBase(); - -async function fetchApi(path, options = {}) { - const headers = { ...(options.headers || {}) }; - if (options.body) headers['Content-Type'] = 'application/json'; - - const response = await fetch(`${apiBase}${path}`, { - ...options, - headers - }); - - if (!response.ok) throw new Error(`API request failed with ${response.status}`); - return response.json(); -} - function pageSuffix() { return pageType.charAt(0).toUpperCase() + pageType.slice(1); } @@ -823,10 +804,18 @@ async function submitCommunityValidation() { if (validationResult) validationResult.replaceChildren(); try { - const result = await fetchApi('/community-signals', { - method: 'POST', - body: JSON.stringify({ - node_id: latestNodes[0]?.id || null, + let accessToken = null; + if (latestSource === 'supabase' && latestNodes[0]) { + const { getCurrentUser } = await import('../lib/auth.ts'); + const session = await getCurrentUser(); + accessToken = session?.accessToken || null; + } + + const signalRequest = buildCommunitySignalRequest({ + source: latestSource, + node: latestNodes[0], + accessToken, + signal: { signal_type: 'validated', impact_delta: 12, confidence: 0.86, @@ -835,14 +824,25 @@ async function submitCommunityValidation() { language: currentLanguage, source: 'community-page' } - }) + } }); + if (signalRequest.requiresAuthentication) { + statusLine.textContent = dictionaries[currentLanguage].validationAuthRequired; + return; + } + + const result = await fetchApi('/community-signals', signalRequest.options); + statusLine.textContent = result.persisted ? dictionaries[currentLanguage].validationPersisted : dictionaries[currentLanguage].validationQueued; renderValidationResult(result); - } catch { + } catch (error) { + if (error?.status === 401) { + statusLine.textContent = dictionaries[currentLanguage].validationAuthRequired; + return; + } statusLine.textContent = dictionaries[currentLanguage].validationFailed; renderValidationResult({ ok: true, diff --git a/dashboard.html b/dashboard.html index d1f6602..023bdf8 100644 --- a/dashboard.html +++ b/dashboard.html @@ -10,6 +10,7 @@ + @@ -231,7 +232,7 @@ AI Knowledge Bank
-
@@ -260,6 +261,19 @@

Authentication Required

+ + +