Some checks failed
CD / Build and push images (push) Successful in 3m12s
CI / Lint, typecheck, test (push) Failing after 2m29s
CI / Auth e2e pack (push) Successful in 3m32s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 11s
Instance operators get basic user administration for support, abuse handling, and GDPR groundwork (security.md §Privacy). - api `admin/`: Site-Admin-gated `/admin/users` — a searchable, paginated list (username, e-mail, status, role, pond count, last login) plus lifecycle actions: disable/enable (a disabled user is logged out everywhere and login is refused with the distinct `account_disabled`), resend verification, delete, and grant/revoke Site Admin. Guards: you cannot act on your own account (`cannot_modify_self`) and the last Site Admin cannot be dropped (`last_site_admin`). Every action is audit-logged with the actor. - `PseudonymizationService`: account deletion scrubs the PII, removes all login identities + sessions, and trashes the personal pond — the kept row is what authorship references, so shared content the user authored shows as "Deleted user" (no orphaned/cascaded content). - web: the Admin area gains a 'Users' surface — search, pagination, and the actions (destructive ones behind an inline two-step confirm; self-actions hidden). New `users` i18n namespace (de+en). - tests: `user-admin.e2e.db.test.ts` (disable → logout + login blocked; delete → pseudonymized authorship + personal pond trashed + credentials gone; last Site Admin and self protected; Site-Admin gating); a non-destructive browser `admin-users` pack proving disable-in-UI blocks login and enable restores it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
304 lines
11 KiB
YAML
304 lines
11 KiB
YAML
# CI: every pull request and every push to main must pass these checks
|
|
# (ADR 0014). The deploy pipeline (CD) lives in cd.yml and only runs on
|
|
# main after this workflow's quality bar.
|
|
#
|
|
# Runner requirements: an act_runner with the `ubuntu-latest` label and
|
|
# Docker access (see deploy/stages.md, issue #9).
|
|
|
|
name: CI
|
|
|
|
on:
|
|
pull_request:
|
|
push:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
checks:
|
|
name: Lint, typecheck, test
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: postgres:17.5-alpine
|
|
env:
|
|
POSTGRES_USER: test
|
|
POSTGRES_PASSWORD: test
|
|
POSTGRES_DB: test
|
|
env:
|
|
# Enables the database-backed test suites (vitest.global-setup.ts).
|
|
TEST_DATABASE_URL: postgresql://test:test@postgres:5432/test
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# Build first: package type checks resolve @dorfteich/shared through
|
|
# its built dist, and i18n:check imports the built helpers.
|
|
- name: Build all packages
|
|
run: pnpm build
|
|
|
|
- name: Lint (ESLint + Prettier)
|
|
run: pnpm lint
|
|
|
|
- name: Typecheck
|
|
run: pnpm typecheck
|
|
|
|
- name: Unit and integration tests
|
|
run: pnpm test
|
|
|
|
- name: Translation key parity (de/en)
|
|
run: pnpm i18n:check
|
|
|
|
# Job id/display name kept stable (branch-protection/status-check
|
|
# matching uses the reported "CI / Auth e2e pack" context) even though
|
|
# it now also runs the content pack (issue #32) against the same
|
|
# already-built-and-seeded stack, instead of spinning up a second one.
|
|
auth-e2e:
|
|
name: Auth e2e pack
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: postgres:17.5-alpine
|
|
env:
|
|
POSTGRES_USER: e2e
|
|
POSTGRES_PASSWORD: e2e
|
|
POSTGRES_DB: e2e
|
|
mailpit:
|
|
image: axllent/mailpit:latest
|
|
env:
|
|
DATABASE_URL: postgresql://e2e:e2e@postgres:5432/e2e
|
|
APP_BASE_URL: http://localhost:5173
|
|
SMTP_HOST: mailpit
|
|
SMTP_PORT: '1025'
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build packages
|
|
run: pnpm build
|
|
|
|
- name: Apply migrations
|
|
run: pnpm --filter @dorfteich/api exec prisma migrate deploy
|
|
|
|
- name: Seed fixtures
|
|
run: pnpm --filter @dorfteich/api db:seed
|
|
|
|
- name: Start api, collab, and static web server
|
|
run: |
|
|
(cd apps/api && PORT=3001 node dist/main.js > /tmp/api.log 2>&1 &)
|
|
(cd apps/collab && PORT=3002 node dist/index.js > /tmp/collab.log 2>&1 &)
|
|
(PORT=5173 COLLAB_TARGET=http://127.0.0.1:3002 node scripts/e2e-static-server.mjs > /tmp/web.log 2>&1 &)
|
|
for i in $(seq 1 30); do
|
|
curl -sf http://localhost:3001/api/v1/readyz >/dev/null && break
|
|
sleep 2
|
|
done
|
|
for i in $(seq 1 30); do
|
|
curl -sf http://localhost:3002/healthz >/dev/null && break
|
|
sleep 2
|
|
done
|
|
curl -sf http://localhost:5173/ >/dev/null
|
|
|
|
- name: Install Playwright browser
|
|
run: pnpm --filter @dorfteich/web exec playwright install --with-deps chromium
|
|
|
|
- name: Run auth pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 E2E_MAILPIT_URL=http://mailpit:8025 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/auth.spec.ts
|
|
|
|
# auth.spec.ts's own logins (contextForUser per test) already spend
|
|
# a good chunk of the login rate limit (10/min/IP, operations.md) —
|
|
# reset it before content.spec.ts's five more, or its later tests
|
|
# 429 (found by reproducing this job's failure locally, issue #32).
|
|
- name: Reset login rate limit between e2e packs
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run content pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/content.spec.ts
|
|
|
|
# The collab pack opens two browser contexts per test (more logins),
|
|
# so reset the login rate limit before it as well (see note above).
|
|
- name: Reset login rate limit before collab pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run collab pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/collab.spec.ts
|
|
|
|
# Two contexts per test (owner + a second regular account) and grant
|
|
# changes → reset the login rate limit first (see note above).
|
|
- name: Reset login rate limit before collab-permissions pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run collab-permissions pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/collab-permissions.spec.ts
|
|
|
|
# Two contexts per test (owner + a second regular account) → reset first.
|
|
- name: Reset login rate limit before members pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run members pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/members.spec.ts
|
|
|
|
# Three contexts per test (owner + editor + viewer) → reset first.
|
|
- name: Reset login rate limit before access-rules pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run access-rules pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/access-rules.spec.ts
|
|
|
|
- name: Reset login rate limit before public pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run public pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/public.spec.ts
|
|
|
|
- name: Reset login rate limit before admin-quotas pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run admin-quotas pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/admin-quotas.spec.ts
|
|
|
|
- name: Reset login rate limit before admin-users pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run admin-users pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/admin-users.spec.ts
|
|
|
|
- name: Reset login rate limit before offline pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run offline pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/offline.spec.ts
|
|
|
|
- name: Reset login rate limit before labels pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run labels pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/labels.spec.ts
|
|
|
|
- name: Reset login rate limit before reorder pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run reorder pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/reorder.spec.ts
|
|
|
|
- name: Reset login rate limit before wikilink pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run wikilink pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/wikilink.spec.ts
|
|
|
|
- name: Reset login rate limit before backlinks pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run backlinks pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/backlinks.spec.ts
|
|
|
|
- name: Reset login rate limit before search pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run search pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/search.spec.ts
|
|
|
|
- name: Dump server logs on failure
|
|
if: failure()
|
|
run: tail -50 /tmp/api.log /tmp/collab.log /tmp/web.log || true
|
|
|
|
images:
|
|
name: Build container images
|
|
# PR-only: on main the CD workflow builds and pushes the same images —
|
|
# building twice would waste the runner (ADR 0014: build once, promote).
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
# PRs prove the Dockerfiles still build; pushing happens in cd.yml.
|
|
- name: Build web image
|
|
run: docker build -f apps/web/Dockerfile --build-arg APP_VERSION=${{ github.sha }} -t dorfteich-web:ci .
|
|
|
|
- name: Build api image
|
|
run: docker build -f apps/api/Dockerfile --build-arg APP_VERSION=${{ github.sha }} -t dorfteich-api:ci .
|
|
|
|
- name: Build collab image
|
|
run: docker build -f apps/collab/Dockerfile --build-arg APP_VERSION=${{ github.sha }} -t dorfteich-collab:ci .
|