Compare commits
No commits in common. "cee1b41e89eb0c31d71e04428be0b779bee8b48a" and "a6769623167f3cc5ae63bf258aeeed9d2cc7ed5e" have entirely different histories.
cee1b41e89
...
a676962316
6 changed files with 10 additions and 193 deletions
|
@ -30,8 +30,6 @@ uuid = { version = "1.16", features = ["serde", "v7"] }
|
||||||
random-string = "1.1"
|
random-string = "1.1"
|
||||||
actix-ws = "0.3.0"
|
actix-ws = "0.3.0"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
bunny-api-tokio = "0.2.1"
|
|
||||||
bindet = "0.3.2"
|
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.44"
|
version = "1.44"
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
use actix_web::{put, web, Error, HttpRequest, HttpResponse};
|
|
||||||
use uuid::Uuid;
|
|
||||||
use futures_util::StreamExt as _;
|
|
||||||
|
|
||||||
use crate::{api::v1::auth::check_access_token, structs::{Guild, Member}, utils::get_auth_header, Data};
|
|
||||||
|
|
||||||
#[put("{uuid}/icon")]
|
|
||||||
pub async fn upload(
|
|
||||||
req: HttpRequest,
|
|
||||||
path: web::Path<(Uuid,)>,
|
|
||||||
mut payload: web::Payload,
|
|
||||||
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_result = Guild::fetch_one(&data.pool, guild_uuid).await;
|
|
||||||
|
|
||||||
if let Err(error) = guild_result {
|
|
||||||
return Ok(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut guild = guild_result.unwrap();
|
|
||||||
|
|
||||||
let mut bytes = web::BytesMut::new();
|
|
||||||
while let Some(item) = payload.next().await {
|
|
||||||
bytes.extend_from_slice(&item?);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(error) = guild.set_icon(&data.bunny_cdn, &data.pool, data.config.bunny.cdn_url.clone(), bytes).await {
|
|
||||||
return Ok(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ use uuid::Uuid;
|
||||||
mod channels;
|
mod channels;
|
||||||
mod invites;
|
mod invites;
|
||||||
mod roles;
|
mod roles;
|
||||||
mod icon;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Data,
|
Data,
|
||||||
|
@ -31,8 +30,6 @@ pub fn web() -> Scope {
|
||||||
// Invites
|
// Invites
|
||||||
.service(invites::get)
|
.service(invites::get)
|
||||||
.service(invites::create)
|
.service(invites::create)
|
||||||
// Icon
|
|
||||||
.service(icon::upload)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/{uuid}")]
|
#[get("/{uuid}")]
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use bunny_api_tokio::edge_storage::Endpoint;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::postgres::PgConnectOptions;
|
use sqlx::postgres::PgConnectOptions;
|
||||||
use tokio::fs::read_to_string;
|
use tokio::fs::read_to_string;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ConfigBuilder {
|
pub struct ConfigBuilder {
|
||||||
database: Database,
|
database: Database,
|
||||||
cache_database: CacheDatabase,
|
cache_database: CacheDatabase,
|
||||||
web: Option<WebBuilder>,
|
web: Option<WebBuilder>,
|
||||||
bunny: BunnyBuilder,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
@ -39,14 +36,6 @@ struct WebBuilder {
|
||||||
_ssl: Option<bool>,
|
_ssl: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct BunnyBuilder {
|
|
||||||
api_key: String,
|
|
||||||
endpoint: String,
|
|
||||||
storage_zone: String,
|
|
||||||
cdn_url: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigBuilder {
|
impl ConfigBuilder {
|
||||||
pub async fn load(path: String) -> Result<Self, Error> {
|
pub async fn load(path: String) -> Result<Self, Error> {
|
||||||
debug!("loading config from: {}", path);
|
debug!("loading config from: {}", path);
|
||||||
|
@ -70,31 +59,10 @@ impl ConfigBuilder {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let endpoint = match &*self.bunny.endpoint {
|
|
||||||
"Frankfurt" => Endpoint::Frankfurt,
|
|
||||||
"London" => Endpoint::London,
|
|
||||||
"New York" => Endpoint::NewYork,
|
|
||||||
"Los Angeles" => Endpoint::LosAngeles,
|
|
||||||
"Singapore" => Endpoint::Singapore,
|
|
||||||
"Stockholm" => Endpoint::Stockholm,
|
|
||||||
"Sao Paulo" => Endpoint::SaoPaulo,
|
|
||||||
"Johannesburg" => Endpoint::Johannesburg,
|
|
||||||
"Sydney" => Endpoint::Sydney,
|
|
||||||
url => Endpoint::Custom(url.to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let bunny = Bunny {
|
|
||||||
api_key: self.bunny.api_key,
|
|
||||||
endpoint,
|
|
||||||
storage_zone: self.bunny.storage_zone,
|
|
||||||
cdn_url: self.bunny.cdn_url,
|
|
||||||
};
|
|
||||||
|
|
||||||
Config {
|
Config {
|
||||||
database: self.database,
|
database: self.database,
|
||||||
cache_database: self.cache_database,
|
cache_database: self.cache_database,
|
||||||
web,
|
web,
|
||||||
bunny,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +72,6 @@ pub struct Config {
|
||||||
pub database: Database,
|
pub database: Database,
|
||||||
pub cache_database: CacheDatabase,
|
pub cache_database: CacheDatabase,
|
||||||
pub web: Web,
|
pub web: Web,
|
||||||
pub bunny: Bunny,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -113,14 +80,6 @@ pub struct Web {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Bunny {
|
|
||||||
pub api_key: String,
|
|
||||||
pub endpoint: Endpoint,
|
|
||||||
pub storage_zone: String,
|
|
||||||
pub cdn_url: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
pub fn connect_options(&self) -> PgConnectOptions {
|
pub fn connect_options(&self) -> PgConnectOptions {
|
||||||
PgConnectOptions::new()
|
PgConnectOptions::new()
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -25,10 +25,9 @@ struct Args {
|
||||||
pub 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,
|
||||||
pub argon2: Argon2<'static>,
|
pub argon2: Argon2<'static>,
|
||||||
pub start_time: SystemTime,
|
pub start_time: SystemTime,
|
||||||
pub bunny_cdn: bunny_api_tokio::Client,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -49,10 +48,6 @@ async fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
let cache_pool = redis::Client::open(config.cache_database.url())?;
|
let cache_pool = redis::Client::open(config.cache_database.url())?;
|
||||||
|
|
||||||
let mut bunny_cdn = bunny_api_tokio::Client::new(config.bunny.api_key.clone()).await?;
|
|
||||||
|
|
||||||
bunny_cdn.storage.init(config.bunny.endpoint.clone(), config.bunny.storage_zone.clone())?;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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"
|
||||||
|
@ -99,8 +94,7 @@ async fn main() -> Result<(), Error> {
|
||||||
uuid uuid PRIMARY KEY NOT NULL,
|
uuid uuid PRIMARY KEY NOT NULL,
|
||||||
owner_uuid uuid NOT NULL REFERENCES users(uuid),
|
owner_uuid uuid NOT NULL REFERENCES users(uuid),
|
||||||
name VARCHAR(100) NOT NULL,
|
name VARCHAR(100) NOT NULL,
|
||||||
description VARCHAR(300),
|
description VARCHAR(300)
|
||||||
icon VARCHAR(100) DEFAULT NULL
|
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS guild_members (
|
CREATE TABLE IF NOT EXISTS guild_members (
|
||||||
uuid uuid PRIMARY KEY NOT NULL,
|
uuid uuid PRIMARY KEY NOT NULL,
|
||||||
|
@ -170,11 +164,10 @@ async fn main() -> Result<(), Error> {
|
||||||
let data = Data {
|
let data = Data {
|
||||||
pool,
|
pool,
|
||||||
cache_pool,
|
cache_pool,
|
||||||
config,
|
_config: config,
|
||||||
// TODO: Possibly implement "pepper" into this (thinking it could generate one if it doesnt exist and store it on disk)
|
// TODO: Possibly implement "pepper" into this (thinking it could generate one if it doesnt exist and store it on disk)
|
||||||
argon2: Argon2::default(),
|
argon2: Argon2::default(),
|
||||||
start_time: SystemTime::now(),
|
start_time: SystemTime::now(),
|
||||||
bunny_cdn,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use actix_web::{web::BytesMut, HttpResponse};
|
use actix_web::HttpResponse;
|
||||||
use bindet::FileType;
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{Pool, Postgres, prelude::FromRow};
|
use sqlx::{Pool, Postgres, prelude::FromRow};
|
||||||
use tokio::task;
|
|
||||||
use url::Url;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
@ -291,7 +288,7 @@ pub struct Guild {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
name: String,
|
name: String,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
icon: Option<String>,
|
icon: String,
|
||||||
owner_uuid: Uuid,
|
owner_uuid: Uuid,
|
||||||
pub roles: Vec<Role>,
|
pub roles: Vec<Role>,
|
||||||
member_count: i64,
|
member_count: i64,
|
||||||
|
@ -300,7 +297,7 @@ pub struct Guild {
|
||||||
impl Guild {
|
impl Guild {
|
||||||
pub async fn fetch_one(pool: &Pool<Postgres>, guild_uuid: Uuid) -> Result<Self, HttpResponse> {
|
pub async fn fetch_one(pool: &Pool<Postgres>, guild_uuid: Uuid) -> Result<Self, HttpResponse> {
|
||||||
let row = sqlx::query_as(&format!(
|
let row = sqlx::query_as(&format!(
|
||||||
"SELECT CAST(owner_uuid AS VARCHAR), name, description, icon FROM guilds WHERE uuid = '{}'",
|
"SELECT CAST(owner_uuid AS VARCHAR), name, description FROM guilds WHERE uuid = '{}'",
|
||||||
guild_uuid
|
guild_uuid
|
||||||
))
|
))
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
|
@ -312,7 +309,7 @@ impl Guild {
|
||||||
return Err(HttpResponse::InternalServerError().finish());
|
return Err(HttpResponse::InternalServerError().finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (owner_uuid_raw, name, description, icon): (String, String, Option<String>, Option<String>) = row.unwrap();
|
let (owner_uuid_raw, name, description): (String, String, Option<String>) = row.unwrap();
|
||||||
|
|
||||||
let owner_uuid = Uuid::from_str(&owner_uuid_raw).unwrap();
|
let owner_uuid = Uuid::from_str(&owner_uuid_raw).unwrap();
|
||||||
|
|
||||||
|
@ -324,7 +321,8 @@ impl Guild {
|
||||||
uuid: guild_uuid,
|
uuid: guild_uuid,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
icon,
|
// FIXME: This isnt supposed to be bogus
|
||||||
|
icon: String::from("bogus"),
|
||||||
owner_uuid,
|
owner_uuid,
|
||||||
roles,
|
roles,
|
||||||
member_count,
|
member_count,
|
||||||
|
@ -380,7 +378,7 @@ impl Guild {
|
||||||
uuid: guild_uuid,
|
uuid: guild_uuid,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
icon: None,
|
icon: "bogus".to_string(),
|
||||||
owner_uuid,
|
owner_uuid,
|
||||||
roles: vec![],
|
roles: vec![],
|
||||||
member_count: 1,
|
member_count: 1,
|
||||||
|
@ -445,78 +443,6 @@ impl Guild {
|
||||||
guild_uuid: self.uuid,
|
guild_uuid: self.uuid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Horrible security
|
|
||||||
pub async fn set_icon(&mut self, bunny_cdn: &bunny_api_tokio::Client, pool: &Pool<Postgres>, cdn_url: Url, icon: BytesMut) -> Result<(), HttpResponse> {
|
|
||||||
let ico = icon.clone();
|
|
||||||
|
|
||||||
let result = task::spawn_blocking(move || {
|
|
||||||
let buf = std::io::Cursor::new(ico.to_vec());
|
|
||||||
|
|
||||||
let detect = bindet::detect(buf).map_err(|e| e.kind());
|
|
||||||
|
|
||||||
if let Ok(Some(file_type)) = detect {
|
|
||||||
if file_type.likely_to_be == vec![FileType::Jpg] {
|
|
||||||
return String::from("jpg")
|
|
||||||
} else if file_type.likely_to_be == vec![FileType::Png] {
|
|
||||||
return String::from("png")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String::from("unknown")
|
|
||||||
}).await;
|
|
||||||
|
|
||||||
if let Err(error) = result {
|
|
||||||
error!("{}", error);
|
|
||||||
|
|
||||||
return Err(HttpResponse::InternalServerError().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
let image_type = result.unwrap();
|
|
||||||
|
|
||||||
if image_type == "unknown" {
|
|
||||||
return Err(HttpResponse::BadRequest().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(icon) = &self.icon {
|
|
||||||
let relative_url = icon.trim_start_matches("https://cdn.gorb.app/");
|
|
||||||
|
|
||||||
let delete_result = bunny_cdn.storage.delete(relative_url).await;
|
|
||||||
|
|
||||||
if let Err(error) = delete_result {
|
|
||||||
error!("{}", error);
|
|
||||||
|
|
||||||
return Err(HttpResponse::InternalServerError().finish())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = format!("icons/{}/icon.{}", self.uuid, image_type);
|
|
||||||
|
|
||||||
let upload_result = bunny_cdn.storage.upload(path.clone(), icon.into()).await;
|
|
||||||
|
|
||||||
if let Err(error) = upload_result {
|
|
||||||
error!("{}", error);
|
|
||||||
|
|
||||||
return Err(HttpResponse::InternalServerError().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let icon_url = cdn_url.join(&path).unwrap();
|
|
||||||
|
|
||||||
let row = sqlx::query(&format!("UPDATE guilds SET icon = $1 WHERE uuid = '{}'", self.uuid))
|
|
||||||
.bind(icon_url.as_str())
|
|
||||||
.execute(pool)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(error) = row {
|
|
||||||
error!("{}", error);
|
|
||||||
|
|
||||||
return Err(HttpResponse::InternalServerError().finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
self.icon = Some(icon_url.to_string());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromRow)]
|
#[derive(FromRow)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue