- Add token_usage table with composite unique index for claw_id + date - Add API endpoints: POST /token, GET /token/leaderboard, GET /token/stats - Add TokenLeaderboard component with daily/total period toggle - Add CLI commands: `token` and `stats` for reporting and viewing usage - Add Redis caching for leaderboard with 1-minute TTL - Add shared utilities: authenticateRequest, getTodayDateString - Use UPSERT pattern for atomic token updates - Add i18n translations (en/zh) for new UI elements
92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
import {
|
|
mysqlTable,
|
|
varchar,
|
|
int,
|
|
bigint,
|
|
decimal,
|
|
datetime,
|
|
json,
|
|
index,
|
|
uniqueIndex,
|
|
} from "drizzle-orm/mysql-core";
|
|
import { sql } from "drizzle-orm";
|
|
|
|
export const claws = mysqlTable("claws", {
|
|
id: varchar("id", { length: 21 }).primaryKey(),
|
|
apiKey: varchar("api_key", { length: 64 }).notNull().unique(),
|
|
name: varchar("name", { length: 100 }).notNull(),
|
|
platform: varchar("platform", { length: 20 }),
|
|
model: varchar("model", { length: 50 }),
|
|
ip: varchar("ip", { length: 45 }),
|
|
latitude: decimal("latitude", { precision: 10, scale: 7 }),
|
|
longitude: decimal("longitude", { precision: 10, scale: 7 }),
|
|
city: varchar("city", { length: 100 }),
|
|
country: varchar("country", { length: 100 }),
|
|
countryCode: varchar("country_code", { length: 5 }),
|
|
region: varchar("region", { length: 50 }),
|
|
lastHeartbeat: datetime("last_heartbeat"),
|
|
totalTasks: int("total_tasks").default(0),
|
|
createdAt: datetime("created_at").default(sql`NOW()`),
|
|
updatedAt: datetime("updated_at").default(sql`NOW()`),
|
|
});
|
|
|
|
export const heartbeats = mysqlTable(
|
|
"heartbeats",
|
|
{
|
|
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
|
clawId: varchar("claw_id", { length: 21 }).notNull(),
|
|
ip: varchar("ip", { length: 45 }),
|
|
timestamp: datetime("timestamp").default(sql`NOW()`),
|
|
},
|
|
(table) => [
|
|
index("heartbeats_claw_id_idx").on(table.clawId),
|
|
index("heartbeats_timestamp_idx").on(table.timestamp),
|
|
]
|
|
);
|
|
|
|
export const tasks = mysqlTable(
|
|
"tasks",
|
|
{
|
|
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
|
clawId: varchar("claw_id", { length: 21 }).notNull(),
|
|
summary: varchar("summary", { length: 500 }),
|
|
durationMs: int("duration_ms"),
|
|
model: varchar("model", { length: 50 }),
|
|
toolsUsed: json("tools_used").$type<string[]>(),
|
|
timestamp: datetime("timestamp").default(sql`NOW()`),
|
|
},
|
|
(table) => [
|
|
index("tasks_claw_id_idx").on(table.clawId),
|
|
index("tasks_timestamp_idx").on(table.timestamp),
|
|
]
|
|
);
|
|
|
|
export const geoCache = mysqlTable("geo_cache", {
|
|
ip: varchar("ip", { length: 45 }).primaryKey(),
|
|
latitude: decimal("latitude", { precision: 10, scale: 7 }),
|
|
longitude: decimal("longitude", { precision: 10, scale: 7 }),
|
|
city: varchar("city", { length: 100 }),
|
|
country: varchar("country", { length: 100 }),
|
|
countryCode: varchar("country_code", { length: 5 }),
|
|
region: varchar("region", { length: 50 }),
|
|
updatedAt: datetime("updated_at").default(sql`NOW()`),
|
|
});
|
|
|
|
export const tokenUsage = mysqlTable(
|
|
"token_usage",
|
|
{
|
|
id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
|
clawId: varchar("claw_id", { length: 21 }).notNull(),
|
|
date: varchar("date", { length: 10 }).notNull(), // YYYY-MM-DD
|
|
inputTokens: bigint("input_tokens", { mode: "number" }).notNull().default(0),
|
|
outputTokens: bigint("output_tokens", { mode: "number" }).notNull().default(0),
|
|
createdAt: datetime("created_at").default(sql`NOW()`),
|
|
updatedAt: datetime("updated_at").default(sql`NOW()`),
|
|
},
|
|
(table) => [
|
|
index("token_usage_claw_id_idx").on(table.clawId),
|
|
index("token_usage_date_idx").on(table.date),
|
|
uniqueIndex("token_usage_claw_date_unq").on(table.clawId, table.date),
|
|
]
|
|
);
|