feat(NIP-111): pay to relay (experimental)
This commit is contained in:
committed by
Greg Heartsfield
parent
164603dedd
commit
c0158af18b
@@ -2,9 +2,11 @@ use crate::db::QueryResult;
|
||||
use crate::error::Result;
|
||||
use crate::event::Event;
|
||||
use crate::nip05::VerificationRecord;
|
||||
use crate::payment::{InvoiceInfo, InvoiceStatus};
|
||||
use crate::subscription::Subscription;
|
||||
use crate::utils::unix_time;
|
||||
use async_trait::async_trait;
|
||||
use nostr::Keys;
|
||||
use rand::Rng;
|
||||
|
||||
pub mod postgres;
|
||||
@@ -57,6 +59,33 @@ pub trait NostrRepo: Send + Sync {
|
||||
|
||||
/// Get oldest verification before timestamp
|
||||
async fn get_oldest_user_verification(&self, before: u64) -> Result<VerificationRecord>;
|
||||
|
||||
/// Create a new account
|
||||
async fn create_account(&self, pubkey: &Keys) -> Result<bool>;
|
||||
|
||||
/// Admit an account
|
||||
async fn admit_account(&self, pubkey: &Keys, admission_cost: u64) -> Result<()>;
|
||||
|
||||
/// Gets user balance if they are an admitted pubkey
|
||||
async fn get_account_balance(&self, pubkey: &Keys) -> Result<(bool, u64)>;
|
||||
|
||||
/// Update account balance
|
||||
async fn update_account_balance(
|
||||
&self,
|
||||
pub_key: &Keys,
|
||||
positive: bool,
|
||||
new_balance: u64,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Create invoice record
|
||||
async fn create_invoice_record(&self, pubkey: &Keys, invoice_info: InvoiceInfo) -> Result<()>;
|
||||
|
||||
/// Update Invoice for given payment hash
|
||||
async fn update_invoice(&self, payment_hash: &str, status: InvoiceStatus) -> Result<String>;
|
||||
|
||||
/// Get the most recent invoice for a given pubkey
|
||||
/// invoice must be unpaid and not expired
|
||||
async fn get_unpaid_invoice(&self, pubkey: &Keys) -> Result<Option<InvoiceInfo>>;
|
||||
}
|
||||
|
||||
// Current time, with a slight forward jitter in seconds
|
||||
|
||||
+173
-2
@@ -2,6 +2,7 @@ use crate::db::QueryResult;
|
||||
use crate::error::Result;
|
||||
use crate::event::{single_char_tagname, Event};
|
||||
use crate::nip05::{Nip05Name, VerificationRecord};
|
||||
use crate::payment::{InvoiceInfo, InvoiceStatus};
|
||||
use crate::repo::{now_jitter, NostrRepo};
|
||||
use crate::subscription::{ReqFilter, Subscription};
|
||||
use async_std::stream::StreamExt;
|
||||
@@ -17,6 +18,7 @@ use crate::hexrange::{hex_range, HexSearch};
|
||||
use crate::repo::postgres_migration::run_migrations;
|
||||
use crate::server::NostrMetrics;
|
||||
use crate::utils::{self, is_hex, is_lower_hex};
|
||||
use nostr::key::Keys;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::sync::oneshot::Receiver;
|
||||
use tracing::log::trace;
|
||||
@@ -160,6 +162,7 @@ ON CONFLICT (id) DO NOTHING"#,
|
||||
.execute(&mut tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
if ins_count == 0 {
|
||||
// if the event was a duplicate, no need to insert event or
|
||||
// pubkey references. This will abort the txn.
|
||||
@@ -184,7 +187,8 @@ ON CONFLICT (id) DO NOTHING"#,
|
||||
.bind(tag_name)
|
||||
.bind(hex::decode(tag_val).ok())
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, $3, NULL) \
|
||||
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
|
||||
@@ -192,7 +196,8 @@ ON CONFLICT (id) DO NOTHING"#,
|
||||
.bind(tag_name)
|
||||
.bind(tag_val.as_bytes())
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
@@ -543,6 +548,172 @@ ON CONFLICT (id) DO NOTHING"#,
|
||||
.await?
|
||||
.ok_or(error::Error::SqlxError(RowNotFound))
|
||||
}
|
||||
|
||||
async fn create_account(&self, pub_key: &Keys) -> Result<bool> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
let mut tx = self.conn.begin().await?;
|
||||
|
||||
let result = sqlx::query("INSERT INTO account (pubkey, balance) VALUES ($1, 0);")
|
||||
.bind(pub_key)
|
||||
.execute(&mut tx)
|
||||
.await;
|
||||
|
||||
let success = match result {
|
||||
Ok(res) => {
|
||||
tx.commit().await?;
|
||||
res.rows_affected() == 1
|
||||
}
|
||||
Err(_err) => false,
|
||||
};
|
||||
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
/// Admit account
|
||||
async fn admit_account(&self, pub_key: &Keys, admission_cost: u64) -> Result<()> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
sqlx::query(
|
||||
"UPDATE account SET is_admitted = TRUE, balance = balance - $1 WHERE pubkey = $2",
|
||||
)
|
||||
.bind(admission_cost as i64)
|
||||
.bind(pub_key)
|
||||
.execute(&self.conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets if the account is admitted and balance
|
||||
async fn get_account_balance(&self, pub_key: &Keys) -> Result<(bool, u64)> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
let query = r#"SELECT
|
||||
is_admitted,
|
||||
balance
|
||||
FROM account
|
||||
WHERE pubkey = $1
|
||||
LIMIT 1"#;
|
||||
|
||||
let result = sqlx::query_as::<_, (bool, i64)>(query)
|
||||
.bind(pub_key)
|
||||
.fetch_optional(&self.conn)
|
||||
.await?
|
||||
.ok_or(error::Error::SqlxError(RowNotFound))?;
|
||||
|
||||
Ok((result.0, result.1 as u64))
|
||||
}
|
||||
|
||||
/// Update account balance
|
||||
async fn update_account_balance(
|
||||
&self,
|
||||
pub_key: &Keys,
|
||||
positive: bool,
|
||||
new_balance: u64,
|
||||
) -> Result<()> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
match positive {
|
||||
true => {
|
||||
sqlx::query("UPDATE account SET balance = balance + $1 WHERE pubkey = $2")
|
||||
.bind(new_balance as i64)
|
||||
.bind(pub_key)
|
||||
.execute(&self.conn)
|
||||
.await?
|
||||
}
|
||||
false => {
|
||||
sqlx::query("UPDATE account SET balance = balance - $1 WHERE pubkey = $2")
|
||||
.bind(new_balance as i64)
|
||||
.bind(pub_key)
|
||||
.execute(&self.conn)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create invoice record
|
||||
async fn create_invoice_record(&self, pub_key: &Keys, invoice_info: InvoiceInfo) -> Result<()> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
let mut tx = self.conn.begin().await?;
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO invoice (pubkey, payment_hash, amount, status, description, created_at, invoice) VALUES ($1, $2, $3, $4, $5, now(), $6)",
|
||||
)
|
||||
.bind(pub_key)
|
||||
.bind(invoice_info.payment_hash)
|
||||
.bind(invoice_info.amount as i64)
|
||||
.bind(invoice_info.status)
|
||||
.bind(invoice_info.memo)
|
||||
.bind(invoice_info.bolt11)
|
||||
.execute(&mut tx)
|
||||
.await.unwrap();
|
||||
|
||||
debug!("Invoice added");
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update invoice record
|
||||
async fn update_invoice(&self, payment_hash: &str, status: InvoiceStatus) -> Result<String> {
|
||||
debug!("Payment Hash: {}", payment_hash);
|
||||
let query = "SELECT pubkey, status, amount FROM invoice WHERE payment_hash=$1;";
|
||||
let (pubkey, prev_invoice_status, amount) =
|
||||
sqlx::query_as::<_, (String, InvoiceStatus, i64)>(query)
|
||||
.bind(payment_hash)
|
||||
.fetch_optional(&self.conn)
|
||||
.await?
|
||||
.ok_or(error::Error::SqlxError(RowNotFound))?;
|
||||
|
||||
// If the invoice is paid update the confirmed at timestamp
|
||||
let query = if status.eq(&InvoiceStatus::Paid) {
|
||||
"UPDATE invoice SET status=$1, confirmed_at = now() WHERE payment_hash=$2;"
|
||||
} else {
|
||||
"UPDATE invoice SET status=$1 WHERE payment_hash=$2;"
|
||||
};
|
||||
|
||||
sqlx::query(query)
|
||||
.bind(&status)
|
||||
.bind(payment_hash)
|
||||
.execute(&self.conn)
|
||||
.await?;
|
||||
|
||||
if prev_invoice_status.eq(&InvoiceStatus::Unpaid) && status.eq(&InvoiceStatus::Paid) {
|
||||
sqlx::query("UPDATE account SET balance = balance + $1 WHERE pubkey = $2")
|
||||
.bind(amount)
|
||||
.bind(&pubkey)
|
||||
.execute(&self.conn)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(pubkey)
|
||||
}
|
||||
|
||||
/// Get the most recent invoice for a given pubkey
|
||||
/// invoice must be unpaid and not expired
|
||||
async fn get_unpaid_invoice(&self, pubkey: &Keys) -> Result<Option<InvoiceInfo>> {
|
||||
let query = r#"
|
||||
SELECT amount, payment_hash, description, invoice
|
||||
FROM invoice
|
||||
WHERE pubkey = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1;
|
||||
"#;
|
||||
match sqlx::query_as::<_, (i64, String, String, String)>(query)
|
||||
.bind(pubkey.public_key().to_string())
|
||||
.fetch_optional(&self.conn)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
Some((amount, payment_hash, description, invoice)) => Ok(Some(InvoiceInfo {
|
||||
pubkey: pubkey.public_key().to_string(),
|
||||
payment_hash,
|
||||
bolt11: invoice,
|
||||
amount: amount as u64,
|
||||
status: InvoiceStatus::Unpaid,
|
||||
memo: description,
|
||||
confirmed_at: None,
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a dynamic SQL query and params from a subscription filter.
|
||||
|
||||
@@ -36,6 +36,7 @@ pub async fn run_migrations(db: &PostgresPool) -> crate::error::Result<usize> {
|
||||
}
|
||||
run_migration(m003::migration(), db).await;
|
||||
run_migration(m004::migration(), db).await;
|
||||
run_migration(m005::migration(), db).await;
|
||||
Ok(current_version(db).await as usize)
|
||||
}
|
||||
|
||||
@@ -277,3 +278,43 @@ CREATE INDEX event_expires_at_idx ON "event" (expires_at);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod m005 {
|
||||
use crate::repo::postgres_migration::{Migration, SimpleSqlMigration};
|
||||
|
||||
pub const VERSION: i64 = 5;
|
||||
|
||||
pub fn migration() -> impl Migration {
|
||||
SimpleSqlMigration {
|
||||
serial_number: VERSION,
|
||||
sql: vec![
|
||||
r#"
|
||||
-- Create account table
|
||||
CREATE TABLE "account" (
|
||||
pubkey varchar NOT NULL,
|
||||
is_admitted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
balance BIGINT NOT NULL DEFAULT 0,
|
||||
tos_accepted_at TIMESTAMP,
|
||||
CONSTRAINT account_pkey PRIMARY KEY (pubkey)
|
||||
);
|
||||
|
||||
CREATE TYPE status AS ENUM ('Paid', 'Unpaid', 'Expired');
|
||||
|
||||
|
||||
CREATE TABLE "invoice" (
|
||||
payment_hash varchar NOT NULL,
|
||||
pubkey varchar NOT NULL,
|
||||
invoice varchar NOT NULL,
|
||||
amount BIGINT NOT NULL,
|
||||
status status NOT NULL DEFAULT 'Unpaid',
|
||||
description varchar,
|
||||
created_at timestamp,
|
||||
confirmed_at timestamp,
|
||||
CONSTRAINT invoice_payment_hash PRIMARY KEY (payment_hash),
|
||||
CONSTRAINT invoice_pubkey_fkey FOREIGN KEY (pubkey) REFERENCES account (pubkey) ON DELETE CASCADE
|
||||
);
|
||||
"#,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+206
-2
@@ -2,12 +2,12 @@
|
||||
//use crate::config::SETTINGS;
|
||||
use crate::config::Settings;
|
||||
use crate::db::QueryResult;
|
||||
use crate::error::Error::SqlError;
|
||||
use crate::error::Result;
|
||||
use crate::error::{Error::SqlError, Result};
|
||||
use crate::event::{single_char_tagname, Event};
|
||||
use crate::hexrange::hex_range;
|
||||
use crate::hexrange::HexSearch;
|
||||
use crate::nip05::{Nip05Name, VerificationRecord};
|
||||
use crate::payment::{InvoiceInfo, InvoiceStatus};
|
||||
use crate::repo::sqlite_migration::{upgrade_db, STARTUP_SQL};
|
||||
use crate::server::NostrMetrics;
|
||||
use crate::subscription::{ReqFilter, Subscription};
|
||||
@@ -30,6 +30,7 @@ use tokio::task;
|
||||
use tracing::{debug, info, trace, warn};
|
||||
|
||||
use crate::repo::{now_jitter, NostrRepo};
|
||||
use nostr::key::Keys;
|
||||
|
||||
pub type SqlitePool = r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>;
|
||||
pub type PooledConnection = r2d2::PooledConnection<r2d2_sqlite::SqliteConnectionManager>;
|
||||
@@ -723,6 +724,209 @@ impl NostrRepo for SqliteRepo {
|
||||
Ok(vr)
|
||||
}).await?
|
||||
}
|
||||
|
||||
/// Create account
|
||||
async fn create_account(&self, pub_key: &Keys) -> Result<bool> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
|
||||
let mut conn = self.write_pool.get()?;
|
||||
let ins_count = tokio::task::spawn_blocking(move || {
|
||||
let tx = conn.transaction()?;
|
||||
let ins_count: u64;
|
||||
{
|
||||
// Ignore if user is already in db
|
||||
let query = "INSERT OR IGNORE INTO account (pubkey, is_admitted, balance) VALUES (?1, ?2, ?3);";
|
||||
let mut stmt = tx.prepare(query)?;
|
||||
ins_count = stmt.execute(params![&pub_key, false, 0])? as u64;
|
||||
}
|
||||
tx.commit()?;
|
||||
let ok: Result<u64> = Ok(ins_count);
|
||||
ok
|
||||
}).await??;
|
||||
|
||||
if ins_count != 1 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Admit account
|
||||
async fn admit_account(&self, pub_key: &Keys, admission_cost: u64) -> Result<()> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
let mut conn = self.write_pool.get()?;
|
||||
let pub_key = pub_key.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let tx = conn.transaction()?;
|
||||
{
|
||||
let query = "UPDATE account SET is_admitted = TRUE, tos_accepted_at = strftime('%s','now'), balance = balance - ?1 WHERE pubkey=?2;";
|
||||
let mut stmt = tx.prepare(query)?;
|
||||
stmt.execute(params![admission_cost, pub_key])?;
|
||||
}
|
||||
tx.commit()?;
|
||||
let ok: Result<()> = Ok(());
|
||||
ok
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Gets if the account is admitted and balance
|
||||
async fn get_account_balance(&self, pub_key: &Keys) -> Result<(bool, u64)> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
let mut conn = self.write_pool.get()?;
|
||||
let pub_key = pub_key.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let tx = conn.transaction()?;
|
||||
let query = "SELECT is_admitted, balance FROM account WHERE pubkey = ?1;";
|
||||
let mut stmt = tx.prepare_cached(query)?;
|
||||
let fields = stmt.query_row(params![pub_key], |r| {
|
||||
let is_admitted: bool = r.get(0)?;
|
||||
let balance: u64 = r.get(1)?;
|
||||
// create a tuple since we can't throw non-rusqlite errors in this closure
|
||||
Ok((is_admitted, balance))
|
||||
})?;
|
||||
Ok(fields)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Update account balance
|
||||
async fn update_account_balance(
|
||||
&self,
|
||||
pub_key: &Keys,
|
||||
positive: bool,
|
||||
new_balance: u64,
|
||||
) -> Result<()> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
|
||||
let mut conn = self.write_pool.get()?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let tx = conn.transaction()?;
|
||||
{
|
||||
let query = if positive {
|
||||
"UPDATE account SET balance=balance + ?1 WHERE pubkey=?2"
|
||||
} else {
|
||||
"UPDATE account SET balance=balance - ?1 WHERE pubkey=?2"
|
||||
};
|
||||
let mut stmt = tx.prepare(query)?;
|
||||
stmt.execute(params![new_balance, pub_key])?;
|
||||
}
|
||||
tx.commit()?;
|
||||
let ok: Result<()> = Ok(());
|
||||
ok
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Create invoice record
|
||||
async fn create_invoice_record(&self, pub_key: &Keys, invoice_info: InvoiceInfo) -> Result<()> {
|
||||
let pub_key = pub_key.public_key().to_string();
|
||||
let pub_key = pub_key.to_owned();
|
||||
let mut conn = self.write_pool.get()?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let tx = conn.transaction()?;
|
||||
{
|
||||
let query = "INSERT INTO invoice (pubkey, payment_hash, amount, status, description, created_at, invoice) VALUES (?1, ?2, ?3, ?4, ?5, strftime('%s','now'), ?6);";
|
||||
let mut stmt = tx.prepare(query)?;
|
||||
stmt.execute(params![&pub_key, invoice_info.payment_hash, invoice_info.amount, invoice_info.status.to_string(), invoice_info.memo, invoice_info.bolt11])?;
|
||||
}
|
||||
tx.commit()?;
|
||||
let ok: Result<()> = Ok(());
|
||||
ok
|
||||
}).await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update invoice record
|
||||
async fn update_invoice(&self, payment_hash: &str, status: InvoiceStatus) -> Result<String> {
|
||||
let mut conn = self.write_pool.get()?;
|
||||
let payment_hash = payment_hash.to_owned();
|
||||
let pub_key = tokio::task::spawn_blocking(move || {
|
||||
let tx = conn.transaction()?;
|
||||
let pubkey: String;
|
||||
{
|
||||
|
||||
// Get required invoice info for given payment hash
|
||||
let query = "SELECT pubkey, status, amount FROM invoice WHERE payment_hash=?1;";
|
||||
let mut stmt = tx.prepare(query)?;
|
||||
let (pub_key, prev_status, amount) = stmt.query_row(params![payment_hash], |r| {
|
||||
let pub_key: String = r.get(0)?;
|
||||
let status: String = r.get(1)?;
|
||||
let amount: u64 = r.get(2)?;
|
||||
|
||||
|
||||
Ok((pub_key, status, amount))
|
||||
|
||||
})?;
|
||||
|
||||
// If the invoice is paid update the confirmed_at timestamp
|
||||
let query = if status.eq(&InvoiceStatus::Paid) {
|
||||
"UPDATE invoice SET status=?1, confirmed_at = strftime('%s', 'now') WHERE payment_hash=?2;"
|
||||
} else {
|
||||
"UPDATE invoice SET status=?1 WHERE payment_hash=?2;"
|
||||
};
|
||||
let mut stmt = tx.prepare(query)?;
|
||||
stmt.execute(params![status.to_string(), payment_hash])?;
|
||||
|
||||
// Increase account balance by given invoice amount
|
||||
if prev_status == "Unpaid" && status.eq(&InvoiceStatus::Paid) {
|
||||
let query =
|
||||
"UPDATE account SET balance = balance + ?1 WHERE pubkey = ?2;";
|
||||
let mut stmt = tx.prepare(query)?;
|
||||
stmt.execute(params![amount, pub_key])?;
|
||||
}
|
||||
|
||||
pubkey = pub_key;
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
let ok: Result<String> = Ok(pubkey);
|
||||
ok
|
||||
})
|
||||
.await?;
|
||||
pub_key
|
||||
}
|
||||
|
||||
/// Get the most recent invoice for a given pubkey
|
||||
/// invoice must be unpaid and not expired
|
||||
async fn get_unpaid_invoice(&self, pubkey: &Keys) -> Result<Option<InvoiceInfo>> {
|
||||
let mut conn = self.write_pool.get()?;
|
||||
|
||||
let pubkey = pubkey.to_owned();
|
||||
let pubkey_str = pubkey.clone().public_key().to_string();
|
||||
let (payment_hash, invoice, amount, description) = tokio::task::spawn_blocking(move || {
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
let query = r#"
|
||||
SELECT amount, payment_hash, description, invoice
|
||||
FROM invoice
|
||||
WHERE pubkey = ?1 AND status = 'Unpaid'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1;
|
||||
"#;
|
||||
let mut stmt = tx.prepare(query).unwrap();
|
||||
stmt.query_row(params![&pubkey_str], |r| {
|
||||
let amount: u64 = r.get(0)?;
|
||||
let payment_hash: String = r.get(1)?;
|
||||
let description: String = r.get(2)?;
|
||||
let invoice: String = r.get(3)?;
|
||||
|
||||
Ok((payment_hash, invoice, amount, description))
|
||||
})
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Some(InvoiceInfo {
|
||||
pubkey: pubkey.public_key().to_string(),
|
||||
payment_hash,
|
||||
bolt11: invoice,
|
||||
amount,
|
||||
status: InvoiceStatus::Unpaid,
|
||||
memo: description,
|
||||
confirmed_at: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Decide if there is an index that should be used explicitly
|
||||
|
||||
@@ -23,7 +23,7 @@ pragma mmap_size = 17179869184; -- cap mmap at 16GB
|
||||
"##;
|
||||
|
||||
/// Latest database version
|
||||
pub const DB_VERSION: usize = 17;
|
||||
pub const DB_VERSION: usize = 18;
|
||||
|
||||
/// Schema definition
|
||||
const INIT_SQL: &str = formatcp!(
|
||||
@@ -96,6 +96,35 @@ FOREIGN KEY(metadata_event) REFERENCES event(id) ON UPDATE CASCADE ON DELETE CAS
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS user_verification_name_index ON user_verification(name);
|
||||
CREATE INDEX IF NOT EXISTS user_verification_event_index ON user_verification(metadata_event);
|
||||
|
||||
-- Create account table
|
||||
CREATE TABLE IF NOT EXISTS account (
|
||||
pubkey TEXT PRIMARY KEY,
|
||||
is_admitted INTEGER NOT NULL DEFAULT 0,
|
||||
balance INTEGER NOT NULL DEFAULT 0,
|
||||
tos_accepted_at INTEGER
|
||||
);
|
||||
|
||||
-- Create account index
|
||||
CREATE INDEX IF NOT EXISTS user_pubkey_index ON account(pubkey);
|
||||
|
||||
-- Invoice table
|
||||
CREATE TABLE IF NOT EXISTS invoice (
|
||||
payment_hash TEXT PRIMARY KEY,
|
||||
pubkey TEXT NOT NULL,
|
||||
invoice TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
status TEXT CHECK ( status IN ('Paid', 'Unpaid', 'Expired' ) ) NOT NUll DEFAULT 'Unpaid',
|
||||
description TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
confirmed_at INTEGER,
|
||||
CONSTRAINT invoice_pubkey_fkey FOREIGN KEY (pubkey) REFERENCES account (pubkey) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Create invoice index
|
||||
CREATE INDEX IF NOT EXISTS invoice_pubkey_index ON invoice(pubkey);
|
||||
|
||||
|
||||
"##,
|
||||
DB_VERSION
|
||||
);
|
||||
@@ -213,6 +242,9 @@ pub fn upgrade_db(conn: &mut PooledConnection) -> Result<usize> {
|
||||
if curr_version == 16 {
|
||||
curr_version = mig_16_to_17(conn)?;
|
||||
}
|
||||
if curr_version == 17 {
|
||||
curr_version = mig_17_to_18(conn)?;
|
||||
}
|
||||
|
||||
if curr_version == DB_VERSION {
|
||||
info!(
|
||||
@@ -760,3 +792,50 @@ PRAGMA user_version = 17;
|
||||
}
|
||||
Ok(17)
|
||||
}
|
||||
|
||||
fn mig_17_to_18(conn: &mut PooledConnection) -> Result<usize> {
|
||||
info!("database schema needs update from 17->18");
|
||||
let upgrade_sql = r##"
|
||||
-- Create invoices table
|
||||
CREATE TABLE IF NOT EXISTS invoice (
|
||||
payment_hash TEXT PRIMARY KEY,
|
||||
pubkey TEXT NOT NULL,
|
||||
invoice TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
status TEXT CHECK ( status IN ('Paid', 'Unpaid', 'Expired' ) ) NOT NUll DEFAULT 'Unpaid',
|
||||
description TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
confirmed_at INTEGER,
|
||||
CONSTRAINT invoice_pubkey_fkey FOREIGN KEY (pubkey) REFERENCES account (pubkey) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Create invoice index
|
||||
CREATE INDEX IF NOT EXISTS invoice_pubkey_index ON invoice(pubkey);
|
||||
|
||||
-- Create account table
|
||||
|
||||
CREATE TABLE IF NOT EXISTS account (
|
||||
pubkey TEXT PRIMARY KEY,
|
||||
is_admitted INTEGER NOT NULL DEFAULT 0,
|
||||
balance INTEGER NOT NULL DEFAULT 0,
|
||||
tos_accepted_at INTEGER
|
||||
);
|
||||
|
||||
-- Create account index
|
||||
CREATE INDEX IF NOT EXISTS account_pubkey_index ON account(pubkey);
|
||||
|
||||
|
||||
pragma optimize;
|
||||
PRAGMA user_version = 17;
|
||||
"##;
|
||||
match conn.execute_batch(upgrade_sql) {
|
||||
Ok(()) => {
|
||||
info!("database schema upgraded v17 -> v18");
|
||||
}
|
||||
Err(err) => {
|
||||
error!("update failed: {}", err);
|
||||
panic!("database could not be upgraded");
|
||||
}
|
||||
}
|
||||
Ok(18)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user