Huge refactor, submission system addition & security improvements. +Implementation of moderation cmds.
This commit is contained in:
@@ -47,6 +47,8 @@ impl Database {
|
||||
let migrations = rusqlite_migration::Migrations::new(vec![
|
||||
rusqlite_migration::M::up(include_str!("../../../migrations/001_init.sql")),
|
||||
rusqlite_migration::M::up(include_str!("../../../migrations/002_indexes.sql")),
|
||||
rusqlite_migration::M::up(include_str!("../../../migrations/003_forward_system.sql")),
|
||||
rusqlite_migration::M::up(include_str!("../../../migrations/004_punishments.sql")),
|
||||
]);
|
||||
migrations.to_latest(&mut *conn)
|
||||
.map_err(|e| CgcxError::Database(format!("migration failed: {}", e)))?;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use cgcx_core::{AdminAction, Content, ContentFile, ContentId, ContentStatus, Report, ReportStatus, Result, CgcxError, User};
|
||||
use rusqlite::{params, OptionalExtension};
|
||||
use cgcx_core::{AdminAction, Content, ContentFile, ContentId, ContentStatus, ForwardDefinition, ForwardSubmission, Punishment, Report, ReportStatus, Result, CgcxError, User};
|
||||
use rusqlite::{params, OptionalExtension, Connection};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -197,6 +197,15 @@ impl ContentRepo {
|
||||
tx.commit().map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_password_hash(&self, id: &ContentId, password_hash: Option<&str>) -> Result<()> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"UPDATE contents SET password_hash = ?1 WHERE id = ?2",
|
||||
params![password_hash, id.as_str()],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContentFileRepo {
|
||||
@@ -387,3 +396,304 @@ impl AdminActionRepo {
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ForwardRepo {
|
||||
conn: Arc<Mutex<rusqlite::Connection>>,
|
||||
}
|
||||
|
||||
impl ForwardRepo {
|
||||
pub fn new(conn: Arc<Mutex<rusqlite::Connection>>) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
||||
pub async fn insert(&self, creator_user_id: i64, source_chat_id: i64, destination_chat_id: i64, review_group_id: i64, forward_message: &str, code: &str, share_mode: &str) -> Result<i64> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"INSERT INTO forward_definitions (creator_user_id, source_chat_id, destination_chat_id, review_group_id, forward_message, code, share_mode)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
||||
params![creator_user_id, source_chat_id, destination_chat_id, review_group_id, forward_message, code, share_mode],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
pub async fn get_by_code(&self, code: &str) -> Result<Option<ForwardDefinition>> {
|
||||
let conn = self.conn.lock().await;
|
||||
let row = conn.query_row(
|
||||
"SELECT id, creator_user_id, source_chat_id, destination_chat_id, review_group_id, forward_message, code, share_mode, revoked_at, created_at
|
||||
FROM forward_definitions WHERE code = ?1",
|
||||
params![code],
|
||||
|row| {
|
||||
Ok(ForwardDefinition {
|
||||
id: row.get(0)?,
|
||||
creator_user_id: row.get(1)?,
|
||||
source_chat_id: row.get(2)?,
|
||||
destination_chat_id: row.get(3)?,
|
||||
review_group_id: row.get(4)?,
|
||||
forward_message: row.get(5)?,
|
||||
code: row.get(6)?,
|
||||
share_mode: row.get(7)?,
|
||||
revoked_at: row.get(8)?,
|
||||
created_at: row.get(9)?,
|
||||
})
|
||||
},
|
||||
).optional().map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
pub async fn list_by_source_chat(&self, source_chat_id: i64, limit: usize, offset: usize) -> Result<Vec<ForwardDefinition>> {
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, creator_user_id, source_chat_id, destination_chat_id, review_group_id, forward_message, code, share_mode, revoked_at, created_at
|
||||
FROM forward_definitions WHERE source_chat_id = ?1 ORDER BY created_at DESC LIMIT ?2 OFFSET ?3"
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
let rows = stmt.query_map(params![source_chat_id, limit as i64, offset as i64], |row| {
|
||||
Ok(ForwardDefinition {
|
||||
id: row.get(0)?,
|
||||
creator_user_id: row.get(1)?,
|
||||
source_chat_id: row.get(2)?,
|
||||
destination_chat_id: row.get(3)?,
|
||||
review_group_id: row.get(4)?,
|
||||
forward_message: row.get(5)?,
|
||||
code: row.get(6)?,
|
||||
share_mode: row.get(7)?,
|
||||
revoked_at: row.get(8)?,
|
||||
created_at: row.get(9)?,
|
||||
})
|
||||
}).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
let mut out = Vec::new();
|
||||
for r in rows {
|
||||
out.push(r.map_err(|e| CgcxError::Database(e.to_string()))?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub async fn revoke(&self, id: i64) -> Result<()> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"UPDATE forward_definitions SET revoked_at = datetime('now') WHERE id = ?1",
|
||||
params![id],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn is_allowed(&self, forward_id: i64, user_id: i64) -> Result<bool> {
|
||||
let conn = self.conn.lock().await;
|
||||
let def: Option<ForwardDefinition> = conn.query_row(
|
||||
"SELECT id, creator_user_id, source_chat_id, destination_chat_id, review_group_id, forward_message, code, share_mode, revoked_at, created_at
|
||||
FROM forward_definitions WHERE id = ?1",
|
||||
params![forward_id],
|
||||
|row| {
|
||||
Ok(ForwardDefinition {
|
||||
id: row.get(0)?,
|
||||
creator_user_id: row.get(1)?,
|
||||
source_chat_id: row.get(2)?,
|
||||
destination_chat_id: row.get(3)?,
|
||||
review_group_id: row.get(4)?,
|
||||
forward_message: row.get(5)?,
|
||||
code: row.get(6)?,
|
||||
share_mode: row.get(7)?,
|
||||
revoked_at: row.get(8)?,
|
||||
created_at: row.get(9)?,
|
||||
})
|
||||
},
|
||||
).optional().map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
|
||||
if let Some(def) = def {
|
||||
if def.creator_user_id == user_id {
|
||||
return Ok(true);
|
||||
}
|
||||
let list_entry: Option<String> = conn.query_row(
|
||||
"SELECT list_type FROM forward_lists WHERE forward_id = ?1 AND user_id = ?2",
|
||||
params![forward_id, user_id],
|
||||
|row| row.get(0),
|
||||
).optional().map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
match def.share_mode.as_str() {
|
||||
"w" => Ok(list_entry.map(|t| t == "allow").unwrap_or(false)),
|
||||
_ => Ok(list_entry.map(|t| t != "block").unwrap_or(true)),
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert_submission(&self, forward_id: i64, user_id: i64, content_id: &str) -> Result<i64> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"INSERT INTO forward_submissions (forward_id, user_id, content_id) VALUES (?1, ?2, ?3)",
|
||||
params![forward_id, user_id, content_id],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
pub async fn get_submission(&self, id: i64) -> Result<Option<ForwardSubmission>> {
|
||||
let conn = self.conn.lock().await;
|
||||
let row = conn.query_row(
|
||||
"SELECT id, forward_id, user_id, content_id, status, review_message_id, created_at, resolved_at, resolver_id
|
||||
FROM forward_submissions WHERE id = ?1",
|
||||
params![id],
|
||||
|row| {
|
||||
Ok(ForwardSubmission {
|
||||
id: row.get(0)?,
|
||||
forward_id: row.get(1)?,
|
||||
user_id: row.get(2)?,
|
||||
content_id: ContentId::new_unchecked(row.get(3)?),
|
||||
status: row.get(4)?,
|
||||
review_message_id: row.get(5)?,
|
||||
created_at: row.get(6)?,
|
||||
resolved_at: row.get(7)?,
|
||||
resolver_id: row.get(8)?,
|
||||
})
|
||||
},
|
||||
).optional().map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
pub async fn set_review_message_id(&self, id: i64, message_id: i32) -> Result<()> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"UPDATE forward_submissions SET review_message_id = ?1 WHERE id = ?2",
|
||||
params![message_id, id],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_status(&self, id: i64, status: &str) -> Result<()> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"UPDATE forward_submissions SET status = ?1, resolved_at = datetime('now') WHERE id = ?2",
|
||||
params![status, id],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_definition(&self, id: i64) -> Result<Option<ForwardDefinition>> {
|
||||
let conn = self.conn.lock().await;
|
||||
let row = conn.query_row(
|
||||
"SELECT id, creator_user_id, source_chat_id, destination_chat_id, review_group_id, forward_message, code, share_mode, revoked_at, created_at
|
||||
FROM forward_definitions WHERE id = ?1",
|
||||
params![id],
|
||||
|row| {
|
||||
Ok(ForwardDefinition {
|
||||
id: row.get(0)?,
|
||||
creator_user_id: row.get(1)?,
|
||||
source_chat_id: row.get(2)?,
|
||||
destination_chat_id: row.get(3)?,
|
||||
review_group_id: row.get(4)?,
|
||||
forward_message: row.get(5)?,
|
||||
code: row.get(6)?,
|
||||
share_mode: row.get(7)?,
|
||||
revoked_at: row.get(8)?,
|
||||
created_at: row.get(9)?,
|
||||
})
|
||||
},
|
||||
).optional().map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
pub async fn add_to_list(&self, forward_id: i64, user_id: i64, list_type: &str) -> Result<()> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"INSERT INTO forward_lists (forward_id, user_id, list_type) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING",
|
||||
params![forward_id, user_id, list_type],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_from_list(&self, forward_id: i64, user_id: i64, list_type: &str) -> Result<()> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"DELETE FROM forward_lists WHERE forward_id = ?1 AND user_id = ?2 AND list_type = ?3",
|
||||
params![forward_id, user_id, list_type],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub struct PunishmentRepo {
|
||||
conn: Arc<Mutex<Connection>>,
|
||||
}
|
||||
|
||||
impl PunishmentRepo {
|
||||
pub fn new(conn: Arc<Mutex<Connection>>) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
&self,
|
||||
chat_id: i64,
|
||||
target_user_id: i64,
|
||||
action_type: &str,
|
||||
duration_seconds: Option<i64>,
|
||||
reason: Option<&str>,
|
||||
created_by: i64,
|
||||
) -> Result<i64> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"INSERT INTO punishments (chat_id, target_user_id, action_type, duration_seconds, reason, created_by) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
params![chat_id, target_user_id, action_type, duration_seconds, reason, created_by],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
pub async fn get_active_for_chat_target(&self, chat_id: i64, target_user_id: i64, action_type: &str) -> Result<Vec<Punishment>> {
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, chat_id, target_user_id, action_type, duration_seconds, reason, created_by, created_at, revoked_at, revoked_by, active FROM punishments WHERE chat_id = ?1 AND target_user_id = ?2 AND action_type = ?3 AND active = 1"
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
let rows = stmt.query_map(params![chat_id, target_user_id, action_type], |row| {
|
||||
Ok(Punishment {
|
||||
id: row.get(0)?,
|
||||
chat_id: row.get(1)?,
|
||||
target_user_id: row.get(2)?,
|
||||
action_type: row.get(3)?,
|
||||
duration_seconds: row.get(4)?,
|
||||
reason: row.get(5)?,
|
||||
created_by: row.get(6)?,
|
||||
created_at: row.get(7)?,
|
||||
revoked_at: row.get(8)?,
|
||||
revoked_by: row.get(9)?,
|
||||
active: row.get::<_, i64>(10)? != 0,
|
||||
})
|
||||
}).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
let mut results = vec![];
|
||||
for r in rows {
|
||||
results.push(r.map_err(|e| CgcxError::Database(e.to_string()))?);
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn revoke(&self, id: i64, revoked_by: i64) -> Result<()> {
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"UPDATE punishments SET active = 0, revoked_at = datetime('now'), revoked_by = ?1 WHERE id = ?2",
|
||||
params![revoked_by, id],
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_expired(&self) -> Result<Vec<Punishment>> {
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, chat_id, target_user_id, action_type, duration_seconds, reason, created_by, created_at, revoked_at, revoked_by, active FROM punishments WHERE active = 1 AND duration_seconds IS NOT NULL AND datetime(created_at, '+' || duration_seconds || ' seconds') <= datetime('now')"
|
||||
).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(Punishment {
|
||||
id: row.get(0)?,
|
||||
chat_id: row.get(1)?,
|
||||
target_user_id: row.get(2)?,
|
||||
action_type: row.get(3)?,
|
||||
duration_seconds: row.get(4)?,
|
||||
reason: row.get(5)?,
|
||||
created_by: row.get(6)?,
|
||||
created_at: row.get(7)?,
|
||||
revoked_at: row.get(8)?,
|
||||
revoked_by: row.get(9)?,
|
||||
active: row.get::<_, i64>(10)? != 0,
|
||||
})
|
||||
}).map_err(|e| CgcxError::Database(e.to_string()))?;
|
||||
let mut results = vec![];
|
||||
for r in rows {
|
||||
results.push(r.map_err(|e| CgcxError::Database(e.to_string()))?);
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user