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"
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-native-tls", "postgres"] }
|
||||
redis = { version = "0.30", features= ["tokio-comp"] }
|
||||
tokio-tungstenite = { version = "0.26", features = ["native-tls", "url"] }
|
||||
toml = "0.8"
|
||||
url = { version = "2.5", features = ["serde"] }
|
||||
uuid = { version = "1.16", features = ["serde", "v7"] }
|
||||
|
|
|
@ -3,10 +3,12 @@ use actix_web::{Scope, web};
|
|||
mod auth;
|
||||
mod stats;
|
||||
mod users;
|
||||
mod servers;
|
||||
|
||||
pub fn web() -> Scope {
|
||||
web::scope("/v1")
|
||||
.service(stats::res)
|
||||
.service(auth::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;
|
||||
|
||||
pub mod utils;
|
||||
pub mod structs;
|
||||
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
|
||||
|
@ -21,7 +22,7 @@ struct Args {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Data {
|
||||
pub struct Data {
|
||||
pub pool: Pool<Postgres>,
|
||||
pub cache_pool: redis::Client,
|
||||
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.
|
||||
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(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
uuid uuid PRIMARY KEY UNIQUE NOT NULL,
|
||||
username varchar(32) UNIQUE NOT NULL,
|
||||
uuid uuid PRIMARY KEY NOT NULL,
|
||||
username varchar(32) NOT NULL,
|
||||
display_name varchar(64) DEFAULT NULL,
|
||||
password varchar(512) NOT NULL,
|
||||
email varchar(100) UNIQUE NOT NULL,
|
||||
email_verified boolean NOT NULL DEFAULT FALSE
|
||||
email varchar(100) NOT NULL,
|
||||
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 (
|
||||
uuid uuid NOT NULL REFERENCES users(uuid),
|
||||
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,
|
||||
uuid uuid NOT NULL REFERENCES users(uuid),
|
||||
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)
|
||||
.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 {
|
||||
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
|
||||
}
|
||||
|
||||
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