Compare commits
24 commits
main
...
wip/messag
Author | SHA1 | Date | |
---|---|---|---|
6374963e2f | |||
2e4860323e | |||
1aabe9e524 | |||
ef5fc96d67 | |||
8821287cbe | |||
1de99306a2 | |||
c79451a851 | |||
caee16005d | |||
7ee500bf10 | |||
71f0cc14be | |||
358a7f8336 | |||
67af0c1e74 | |||
cf1476f641 | |||
beb9fc10ba | |||
fb76e6df08 | |||
c9d3b2cd12 | |||
776750578d | |||
d72214eb56 | |||
6abd2a9d52 | |||
f9e1e276f0 | |||
8883ff6400 | |||
8241196284 | |||
34b984a1b5 | |||
838947a7ca |
10 changed files with 694 additions and 6 deletions
|
@ -23,6 +23,7 @@ serde_json = "1.0"
|
||||||
simple_logger = "5.0.0"
|
simple_logger = "5.0.0"
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-native-tls", "postgres"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-native-tls", "postgres"] }
|
||||||
redis = { version = "0.30", features= ["tokio-comp"] }
|
redis = { version = "0.30", features= ["tokio-comp"] }
|
||||||
|
tokio-tungstenite = { version = "0.26", features = ["native-tls", "url"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
url = { version = "2.5", features = ["serde"] }
|
url = { version = "2.5", features = ["serde"] }
|
||||||
uuid = { version = "1.16", features = ["serde", "v7"] }
|
uuid = { version = "1.16", features = ["serde", "v7"] }
|
||||||
|
|
|
@ -3,10 +3,12 @@ use actix_web::{Scope, web};
|
||||||
mod auth;
|
mod auth;
|
||||||
mod stats;
|
mod stats;
|
||||||
mod users;
|
mod users;
|
||||||
|
mod servers;
|
||||||
|
|
||||||
pub fn web() -> Scope {
|
pub fn web() -> Scope {
|
||||||
web::scope("/v1")
|
web::scope("/v1")
|
||||||
.service(stats::res)
|
.service(stats::res)
|
||||||
.service(auth::web())
|
.service(auth::web())
|
||||||
.service(users::web())
|
.service(users::web())
|
||||||
|
.service(servers::web())
|
||||||
}
|
}
|
||||||
|
|
46
src/api/v1/servers/mod.rs
Normal file
46
src/api/v1/servers/mod.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use actix_web::{post, web, Error, HttpRequest, HttpResponse, Scope};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
mod uuid;
|
||||||
|
|
||||||
|
use crate::{api::v1::auth::check_access_token, structs::Guild, utils::get_auth_header, Data};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct GuildInfo {
|
||||||
|
name: String,
|
||||||
|
description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn web() -> Scope {
|
||||||
|
web::scope("/servers")
|
||||||
|
.service(res)
|
||||||
|
.service(uuid::web())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("")]
|
||||||
|
pub async fn res(req: HttpRequest, guild_info: web::Json<GuildInfo>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
let headers = req.headers();
|
||||||
|
|
||||||
|
let auth_header = get_auth_header(headers);
|
||||||
|
|
||||||
|
if let Err(error) = auth_header {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let authorized = check_access_token(auth_header.unwrap(), &data.pool).await;
|
||||||
|
|
||||||
|
if let Err(error) = authorized {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid = authorized.unwrap();
|
||||||
|
|
||||||
|
let guild = Guild::new(&data.pool, guild_info.name.clone(), guild_info.description.clone(), uuid).await;
|
||||||
|
|
||||||
|
if let Err(error) = guild {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(guild.unwrap()))
|
||||||
|
}
|
||||||
|
|
100
src/api/v1/servers/uuid/channels/mod.rs
Normal file
100
src/api/v1/servers/uuid/channels/mod.rs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
use actix_web::{get, post, web, Error, HttpRequest, HttpResponse};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use crate::{api::v1::auth::check_access_token, structs::{Channel, Member}, utils::get_auth_header, Data};
|
||||||
|
use ::uuid::Uuid;
|
||||||
|
use log::error;
|
||||||
|
|
||||||
|
pub mod uuid;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ChannelInfo {
|
||||||
|
name: String,
|
||||||
|
description: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("{uuid}/channels")]
|
||||||
|
pub async fn response(req: HttpRequest, path: web::Path<(Uuid,)>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
let headers = req.headers();
|
||||||
|
|
||||||
|
let auth_header = get_auth_header(headers);
|
||||||
|
|
||||||
|
if let Err(error) = auth_header {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let guild_uuid = path.into_inner().0;
|
||||||
|
|
||||||
|
let authorized = check_access_token(auth_header.unwrap(), &data.pool).await;
|
||||||
|
|
||||||
|
if let Err(error) = authorized {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid = authorized.unwrap();
|
||||||
|
|
||||||
|
let member = Member::fetch_one(&data.pool, uuid, guild_uuid).await;
|
||||||
|
|
||||||
|
if let Err(error) = member {
|
||||||
|
return Ok(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cache_result = data.get_cache_key(format!("{}_channels", guild_uuid)).await;
|
||||||
|
|
||||||
|
if let Ok(cache_hit) = cache_result {
|
||||||
|
return Ok(HttpResponse::Ok().content_type("application/json").body(cache_hit))
|
||||||
|
}
|
||||||
|
|
||||||
|
let channels_result = Channel::fetch_all(&data.pool, guild_uuid).await;
|
||||||
|
|
||||||
|
if let Err(error) = channels_result {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let channels = channels_result.unwrap();
|
||||||
|
|
||||||
|
let cache_result = data.set_cache_key(format!("{}_channels", guild_uuid), channels.clone(), 1800).await;
|
||||||
|
|
||||||
|
if let Err(error) = cache_result {
|
||||||
|
error!("{}", error);
|
||||||
|
return Ok(HttpResponse::InternalServerError().finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(channels))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("{uuid}/channels")]
|
||||||
|
pub async fn response_post(req: HttpRequest, channel_info: web::Json<ChannelInfo>, path: web::Path<(Uuid,)>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
let headers = req.headers();
|
||||||
|
|
||||||
|
let auth_header = get_auth_header(headers);
|
||||||
|
|
||||||
|
if let Err(error) = auth_header {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let guild_uuid = path.into_inner().0;
|
||||||
|
|
||||||
|
let authorized = check_access_token(auth_header.unwrap(), &data.pool).await;
|
||||||
|
|
||||||
|
if let Err(error) = authorized {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid = authorized.unwrap();
|
||||||
|
|
||||||
|
let member = Member::fetch_one(&data.pool, uuid, guild_uuid).await;
|
||||||
|
|
||||||
|
if let Err(error) = member {
|
||||||
|
return Ok(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Logic to check permissions, should probably be done in utils.rs
|
||||||
|
|
||||||
|
let channel = Channel::new(data.clone(), guild_uuid, channel_info.name.clone(), channel_info.description.clone()).await;
|
||||||
|
|
||||||
|
if let Err(error) = channel {
|
||||||
|
return Ok(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(channel.unwrap()))
|
||||||
|
}
|
0
src/api/v1/servers/uuid/channels/uuid/messages.rs
Normal file
0
src/api/v1/servers/uuid/channels/uuid/messages.rs
Normal file
56
src/api/v1/servers/uuid/channels/uuid/mod.rs
Normal file
56
src/api/v1/servers/uuid/channels/uuid/mod.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
mod messages;
|
||||||
|
|
||||||
|
use actix_web::{get, web, Error, HttpRequest, HttpResponse};
|
||||||
|
use crate::{api::v1::auth::check_access_token, structs::{Channel, Member}, utils::get_auth_header, Data};
|
||||||
|
use ::uuid::Uuid;
|
||||||
|
use log::error;
|
||||||
|
|
||||||
|
#[get("{uuid}/channels/{channel_uuid}")]
|
||||||
|
pub async fn res(req: HttpRequest, path: web::Path<(Uuid, Uuid)>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
let headers = req.headers();
|
||||||
|
|
||||||
|
let auth_header = get_auth_header(headers);
|
||||||
|
|
||||||
|
if let Err(error) = auth_header {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let (guild_uuid, channel_uuid) = path.into_inner();
|
||||||
|
|
||||||
|
let authorized = check_access_token(auth_header.unwrap(), &data.pool).await;
|
||||||
|
|
||||||
|
if let Err(error) = authorized {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid = authorized.unwrap();
|
||||||
|
|
||||||
|
let member = Member::fetch_one(&data.pool, uuid, guild_uuid).await;
|
||||||
|
|
||||||
|
if let Err(error) = member {
|
||||||
|
return Ok(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cache_result = data.get_cache_key(format!("{}", channel_uuid)).await;
|
||||||
|
|
||||||
|
if let Ok(cache_hit) = cache_result {
|
||||||
|
return Ok(HttpResponse::Ok().content_type("application/json").body(cache_hit))
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_result = Channel::fetch_one(&data.pool, guild_uuid, channel_uuid).await;
|
||||||
|
|
||||||
|
if let Err(error) = channel_result {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel = channel_result.unwrap();
|
||||||
|
|
||||||
|
let cache_result = data.set_cache_key(format!("{}", channel_uuid), channel.clone(), 60).await;
|
||||||
|
|
||||||
|
if let Err(error) = cache_result {
|
||||||
|
error!("{}", error);
|
||||||
|
return Ok(HttpResponse::InternalServerError().finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(channel))
|
||||||
|
}
|
50
src/api/v1/servers/uuid/mod.rs
Normal file
50
src/api/v1/servers/uuid/mod.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use actix_web::{get, web, Error, HttpRequest, HttpResponse, Scope};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
mod channels;
|
||||||
|
|
||||||
|
use crate::{api::v1::auth::check_access_token, structs::{Guild, Member}, utils::get_auth_header, Data};
|
||||||
|
|
||||||
|
pub fn web() -> Scope {
|
||||||
|
web::scope("")
|
||||||
|
.service(res)
|
||||||
|
.service(channels::response)
|
||||||
|
.service(channels::uuid::res)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{uuid}")]
|
||||||
|
pub async fn res(req: HttpRequest, path: web::Path<(Uuid,)>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
let headers = req.headers();
|
||||||
|
|
||||||
|
let auth_header = get_auth_header(headers);
|
||||||
|
|
||||||
|
if let Err(error) = auth_header {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let guild_uuid = path.into_inner().0;
|
||||||
|
|
||||||
|
let authorized = check_access_token(auth_header.unwrap(), &data.pool).await;
|
||||||
|
|
||||||
|
if let Err(error) = authorized {
|
||||||
|
return Ok(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid = authorized.unwrap();
|
||||||
|
|
||||||
|
let member = Member::fetch_one(&data.pool, uuid, guild_uuid).await;
|
||||||
|
|
||||||
|
if let Err(error) = member {
|
||||||
|
return Ok(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let guild = Guild::fetch_one(&data.pool, guild_uuid).await;
|
||||||
|
|
||||||
|
if let Err(error) = guild {
|
||||||
|
return Ok(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(guild.unwrap()))
|
||||||
|
}
|
||||||
|
|
86
src/main.rs
86
src/main.rs
|
@ -10,6 +10,7 @@ use config::{Config, ConfigBuilder};
|
||||||
mod api;
|
mod api;
|
||||||
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
pub mod structs;
|
||||||
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ struct Args {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Data {
|
pub struct Data {
|
||||||
pub pool: Pool<Postgres>,
|
pub pool: Pool<Postgres>,
|
||||||
pub cache_pool: redis::Client,
|
pub cache_pool: redis::Client,
|
||||||
pub _config: Config,
|
pub _config: Config,
|
||||||
|
@ -50,17 +51,29 @@ async fn main() -> Result<(), Error> {
|
||||||
/*
|
/*
|
||||||
TODO: Figure out if a table should be used here and if not then what.
|
TODO: Figure out if a table should be used here and if not then what.
|
||||||
Also figure out if these should be different types from what they currently are and if we should add more "constraints"
|
Also figure out if these should be different types from what they currently are and if we should add more "constraints"
|
||||||
|
|
||||||
|
TODO: References to time should be removed in favor of using the timestamp built in to UUIDv7 (apart from deleted_at in users)
|
||||||
*/
|
*/
|
||||||
sqlx::raw_sql(
|
sqlx::raw_sql(
|
||||||
r#"
|
r#"
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
uuid uuid PRIMARY KEY UNIQUE NOT NULL,
|
uuid uuid PRIMARY KEY NOT NULL,
|
||||||
username varchar(32) UNIQUE NOT NULL,
|
username varchar(32) NOT NULL,
|
||||||
display_name varchar(64) DEFAULT NULL,
|
display_name varchar(64) DEFAULT NULL,
|
||||||
password varchar(512) NOT NULL,
|
password varchar(512) NOT NULL,
|
||||||
email varchar(100) UNIQUE NOT NULL,
|
email varchar(100) NOT NULL,
|
||||||
email_verified boolean NOT NULL DEFAULT FALSE
|
email_verified boolean NOT NULL DEFAULT FALSE,
|
||||||
|
is_deleted boolean NOT NULL DEFAULT FALSE,
|
||||||
|
deleted_at int8 DEFAULT NULL,
|
||||||
|
CONSTRAINT unique_username_active UNIQUE NULLS NOT DISTINCT (username, is_deleted),
|
||||||
|
CONSTRAINT unique_email_active UNIQUE NULLS NOT DISTINCT (email, is_deleted)
|
||||||
);
|
);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_username_active
|
||||||
|
ON users(username)
|
||||||
|
WHERE is_deleted = FALSE;
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_email_active
|
||||||
|
ON users(email)
|
||||||
|
WHERE is_deleted = FALSE;
|
||||||
CREATE TABLE IF NOT EXISTS instance_permissions (
|
CREATE TABLE IF NOT EXISTS instance_permissions (
|
||||||
uuid uuid NOT NULL REFERENCES users(uuid),
|
uuid uuid NOT NULL REFERENCES users(uuid),
|
||||||
administrator boolean NOT NULL DEFAULT FALSE
|
administrator boolean NOT NULL DEFAULT FALSE
|
||||||
|
@ -76,12 +89,73 @@ async fn main() -> Result<(), Error> {
|
||||||
refresh_token varchar(64) UNIQUE NOT NULL REFERENCES refresh_tokens(token) ON UPDATE CASCADE ON DELETE CASCADE,
|
refresh_token varchar(64) UNIQUE NOT NULL REFERENCES refresh_tokens(token) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
uuid uuid NOT NULL REFERENCES users(uuid),
|
uuid uuid NOT NULL REFERENCES users(uuid),
|
||||||
created_at int8 NOT NULL
|
created_at int8 NOT NULL
|
||||||
)
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS guilds (
|
||||||
|
uuid uuid PRIMARY KEY NOT NULL,
|
||||||
|
owner_uuid uuid NOT NULL REFERENCES users(uuid),
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
description VARCHAR(300)
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS guild_members (
|
||||||
|
uuid uuid PRIMARY KEY NOT NULL,
|
||||||
|
guild_uuid uuid NOT NULL REFERENCES guilds(uuid) ON DELETE CASCADE,
|
||||||
|
user_uuid uuid NOT NULL REFERENCES users(uuid),
|
||||||
|
nickname VARCHAR(100) DEFAULT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS roles (
|
||||||
|
uuid uuid UNIQUE NOT NULL,
|
||||||
|
guild_uuid uuid NOT NULL REFERENCES guilds(uuid) ON DELETE CASCADE,
|
||||||
|
name VARCHAR(50) NOT NULL,
|
||||||
|
color int NOT NULL DEFAULT 16777215,
|
||||||
|
position int NOT NULL,
|
||||||
|
permissions int8 NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (uuid, guild_uuid)
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS role_members (
|
||||||
|
role_uuid uuid NOT NULL REFERENCES roles(uuid) ON DELETE CASCADE,
|
||||||
|
member_uuid uuid NOT NULL REFERENCES guild_members(uuid) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (role_uuid, member_uuid)
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS channels (
|
||||||
|
uuid uuid PRIMARY KEY NOT NULL,
|
||||||
|
guild_uuid uuid NOT NULL REFERENCES guilds(uuid) ON DELETE CASCADE,
|
||||||
|
name varchar(32) NOT NULL,
|
||||||
|
description varchar(500) NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS channel_permissions (
|
||||||
|
channel_uuid uuid NOT NULL REFERENCES channels(uuid) ON DELETE CASCADE,
|
||||||
|
role_uuid uuid NOT NULL REFERENCES roles(uuid) ON DELETE CASCADE,
|
||||||
|
permissions int8 NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (channel_uuid, role_uuid)
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
uuid uuid PRIMARY KEY NOT NULL,
|
||||||
|
channel_uuid uuid NOT NULL REFERENCES channels(uuid) ON DELETE CASCADE,
|
||||||
|
user_uuid uuid NOT NULL REFERENCES users(uuid),
|
||||||
|
message varchar(4000) NOT NULL
|
||||||
|
);
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
**Stored for later possible use**
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS emojis (
|
||||||
|
uuid uuid PRIMARY KEY NOT NULL,
|
||||||
|
name varchar(32) NOT NULL,
|
||||||
|
guild_uuid uuid REFERENCES guilds(uuid) ON DELETE SET NULL,
|
||||||
|
deleted boolean DEFAULT FALSE
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS message_reactions (
|
||||||
|
message_uuid uuid NOT NULL REFERENCES messages(uuid),
|
||||||
|
user_uuid uuid NOT NULL REFERENCES users(uuid),
|
||||||
|
emoji_uuid uuid NOT NULL REFERENCES emojis(uuid),
|
||||||
|
PRIMARY KEY (message_uuid, user_uuid, emoji_uuid)
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
|
||||||
let data = Data {
|
let data = Data {
|
||||||
pool,
|
pool,
|
||||||
cache_pool,
|
cache_pool,
|
||||||
|
|
351
src/structs.rs
Normal file
351
src/structs.rs
Normal file
|
@ -0,0 +1,351 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
use sqlx::{prelude::FromRow, Pool, Postgres};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use log::error;
|
||||||
|
|
||||||
|
use crate::Data;
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct Channel {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub guild_uuid: Uuid,
|
||||||
|
name: String,
|
||||||
|
description: Option<String>,
|
||||||
|
pub permissions: Vec<ChannelPermission>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, FromRow)]
|
||||||
|
struct ChannelPermissionBuilder {
|
||||||
|
role_uuid: String,
|
||||||
|
permissions: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChannelPermissionBuilder {
|
||||||
|
fn build(&self) -> ChannelPermission {
|
||||||
|
ChannelPermission {
|
||||||
|
role_uuid: Uuid::from_str(&self.role_uuid).unwrap(),
|
||||||
|
permissions: self.permissions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone, FromRow)]
|
||||||
|
pub struct ChannelPermission {
|
||||||
|
pub role_uuid: Uuid,
|
||||||
|
pub permissions: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Channel {
|
||||||
|
pub async fn fetch_all(pool: &Pool<Postgres>, guild_uuid: Uuid) -> Result<Vec<Self>, HttpResponse> {
|
||||||
|
let row = sqlx::query_as(&format!("SELECT CAST(uuid AS VARCHAR), name, description FROM channels WHERE guild_uuid = '{}'", guild_uuid))
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
let channels: Vec<(String, String, Option<String>)> = row.unwrap();
|
||||||
|
|
||||||
|
let futures = channels.iter().map(async |t| {
|
||||||
|
let (uuid, name, description) = t.to_owned();
|
||||||
|
|
||||||
|
let row = sqlx::query_as(&format!("SELECT CAST(role_uuid AS VARCHAR), permissions FROM channel_permissions WHERE channel_uuid = '{}'", uuid))
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_permission_builders: Vec<ChannelPermissionBuilder> = row.unwrap();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uuid: Uuid::from_str(&uuid).unwrap(),
|
||||||
|
guild_uuid,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
permissions: channel_permission_builders.iter().map(|b| b.build()).collect(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let channels = futures::future::join_all(futures).await;
|
||||||
|
|
||||||
|
let channels: Result<Vec<Channel>, HttpResponse> = channels.into_iter().collect();
|
||||||
|
|
||||||
|
Ok(channels?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_one(pool: &Pool<Postgres>, guild_uuid: Uuid, channel_uuid: Uuid) -> Result<Self, HttpResponse> {
|
||||||
|
let row = sqlx::query_as(&format!("SELECT name, description FROM channels WHERE guild_uuid = '{}' AND uuid = '{}'", guild_uuid, channel_uuid))
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
let (name, description): (String, Option<String>) = row.unwrap();
|
||||||
|
|
||||||
|
let row = sqlx::query_as(&format!("SELECT CAST(role_uuid AS VARCHAR), permissions FROM channel_permissions WHERE channel_uuid = '{}'", channel_uuid))
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_permission_builders: Vec<ChannelPermissionBuilder> = row.unwrap();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uuid: channel_uuid,
|
||||||
|
guild_uuid,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
permissions: channel_permission_builders.iter().map(|b| b.build()).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new(data: actix_web::web::Data<Data>, guild_uuid: Uuid, name: String, description: Option<String>) -> Result<Self, HttpResponse> {
|
||||||
|
let channel_uuid = Uuid::now_v7();
|
||||||
|
|
||||||
|
let row = sqlx::query(&format!("INSERT INTO channels (uuid, guild_uuid, name, description) VALUES ('{}', '{}', $1, $2)", channel_uuid, guild_uuid))
|
||||||
|
.bind(&name)
|
||||||
|
.bind(&description)
|
||||||
|
.execute(&data.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel = Self {
|
||||||
|
uuid: channel_uuid,
|
||||||
|
guild_uuid,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
permissions: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let cache_result = data.set_cache_key(channel_uuid.to_string(), channel.clone(), 1800).await;
|
||||||
|
|
||||||
|
if let Err(error) = cache_result {
|
||||||
|
error!("{}", error);
|
||||||
|
return Err(HttpResponse::InternalServerError().finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cache_deletion_result = data.del_cache_key(format!("{}_channels", guild_uuid)).await;
|
||||||
|
|
||||||
|
if let Err(error) = cache_deletion_result {
|
||||||
|
error!("{}", error);
|
||||||
|
return Err(HttpResponse::InternalServerError().finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Permissions {
|
||||||
|
SendMessage = 1,
|
||||||
|
CreateChannel = 2,
|
||||||
|
DeleteChannel = 4,
|
||||||
|
ManageChannel = 8,
|
||||||
|
CreateRole = 16,
|
||||||
|
DeleteRole = 32,
|
||||||
|
ManageRole = 64,
|
||||||
|
CreateInvite = 128,
|
||||||
|
ManageInvite = 256,
|
||||||
|
ManageServer = 512,
|
||||||
|
ManageMember = 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Permissions {
|
||||||
|
pub fn fetch_permissions(permissions: i64) -> Vec<Self> {
|
||||||
|
let all_perms = vec![
|
||||||
|
Self::SendMessage,
|
||||||
|
Self::CreateChannel,
|
||||||
|
Self::DeleteChannel,
|
||||||
|
Self::ManageChannel,
|
||||||
|
Self::CreateRole,
|
||||||
|
Self::DeleteRole,
|
||||||
|
Self::ManageRole,
|
||||||
|
Self::CreateInvite,
|
||||||
|
Self::ManageInvite,
|
||||||
|
Self::ManageServer,
|
||||||
|
Self::ManageMember,
|
||||||
|
];
|
||||||
|
|
||||||
|
all_perms.into_iter()
|
||||||
|
.filter(|p| permissions & (*p as i64) != 0)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Guild {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
name: String,
|
||||||
|
description: Option<String>,
|
||||||
|
icon: String,
|
||||||
|
owner_uuid: Uuid,
|
||||||
|
pub roles: Vec<Role>,
|
||||||
|
member_count: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Guild {
|
||||||
|
pub async fn fetch_one(pool: &Pool<Postgres>, guild_uuid: Uuid) -> Result<Self, HttpResponse> {
|
||||||
|
let row = sqlx::query_as(&format!("SELECT CAST(owner_uuid AS VARCHAR), name, description FROM guilds WHERE uuid = '{}'", guild_uuid))
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
let (owner_uuid_raw, name, description): (String, String, Option<String>) = row.unwrap();
|
||||||
|
|
||||||
|
let owner_uuid = Uuid::from_str(&owner_uuid_raw).unwrap();
|
||||||
|
|
||||||
|
let member_count = Member::count(pool, guild_uuid).await?;
|
||||||
|
|
||||||
|
let roles = Role::fetch_all(pool, guild_uuid).await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uuid: guild_uuid,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
// FIXME: This isnt supposed to be bogus
|
||||||
|
icon: String::from("bogus"),
|
||||||
|
owner_uuid,
|
||||||
|
roles,
|
||||||
|
member_count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new(pool: &Pool<Postgres>, name: String, description: Option<String>, owner_uuid: Uuid) -> Result<Self, HttpResponse> {
|
||||||
|
let guild_uuid = Uuid::now_v7();
|
||||||
|
|
||||||
|
let row = sqlx::query(&format!("INSERT INTO guilds (uuid, owner_uuid, name, description) VALUES ('{}', '{}', $1, $2)", guild_uuid, owner_uuid))
|
||||||
|
.bind(&name)
|
||||||
|
.bind(&description)
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
let row = sqlx::query(&format!("INSERT INTO guild_members (uuid, guild_uuid, user_uuid) VALUES ('{}', '{}', '{}')", Uuid::now_v7(), guild_uuid, owner_uuid))
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
let row = sqlx::query(&format!("DELETE FROM guilds WHERE uuid = '{}'", guild_uuid))
|
||||||
|
.execute(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Guild {
|
||||||
|
uuid: guild_uuid,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon: "bogus".to_string(),
|
||||||
|
owner_uuid,
|
||||||
|
roles: vec![],
|
||||||
|
member_count: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, FromRow)]
|
||||||
|
pub struct Role {
|
||||||
|
uuid: String,
|
||||||
|
name: String,
|
||||||
|
color: i64,
|
||||||
|
position: i32,
|
||||||
|
permissions: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Role {
|
||||||
|
pub async fn fetch_all(pool: &Pool<Postgres>, guild_uuid: Uuid) -> Result<Vec<Self>, HttpResponse> {
|
||||||
|
let roles = sqlx::query_as(&format!("SELECT (uuid, name, color, position, permissions) FROM roles WHERE guild_uuid = '{}'", guild_uuid))
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = roles {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(roles.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Member {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub nickname: String,
|
||||||
|
pub user_uuid: Uuid,
|
||||||
|
pub guild_uuid: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Member {
|
||||||
|
async fn count(pool: &Pool<Postgres>, guild_uuid: Uuid) -> Result<i64, HttpResponse> {
|
||||||
|
let member_count = sqlx::query_scalar(&format!("SELECT COUNT(uuid) FROM guild_members WHERE guild_uuid = '{}'", guild_uuid))
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = member_count {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(member_count.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_one(pool: &Pool<Postgres>, user_uuid: Uuid, guild_uuid: Uuid) -> Result<Self, HttpResponse> {
|
||||||
|
let row = sqlx::query_as(&format!("SELECT CAST(uuid AS VARCHAR), nickname FROM guild_members WHERE guild_uuid = '{}' AND user_uuid = '{}'", guild_uuid, user_uuid))
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = row {
|
||||||
|
error!("{}", error);
|
||||||
|
|
||||||
|
return Err(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
let (uuid, nickname): (String, String) = row.unwrap();
|
||||||
|
|
||||||
|
Ok(Member {
|
||||||
|
uuid: Uuid::from_str(&uuid).unwrap(),
|
||||||
|
nickname,
|
||||||
|
user_uuid,
|
||||||
|
guild_uuid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,5 +70,13 @@ impl Data {
|
||||||
|
|
||||||
redis::cmd("GET").arg(key_encoded).query_async(&mut conn).await
|
redis::cmd("GET").arg(key_encoded).query_async(&mut conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn del_cache_key(&self, key: String) -> Result<(), RedisError> {
|
||||||
|
let mut conn = self.cache_pool.get_multiplexed_tokio_connection().await?;
|
||||||
|
|
||||||
|
let key_encoded = encode(key);
|
||||||
|
|
||||||
|
redis::cmd("DEL").arg(key_encoded).query_async(&mut conn).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue