Skip to content

Commit 15cd4e3

Browse files
committed
fix: rename agentic sessions table to agent_sessions
The old `sessions` table (dashboard login) and the new agentic prepaid sessions shared the same name. On mainnet, CREATE TABLE IF NOT EXISTS was a no-op since the old table already existed, causing data purge to fail with "no such column: status". Rename to `agent_sessions` to avoid the collision. Also clean up agent_sessions on merchant deletion and token regeneration.
1 parent 0b7cf3e commit 15cd4e3

3 files changed

Lines changed: 30 additions & 18 deletions

File tree

src/db.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -641,9 +641,9 @@ pub async fn create_pool(database_url: &str) -> anyhow::Result<SqlitePool> {
641641
sqlx::query("ALTER TABLE x402_verifications ADD COLUMN protocol TEXT NOT NULL DEFAULT 'x402'")
642642
.execute(&pool).await.ok();
643643

644-
// Sessions table (agentic prepaid credit)
644+
// Agent sessions table (agentic prepaid credit)
645645
sqlx::query(
646-
"CREATE TABLE IF NOT EXISTS sessions (
646+
"CREATE TABLE IF NOT EXISTS agent_sessions (
647647
id TEXT PRIMARY KEY,
648648
merchant_id TEXT NOT NULL REFERENCES merchants(id),
649649
deposit_txid TEXT NOT NULL,
@@ -663,9 +663,9 @@ pub async fn create_pool(database_url: &str) -> anyhow::Result<SqlitePool> {
663663
.await
664664
.ok();
665665

666-
sqlx::query("CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(bearer_token)")
666+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_agent_sessions_token ON agent_sessions(bearer_token)")
667667
.execute(&pool).await.ok();
668-
sqlx::query("CREATE INDEX IF NOT EXISTS idx_sessions_merchant ON sessions(merchant_id, status)")
668+
sqlx::query("CREATE INDEX IF NOT EXISTS idx_agent_sessions_merchant ON agent_sessions(merchant_id, status)")
669669
.execute(&pool).await.ok();
670670

671671
// Price type columns (one_time vs recurring)
@@ -923,12 +923,18 @@ pub async fn set_scanner_state(pool: &SqlitePool, key: &str, value: &str) -> any
923923
pub async fn run_data_purge(pool: &SqlitePool, purge_days: i64) -> anyhow::Result<()> {
924924
let cutoff = format!("-{} days", purge_days);
925925

926-
// Expired sessions + closed/depleted sessions older than cutoff
927-
let sessions = sqlx::query(
928-
"DELETE FROM sessions WHERE
926+
// Expired agent sessions + closed/depleted sessions older than cutoff
927+
let agent_sessions_purged = sqlx::query(
928+
"DELETE FROM agent_sessions WHERE
929929
expires_at < strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
930930
OR (status IN ('closed', 'depleted') AND created_at < strftime('%Y-%m-%dT%H:%M:%SZ', 'now', ?))"
931-
).bind(&cutoff).execute(pool).await?;
931+
).bind(&cutoff).execute(pool).await
932+
.map(|r| r.rows_affected()).unwrap_or(0);
933+
934+
// Expired dashboard login sessions
935+
let _ = sqlx::query(
936+
"DELETE FROM sessions WHERE expires_at < strftime('%Y-%m-%dT%H:%M:%SZ', 'now')"
937+
).execute(pool).await;
932938

933939
// Expired recovery tokens
934940
let tokens = sqlx::query(
@@ -947,13 +953,13 @@ pub async fn run_data_purge(pool: &SqlitePool, purge_days: i64) -> anyhow::Resul
947953
AND created_at < strftime('%Y-%m-%dT%H:%M:%SZ', 'now', ?)"
948954
).bind(&cutoff).execute(pool).await?;
949955

950-
let total = sessions.rows_affected()
956+
let total = agent_sessions_purged
951957
+ tokens.rows_affected()
952958
+ webhooks.rows_affected()
953959
+ tickets.rows_affected();
954960
if total > 0 {
955961
tracing::info!(
956-
sessions = sessions.rows_affected(),
962+
agent_sessions = agent_sessions_purged,
957963
tokens = tokens.rows_affected(),
958964
webhooks = webhooks.rows_affected(),
959965
tickets = tickets.rows_affected(),

src/merchants/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ pub async fn regenerate_dashboard_token(pool: &SqlitePool, merchant_id: &str) ->
260260
.bind(merchant_id)
261261
.execute(pool)
262262
.await?;
263+
let _ = sqlx::query("DELETE FROM agent_sessions WHERE merchant_id = ?")
264+
.bind(merchant_id)
265+
.execute(pool)
266+
.await;
263267

264268
tracing::info!(merchant_id, "Dashboard token regenerated, all sessions invalidated");
265269
Ok(new_token)
@@ -353,6 +357,8 @@ pub async fn delete_merchant(pool: &SqlitePool, merchant_id: &str) -> anyhow::Re
353357

354358
sqlx::query("DELETE FROM sessions WHERE merchant_id = ?")
355359
.bind(merchant_id).execute(&mut *conn).await?;
360+
let _ = sqlx::query("DELETE FROM agent_sessions WHERE merchant_id = ?")
361+
.bind(merchant_id).execute(&mut *conn).await;
356362
sqlx::query("DELETE FROM recovery_tokens WHERE merchant_id = ?")
357363
.bind(merchant_id).execute(&mut *conn).await?;
358364
sqlx::query("DELETE FROM fee_ledger WHERE merchant_id = ?")

src/sessions/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub async fn create_session(
4040
let bearer_token = generate_token();
4141

4242
sqlx::query(
43-
"INSERT INTO sessions (id, merchant_id, deposit_txid, bearer_token, balance_zatoshis, balance_remaining, cost_per_request, requests_made, refund_address, status, expires_at)
43+
"INSERT INTO agent_sessions (id, merchant_id, deposit_txid, bearer_token, balance_zatoshis, balance_remaining, cost_per_request, requests_made, refund_address, status, expires_at)
4444
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, 'active', strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '+' || ? || ' hours'))"
4545
)
4646
.bind(&id)
@@ -72,7 +72,7 @@ pub async fn create_session(
7272
pub async fn get_session(pool: &SqlitePool, session_id: &str) -> Result<Option<Session>> {
7373
let row = sqlx::query_as::<_, (String, String, String, String, i64, i64, i64, i64, Option<String>, String, String, String, Option<String>)>(
7474
"SELECT id, merchant_id, deposit_txid, bearer_token, balance_zatoshis, balance_remaining, cost_per_request, requests_made, refund_address, status, expires_at, created_at, closed_at
75-
FROM sessions WHERE id = ?"
75+
FROM agent_sessions WHERE id = ?"
7676
)
7777
.bind(session_id)
7878
.fetch_optional(pool)
@@ -99,7 +99,7 @@ pub async fn validate_and_deduct(pool: &SqlitePool, bearer_token: &str) -> Resul
9999
// Atomic deduction: single UPDATE with WHERE guards prevents race conditions.
100100
// If balance < cost or session is expired/inactive, rows_affected == 0.
101101
let result = sqlx::query(
102-
"UPDATE sessions SET
102+
"UPDATE agent_sessions SET
103103
balance_remaining = balance_remaining - cost_per_request,
104104
requests_made = requests_made + 1
105105
WHERE bearer_token = ?
@@ -114,7 +114,7 @@ pub async fn validate_and_deduct(pool: &SqlitePool, bearer_token: &str) -> Resul
114114
if result.rows_affected() == 0 {
115115
// Mark depleted/expired sessions so they don't linger
116116
sqlx::query(
117-
"UPDATE sessions SET status = CASE
117+
"UPDATE agent_sessions SET status = CASE
118118
WHEN expires_at < strftime('%Y-%m-%dT%H:%M:%SZ', 'now') THEN 'expired'
119119
WHEN balance_remaining < cost_per_request THEN 'depleted'
120120
ELSE status END
@@ -131,7 +131,7 @@ pub async fn validate_and_deduct(pool: &SqlitePool, bearer_token: &str) -> Resul
131131
// Read back the updated session state
132132
let row = sqlx::query_as::<_, (String, String, i64, i64, i64, Option<String>)>(
133133
"SELECT id, merchant_id, balance_remaining, cost_per_request, requests_made, refund_address
134-
FROM sessions WHERE bearer_token = ?"
134+
FROM agent_sessions WHERE bearer_token = ?"
135135
)
136136
.bind(bearer_token)
137137
.fetch_optional(pool)
@@ -177,7 +177,7 @@ pub async fn close_session(pool: &SqlitePool, session_id: &str) -> Result<Option
177177
};
178178

179179
sqlx::query(
180-
"UPDATE sessions SET status = 'closed', closed_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = ?"
180+
"UPDATE agent_sessions SET status = 'closed', closed_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = ?"
181181
)
182182
.bind(session_id)
183183
.execute(pool)
@@ -219,7 +219,7 @@ pub async fn get_summary(pool: &SqlitePool, session_id: &str) -> Result<Option<S
219219
pub async fn list_for_merchant(pool: &SqlitePool, merchant_id: &str) -> Result<Vec<Session>> {
220220
let rows = sqlx::query_as::<_, (String, String, String, String, i64, i64, i64, i64, Option<String>, String, String, String, Option<String>)>(
221221
"SELECT id, merchant_id, deposit_txid, bearer_token, balance_zatoshis, balance_remaining, cost_per_request, requests_made, refund_address, status, expires_at, created_at, closed_at
222-
FROM sessions WHERE merchant_id = ? ORDER BY created_at DESC LIMIT 100"
222+
FROM agent_sessions WHERE merchant_id = ? ORDER BY created_at DESC LIMIT 100"
223223
)
224224
.bind(merchant_id)
225225
.fetch_all(pool)
@@ -245,7 +245,7 @@ pub async fn list_for_merchant(pool: &SqlitePool, merchant_id: &str) -> Result<V
245245
/// Check if a deposit txid has already been used for a session
246246
pub async fn txid_already_used(pool: &SqlitePool, txid: &str) -> bool {
247247
sqlx::query_scalar::<_, i64>(
248-
"SELECT COUNT(*) FROM sessions WHERE deposit_txid = ?"
248+
"SELECT COUNT(*) FROM agent_sessions WHERE deposit_txid = ?"
249249
)
250250
.bind(txid)
251251
.fetch_one(pool)

0 commit comments

Comments
 (0)