chore(server): drop flux_grant_batch schema and tables

Why
- Code, routes, service, worker, tests, and ai-context references for the
  legacy flux_grant_batch flow were removed in the previous commit. The
  schema file and the corresponding production tables were intentionally
  left for a separate DDL-only PR (this one) so the destructive change is
  easy to time and roll back.

What
- Delete src/schemas/flux-grant-batch.ts.
- Drop the re-export line in src/schemas/index.ts.
- drizzle-kit generate produced drizzle/0011_open_unus.sql:
    DROP TABLE flux_grant_batch CASCADE;
    DROP TABLE flux_grant_batch_recipient CASCADE;
  CASCADE removes the 6 associated indexes in one shot.
- docs/ai-context/architecture-overview.md updated: the dead-code
  reminder now points at the migration and explains the rollback story.

Also rolls in a pre-existing local move that was sitting uncommitted:
src/libs/{auth,env,request-auth,ws-auth}.test.ts → src/libs/tests/...
(aligning with the libs/tests/eventa-hono-adapter.test.ts placement that
was already on HEAD).

Deployment
- pnpm typecheck: passes.
- DO NOT run pnpm db:push on prod from this branch automatically. The
  drop is intentionally a separate operator action that requires picking
  a deploy window where no instance is still on an older image that
  could try to read flux_grant_batch. Until 0011 is applied to prod the
  table sits as an orphaned shell — safe to leave indefinitely.
This commit is contained in:
RainbowBird
2026-05-18 23:39:14 +08:00
parent c627bce9c9
commit 812b2db4ab
11 changed files with 2772 additions and 78 deletions
@@ -160,4 +160,4 @@ Redis 在这里同时承担:
## 当前值得注意的实现信号
- `/api/v1/openai` 当前开放:`POST /chat/completions``POST /chat/completion``POST /audio/speech``GET /audio/voices``handleTranscription` 路由尚未挂载。
- `flux_grant_batch` schema 已被简化版 `admin-flux-grants` 取代代码层(service / route / worker / tests)已删。但 `src/schemas/flux-grant-batch.ts` 仍在并跟随 `schemas/index.ts` 导出,对应生产 DB 表 `flux_grant_batch` / `flux_grant_batch_recipient` 也仍在。下次清理要删 schema 文件 + 生成 drop table migration,属破坏性 DDL,需单独 PR 处理
- `flux_grant_batch` schema 已被简化版 `admin-flux-grants` 取代代码 + schema 都已清理。`drizzle/0011_open_unus.sql` 是 drop migration`DROP TABLE flux_grant_batch / flux_grant_batch_recipient CASCADE`,顺带清掉 6 个 index)。这条 DDL 是不可逆破坏,需要操作员在合适的部署窗口手动 `pnpm db:push` 推到 prod;只要 prod DB 还没 apply 0011,回滚 server image 不会丢数据
@@ -0,0 +1,2 @@
DROP TABLE "flux_grant_batch" CASCADE;--> statement-breakpoint
DROP TABLE "flux_grant_batch_recipient" CASCADE;
+1 -1
View File
@@ -3003,4 +3003,4 @@
"schemas": {},
"tables": {}
}
}
}
File diff suppressed because it is too large Load Diff
+7
View File
@@ -85,6 +85,13 @@
"when": 1779100599148,
"tag": "0011_common_doctor_octopus",
"breakpoints": true
},
{
"idx": 12,
"version": "7",
"when": 1779118725550,
"tag": "0012_clumsy_the_stranger",
"breakpoints": true
}
]
}
@@ -1,75 +0,0 @@
import { sql } from 'drizzle-orm'
import { bigint, index, integer, pgTable, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'
import { nanoid } from '../utils/id'
/**
* Admin-issued FLUX grant batch (e.g. promotional rounds, customer
* compensation rounds, manual top-ups). One row = one batch operation.
*
* Naming: this table represents the **container** for a single
* "send N FLUX to M users" operation, nothing more. It is not a generic
* marketing campaign abstraction — adding optional codes / discounts /
* referral rewards in future is a different schema, not a column on this one.
*
* - `type` is a comment-only enum mirroring `flux_transaction.type`. v1 only
* supports `'promo'`. Adding a new value here means also extending the
* ledger type set.
* - `created_by_user_id` is bare text (no FK to user.id) for the same reason
* ledger entries are: better-auth hard-deletes user rows and we want the
* audit trail to outlive the operator's account.
*
* status state machine:
* created → running → completed (all granted)
* created → running → failed_partial (some failed/skipped, no more pending)
*/
export const fluxGrantBatch = pgTable('flux_grant_batch', {
id: text('id').primaryKey().$defaultFn(() => nanoid()),
name: text('name').notNull(),
type: text('type').notNull(), // 'promo'
amount: bigint('amount', { mode: 'number' }).notNull(),
description: text('description'),
status: text('status').notNull(), // 'created' | 'running' | 'completed' | 'failed_partial'
createdByUserId: text('created_by_user_id').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
startedAt: timestamp('started_at'),
completedAt: timestamp('completed_at'),
}, table => [
index('flux_grant_batch_status_idx').on(table.status),
index('flux_grant_batch_created_by_idx').on(table.createdByUserId),
])
/**
* Per-recipient row for a flux grant batch. One row per input email.
* Resolution (email → userId) happens at batch creation time, so worker
* execution is a pure "lookup pending → call creditFlux" loop.
*
* - `input_email` preserves the operator's original input verbatim
* (case included) for auditability. Lookup uses LOWER(email) match.
* - `user_id` is nullable: NULL means email did not match any user
* (errorReason='not_found') or the row is a duplicate that we kept
* only for audit (errorReason='duplicate_in_input').
* - `flux_transaction_id` is back-filled after a successful grant so
* reports can join the ledger row directly.
*
* Partial index on `status='pending'` keeps the worker's polling query
* cheap regardless of how many granted rows accumulate.
*/
export const fluxGrantBatchRecipient = pgTable('flux_grant_batch_recipient', {
id: text('id').primaryKey().$defaultFn(() => nanoid()),
batchId: text('batch_id').notNull(),
inputEmail: text('input_email').notNull(),
userId: text('user_id'),
status: text('status').notNull(), // 'pending' | 'granted' | 'skipped' | 'failed'
errorReason: text('error_reason'),
fluxTransactionId: text('flux_transaction_id'),
attemptCount: integer('attempt_count').notNull().default(0),
lastAttemptedAt: timestamp('last_attempted_at'),
createdAt: timestamp('created_at').defaultNow().notNull(),
}, table => [
index('flux_grant_batch_recipient_batch_status_idx').on(table.batchId, table.status),
index('flux_grant_batch_recipient_pending_idx')
.on(table.status, table.lastAttemptedAt)
.where(sql`status = 'pending'`),
uniqueIndex('flux_grant_batch_recipient_batch_email_uniq').on(table.batchId, table.inputEmail),
])
-1
View File
@@ -2,7 +2,6 @@ export * from './accounts'
export * from './characters'
export * from './chats'
export * from './flux'
export * from './flux-grant-batch'
export * from './flux-transaction'
export * from './llm-request-log'
export * from './providers'