Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 40 additions & 13 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,28 +1,55 @@
# The browser uses the same-origin API path in development and production.
# Override only when a reverse proxy exposes this service at another path.
# Frontend uses the same-origin API path behind Vercel.
VITE_API_BASE_URL=/api/v1

# API server settings. The database and uploads stay local unless explicitly changed.
# Fastify / Railway
NODE_ENV=development
HOST=127.0.0.1
PORT=7777
DATABASE_PATH=.data/platform.db
UPLOAD_DIR=.data/uploads
# Per-account quota defaults to 250 MiB; the platform cap defaults to 5 GiB.
DATABASE_URL=postgresql://platform:platform@127.0.0.1:5432/platform
DATABASE_SSL=false
DATABASE_POOL_MAX=10
APP_ORIGINS=http://localhost:5173,http://localhost:7777
PUBLIC_WEBSOCKET_ORIGIN=ws://localhost:7777
SESSION_TTL_HOURS=12
VERIFICATION_CODE_SECRET=
SECURE_COOKIES=false
ALLOW_BEARER_AUTH=true
# Use 1 behind Railway; keep false only when the API is reached directly.
TRUST_PROXY=false
LOGGER=true
SEED_ON_START=true

# Persistent object storage (MinIO locally, Cloudflare R2 in production).
S3_ENDPOINT=http://127.0.0.1:9000
S3_REGION=us-east-1
S3_BUCKET=platform-files
S3_ACCESS_KEY_ID=platform
S3_SECRET_ACCESS_KEY=platform-development-secret
S3_FORCE_PATH_STYLE=true
UPLOAD_OWNER_QUOTA_BYTES=262144000
UPLOAD_TOTAL_QUOTA_BYTES=5368709120
UPLOAD_MAX_CONCURRENT=4
APP_ORIGIN=http://localhost:5173
SESSION_TTL_HOURS=12
VERIFICATION_CODE_SECRET=

# Production Railway Hobby uses an HMAC-authenticated HTTPS relay hosted by Vercel.
# Configure the same random secret (at least 32 characters) in Railway and Vercel.
MAIL_RELAY_URL=
MAIL_RELAY_SECRET=

# Gmail SMTP belongs to the Vercel relay in production. These variables can also
# be used directly by the API during local development when MAIL_RELAY_URL is empty.
SMTP_HOST=smtp.gmail.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=
SMTP_PASS=
MAIL_FROM=International Chinese Platform <no-reply@example.com>

# One-time production administrator bootstrap (pnpm admin:bootstrap).
ADMIN_EMAIL=
ADMIN_EMAIL=yanghanyu2023@gmail.com
ADMIN_PASSWORD=
ADMIN_DISPLAY_NAME=平台管理员

# Optional external adapters. Core demo flows work without these values.
SMTP_URL=
MAIL_FROM=International Chinese Platform <no-reply@example.com>
# Optional adapters; deterministic local dialogue remains available without AI.
AI_API_URL=
AI_API_KEY=
TURN_URL=
Expand Down
73 changes: 67 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ jobs:
name: Validate
runs-on: ubuntu-latest
timeout-minutes: 15
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_DB: platform
POSTGRES_USER: platform
POSTGRES_PASSWORD: platform
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U platform -d platform"
--health-interval 5s
--health-timeout 3s
--health-retries 20

steps:
- name: Check out repository
Expand All @@ -40,6 +54,7 @@ jobs:
env:
NODE_ENV: test
CI: true
TEST_DATABASE_URL: postgresql://platform:platform@127.0.0.1:5432/platform

- name: Upload production bundle
uses: actions/upload-artifact@v4
Expand All @@ -53,6 +68,20 @@ jobs:
name: Cross-role browser E2E
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_DB: platform
POSTGRES_USER: platform
POSTGRES_PASSWORD: platform
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U platform -d platform"
--health-interval 5s
--health-timeout 3s
--health-retries 20

steps:
- name: Check out repository
Expand Down Expand Up @@ -83,6 +112,30 @@ jobs:
- name: Build production application
run: pnpm build

