mirror of
https://github.com/v0l/zap-stream-core.git
synced 2025-06-16 08:59:35 +00:00
feat: pure vibes
feat: implement API
This commit is contained in:
@ -1,17 +1,33 @@
|
||||
-- Add migration script here
|
||||
create table user
|
||||
(
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
pubkey binary(32) not null,
|
||||
created timestamp default current_timestamp,
|
||||
balance bigint not null default 0,
|
||||
tos_accepted timestamp,
|
||||
stream_key text not null default uuid(),
|
||||
is_admin bool not null default false,
|
||||
is_blocked bool not null default false,
|
||||
recording bool not null default false
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
pubkey binary(32) not null,
|
||||
created timestamp not null default current_timestamp,
|
||||
balance bigint not null default 0,
|
||||
tos_accepted timestamp,
|
||||
stream_key text not null default uuid(),
|
||||
is_admin bool not null default false,
|
||||
is_blocked bool not null default false,
|
||||
recording bool not null default false,
|
||||
title text,
|
||||
summary text,
|
||||
image text,
|
||||
tags text,
|
||||
content_warning text,
|
||||
goal text
|
||||
);
|
||||
create unique index ix_user_pubkey on user (pubkey);
|
||||
|
||||
-- Add ingest endpoints table for pipeline configuration (must come before user_stream)
|
||||
create table ingest_endpoint
|
||||
(
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
name varchar(255) not null,
|
||||
cost bigint unsigned not null default 10000,
|
||||
capabilities text
|
||||
);
|
||||
|
||||
create table user_stream
|
||||
(
|
||||
id varchar(50) not null primary key,
|
||||
@ -35,7 +51,56 @@ create table user_stream
|
||||
fee integer unsigned,
|
||||
-- current nostr event json
|
||||
event text,
|
||||
-- endpoint id if using specific endpoint
|
||||
endpoint_id integer unsigned,
|
||||
-- timestamp of last segment
|
||||
last_segment timestamp,
|
||||
|
||||
constraint fk_user_stream_user
|
||||
foreign key (user_id) references user (id),
|
||||
constraint fk_user_stream_endpoint
|
||||
foreign key (endpoint_id) references ingest_endpoint (id)
|
||||
);
|
||||
|
||||
-- Add forwards table for payment forwarding
|
||||
create table user_stream_forward
|
||||
(
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
user_id integer unsigned not null,
|
||||
name text not null,
|
||||
target text not null,
|
||||
constraint fk_user_stream_forward_user
|
||||
foreign key (user_id) references user (id)
|
||||
);
|
||||
);
|
||||
|
||||
-- Add keys table for stream keys
|
||||
create table user_stream_key
|
||||
(
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
user_id integer unsigned not null,
|
||||
`key` text not null,
|
||||
created timestamp not null default current_timestamp,
|
||||
expires timestamp,
|
||||
stream_id varchar(50) not null,
|
||||
constraint fk_user_stream_key_user
|
||||
foreign key (user_id) references user (id),
|
||||
constraint fk_user_stream_key_stream
|
||||
foreign key (stream_id) references user_stream (id)
|
||||
);
|
||||
|
||||
-- Add payments table for payment logging
|
||||
create table payment
|
||||
(
|
||||
payment_hash binary(32) not null primary key,
|
||||
user_id integer unsigned not null,
|
||||
invoice text,
|
||||
is_paid bool not null default false,
|
||||
amount bigint unsigned not null,
|
||||
created timestamp not null default current_timestamp,
|
||||
nostr text,
|
||||
payment_type tinyint unsigned not null,
|
||||
fee bigint unsigned not null default 0,
|
||||
constraint fk_payment_user
|
||||
foreign key (user_id) references user (id)
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
use crate::{User, UserStream};
|
||||
use crate::{
|
||||
IngestEndpoint, Payment, PaymentType, User, UserStream, UserStreamForward, UserStreamKey,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use sqlx::{Executor, MySqlPool, Row};
|
||||
use sqlx::{MySqlPool, Row};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -53,6 +55,15 @@ impl ZapStreamDb {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark TOS as accepted for a user
|
||||
pub async fn accept_tos(&self, uid: u64) -> Result<()> {
|
||||
sqlx::query("update user set tos_accepted = NOW() where id = ?")
|
||||
.bind(uid)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result<u64> {
|
||||
let res = sqlx::query("insert ignore into user(pubkey) values(?) returning id")
|
||||
.bind(pubkey.as_slice())
|
||||
@ -153,4 +164,220 @@ impl ZapStreamDb {
|
||||
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
/// Create a new forward
|
||||
pub async fn create_forward(&self, user_id: u64, name: &str, target: &str) -> Result<u64> {
|
||||
let result =
|
||||
sqlx::query("insert into user_stream_forward (user_id, name, target) values (?, ?, ?)")
|
||||
.bind(user_id)
|
||||
.bind(name)
|
||||
.bind(target)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(result.last_insert_id())
|
||||
}
|
||||
|
||||
/// Get all forwards for a user
|
||||
pub async fn get_user_forwards(&self, user_id: u64) -> Result<Vec<UserStreamForward>> {
|
||||
Ok(
|
||||
sqlx::query_as("select * from user_stream_forward where user_id = ?")
|
||||
.bind(user_id)
|
||||
.fetch_all(&self.db)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Delete a forward
|
||||
pub async fn delete_forward(&self, user_id: u64, forward_id: u64) -> Result<()> {
|
||||
sqlx::query("delete from user_stream_forward where id = ? and user_id = ?")
|
||||
.bind(forward_id)
|
||||
.bind(user_id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new stream key
|
||||
pub async fn create_stream_key(
|
||||
&self,
|
||||
user_id: u64,
|
||||
key: &str,
|
||||
expires: Option<chrono::DateTime<chrono::Utc>>,
|
||||
stream_id: &str,
|
||||
) -> Result<u64> {
|
||||
let result = sqlx::query(
|
||||
"insert into user_stream_key (user_id, key, expires, stream_id) values (?, ?, ?, ?)",
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(key)
|
||||
.bind(expires)
|
||||
.bind(stream_id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(result.last_insert_id())
|
||||
}
|
||||
|
||||
/// Get all stream keys for a user
|
||||
pub async fn get_user_stream_keys(&self, user_id: u64) -> Result<Vec<UserStreamKey>> {
|
||||
Ok(
|
||||
sqlx::query_as("select * from user_stream_key where user_id = ?")
|
||||
.bind(user_id)
|
||||
.fetch_all(&self.db)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Delete a stream key
|
||||
pub async fn delete_stream_key(&self, user_id: u64, key_id: u64) -> Result<()> {
|
||||
sqlx::query("delete from user_stream_key where id = ? and user_id = ?")
|
||||
.bind(key_id)
|
||||
.bind(user_id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find user by stream key (including temporary keys)
|
||||
pub async fn find_user_by_any_stream_key(&self, key: &str) -> Result<Option<u64>> {
|
||||
#[cfg(feature = "test-pattern")]
|
||||
if key == "test" {
|
||||
return Ok(Some(self.upsert_user(&[0; 32]).await?));
|
||||
}
|
||||
|
||||
// First check primary stream key
|
||||
if let Some(uid) = self.find_user_stream_key(key).await? {
|
||||
return Ok(Some(uid));
|
||||
}
|
||||
|
||||
// Then check temporary stream keys
|
||||
Ok(sqlx::query("select user_id from user_stream_key where key = ? and (expires is null or expires > now())")
|
||||
.bind(key)
|
||||
.fetch_optional(&self.db)
|
||||
.await?
|
||||
.map(|r| r.try_get(0).unwrap()))
|
||||
}
|
||||
|
||||
/// Create a payment record
|
||||
pub async fn create_payment(
|
||||
&self,
|
||||
payment_hash: &[u8],
|
||||
user_id: u64,
|
||||
invoice: Option<&str>,
|
||||
amount: u64,
|
||||
payment_type: PaymentType,
|
||||
fee: u64,
|
||||
) -> Result<()> {
|
||||
sqlx::query("insert into payment (payment_hash, user_id, invoice, amount, payment_type, fee) values (?, ?, ?, ?, ?, ?)")
|
||||
.bind(payment_hash)
|
||||
.bind(user_id)
|
||||
.bind(invoice)
|
||||
.bind(amount)
|
||||
.bind(payment_type)
|
||||
.bind(fee)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark payment as paid
|
||||
pub async fn mark_payment_paid(&self, payment_hash: &[u8]) -> Result<()> {
|
||||
sqlx::query("update payment set is_paid = true where payment_hash = ?")
|
||||
.bind(payment_hash)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update payment fee and mark as paid
|
||||
pub async fn complete_payment(&self, payment_hash: &[u8], fee: u64) -> Result<()> {
|
||||
sqlx::query("update payment set fee = ?, is_paid = true where payment_hash = ?")
|
||||
.bind(fee)
|
||||
.bind(payment_hash)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get payment by hash
|
||||
pub async fn get_payment(&self, payment_hash: &[u8]) -> Result<Option<Payment>> {
|
||||
Ok(
|
||||
sqlx::query_as("select * from payment where payment_hash = ?")
|
||||
.bind(payment_hash)
|
||||
.fetch_optional(&self.db)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get payment history for user
|
||||
pub async fn get_payment_history(
|
||||
&self,
|
||||
user_id: u64,
|
||||
offset: u64,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Payment>> {
|
||||
Ok(sqlx::query_as(
|
||||
"select * from payment where user_id = ? order by created desc limit ? offset ?",
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Update user default stream info
|
||||
pub async fn update_user_defaults(
|
||||
&self,
|
||||
user_id: u64,
|
||||
title: Option<&str>,
|
||||
summary: Option<&str>,
|
||||
image: Option<&str>,
|
||||
tags: Option<&str>,
|
||||
content_warning: Option<&str>,
|
||||
goal: Option<&str>,
|
||||
) -> Result<()> {
|
||||
sqlx::query("update user set title = ?, summary = ?, image = ?, tags = ?, content_warning = ?, goal = ? where id = ?")
|
||||
.bind(title)
|
||||
.bind(summary)
|
||||
.bind(image)
|
||||
.bind(tags)
|
||||
.bind(content_warning)
|
||||
.bind(goal)
|
||||
.bind(user_id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all ingest endpoints
|
||||
pub async fn get_ingest_endpoints(&self) -> Result<Vec<IngestEndpoint>> {
|
||||
Ok(sqlx::query_as("select * from ingest_endpoint")
|
||||
.fetch_all(&self.db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Get ingest endpoint by id
|
||||
pub async fn get_ingest_endpoint(&self, endpoint_id: u64) -> Result<Option<IngestEndpoint>> {
|
||||
Ok(sqlx::query_as("select * from ingest_endpoint where id = ?")
|
||||
.bind(endpoint_id)
|
||||
.fetch_optional(&self.db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Create ingest endpoint
|
||||
pub async fn create_ingest_endpoint(
|
||||
&self,
|
||||
name: &str,
|
||||
cost: u64,
|
||||
capabilities: Option<&str>,
|
||||
) -> Result<u64> {
|
||||
let result =
|
||||
sqlx::query("insert into ingest_endpoint (name, cost, capabilities) values (?, ?, ?)")
|
||||
.bind(name)
|
||||
.bind(cost)
|
||||
.bind(capabilities)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(result.last_insert_id())
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,18 @@ pub struct User {
|
||||
pub is_blocked: bool,
|
||||
/// Streams are recorded
|
||||
pub recording: bool,
|
||||
/// Default stream title
|
||||
pub title: Option<String>,
|
||||
/// Default stream summary
|
||||
pub summary: Option<String>,
|
||||
/// Default stream image
|
||||
pub image: Option<String>,
|
||||
/// Default tags (comma separated)
|
||||
pub tags: Option<String>,
|
||||
/// Default content warning
|
||||
pub content_warning: Option<String>,
|
||||
/// Default stream goal
|
||||
pub goal: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Type)]
|
||||
@ -64,4 +76,56 @@ pub struct UserStream {
|
||||
pub duration: f32,
|
||||
pub fee: Option<u32>,
|
||||
pub event: Option<String>,
|
||||
pub endpoint_id: Option<u64>,
|
||||
pub last_segment: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct UserStreamForward {
|
||||
pub id: u64,
|
||||
pub user_id: u64,
|
||||
pub name: String,
|
||||
pub target: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct UserStreamKey {
|
||||
pub id: u64,
|
||||
pub user_id: u64,
|
||||
pub key: String,
|
||||
pub created: DateTime<Utc>,
|
||||
pub expires: Option<DateTime<Utc>>,
|
||||
pub stream_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Type)]
|
||||
#[repr(u8)]
|
||||
pub enum PaymentType {
|
||||
#[default]
|
||||
TopUp = 0,
|
||||
Zap = 1,
|
||||
Credit = 2,
|
||||
Withdrawal = 3,
|
||||
AdmissionFee = 4,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct Payment {
|
||||
pub payment_hash: Vec<u8>,
|
||||
pub user_id: u64,
|
||||
pub invoice: Option<String>,
|
||||
pub is_paid: bool,
|
||||
pub amount: u64,
|
||||
pub created: DateTime<Utc>,
|
||||
pub nostr: Option<String>,
|
||||
pub payment_type: PaymentType,
|
||||
pub fee: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct IngestEndpoint {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub cost: u64,
|
||||
pub capabilities: Option<String>, // JSON array stored as string
|
||||
}
|
||||
|
Reference in New Issue
Block a user