use chrono::Utc;
use lunar_lib::{
database::{
CompareAndSwapTransaction, Createable, CustomTransactionError, DatabaseEntry, Db, DbIdExt,
Deleteable, Entry, TransactionError, caching::Cacheable,
},
id::Id,
iterator_ext::IteratorExtensions,
};
use crate::{
accounts::account::Account,
database::Selene,
library::{
collectable::Collectable,
collection::{Collection, CollectionCreationError},
},
};
#[derive(Debug, Clone)]
pub struct CollectionCreateArgs {
name: String,
items: Vec<Collectable>,
account: Id<Account>,
public: bool,
}
impl CollectionCreateArgs {
pub fn new(name: impl Into<String>, user: Id<Account>, public: bool) -> Self {
Self {
name: name.into(),
items: Vec::new(),
account: user,
public,
}
}
#[must_use]
pub fn with_items(mut self, items: Vec<Collectable>) -> Self {
self.items = items;
self
}
}
impl DatabaseEntry for Collection {
type DbInner = Selene;
const VERSION_NUMBER: u32 = 1;
const TREE_NAME: &str = "collections";
}
impl Cacheable for Collection {}
pub static COLLECTION_OWNER_INDEX: &str = "collection_owner_map";
impl Createable for Collection {
type CreateArgs = CollectionCreateArgs;
type Err = CollectionCreationError;
fn create(
CollectionCreateArgs {
name,
items,
account,
public,
}: Self::CreateArgs,
cas_tx: &mut CompareAndSwapTransaction<Self::DbInner>,
) -> Result<Entry<Self>, CustomTransactionError<Self::Err>> {
let len = name.chars().take(33).count();
if !(3..=32).contains(&len) {
return Err(CustomTransactionError::Closure(
CollectionCreationError::InvalidNameLength(len as u32),
));
}
let id = loop {
let id = Id::generate();
if !id.tx_check(cas_tx)? {
break id;
}
};
let now = Utc::now();
let collection = Collection {
name,
owner: account,
cover_art: None,
items,
public,
created: now,
modified: now,
};
let index = cas_tx.get_or_new_index(COLLECTION_OWNER_INDEX);
let mut key = [0u8; 64];
key[0..32].copy_from_slice(&*account);
key[32..64].copy_from_slice(&*id);
index.upsert(key.as_slice(), &[u8::from(public)])?;
Ok(collection.to_entry(id))
}
}
impl Deleteable for Collection {
fn unlink_references(
old: Entry<Self>,
cas_tx: &mut CompareAndSwapTransaction<Self::DbInner>,
) -> Result<(), TransactionError> {
let index = cas_tx.get_or_new_index(COLLECTION_OWNER_INDEX);
let mut key = [0u8; 64];
key[0..32].copy_from_slice(&*old.owner);
key[32..64].copy_from_slice(&*old.id());
index.delete(key.as_slice())?;
Ok(())
}
}
impl Collection {
pub fn get_collections_by_user(
user: Id<Account>,
db: &Db<Selene>,
) -> Result<Vec<(Id<Collection>, bool)>, TransactionError> {
let index = db.index(COLLECTION_OWNER_INDEX);
index
.scan_prefix(user)
.map(|r| {
let (key, value) = r?;
Ok((
Id::<Collection>::try_from(&key[32..64]).expect("IDs are 32 bytes"),
value[0] != 0,
))
})
.try_to_vec()
}
}