- name: Start isolated MinIO object storage
run: |
docker run --detach --name icp-minio \
--publish 9000:9000 \
--env MINIO_ROOT_USER=platform \
--env MINIO_ROOT_PASSWORD=platform-development-secret \
minio/minio:latest server /data
for attempt in $(seq 1 30); do
if curl --fail --silent http://127.0.0.1:9000/minio/health/live; then break; fi
if [ "$attempt" -eq 30 ]; then exit 1; fi
sleep 1
done
docker run --rm --network host --entrypoint /bin/sh minio/mc:latest -c \
"mc alias set local http://127.0.0.1:9000 platform platform-development-secret && mc mb --ignore-existing local/platform-files"

- name: Initialize E2E database
env:
NODE_ENV: test
DATABASE_URL: postgresql://platform:platform@127.0.0.1:5432/platform
DATABASE_SSL: 'false'
run: |
pnpm db:migrate
pnpm db:seed

- name: Install browser test dependencies
run: |
python -m pip install --requirement e2e/requirements.txt
Expand All @@ -91,14 +144,22 @@ jobs:
- name: Start isolated production service
env:
NODE_ENV: production
SEED_ON_START: 'true'
SEED_ON_START: 'false'
SECURE_COOKIES: 'false'
APP_ORIGIN: http://localhost:7777
ALLOW_BEARER_AUTH: 'false'
DATABASE_URL: postgresql://platform:platform@127.0.0.1:5432/platform
DATABASE_SSL: 'false'
APP_ORIGINS: http://localhost:7777
PUBLIC_WEBSOCKET_ORIGIN: ws://localhost:7777
VERIFICATION_CODE_SECRET: e2e-only-verification-secret-32-characters
DATABASE_PATH: .data/e2e.db
S3_ENDPOINT: http://127.0.0.1:9000
S3_REGION: us-east-1
S3_BUCKET: platform-files
S3_ACCESS_KEY_ID: platform
S3_SECRET_ACCESS_KEY: platform-development-secret
S3_FORCE_PATH_STYLE: 'true'
run: |
mkdir -p test-results/e2e .data
rm -f .data/e2e.db .data/e2e.db-shm .data/e2e.db-wal
mkdir -p test-results/e2e
pnpm start > test-results/e2e/service.log 2>&1 &
echo $! > test-results/e2e/service.pid

Expand All @@ -114,7 +175,6 @@ jobs:
name: international-chinese-platform-e2e-failure
path: |
test-results/e2e
.data/e2e.db
if-no-files-found: warn
retention-days: 7

Expand All @@ -124,3 +184,4 @@ jobs:
if [ -f test-results/e2e/service.pid ]; then
kill "$(cat test-results/e2e/service.pid)" || true
fi
docker rm --force icp-minio || true
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ __pycache__/
*.sw?

*.tsbuildinfo

.vercel/

.vercel
.env*
22 changes: 13 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS build

ARG PNPM_VERSION=11.9.0
Expand All @@ -18,8 +16,7 @@ RUN corepack enable \
WORKDIR /app

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
pnpm install --frozen-lockfile
RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm build && pnpm prune --prod
Expand All @@ -34,14 +31,22 @@ LABEL org.opencontainers.image.title="International Chinese Platform" \
ENV NODE_ENV=production \
HOST=0.0.0.0 \
PORT=7777 \
DATABASE_PATH=/app/data/platform.db \
UPLOAD_DIR=/app/uploads \
DIST_DIR=/app/dist \
SEED_ON_START=false

RUN groupadd --system --gid 10001 platform \
RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates curl \
&& install -d /usr/share/postgresql-common/pgdg \
&& curl --fail --silent --show-error \
--output /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \
https://www.postgresql.org/media/keys/ACCC4CF8.asc \
&& echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
&& apt-get update \
&& apt-get install --yes --no-install-recommends postgresql-client-18 \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system --gid 10001 platform \
&& useradd --system --uid 10001 --gid platform --home-dir /app --shell /usr/sbin/nologin platform \
&& mkdir -p /app/data /app/uploads \
&& mkdir -p /app \
&& chown -R platform:platform /app

WORKDIR /app
Expand All @@ -54,7 +59,6 @@ COPY --from=build --chown=platform:platform /app/dist ./dist
USER 10001:10001

EXPOSE 7777
VOLUME ["/app/data", "/app/uploads"]
STOPSIGNAL SIGTERM

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
Expand Down
Loading
Loading