Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34a7823e1a | ||
|
|
ea4efbe1d7 |
@@ -0,0 +1,17 @@
|
|||||||
|
.git
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
**/dist
|
||||||
|
**/.tmp
|
||||||
|
**/.vite
|
||||||
|
*.log
|
||||||
|
api*.json
|
||||||
|
session-archive
|
||||||
|
coverage
|
||||||
|
*.tsbuildinfo
|
||||||
|
.claude
|
||||||
|
.agents
|
||||||
|
agent
|
||||||
+11
-3
@@ -1,10 +1,15 @@
|
|||||||
# ── new-api-key-usage · 本地环境变量示例 ──────────────────────────
|
# ── new-api-key-usage · 环境变量示例 ──────────────────────────
|
||||||
# 复制为 .env 后填写(.env 已被 .gitignore,勿提交)
|
# 复制为 .env 后填写(.env 已被 .gitignore,勿提交)
|
||||||
|
# 开发:任意可用的 PostgreSQL,DATABASE_URL 指向即可
|
||||||
|
# 部署:docker compose up -d --build(见 README)
|
||||||
|
|
||||||
# 本服务端口(避开 3000/3001 等常见占用)
|
# 本服务端口(避开 3000/3001 等常见占用;容器内固定 18456 即可)
|
||||||
PORT=18456
|
PORT=18456
|
||||||
|
|
||||||
# PostgreSQL 连接串(开发 compose 默认如下)
|
# PostgreSQL 连接串——数据库配置的唯一来源
|
||||||
|
# 开发:指向你本机/已有的库(主机 127.0.0.1 或 localhost)
|
||||||
|
# 部署:同一份即可;compose 会把主机改写为容器服务名 postgres
|
||||||
|
# 密码含 @ : / 等字符时请 URL 编码
|
||||||
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/new_api_key_usage
|
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/new_api_key_usage
|
||||||
|
|
||||||
# 已登记 sk-key 的加密密钥:≥32 字节随机数,base64url。
|
# 已登记 sk-key 的加密密钥:≥32 字节随机数,base64url。
|
||||||
@@ -24,6 +29,9 @@ COOKIE_SECURE=false
|
|||||||
# 业务日志级别:debug | info | warning | error | fatal(访问日志另由 hono/logger 输出)
|
# 业务日志级别:debug | info | warning | error | fatal(访问日志另由 hono/logger 输出)
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
# 同步引擎周期(毫秒,≥1000;默认 300000 = 5 分钟)
|
||||||
|
# SYNC_INTERVAL_MS=300000
|
||||||
|
|
||||||
# 空库双通道:可选。用户名与密码同时存在时,首次 setup-state 静默落库;
|
# 空库双通道:可选。用户名与密码同时存在时,首次 setup-state 静默落库;
|
||||||
# 只填一个视为未配置。此后改 env 不改密;改密走管理面表单。
|
# 只填一个视为未配置。此后改 env 不改密;改密走管理面表单。
|
||||||
# ADMIN_INIT_USERNAME=
|
# ADMIN_INIT_USERNAME=
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Bun 工具链(选型见 `docs/design/技术选型.md`)。**Windows 上 `bun`
|
|||||||
| `bun run dev:engine` | 开发跑同步引擎(独立进程,读 `SYNC_*` env) |
|
| `bun run dev:engine` | 开发跑同步引擎(独立进程,读 `SYNC_*` env) |
|
||||||
| `bun run dev:web` | 开发跑前端 Vite |
|
| `bun run dev:web` | 开发跑前端 Vite |
|
||||||
| `bun run build:web` | 前端类型检查 + 产物构建 |
|
| `bun run build:web` | 前端类型检查 + 产物构建 |
|
||||||
| `docker compose up -d` | 起开发用 PostgreSQL |
|
| `docker compose up -d --build` | 部署一键起(server / engine / caddy / pg) |
|
||||||
|
|
||||||
环境变量见 `.env.example`(复制为 `.env` 后填写)。
|
环境变量见 `.env.example`(复制为 `.env` 后填写)。
|
||||||
|
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# 语法:docker/dockerfile:1
|
||||||
|
# 构建产物:/app/naku-server · /app/naku-engine · /app/drizzle · /srv(前端)
|
||||||
|
|
||||||
|
# ---------- 构建:依赖 + 前端 + 两个单文件 ----------
|
||||||
|
FROM oven/bun:1.4-alpine AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
COPY package.json bun.lock ./
|
||||||
|
COPY apps ./apps
|
||||||
|
COPY packages ./packages
|
||||||
|
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
# 前端静态资源
|
||||||
|
RUN bun run --cwd apps/web build-only
|
||||||
|
|
||||||
|
# 单文件(linux-x64;构建发生在 Linux 容器内,无需交叉编译)
|
||||||
|
RUN mkdir -p /out \
|
||||||
|
&& bun build --compile --outfile /out/naku-server apps/server/src/index.ts \
|
||||||
|
&& bun build --compile --outfile /out/naku-engine apps/engine/src/index.ts
|
||||||
|
|
||||||
|
# ---------- 应用运行时:两个二进制 + 迁移 SQL ----------
|
||||||
|
FROM alpine:3.20 AS app
|
||||||
|
WORKDIR /app
|
||||||
|
# Bun 单文件仍需 C++ 运行时与 CA;从构建镜像拷贝,避免 apk 联网
|
||||||
|
COPY --from=build /usr/lib/libstdc++.so.6 /usr/lib/libgcc_s.so.1 /usr/lib/
|
||||||
|
COPY --from=build /etc/ssl/certs /etc/ssl/certs
|
||||||
|
COPY --from=build /out/naku-server /out/naku-engine /app/
|
||||||
|
COPY --from=build /src/packages/db/drizzle /app/drizzle
|
||||||
|
ENV MIGRATIONS_FOLDER=/app/drizzle
|
||||||
|
# 默认跑 server;compose 里 engine 覆盖 command
|
||||||
|
CMD ["/app/naku-server"]
|
||||||
|
|
||||||
|
# ---------- 网关:Caddy + 前端静态资源 ----------
|
||||||
|
FROM caddy:2-alpine AS proxy
|
||||||
|
COPY deploy/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
COPY --from=build /src/apps/web/dist /srv
|
||||||
@@ -64,13 +64,12 @@ packages/
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
前置:Bun、Docker(开发用 PostgreSQL)。
|
前置:Bun、一个可用的 PostgreSQL(本机已有或别处均可)。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install
|
bun install
|
||||||
docker compose up -d # 起 PostgreSQL
|
cp .env.example .env # DATABASE_URL 指向你已有的 PostgreSQL
|
||||||
cp .env.example .env # 按注释填写
|
bun run db:push # 开发期推送 schema
|
||||||
bun run db:push # 推送 schema
|
|
||||||
bun run dev:server # 后端(默认 18456)
|
bun run dev:server # 后端(默认 18456)
|
||||||
bun run dev:engine # 同步引擎(另开终端)
|
bun run dev:engine # 同步引擎(另开终端)
|
||||||
bun run dev:web # 前端 Vite(5175)
|
bun run dev:web # 前端 Vite(5175)
|
||||||
@@ -80,6 +79,59 @@ bun run dev:web # 前端 Vite(5175)
|
|||||||
|
|
||||||
更多命令与协作约定见 [`AGENTS.md`](AGENTS.md)。
|
更多命令与协作约定见 [`AGENTS.md`](AGENTS.md)。
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
生产形态:**Caddy**(前端静态 + `/api` 反代)+ **server / engine 单文件**(Bun `--compile`)+ PostgreSQL;启动时自动应用 `packages/db/drizzle` 迁移。开发不依赖本 Compose——把 `DATABASE_URL` 指到任意可用 PostgreSQL 即可。
|
||||||
|
|
||||||
|
### 起服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# 必改:DATABASE_URL、SK_KEY_ENCRYPTION_SECRET、NEWAPI_BASE、COOKIE_SECURE
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
- 页面:`http://<服务器>/`;接口文档:`http://<服务器>/api/docs`
|
||||||
|
- 升级:`git pull` 后重复 `docker compose up -d --build`(改表随启动迁移)
|
||||||
|
|
||||||
|
### 环境变量要点
|
||||||
|
|
||||||
|
| 变量 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `DATABASE_URL` | 数据库配置的**唯一来源**(用户 / 密码 / 库名都在串里)。开发指向本机库;部署同一份即可,compose 会把主机改写为容器服务名 `postgres` |
|
||||||
|
| `SK_KEY_ENCRYPTION_SECRET` | ≥32 字节随机数(base64url)。**生成一次、入库后不可更换**——丢失则已登记 key 无法解密,与库同等备份 |
|
||||||
|
| `NEWAPI_BASE` | 上游 NewAPI 实例根地址 |
|
||||||
|
| `COOKIE_SECURE` | HTTPS / 域名用 `true`;纯 IP + HTTP 用 `false`(否则登录后立刻 401) |
|
||||||
|
| `ADMIN_INIT_USERNAME` / `ADMIN_INIT_PASSWORD` | 可选;同时存在时空库首次静默建管理员 |
|
||||||
|
|
||||||
|
### 服务分工
|
||||||
|
|
||||||
|
| 服务 | 职责 | 对外 |
|
||||||
|
|------|------|------|
|
||||||
|
| `caddy` | 前端静态 + `/api` 反代 | 仅 80 / 443 |
|
||||||
|
| `server` | 管理面 / 成员视图 API | 内网 |
|
||||||
|
| `engine` | 轮询上游、去重入库 | 内网 |
|
||||||
|
| `postgres` | 数据(卷 `pgdata`) | 内网 |
|
||||||
|
|
||||||
|
### HTTPS
|
||||||
|
|
||||||
|
把 `deploy/Caddyfile` 站点行从 `:80` 改成域名(如 `your.domain.example`),重建后 Caddy 自动签证书;同时设 `COOKIE_SECURE=true`。
|
||||||
|
|
||||||
|
### 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose ps # 运行状态
|
||||||
|
docker compose logs -f engine # 看同步
|
||||||
|
docker compose restart server # 只重启后端
|
||||||
|
docker compose down # 全停(数据在卷里,不丢)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注意
|
||||||
|
|
||||||
|
1. `COOKIE_SECURE` 必须与访问方式匹配:HTTP + `true` 会「登录后立刻 401」
|
||||||
|
2. `SK_KEY_ENCRYPTION_SECRET` 更换等于已登记 key 全部作废
|
||||||
|
3. `DATABASE_URL` 密码含 `@` `:` `/` 等字符时做 URL 编码
|
||||||
|
|
||||||
## 文档地图
|
## 文档地图
|
||||||
|
|
||||||
| 文档 | 什么时候读 | 状态 |
|
| 文档 | 什么时候读 | 状态 |
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import { appLogger, setupLogging } from "@new-api-key-usage/core";
|
import { appLogger, setupLogging } from "@new-api-key-usage/core";
|
||||||
import { createDb, createRepos } from "@new-api-key-usage/db";
|
import { createDb, createRepos, runMigrations } from "@new-api-key-usage/db";
|
||||||
import { loadEnv } from "./env.ts";
|
import { loadEnv } from "./env.ts";
|
||||||
import { createSyncLoop } from "./loop.ts";
|
import { createSyncLoop } from "./loop.ts";
|
||||||
|
|
||||||
@@ -8,6 +9,13 @@ setupLogging(env.LOG_LEVEL);
|
|||||||
const log = appLogger("engine");
|
const log = appLogger("engine");
|
||||||
|
|
||||||
const db = createDb(env.DATABASE_URL);
|
const db = createDb(env.DATABASE_URL);
|
||||||
|
|
||||||
|
const migrationsFolder =
|
||||||
|
process.env.MIGRATIONS_FOLDER ??
|
||||||
|
fileURLToPath(new URL("../../../packages/db/drizzle", import.meta.url));
|
||||||
|
await runMigrations(db, migrationsFolder);
|
||||||
|
log.info("数据库迁移已就绪");
|
||||||
|
|
||||||
const repos = createRepos(db);
|
const repos = createRepos(db);
|
||||||
|
|
||||||
const loop = createSyncLoop({
|
const loop = createSyncLoop({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import { createApp } from "./app.ts";
|
import { createApp } from "./app.ts";
|
||||||
import { loadEnv } from "./env.ts";
|
import { loadEnv } from "./env.ts";
|
||||||
import { createDb } from "@new-api-key-usage/db";
|
import { createDb, runMigrations } from "@new-api-key-usage/db";
|
||||||
import { appLogger, setupLogging } from "@new-api-key-usage/core";
|
import { appLogger, setupLogging } from "@new-api-key-usage/core";
|
||||||
|
|
||||||
const env = loadEnv(process.env);
|
const env = loadEnv(process.env);
|
||||||
@@ -8,6 +9,13 @@ setupLogging(env.LOG_LEVEL);
|
|||||||
|
|
||||||
const db = createDb(env.DATABASE_URL);
|
const db = createDb(env.DATABASE_URL);
|
||||||
|
|
||||||
|
const migrationsFolder =
|
||||||
|
process.env.MIGRATIONS_FOLDER ??
|
||||||
|
fileURLToPath(new URL("../../../packages/db/drizzle", import.meta.url));
|
||||||
|
|
||||||
|
await runMigrations(db, migrationsFolder);
|
||||||
|
appLogger("server").info("数据库迁移已就绪");
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
db,
|
db,
|
||||||
skKeyEncryptionSecret: env.SK_KEY_ENCRYPTION_SECRET,
|
skKeyEncryptionSecret: env.SK_KEY_ENCRYPTION_SECRET,
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ declare module "vue-router" {
|
|||||||
export const router: Router = createRouter({
|
export const router: Router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{ path: "/", redirect: "/admin/overview" },
|
{ path: "/", redirect: "/member" },
|
||||||
...adminRoutes,
|
...adminRoutes,
|
||||||
...memberRoutes,
|
...memberRoutes,
|
||||||
{ path: "/:pathMatch(.*)*", redirect: "/admin/overview" },
|
{ path: "/:pathMatch(.*)*", redirect: "/member" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# 站点地址按部署环境改:
|
||||||
|
# 本地/内网 HTTP → :80
|
||||||
|
# 公网 HTTPS → your.domain.example(Caddy 自动签证书)
|
||||||
|
:80 {
|
||||||
|
root * /srv
|
||||||
|
|
||||||
|
# 接口反代到 server 容器
|
||||||
|
handle /api/* {
|
||||||
|
reverse_proxy server:18456
|
||||||
|
}
|
||||||
|
|
||||||
|
# SPA:非 /api 的路径回退 index.html
|
||||||
|
handle {
|
||||||
|
try_files {path} /index.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# 把 DATABASE_URL 的主机改写为 compose 服务名 postgres,再启动应用
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "$DATABASE_URL" ]; then
|
||||||
|
echo "DATABASE_URL is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export DATABASE_URL="$(echo "$DATABASE_URL" | sed -E 's#(@)[^:/]+(:[0-9]+)?(/)#\1postgres\2\3#')"
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# 从 DATABASE_URL 拆出官方镜像建库所需的 POSTGRES_*,再交回原入口
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "$DATABASE_URL" ]; then
|
||||||
|
echo "DATABASE_URL is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
u="${DATABASE_URL#*://}"
|
||||||
|
auth="${u%%@*}"
|
||||||
|
rest="${u#*@}"
|
||||||
|
|
||||||
|
export POSTGRES_USER="${auth%%:*}"
|
||||||
|
export POSTGRES_PASSWORD="${auth#*:}"
|
||||||
|
export POSTGRES_DB="${rest#*/}"
|
||||||
|
export POSTGRES_DB="${POSTGRES_DB%%\?*}"
|
||||||
|
|
||||||
|
exec docker-entrypoint.sh postgres
|
||||||
+64
-8
@@ -1,20 +1,76 @@
|
|||||||
|
# 部署一键起:docker compose up -d --build
|
||||||
|
# 配置只认 DATABASE_URL(见 .env.example);开发不依赖本文件
|
||||||
|
# 注意:PASSWORD 含 @ : / 等字符时需 URL 编码
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17-alpine
|
image: postgres:17-alpine
|
||||||
container_name: new-api-key-usage-pg
|
entrypoint: ["/bin/sh", "/entrypoint-pg.sh"]
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
DATABASE_URL: ${DATABASE_URL:?set DATABASE_URL in .env}
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
POSTGRES_DB: new_api_key_usage
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./deploy/entrypoint-pg.sh:/entrypoint-pg.sh:ro
|
||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres -d new_api_key_usage"]
|
test: ["CMD-SHELL", "pg_isready -U $$(echo $$DATABASE_URL | sed -E 's#^[^:]+://([^:]+):.*#\\1#')"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 10
|
retries: 12
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: app
|
||||||
|
entrypoint: ["/bin/sh", "/entrypoint-app.sh"]
|
||||||
|
command: ["/app/naku-server"]
|
||||||
|
environment:
|
||||||
|
PORT: "18456"
|
||||||
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
|
SK_KEY_ENCRYPTION_SECRET: ${SK_KEY_ENCRYPTION_SECRET:?set SK_KEY_ENCRYPTION_SECRET in .env}
|
||||||
|
NEWAPI_BASE: ${NEWAPI_BASE:?set NEWAPI_BASE in .env}
|
||||||
|
COOKIE_SECURE: ${COOKIE_SECURE:-true}
|
||||||
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||||
|
ADMIN_INIT_USERNAME: ${ADMIN_INIT_USERNAME:-}
|
||||||
|
ADMIN_INIT_PASSWORD: ${ADMIN_INIT_PASSWORD:-}
|
||||||
|
MIGRATIONS_FOLDER: /app/drizzle
|
||||||
|
volumes:
|
||||||
|
- ./deploy/entrypoint-app.sh:/entrypoint-app.sh:ro
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
engine:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: app
|
||||||
|
entrypoint: ["/bin/sh", "/entrypoint-app.sh"]
|
||||||
|
command: ["/app/naku-engine"]
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
|
SK_KEY_ENCRYPTION_SECRET: ${SK_KEY_ENCRYPTION_SECRET}
|
||||||
|
NEWAPI_BASE: ${NEWAPI_BASE}
|
||||||
|
SYNC_INTERVAL_MS: ${SYNC_INTERVAL_MS:-300000}
|
||||||
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||||
|
MIGRATIONS_FOLDER: /app/drizzle
|
||||||
|
volumes:
|
||||||
|
- ./deploy/entrypoint-app.sh:/entrypoint-app.sh:ro
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
caddy:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: proxy
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
(拓扑见下文描述,实现时以此为准)
|
(拓扑见下文描述,实现时以此为准)
|
||||||
```
|
```
|
||||||
|
|
||||||
- **管理面**(作者专属):入口为登录页(账号密码)→ 侧边栏布局;空库时按三态分流(见《交互设计-登录与会话》)
|
- **管理面**(作者专属):入口为登录页(账号密码)→ 侧边栏布局;空库时按三态分流(见《交互设计-登录与会话》);地址栏访问 `/admin`(根路径不指向此处)
|
||||||
- **成员视图**(成员专属):入口为登录壳(令牌)→ 侧边栏布局(与管理端同构壳,菜单三项:总览 / 调用日志 / 支付参数);与管理面物理隔离(两套路由、两套凭证中间件,令牌进不了管理面,会话进不了成员视图)
|
- **成员视图**(成员专属):入口为登录壳(令牌)→ 侧边栏布局(与管理端同构壳,菜单三项:总览 / 调用日志 / 支付参数);与管理面物理隔离(两套路由、两套凭证中间件,令牌进不了管理面,会话进不了成员视图);**根路径 `/` 默认进入本世界**(未知路径同样回退到此)
|
||||||
- **没有第三种入口**:无开放查询页、无游客态;任何人第一眼看到的必然是两个登录入口之一
|
- **没有第三种入口**:无开放查询页、无游客态、无分发页;根地址即成员入口。管理面的入口形态待定(当前经地址栏 `/admin` 进入)
|
||||||
- 401 分流统一约定:管理面任意请求 401 → 登录页(提示「会话已过期」);成员视图任意请求 401 → 登录壳(提示「令牌无效或已停用」)——各自回各自的家,不交叉
|
- 401 分流统一约定:管理面任意请求 401 → 登录页(提示「会话已过期」);成员视图任意请求 401 → 登录壳(提示「令牌无效或已停用」)——各自回各自的家,不交叉
|
||||||
|
|
||||||
## 2. 管理面导航:侧边栏
|
## 2. 管理面导航:侧边栏
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ GET 管理面入口
|
|||||||
|
|
||||||
## 4. 成员登录壳
|
## 4. 成员登录壳
|
||||||
|
|
||||||
- 成员视图的唯一入口:令牌输入框(单字段)+ 进入按钮
|
- **唯一入口**:令牌输入框(单字段)+ 进入按钮;**根路径 `/` 默认落到本页**(已持令牌则进成员总览);管理面入口形态(是否在界面露出)待定,当前经地址栏 `/admin` 进入
|
||||||
- 提交 → 服务端哈希比对 + `status` 检查:
|
- 提交 → 服务端哈希比对 + `status` 检查:
|
||||||
- 通过 → 令牌存 sessionStorage → 进入成员视图(侧边栏布局,落地「总览」,见《交互设计-成员视图》)
|
- 通过 → 令牌存 sessionStorage → 进入成员视图(侧边栏布局,落地「总览」,见《交互设计-成员视图》)
|
||||||
- 失败/停用 → 「令牌无效或已停用」(同一提示,不泄露停用与错误的区别——成员侧无需区分,反正都是找作者)
|
- 失败/停用 → 「令牌无效或已停用」(同一提示,不泄露停用与错误的区别——成员侧无需区分,反正都是找作者)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { drizzle } from "drizzle-orm/postgres-js";
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
|
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
import * as schema from "./schema.ts";
|
import * as schema from "./schema.ts";
|
||||||
|
|
||||||
@@ -9,5 +10,10 @@ export function createDb(connectionString: string) {
|
|||||||
|
|
||||||
export type Db = ReturnType<typeof createDb>;
|
export type Db = ReturnType<typeof createDb>;
|
||||||
|
|
||||||
|
/** 启动时按 journal 应用缺失迁移(幂等);`migrationsFolder` 指向 drizzle/ 目录 */
|
||||||
|
export async function runMigrations(db: Db, migrationsFolder: string) {
|
||||||
|
await migrate(db, { migrationsFolder });
|
||||||
|
}
|
||||||
|
|
||||||
export * from "./schema.ts";
|
export * from "./schema.ts";
|
||||||
export * from "./repos.ts";
|
export * from "./repos.ts";
|
||||||
|
|||||||
Reference in New Issue
Block a user