Implement Rust-based emote database and REST API
Co-authored-by: Ganonmaster <168445+Ganonmaster@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use sqlx::AnyPool;
|
||||
|
||||
use crate::{
|
||||
config::AppConfig,
|
||||
models::{EmoteRow, new_uuid},
|
||||
};
|
||||
|
||||
/// Thin database abstraction that works with both SQLite and PostgreSQL
|
||||
/// through `sqlx::AnyPool`.
|
||||
#[derive(Clone)]
|
||||
pub struct Database {
|
||||
pub pool: AnyPool,
|
||||
pub config: Arc<AppConfig>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub async fn connect(config: Arc<AppConfig>) -> Result<Self, sqlx::Error> {
|
||||
let pool = AnyPool::connect(&config.database.url).await?;
|
||||
Ok(Self { pool, config })
|
||||
}
|
||||
|
||||
/// Run pending migrations.
|
||||
pub async fn migrate(&self) -> Result<(), sqlx::migrate::MigrateError> {
|
||||
sqlx::migrate!("./migrations").run(&self.pool).await
|
||||
}
|
||||
|
||||
pub async fn list_emotes(&self) -> Result<Vec<EmoteRow>, sqlx::Error> {
|
||||
let rows = sqlx::query_as::<_, EmoteRow>(
|
||||
"SELECT uuid, name, alias, image_key, created, modified FROM emotes ORDER BY created DESC",
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
pub async fn get_emote_by_id(&self, uuid: &str) -> Result<Option<EmoteRow>, sqlx::Error> {
|
||||
let row = sqlx::query_as::<_, EmoteRow>(
|
||||
"SELECT uuid, name, alias, image_key, created, modified FROM emotes WHERE uuid = $1",
|
||||
)
|
||||
.bind(uuid)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
pub async fn create_emote(
|
||||
&self,
|
||||
name: &str,
|
||||
alias: Option<&str>,
|
||||
image_key: &str,
|
||||
) -> Result<EmoteRow, sqlx::Error> {
|
||||
let id = new_uuid();
|
||||
let now = Utc::now().to_rfc3339();
|
||||
sqlx::query(
|
||||
"INSERT INTO emotes (uuid, name, alias, image_key, created, modified) VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
)
|
||||
.bind(&id)
|
||||
.bind(name)
|
||||
.bind(alias)
|
||||
.bind(image_key)
|
||||
.bind(&now)
|
||||
.bind(&now)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(EmoteRow {
|
||||
uuid: id,
|
||||
name: name.to_string(),
|
||||
alias: alias.map(|s| s.to_string()),
|
||||
image_key: image_key.to_string(),
|
||||
modified: now.clone(),
|
||||
created: now,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn update_emote(
|
||||
&self,
|
||||
uuid: &str,
|
||||
name: Option<&str>,
|
||||
alias: Option<Option<&str>>,
|
||||
image_key: Option<&str>,
|
||||
) -> Result<Option<EmoteRow>, sqlx::Error> {
|
||||
let existing = match self.get_emote_by_id(uuid).await? {
|
||||
Some(e) => e,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let new_name = name.unwrap_or(&existing.name);
|
||||
let new_image_key = image_key.unwrap_or(&existing.image_key);
|
||||
let new_alias: Option<String> = match alias {
|
||||
Some(Some(a)) => Some(a.to_string()),
|
||||
Some(None) => None,
|
||||
None => existing.alias.clone(),
|
||||
};
|
||||
let now = Utc::now().to_rfc3339();
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE emotes SET name = $1, alias = $2, image_key = $3, modified = $4 WHERE uuid = $5",
|
||||
)
|
||||
.bind(new_name)
|
||||
.bind(new_alias.as_deref())
|
||||
.bind(new_image_key)
|
||||
.bind(&now)
|
||||
.bind(uuid)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(Some(EmoteRow {
|
||||
uuid: uuid.to_string(),
|
||||
name: new_name.to_string(),
|
||||
alias: new_alias,
|
||||
image_key: new_image_key.to_string(),
|
||||
created: existing.created,
|
||||
modified: now,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn delete_emote(&self, uuid: &str) -> Result<bool, sqlx::Error> {
|
||||
let result = sqlx::query("DELETE FROM emotes WHERE uuid = $1")
|
||||
.bind(uuid)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(result.rows_affected() > 0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user