fedimovies/fedimovies-models/src/subscriptions/queries.rs

255 lines
7.5 KiB
Rust
Raw Normal View History

use chrono::{DateTime, Utc};
use uuid::Uuid;
2023-04-25 13:49:35 +00:00
use fedimovies_utils::caip2::ChainId;
2023-02-18 22:25:49 +00:00
2023-04-24 15:35:32 +00:00
use crate::database::{catch_unique_violation, DatabaseClient, DatabaseError};
use crate::invoices::types::DbChainId;
use crate::profiles::types::PaymentType;
use crate::relationships::{
queries::{subscribe, subscribe_opt},
types::RelationshipType,
2023-02-12 23:07:19 +00:00
};
use super::types::{DbSubscription, Subscription};
pub async fn create_subscription(
db_client: &mut impl DatabaseClient,
sender_id: &Uuid,
sender_address: Option<&str>,
recipient_id: &Uuid,
chain_id: &ChainId,
expires_at: &DateTime<Utc>,
updated_at: &DateTime<Utc>,
) -> Result<(), DatabaseError> {
assert!(chain_id.is_ethereum() == sender_address.is_some());
let mut transaction = db_client.transaction().await?;
2023-04-24 15:35:32 +00:00
transaction
.execute(
"
INSERT INTO subscription (
sender_id,
sender_address,
recipient_id,
chain_id,
expires_at,
updated_at
)
VALUES ($1, $2, $3, $4, $5, $6)
",
2023-04-24 15:35:32 +00:00
&[
&sender_id,
&sender_address,
&recipient_id,
&DbChainId::new(chain_id),
&expires_at,
&updated_at,
],
)
.await
.map_err(catch_unique_violation("subscription"))?;
subscribe(&mut transaction, sender_id, recipient_id).await?;
transaction.commit().await?;
Ok(())
}
pub async fn update_subscription(
db_client: &mut impl DatabaseClient,
subscription_id: i32,
expires_at: &DateTime<Utc>,
updated_at: &DateTime<Utc>,
) -> Result<(), DatabaseError> {
let mut transaction = db_client.transaction().await?;
2023-04-24 15:35:32 +00:00
let maybe_row = transaction
.query_opt(
"
UPDATE subscription
SET
expires_at = $2,
updated_at = $3
WHERE id = $1
RETURNING sender_id, recipient_id
",
2023-04-24 15:35:32 +00:00
&[&subscription_id, &expires_at, &updated_at],
)
.await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("subscription"))?;
let sender_id: Uuid = row.try_get("sender_id")?;
let recipient_id: Uuid = row.try_get("recipient_id")?;
if *expires_at > Utc::now() {
subscribe_opt(&mut transaction, &sender_id, &recipient_id).await?;
};
transaction.commit().await?;
Ok(())
}
pub async fn get_subscription_by_participants(
db_client: &impl DatabaseClient,
sender_id: &Uuid,
recipient_id: &Uuid,
) -> Result<DbSubscription, DatabaseError> {
2023-04-24 15:35:32 +00:00
let maybe_row = db_client
.query_opt(
"
SELECT subscription
FROM subscription
WHERE sender_id = $1 AND recipient_id = $2
",
2023-04-24 15:35:32 +00:00
&[sender_id, recipient_id],
)
.await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("subscription"))?;
let subscription: DbSubscription = row.try_get("subscription")?;
Ok(subscription)
}
pub async fn get_expired_subscriptions(
db_client: &impl DatabaseClient,
) -> Result<Vec<DbSubscription>, DatabaseError> {
2023-04-24 15:35:32 +00:00
let rows = db_client
.query(
"
SELECT subscription
FROM subscription
JOIN relationship
ON (
relationship.source_id = subscription.sender_id
AND relationship.target_id = subscription.recipient_id
AND relationship.relationship_type = $1
)
WHERE subscription.expires_at <= CURRENT_TIMESTAMP
",
2023-04-24 15:35:32 +00:00
&[&RelationshipType::Subscription],
)
.await?;
let subscriptions = rows
.iter()
.map(|row| row.try_get("subscription"))
.collect::<Result<_, _>>()?;
Ok(subscriptions)
}
pub async fn get_incoming_subscriptions(
db_client: &impl DatabaseClient,
recipient_id: &Uuid,
max_subscription_id: Option<i32>,
2022-09-29 21:36:55 +00:00
limit: u16,
) -> Result<Vec<Subscription>, DatabaseError> {
2023-04-24 15:35:32 +00:00
let rows = db_client
.query(
"
SELECT subscription, actor_profile AS sender
FROM actor_profile
JOIN subscription
ON (actor_profile.id = subscription.sender_id)
WHERE
subscription.recipient_id = $1
AND ($2::integer IS NULL OR subscription.id < $2)
ORDER BY subscription.id DESC
LIMIT $3
",
2023-04-24 15:35:32 +00:00
&[&recipient_id, &max_subscription_id, &i64::from(limit)],
)
.await?;
let subscriptions = rows
.iter()
.map(Subscription::try_from)
.collect::<Result<_, _>>()?;
Ok(subscriptions)
}
2022-09-07 20:23:54 +00:00
pub async fn reset_subscriptions(
db_client: &mut impl DatabaseClient,
2022-09-07 20:23:54 +00:00
ethereum_contract_replaced: bool,
) -> Result<(), DatabaseError> {
let transaction = db_client.transaction().await?;
2022-09-07 20:23:54 +00:00
if ethereum_contract_replaced {
// Ethereum subscription configuration is stored in contract.
// If contract is replaced, payment option needs to be deleted.
2023-04-24 15:35:32 +00:00
transaction
.execute(
"
2022-09-07 20:23:54 +00:00
UPDATE actor_profile
SET payment_options = '[]'
WHERE
actor_json IS NULL
AND
EXISTS (
SELECT 1
FROM jsonb_array_elements(payment_options) AS option
WHERE CAST(option ->> 'payment_type' AS SMALLINT) = $1
)
",
2023-04-24 15:35:32 +00:00
&[&i16::from(&PaymentType::EthereumSubscription)],
)
.await?;
2022-09-07 20:23:54 +00:00
};
2023-04-24 15:35:32 +00:00
transaction
.execute(
"
2022-09-07 20:23:54 +00:00
DELETE FROM relationship
WHERE relationship_type = $1
",
2023-04-24 15:35:32 +00:00
&[&RelationshipType::Subscription],
)
.await?;
transaction
.execute("UPDATE actor_profile SET subscriber_count = 0", &[])
.await?;
transaction.execute("DELETE FROM subscription", &[]).await?;
transaction.commit().await?;
2022-09-07 20:23:54 +00:00
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
2023-04-24 15:35:32 +00:00
use crate::database::test_utils::create_test_database;
use crate::profiles::{queries::create_profile, types::ProfileCreateData};
use crate::relationships::{queries::has_relationship, types::RelationshipType};
use crate::users::{queries::create_user, types::UserCreateData};
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_create_subscription() {
let db_client = &mut create_test_database().await;
let sender_data = ProfileCreateData {
username: "sender".to_string(),
..Default::default()
};
let sender = create_profile(db_client, sender_data).await.unwrap();
let sender_address = "0xb9c5714089478a327f09197987f16f9e5d936e8a";
let recipient_data = UserCreateData {
username: "recipient".to_string(),
2023-04-10 21:20:47 +00:00
password_hash: Some("test".to_string()),
..Default::default()
};
let recipient = create_user(db_client, recipient_data).await.unwrap();
let chain_id = ChainId::ethereum_mainnet();
let expires_at = Utc::now();
let updated_at = Utc::now();
create_subscription(
db_client,
&sender.id,
Some(sender_address),
&recipient.id,
&chain_id,
&expires_at,
&updated_at,
2023-04-24 15:35:32 +00:00
)
.await
.unwrap();
let is_subscribed = has_relationship(
db_client,
&sender.id,
&recipient.id,
RelationshipType::Subscription,
2023-04-24 15:35:32 +00:00
)
.await
.unwrap();
2023-04-27 22:01:24 +00:00
assert!(is_subscribed);
}
}