Compare commits
No commits in common. "860fa7a66e61b7270824a2da827440f8fa71568e" and "82ac501519ed4493ebcaec9edbaecabc18bbb4fa" have entirely different histories.
860fa7a66e
...
82ac501519
16 changed files with 87 additions and 335 deletions
|
@ -29,14 +29,11 @@ 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"
|
|
||||||
deadpool = "0.12"
|
deadpool = "0.12"
|
||||||
diesel = { version = "2.2", features = ["uuid"] }
|
diesel = { version = "2.2", features = ["uuid"] }
|
||||||
diesel-async = { version = "0.5", features = ["deadpool", "postgres", "async-connection-wrapper"] }
|
diesel-async = { version = "0.5", features = ["deadpool", "postgres", "async-connection-wrapper"] }
|
||||||
diesel_migrations = { version = "2.2.0", features = ["postgres"] }
|
diesel_migrations = { version = "2.2.0", features = ["postgres"] }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
actix-multipart = "0.7.2"
|
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.44"
|
version = "1.44"
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
-- This file should undo anything in `up.sql`
|
|
||||||
ALTER TABLE guilds DROP COLUMN icon;
|
|
|
@ -1,2 +0,0 @@
|
||||||
-- Your SQL goes here
|
|
||||||
ALTER TABLE guilds ADD COLUMN icon VARCHAR(100) DEFAULT NULL;
|
|
|
@ -1,2 +0,0 @@
|
||||||
-- This file should undo anything in `up.sql`
|
|
||||||
ALTER TABLE users DROP COLUMN avatar;
|
|
|
@ -1,2 +0,0 @@
|
||||||
-- Your SQL goes here
|
|
||||||
ALTER TABLE users ADD COLUMN avatar varchar(100) DEFAULT NULL;
|
|
|
@ -1,36 +0,0 @@
|
||||||
use actix_web::{put, web, HttpRequest, HttpResponse};
|
|
||||||
use uuid::Uuid;
|
|
||||||
use futures_util::StreamExt as _;
|
|
||||||
|
|
||||||
use crate::{error::Error, 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)?;
|
|
||||||
|
|
||||||
let guild_uuid = path.into_inner().0;
|
|
||||||
|
|
||||||
let mut conn = data.pool.get().await?;
|
|
||||||
|
|
||||||
let uuid = check_access_token(auth_header, &mut conn).await?;
|
|
||||||
|
|
||||||
Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
|
|
||||||
|
|
||||||
let mut guild = Guild::fetch_one(&mut conn, guild_uuid).await?;
|
|
||||||
|
|
||||||
let mut bytes = web::BytesMut::new();
|
|
||||||
while let Some(item) = payload.next().await {
|
|
||||||
bytes.extend_from_slice(&item?);
|
|
||||||
}
|
|
||||||
|
|
||||||
guild.set_icon(&data.bunny_cdn, &mut conn, data.config.bunny.cdn_url.clone(), bytes).await?;
|
|
||||||
|
|
||||||
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::{
|
||||||
error::Error,
|
error::Error,
|
||||||
|
@ -32,8 +31,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,8 +1,20 @@
|
||||||
use actix_web::{get, patch, web, HttpRequest, HttpResponse};
|
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||||
use actix_multipart::form::{json::Json as MpJson, tempfile::TempFile, MultipartForm};
|
use diesel::{prelude::Queryable, ExpressionMethods, QueryDsl, Selectable, SelectableHelper};
|
||||||
use serde::Deserialize;
|
use diesel_async::RunQueryDsl;
|
||||||
|
use log::error;
|
||||||
|
use serde::Serialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{error::Error, structs::Me, api::v1::auth::check_access_token, utils::get_auth_header, Data};
|
use crate::{error::Error, api::v1::auth::check_access_token, schema::users::{self, dsl}, utils::get_auth_header, Data};
|
||||||
|
|
||||||
|
#[derive(Serialize, Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
struct Response {
|
||||||
|
uuid: Uuid,
|
||||||
|
username: String,
|
||||||
|
display_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/me")]
|
#[get("/me")]
|
||||||
pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||||
|
@ -14,63 +26,16 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
|
||||||
|
|
||||||
let uuid = check_access_token(auth_header, &mut conn).await?;
|
let uuid = check_access_token(auth_header, &mut conn).await?;
|
||||||
|
|
||||||
let me = Me::get(&mut conn, uuid).await?;
|
let user: Result<Response, diesel::result::Error> = dsl::users
|
||||||
|
.filter(dsl::uuid.eq(uuid))
|
||||||
|
.select(Response::as_select())
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(me))
|
if let Err(error) = user {
|
||||||
}
|
error!("{}", error);
|
||||||
|
return Ok(HttpResponse::InternalServerError().finish())
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct NewInfo {
|
|
||||||
username: Option<String>,
|
|
||||||
display_name: Option<String>,
|
|
||||||
password: Option<String>,
|
|
||||||
email: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, MultipartForm)]
|
|
||||||
struct UploadForm {
|
|
||||||
#[multipart(limit = "100MB")]
|
|
||||||
avatar: Option<TempFile>,
|
|
||||||
json: Option<MpJson<NewInfo>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[patch("/me")]
|
|
||||||
pub async fn update(req: HttpRequest, MultipartForm(form): MultipartForm<UploadForm>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
|
||||||
let headers = req.headers();
|
|
||||||
|
|
||||||
let auth_header = get_auth_header(headers)?;
|
|
||||||
|
|
||||||
let mut conn = data.pool.get().await?;
|
|
||||||
|
|
||||||
let uuid = check_access_token(auth_header, &mut conn).await?;
|
|
||||||
|
|
||||||
let mut me = Me::get(&mut conn, uuid).await?;
|
|
||||||
|
|
||||||
if let Some(avatar) = form.avatar {
|
|
||||||
let bytes = tokio::fs::read(avatar.file).await?;
|
|
||||||
|
|
||||||
let byte_slice: &[u8] = &bytes;
|
|
||||||
|
|
||||||
me.set_avatar(&data.bunny_cdn, &mut conn, data.config.bunny.cdn_url.clone(), byte_slice.into()).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(new_info) = form.json {
|
Ok(HttpResponse::Ok().json(user.unwrap()))
|
||||||
if let Some(username) = &new_info.username {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(display_name) = &new_info.display_name {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(password) = &new_info.password {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(email) = &new_info.email {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
use actix_web::{HttpRequest, HttpResponse, Scope, get, web};
|
use actix_web::{HttpRequest, HttpResponse, Scope, get, web};
|
||||||
|
use diesel::{prelude::Queryable, QueryDsl, Selectable, SelectableHelper};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde::Serialize;
|
||||||
|
use ::uuid::Uuid;
|
||||||
|
|
||||||
use crate::{api::v1::auth::check_access_token, error::Error, structs::{StartAmountQuery, User}, utils::get_auth_header, Data};
|
use crate::{error::Error,api::v1::auth::check_access_token, schema::users::{self, dsl}, structs::StartAmountQuery, utils::get_auth_header, Data};
|
||||||
|
|
||||||
mod me;
|
mod me;
|
||||||
mod uuid;
|
mod uuid;
|
||||||
|
|
||||||
|
#[derive(Serialize, Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
struct Response {
|
||||||
|
uuid: Uuid,
|
||||||
|
username: String,
|
||||||
|
display_name: Option<String>,
|
||||||
|
email: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn web() -> Scope {
|
pub fn web() -> Scope {
|
||||||
web::scope("/users")
|
web::scope("/users")
|
||||||
.service(res)
|
.service(res)
|
||||||
.service(me::res)
|
.service(me::res)
|
||||||
.service(me::update)
|
|
||||||
.service(uuid::res)
|
.service(uuid::res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +48,13 @@ pub async fn res(
|
||||||
|
|
||||||
check_access_token(auth_header, &mut conn).await?;
|
check_access_token(auth_header, &mut conn).await?;
|
||||||
|
|
||||||
let users = User::fetch_amount(&mut conn, start, amount).await?;
|
let users: Vec<Response> = dsl::users
|
||||||
|
.order_by(dsl::username)
|
||||||
|
.offset(start)
|
||||||
|
.limit(amount)
|
||||||
|
.select(Response::as_select())
|
||||||
|
.load(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(users))
|
Ok(HttpResponse::Ok().json(users))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||||
|
use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use log::error;
|
||||||
|
use serde::Serialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{error::Error, api::v1::auth::check_access_token, structs::User, utils::get_auth_header, Data};
|
use crate::{error::Error, api::v1::auth::check_access_token, schema::users::{self, dsl}, utils::get_auth_header, Data};
|
||||||
|
|
||||||
|
#[derive(Serialize, Queryable, Selectable, Clone)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
struct Response {
|
||||||
|
uuid: Uuid,
|
||||||
|
username: String,
|
||||||
|
display_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/{uuid}")]
|
#[get("/{uuid}")]
|
||||||
pub async fn res(
|
pub async fn res(
|
||||||
|
@ -20,17 +32,28 @@ pub async fn res(
|
||||||
|
|
||||||
check_access_token(auth_header, &mut conn).await?;
|
check_access_token(auth_header, &mut conn).await?;
|
||||||
|
|
||||||
if let Ok(cache_hit) = data.get_cache_key(uuid.to_string()).await {
|
let cache_result = data.get_cache_key(uuid.to_string()).await;
|
||||||
|
|
||||||
|
if let Ok(cache_hit) = cache_result {
|
||||||
return Ok(HttpResponse::Ok()
|
return Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(cache_hit));
|
.body(cache_hit));
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = User::fetch_one(&mut conn, uuid).await?;
|
let user: Response = dsl::users
|
||||||
|
.filter(dsl::uuid.eq(uuid))
|
||||||
data
|
.select(Response::as_select())
|
||||||
.set_cache_key(uuid.to_string(), user.clone(), 1800)
|
.get_result(&mut conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let cache_result = data
|
||||||
|
.set_cache_key(uuid.to_string(), user.clone(), 1800)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = cache_result {
|
||||||
|
error!("{}", error);
|
||||||
|
return Ok(HttpResponse::InternalServerError().finish());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(user))
|
Ok(HttpResponse::Ok().json(user))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
use bunny_api_tokio::edge_storage::Endpoint;
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
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)]
|
||||||
|
@ -38,14 +35,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);
|
||||||
|
@ -69,31 +58,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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +71,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)]
|
||||||
|
@ -112,14 +79,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 url(&self) -> String {
|
pub fn url(&self) -> String {
|
||||||
let mut url = String::from("postgres://");
|
let mut url = String::from("postgres://");
|
||||||
|
|
11
src/error.rs
11
src/error.rs
|
@ -1,6 +1,6 @@
|
||||||
use std::{io, time::SystemTimeError};
|
use std::{io, time::SystemTimeError};
|
||||||
|
|
||||||
use actix_web::{error::{PayloadError, ResponseError}, http::{header::{ContentType, ToStrError}, StatusCode}, HttpResponse};
|
use actix_web::{error::ResponseError, http::{header::{ContentType, ToStrError}, StatusCode}, HttpResponse};
|
||||||
use deadpool::managed::{BuildError, PoolError};
|
use deadpool::managed::{BuildError, PoolError};
|
||||||
use redis::RedisError;
|
use redis::RedisError;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -10,7 +10,7 @@ use diesel_async::pooled_connection::PoolError as DieselPoolError;
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
use serde_json::Error as JsonError;
|
use serde_json::Error as JsonError;
|
||||||
use toml::de::Error as TomlError;
|
use toml::de::Error as TomlError;
|
||||||
use log::{debug, error};
|
use log::error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -38,12 +38,6 @@ pub enum Error {
|
||||||
ToStrError(#[from] ToStrError),
|
ToStrError(#[from] ToStrError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
RandomError(#[from] getrandom::Error),
|
RandomError(#[from] getrandom::Error),
|
||||||
#[error(transparent)]
|
|
||||||
BunnyError(#[from] bunny_api_tokio::error::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
UrlParseError(#[from] url::ParseError),
|
|
||||||
#[error(transparent)]
|
|
||||||
PayloadError(#[from] PayloadError),
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
PasswordHashError(String),
|
PasswordHashError(String),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
|
@ -54,7 +48,6 @@ pub enum Error {
|
||||||
|
|
||||||
impl ResponseError for Error {
|
impl ResponseError for Error {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
debug!("{:?}", self);
|
|
||||||
error!("{}: {}", self.status_code(), self.to_string());
|
error!("{}: {}", self.status_code(), self.to_string());
|
||||||
|
|
||||||
HttpResponse::build(self.status_code())
|
HttpResponse::build(self.status_code())
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -32,10 +32,9 @@ struct Args {
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
pub pool: deadpool::managed::Pool<AsyncDieselConnectionManager<diesel_async::AsyncPgConnection>, Conn>,
|
pub pool: deadpool::managed::Pool<AsyncDieselConnectionManager<diesel_async::AsyncPgConnection>, Conn>,
|
||||||
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]
|
||||||
|
@ -58,10 +57,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())?;
|
|
||||||
|
|
||||||
let database_url = config.database.url();
|
let database_url = config.database.url();
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
|
@ -95,11 +90,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 || {
|
||||||
|
|
|
@ -48,8 +48,6 @@ diesel::table! {
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
#[max_length = 300]
|
#[max_length = 300]
|
||||||
description -> Nullable<Varchar>,
|
description -> Nullable<Varchar>,
|
||||||
#[max_length = 100]
|
|
||||||
icon -> Nullable<Varchar>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,8 +121,6 @@ diesel::table! {
|
||||||
email_verified -> Bool,
|
email_verified -> Bool,
|
||||||
is_deleted -> Bool,
|
is_deleted -> Bool,
|
||||||
deleted_at -> Nullable<Int8>,
|
deleted_at -> Nullable<Int8>,
|
||||||
#[max_length = 100]
|
|
||||||
avatar -> Nullable<Varchar>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
140
src/structs.rs
140
src/structs.rs
|
@ -1,12 +1,9 @@
|
||||||
use diesel::{delete, insert_into, prelude::{Insertable, Queryable}, update, ExpressionMethods, QueryDsl, Selectable, SelectableHelper};
|
use diesel::{delete, insert_into, prelude::{Insertable, Queryable}, ExpressionMethods, QueryDsl, Selectable, SelectableHelper};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use diesel_async::{pooled_connection::AsyncDieselConnectionManager, RunQueryDsl};
|
use diesel_async::{pooled_connection::AsyncDieselConnectionManager, RunQueryDsl};
|
||||||
use tokio::task;
|
|
||||||
use url::Url;
|
|
||||||
use actix_web::web::BytesMut;
|
|
||||||
|
|
||||||
use crate::{error::Error, schema::*, utils::image_check, Conn, Data};
|
use crate::{error::Error, Conn, Data, schema::*};
|
||||||
|
|
||||||
fn load_or_empty<T>(query_result: Result<Vec<T>, diesel::result::Error>) -> Result<Vec<T>, diesel::result::Error> {
|
fn load_or_empty<T>(query_result: Result<Vec<T>, diesel::result::Error>) -> Result<Vec<T>, diesel::result::Error> {
|
||||||
match query_result {
|
match query_result {
|
||||||
|
@ -241,7 +238,6 @@ struct GuildBuilder {
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
name: String,
|
name: String,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
icon: Option<String>,
|
|
||||||
owner_uuid: Uuid,
|
owner_uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,7 +251,7 @@ impl GuildBuilder {
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
name: self.name,
|
name: self.name,
|
||||||
description: self.description,
|
description: self.description,
|
||||||
icon: self.icon.and_then(|i| i.parse().ok()),
|
icon: String::from("bogus"),
|
||||||
owner_uuid: self.owner_uuid,
|
owner_uuid: self.owner_uuid,
|
||||||
roles: roles,
|
roles: roles,
|
||||||
member_count: member_count,
|
member_count: member_count,
|
||||||
|
@ -268,7 +264,7 @@ pub struct Guild {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
name: String,
|
name: String,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
icon: Option<Url>,
|
icon: String,
|
||||||
owner_uuid: Uuid,
|
owner_uuid: Uuid,
|
||||||
pub roles: Vec<Role>,
|
pub roles: Vec<Role>,
|
||||||
member_count: i64,
|
member_count: i64,
|
||||||
|
@ -327,7 +323,6 @@ impl Guild {
|
||||||
uuid: guild_uuid,
|
uuid: guild_uuid,
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
description: description.clone(),
|
description: description.clone(),
|
||||||
icon: None,
|
|
||||||
owner_uuid,
|
owner_uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -354,7 +349,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,
|
||||||
|
@ -406,37 +401,6 @@ impl Guild {
|
||||||
|
|
||||||
Ok(invite)
|
Ok(invite)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Horrible security
|
|
||||||
pub async fn set_icon(&mut self, bunny_cdn: &bunny_api_tokio::Client, conn: &mut Conn, cdn_url: Url, icon: BytesMut) -> Result<(), Error> {
|
|
||||||
let icon_clone = icon.clone();
|
|
||||||
let image_type = task::spawn_blocking(move || image_check(icon_clone)).await??;
|
|
||||||
|
|
||||||
if let Some(icon) = &self.icon {
|
|
||||||
let relative_url = icon
|
|
||||||
.path()
|
|
||||||
.trim_start_matches('/');
|
|
||||||
|
|
||||||
bunny_cdn.storage.delete(relative_url).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = format!("icons/{}/icon.{}", self.uuid, image_type);
|
|
||||||
|
|
||||||
bunny_cdn.storage.upload(path.clone(), icon.into()).await?;
|
|
||||||
|
|
||||||
let icon_url = cdn_url.join(&path)?;
|
|
||||||
|
|
||||||
use guilds::dsl;
|
|
||||||
update(guilds::table)
|
|
||||||
.filter(dsl::uuid.eq(self.uuid))
|
|
||||||
.set(dsl::icon.eq(icon_url.as_str()))
|
|
||||||
.execute(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.icon = Some(icon_url);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone, Queryable, Selectable, Insertable)]
|
#[derive(Serialize, Clone, Queryable, Selectable, Insertable)]
|
||||||
|
@ -607,100 +571,6 @@ impl Invite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone, Queryable, Selectable)]
|
|
||||||
#[diesel(table_name = users)]
|
|
||||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
|
||||||
pub struct User {
|
|
||||||
uuid: Uuid,
|
|
||||||
username: String,
|
|
||||||
display_name: Option<String>,
|
|
||||||
avatar: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl User {
|
|
||||||
pub async fn fetch_one(conn: &mut Conn, user_uuid: Uuid) -> Result<Self, Error> {
|
|
||||||
use users::dsl;
|
|
||||||
let user: User = dsl::users
|
|
||||||
.filter(dsl::uuid.eq(user_uuid))
|
|
||||||
.select(User::as_select())
|
|
||||||
.get_result(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_amount(conn: &mut Conn, offset: i64, amount: i64) -> Result<Vec<Self>, Error> {
|
|
||||||
use users::dsl;
|
|
||||||
let users: Vec<User> = load_or_empty(
|
|
||||||
dsl::users
|
|
||||||
.limit(amount)
|
|
||||||
.offset(offset)
|
|
||||||
.select(User::as_select())
|
|
||||||
.load(conn)
|
|
||||||
.await
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(users)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Queryable, Selectable)]
|
|
||||||
#[diesel(table_name = users)]
|
|
||||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
|
||||||
pub struct Me {
|
|
||||||
uuid: Uuid,
|
|
||||||
username: String,
|
|
||||||
display_name: Option<String>,
|
|
||||||
avatar: Option<String>,
|
|
||||||
email: String,
|
|
||||||
email_verified: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Me {
|
|
||||||
pub async fn get(conn: &mut Conn, user_uuid: Uuid) -> Result<Self, Error> {
|
|
||||||
use users::dsl;
|
|
||||||
let me: Me = dsl::users
|
|
||||||
.filter(dsl::uuid.eq(user_uuid))
|
|
||||||
.select(Me::as_select())
|
|
||||||
.get_result(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(me)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_avatar(&mut self, bunny_cdn: &bunny_api_tokio::Client, conn: &mut Conn, cdn_url: Url, avatar: BytesMut) -> Result<(), Error> {
|
|
||||||
let avatar_clone = avatar.clone();
|
|
||||||
let image_type = task::spawn_blocking(move || image_check(avatar_clone)).await??;
|
|
||||||
|
|
||||||
if let Some(avatar) = &self.avatar {
|
|
||||||
let avatar_url: Url = avatar.parse()?;
|
|
||||||
|
|
||||||
let relative_url = avatar_url
|
|
||||||
.path()
|
|
||||||
.trim_start_matches('/');
|
|
||||||
|
|
||||||
bunny_cdn.storage.delete(relative_url).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = format!("avatar/{}/avatar.{}", self.uuid, image_type);
|
|
||||||
|
|
||||||
bunny_cdn.storage.upload(path.clone(), avatar.into()).await?;
|
|
||||||
|
|
||||||
let avatar_url = cdn_url.join(&path)?;
|
|
||||||
|
|
||||||
use users::dsl;
|
|
||||||
update(users::table)
|
|
||||||
.filter(dsl::uuid.eq(self.uuid))
|
|
||||||
.set(dsl::avatar.eq(avatar_url.as_str()))
|
|
||||||
.execute(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.avatar = Some(avatar_url.to_string());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct StartAmountQuery {
|
pub struct StartAmountQuery {
|
||||||
pub start: Option<i64>,
|
pub start: Option<i64>,
|
||||||
|
|
21
src/utils.rs
21
src/utils.rs
|
@ -1,8 +1,7 @@
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
cookie::{time::Duration, Cookie, SameSite},
|
cookie::{Cookie, SameSite, time::Duration},
|
||||||
http::header::HeaderMap, web::BytesMut,
|
http::header::HeaderMap,
|
||||||
};
|
};
|
||||||
use bindet::FileType;
|
|
||||||
use getrandom::fill;
|
use getrandom::fill;
|
||||||
use hex::encode;
|
use hex::encode;
|
||||||
use redis::RedisError;
|
use redis::RedisError;
|
||||||
|
@ -60,22 +59,6 @@ pub fn generate_refresh_token() -> Result<String, getrandom::Error> {
|
||||||
Ok(encode(buf))
|
Ok(encode(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_check(icon: BytesMut) -> Result<String, Error> {
|
|
||||||
let buf = std::io::Cursor::new(icon);
|
|
||||||
|
|
||||||
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 Ok(String::from("jpg"))
|
|
||||||
} else if file_type.likely_to_be == vec![FileType::Png] {
|
|
||||||
return Ok(String::from("png"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::BadRequest("Uploaded file is not an image".to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
pub async fn set_cache_key(
|
pub async fn set_cache_key(
|
||||||
&self,
|
&self,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue