Initialize Twilight project, set up basic framework, initialize DB module
This commit is contained in:
@@ -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
|
||||
);
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user