Initial commit
This commit is contained in:
10
crates/cgcx-core/Cargo.toml
Normal file
10
crates/cgcx-core/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "cgcx-core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
rand = "0.8"
|
||||
75
crates/cgcx-core/src/id.rs
Normal file
75
crates/cgcx-core/src/id.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
const CXID_ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
const CXID_LENGTH: usize = 12;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
pub struct ContentId(String);
|
||||
|
||||
impl ContentId {
|
||||
pub fn generate() -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let alphabet_len = CXID_ALPHABET.len() as u32;
|
||||
let mut s = String::with_capacity(CXID_LENGTH);
|
||||
while s.len() < CXID_LENGTH {
|
||||
let val: u32 = rng.gen();
|
||||
// Rejection sampling: only use values that map uniformly
|
||||
let max = u32::MAX - (u32::MAX % alphabet_len);
|
||||
if val < max {
|
||||
s.push(CXID_ALPHABET[(val % alphabet_len) as usize] as char);
|
||||
}
|
||||
}
|
||||
Self(s)
|
||||
}
|
||||
|
||||
pub fn is_valid(s: &str) -> bool {
|
||||
s.len() == CXID_LENGTH
|
||||
&& s.bytes().all(|b| CXID_ALPHABET.contains(&b))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Construct from a string without validation.
|
||||
/// Only use when the source is trusted (e.g., DB row with FK constraint).
|
||||
pub fn new_unchecked(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ContentId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for ContentId {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ContentId {
|
||||
type Error = crate::CgcxError;
|
||||
fn try_from(value: String) -> crate::Result<Self> {
|
||||
if Self::is_valid(&value) {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(crate::CgcxError::InvalidContentId(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for ContentId {
|
||||
type Error = crate::CgcxError;
|
||||
fn try_from(value: &str) -> crate::Result<Self> {
|
||||
if Self::is_valid(value) {
|
||||
Ok(Self(value.to_string()))
|
||||
} else {
|
||||
Err(crate::CgcxError::InvalidContentId(value.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
37
crates/cgcx-core/src/lib.rs
Normal file
37
crates/cgcx-core/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
pub mod id;
|
||||
pub mod models;
|
||||
|
||||
pub use id::ContentId;
|
||||
pub use models::*;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum CgcxError {
|
||||
#[error("invalid content id: {0}")]
|
||||
InvalidContentId(String),
|
||||
#[error("crypto error: {0}")]
|
||||
Crypto(String),
|
||||
#[error("database error: {0}")]
|
||||
Database(String),
|
||||
#[error("storage error: {0}")]
|
||||
Storage(String),
|
||||
#[error("config error: {0}")]
|
||||
Config(String),
|
||||
#[error("moderation error: {0}")]
|
||||
Moderation(String),
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
#[error("unauthorized")]
|
||||
Unauthorized,
|
||||
#[error("forbidden")]
|
||||
Forbidden,
|
||||
#[error("rate limited")]
|
||||
RateLimited,
|
||||
#[error("bad request: {0}")]
|
||||
BadRequest(String),
|
||||
#[error("insufficient storage")]
|
||||
InsufficientStorage,
|
||||
#[error("io error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, CgcxError>;
|
||||
86
crates/cgcx-core/src/models.rs
Normal file
86
crates/cgcx-core/src/models.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::id::ContentId;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum UserRole {
|
||||
User,
|
||||
Admin,
|
||||
Banned,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum ContentStatus {
|
||||
Staged,
|
||||
Active,
|
||||
Deleted,
|
||||
Blacklisted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum ReportStatus {
|
||||
Open,
|
||||
Dismissed,
|
||||
Actioned,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct User {
|
||||
pub id: i64,
|
||||
pub telegram_username: Option<String>,
|
||||
pub first_name: String,
|
||||
pub role: UserRole,
|
||||
pub accepted_terms_at: Option<DateTime<Utc>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Content {
|
||||
pub id: ContentId,
|
||||
pub user_id: i64,
|
||||
pub status: ContentStatus,
|
||||
pub view_count: u64,
|
||||
pub max_views: Option<u64>,
|
||||
pub allow_download: bool,
|
||||
pub password_hash: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub deleted_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ContentFile {
|
||||
pub content_id: ContentId,
|
||||
pub file_index: u32,
|
||||
pub original_name: String,
|
||||
pub stored_path: std::path::PathBuf,
|
||||
pub mime_type: String,
|
||||
pub size_bytes: u64,
|
||||
pub ciphertext_size_bytes: u64,
|
||||
pub encrypted_key_wrapped: Vec<u8>,
|
||||
pub encrypted_hash: Vec<u8>,
|
||||
pub render_flags: u32,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Report {
|
||||
pub id: i64,
|
||||
pub content_id: ContentId,
|
||||
pub reporter_user_id: i64,
|
||||
pub reason: String,
|
||||
pub status: ReportStatus,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub resolved_at: Option<DateTime<Utc>>,
|
||||
pub resolver_id: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct AdminAction {
|
||||
pub id: i64,
|
||||
pub admin_user_id: i64,
|
||||
pub target_type: String,
|
||||
pub target_id: String,
|
||||
pub action: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
Reference in New Issue
Block a user