Initialize Twilight project, set up basic framework, initialize DB module

This commit is contained in:
2025-08-07 04:32:34 -03:00
parent 00d61545eb
commit 24d2344f37
29 changed files with 4095 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
CREATE KEYSPACE IF NOT EXISTS payphone
WITH replication = {
'class': 'NetworkTopologyStrategy',
'replication_factor': 3
};
CREATE TABLE IF NOT EXISTS payphone.users (
user_id BIGINT PRIMARY KEY,
name TEXT,
email TEXT,
is_masked BOOLEAN,
is_moderator BOOLEAN,
joined_at TIMESTAMP,
last_seen_at TIMESTAMP,
is_supporter_since TIMESTAMP,
is_supporter_until TIMESTAMP
);
CREATE TABLE IF NOT EXISTS payphone.calls (
call_id BIGINT PRIMARY KEY,
started_at TIMESTAMP,
finished_at TIMESTAMP,
);
CREATE TABLE IF NOT EXISTS payphone.channels (
channel_id BIGINT PRIMARY KEY,
server_id BIGINT,
name TEXT,
is_searching BOOLEAN,
recently_called_channel_ids LIST<BIGINT>
);
CREATE TABLE IF NOT EXISTS payphone.participants (
call_id BIGINT,
user_id BIGINT,
user_name TEXT,
user_avatar_url TEXT,
user_mask TEXT,
PRIMARY KEY (call_id, user_id)
);
-- TTL 30 days.
CREATE TABLE IF NOT EXISTS payphone.messages (
call_id BIGINT,
message_id BIGINT,
author_id BIGINT,
author_name TEXT,
author_avatar_url TEXT,
author_mask ASCII,
content TEXT,
attachments LIST<TEXT>,
sticker BIGINT,
replying_to_id BIGINT,
is_deleted BOOLEAN,
is_edited BOOLEAN,
is_censored BOOLEAN,
PRIMARY KEY (call_id, message_id)
);
CREATE TABLE IF NOT EXISTS payphone.servers (
server_id BIGINT PRIMARY KEY,
name TEXT,
prefix TEXT,
);
CREATE TABLE IF NOT EXISTS payphone.reports (
report_id BIGINT,
message_author_id BIGINT,
message_author_name TEXT,
message_author_avatar_url TEXT,
message_author_mask ASCII,
message_id BIGINT,
message_content TEXT,
message_attachments LIST<TEXT>,
message_sticker BIGINT,
message_replying_to_id BIGINT,
message_replying_to_content TEXT,
message_replying_to_attachments LIST<TEXT>,
message_replying_to_sticker BIGINT,
created_at TIMESTAMP,
reviewed_at TIMESTAMP,
created_by_ids SET<BIGINT>,
reviewed_by_id BIGINT,
resolution ASCII,
PRIMARY KEY (message_author_id, report_id)
);
CREATE TABLE IF NOT EXISTS payphone.user_incidents (
user_id BIGINT,
incident_id BIGINT,
-- ban, warning
type ASCII,
call_id BIGINT,
report_id BIGINT,
message_id BIGINT,
message_content TEXT,
message_attachments LIST<TEXT>,
message_sticker BIGINT,
message_replying_to_id BIGINT,
message_replying_to_content TEXT,
message_replying_to_attachments LIST<TEXT>,
message_replying_to_sticker BIGINT,
reason TEXT,
created_by_id BIGINT,
created_at TIMESTAMP,
expires_at TIMESTAMP,
duration ASCII,
PRIMARY KEY (user_id)
);
CREATE TABLE IF NOT EXISTS payphone.server_incidents (
server_id BIGINT PRIMARY KEY,
incident_id BIGINT,
reason TEXT,
created_by_id BIGINT,
created_at TIMESTAMP,
expires_at TIMESTAMP,
duration ASCII
);
+93
View File
@@ -0,0 +1,93 @@
use include_dir::{Dir, include_dir};
use scylla::{
client::{session::Session, session_builder::SessionBuilder},
errors::ExecutionError,
};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DatabaseError {
#[error("A connection was attempted to be established to the database, but it failed")]
ConnectionFailed,
#[error("The database connection was established but the migrations could not be applied")]
MigrationFailed,
#[error("The migration file was invalidly formatted (not UTF-8)")]
MigrationFileInvalid(String),
}
static MIGRATIONS: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/database/migrations");
pub async fn init(uri: impl ToString) -> Result<Session, DatabaseError> {
let session = SessionBuilder::new()
.known_node(uri.to_string())
.build()
.await
.inspect_err(|e| log::error!("Could not connect to the database! {e:?}"))
.map_err(|_| DatabaseError::ConnectionFailed)?;
execute_file(
&session,
r#"
CREATE KEYSPACE IF NOT EXISTS migrations
WITH replication = {
'class': 'NetworkTopologyStrategy',
'replication_factor': 3
};
CREATE TABLE IF NOT EXISTS migrations.applied (
name TEXT PRIMARY KEY,
applied_at TIMESTAMP
);
"#
.into(),
)
.await
.inspect(|_| log::info!("The migrations keyspace was successfully set up"))
.inspect_err(|e| log::error!("Could not set up migrations keyspace and table! {e:?}"))
.map_err(|_| DatabaseError::MigrationFailed)?;
for file in MIGRATIONS.files() {
execute_file(
&session,
file.contents_utf8()
.ok_or(DatabaseError::MigrationFileInvalid(
file.path().to_string_lossy().to_string(),
))?
.to_string(),
)
.await
.inspect_err(|e| log::error!("Could not migrate {}! {e:?}", file.path().to_str().unwrap()))
.map_err(|_| DatabaseError::MigrationFailed)?;
}
Ok(session)
}
#[derive(Debug, Error)]
#[error("An error occurred while executing a statement in the file")]
struct FileExecutionError {
pub statement_index: usize,
pub error: ExecutionError,
}
async fn execute_file(session: &Session, file: String) -> Result<(), FileExecutionError> {
let statements: Vec<&str> = file.split(";").collect();
for (statement_index, statement) in statements.iter().enumerate() {
if statement.trim().is_empty() {
continue;
}
session
.query_unpaged(format!("{};", statement.trim()), ())
.await
.map_err(|error| FileExecutionError {
error,
statement_index,
})?;
}
Ok(())
}