mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 16:06:08 +00:00
feat: add sqlite storage schema
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -7,7 +7,7 @@ Update this file before each feature commit.
|
||||
| Scaffold | Completed | initial scaffold commit | `cargo fmt --check`; `cargo test`; `cargo clippy --all-targets --all-features --locked -- -D warnings` | Rust crate, lint policy, pinned TLS-sensitive dependencies, `.gitignore`, and minimal entry points are in place. |
|
||||
| API contract docs | Completed | initial scaffold commit | `cargo fmt --check`; `cargo test`; `cargo clippy --all-targets --all-features --locked -- -D warnings` | `/v1/*` OpenAI-compatible body, `/admin/*` frontend envelope, body codes, camelCase, and request ID policy are documented. |
|
||||
| Configuration | Completed | pending config commit | `cargo test default_config_keeps_only_codex_backend`; full `fmt/test/clippy` before commit | Defines Codex-only config, default YAML, and `Arc<AppServices>` state shell. |
|
||||
| SQLite storage | Planned | | | |
|
||||
| SQLite storage | Completed | pending storage commit | `cargo test migrations_create_accounts_and_event_tables`; full `fmt/test/clippy` before commit | Creates SQLite WAL connector, migrations for accounts/API keys/sessions/cookies/fingerprints/events, and event/account indexes. |
|
||||
| Admin auth and client API keys | Planned | | | |
|
||||
| Secret encryption | Planned | | | |
|
||||
| Logging and pagination | Planned | | | |
|
||||
|
||||
@@ -929,7 +929,7 @@ git commit -m "feat: add codex-only configuration"
|
||||
- Modify: `/home/zyy/桌面/Codes/codex-proxy-rs/src/lib.rs`
|
||||
- Test: `/home/zyy/桌面/Codes/codex-proxy-rs/tests/storage_migration_test.rs`
|
||||
|
||||
- [ ] **Step 1: Write failing migration test**
|
||||
- [x] **Step 1: Write failing migration test**
|
||||
|
||||
Create `/home/zyy/桌面/Codes/codex-proxy-rs/tests/storage_migration_test.rs`:
|
||||
|
||||
@@ -951,7 +951,7 @@ async fn migrations_create_accounts_and_event_tables() {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test and verify RED**
|
||||
- [x] **Step 2: Run test and verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -961,7 +961,7 @@ cargo test migrations_create_accounts_and_event_tables
|
||||
|
||||
Expected: compile failure because `storage::db` does not exist.
|
||||
|
||||
- [ ] **Step 3: Create migrations**
|
||||
- [x] **Step 3: Create migrations**
|
||||
|
||||
Create `/home/zyy/桌面/Codes/codex-proxy-rs/migrations/0001_initial.sql`:
|
||||
|
||||
@@ -1068,7 +1068,7 @@ create index if not exists idx_client_api_keys_prefix on client_api_keys(prefix)
|
||||
create index if not exists idx_account_cookies_account on account_cookies(account_id);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Implement database connector**
|
||||
- [x] **Step 4: Implement database connector**
|
||||
|
||||
Modify `/home/zyy/桌面/Codes/codex-proxy-rs/src/lib.rs`:
|
||||
|
||||
@@ -1103,7 +1103,7 @@ pub async fn connect_sqlite(database_url: &str) -> Result<SqlitePool, sqlx::Erro
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify migrations**
|
||||
- [x] **Step 5: Verify migrations**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -1113,7 +1113,7 @@ cargo test migrations_create_accounts_and_event_tables
|
||||
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 6: Commit storage**
|
||||
- [x] **Step 6: Commit storage**
|
||||
|
||||
```bash
|
||||
git add Cargo.toml migrations src/storage src/lib.rs tests/storage_migration_test.rs
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
pragma foreign_keys = on;
|
||||
|
||||
create table if not exists admin_users (
|
||||
id text primary key,
|
||||
password_hash text not null,
|
||||
created_at text not null,
|
||||
updated_at text not null
|
||||
);
|
||||
|
||||
create table if not exists admin_sessions (
|
||||
id text primary key,
|
||||
user_id text not null references admin_users(id) on delete cascade,
|
||||
expires_at text not null,
|
||||
created_at text not null
|
||||
);
|
||||
|
||||
create table if not exists client_api_keys (
|
||||
id text primary key,
|
||||
name text not null,
|
||||
prefix text not null,
|
||||
key_hash text not null,
|
||||
enabled integer not null default 1,
|
||||
created_at text not null,
|
||||
last_used_at text
|
||||
);
|
||||
|
||||
create table if not exists accounts (
|
||||
id text primary key,
|
||||
email text,
|
||||
account_id text,
|
||||
user_id text,
|
||||
label text,
|
||||
plan_type text,
|
||||
access_token_cipher text not null,
|
||||
refresh_token_cipher text,
|
||||
status text not null,
|
||||
quota_json text,
|
||||
quota_fetched_at text,
|
||||
added_at text not null,
|
||||
updated_at text not null
|
||||
);
|
||||
|
||||
create table if not exists account_usage (
|
||||
account_id text primary key references accounts(id) on delete cascade,
|
||||
request_count integer not null default 0,
|
||||
input_tokens integer not null default 0,
|
||||
output_tokens integer not null default 0,
|
||||
cached_tokens integer not null default 0,
|
||||
last_used_at text
|
||||
);
|
||||
|
||||
create table if not exists account_cookies (
|
||||
id text primary key,
|
||||
account_id text not null references accounts(id) on delete cascade,
|
||||
domain text not null,
|
||||
name text not null,
|
||||
value_cipher text not null,
|
||||
path text not null default '/',
|
||||
expires_at text,
|
||||
updated_at text not null,
|
||||
unique(account_id, domain, name, path)
|
||||
);
|
||||
|
||||
create table if not exists fingerprints (
|
||||
id text primary key,
|
||||
app_version text not null,
|
||||
build_number text not null,
|
||||
platform text not null,
|
||||
arch text not null,
|
||||
chromium_version text not null,
|
||||
user_agent_template text not null,
|
||||
source text not null,
|
||||
created_at text not null
|
||||
);
|
||||
|
||||
create table if not exists event_logs (
|
||||
id text primary key,
|
||||
request_id text,
|
||||
kind text not null,
|
||||
level text not null,
|
||||
account_id text,
|
||||
route text,
|
||||
model text,
|
||||
status_code integer,
|
||||
latency_ms integer,
|
||||
message text not null,
|
||||
metadata_json text not null,
|
||||
created_at text not null
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
create index if not exists idx_event_logs_created_id on event_logs(created_at desc, id desc);
|
||||
create index if not exists idx_event_logs_kind_created on event_logs(kind, created_at desc);
|
||||
create index if not exists idx_event_logs_request_id on event_logs(request_id);
|
||||
create index if not exists idx_accounts_status on accounts(status);
|
||||
create index if not exists idx_client_api_keys_prefix on client_api_keys(prefix);
|
||||
create index if not exists idx_account_cookies_account on account_cookies(account_id);
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod state;
|
||||
pub mod storage;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use sqlx::{sqlite::SqliteConnectOptions, SqlitePool};
|
||||
|
||||
pub async fn connect_sqlite(database_url: &str) -> Result<SqlitePool, sqlx::Error> {
|
||||
let options = SqliteConnectOptions::from_str(database_url)?
|
||||
.create_if_missing(true)
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.foreign_keys(true)
|
||||
.busy_timeout(Duration::from_secs(5));
|
||||
|
||||
// 中文注释:WAL 和外键必须在连接层统一启用,避免后续仓储绕过一致性约束。
|
||||
let pool = SqlitePool::connect_with(options).await?;
|
||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
||||
Ok(pool)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod db;
|
||||
@@ -0,0 +1,17 @@
|
||||
use codex_proxy_rs::storage::db::connect_sqlite;
|
||||
|
||||
#[tokio::test]
|
||||
async fn migrations_create_accounts_and_event_tables() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let db = dir.path().join("test.sqlite");
|
||||
let url = format!("sqlite://{}", db.display());
|
||||
let pool = connect_sqlite(&url).await.unwrap();
|
||||
|
||||
let row: (i64,) = sqlx::query_as(
|
||||
"select count(*) from sqlite_master where type = 'table' and name in ('accounts', 'client_api_keys', 'event_logs')",
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(row.0, 3);
|
||||
}
|
||||
Reference in New Issue
Block a user