diff --git a/Cargo.lock b/Cargo.lock index 21ab753e..633fcabe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2042,9 +2042,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.94" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" +checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" [[package]] name = "line-wrap" diff --git a/actors/web-actor/src/avatar.rs b/actors/web-actor/src/avatar.rs index cf415250..4c4350fe 100644 --- a/actors/web-actor/src/avatar.rs +++ b/actors/web-actor/src/avatar.rs @@ -16,6 +16,7 @@ use jirs_data::{User, UserId, WsMsg}; use websocket_actor::server::InnerMsg::BroadcastToChannel; use websocket_actor::server::WsServer; +#[cfg(feature = "aws-s3")] #[post("/")] pub async fn upload( mut payload: Multipart, @@ -80,11 +81,74 @@ pub async fn upload( } } +#[cfg(not(feature = "aws-s3"))] +#[post("/")] +pub async fn upload( + mut payload: Multipart, + db: Data>, + ws: Data>, + fs: Data>, +) -> Result { + let mut user_id: Option = None; + let mut avatar_url: Option = None; + + while let Ok(Some(field)) = payload.try_next().await { + let disposition: ContentDisposition = match field.content_disposition() { + Some(d) => d, + _ => continue, + }; + if !disposition.is_form_data() { + return Ok(HttpResponse::BadRequest().finish()); + } + match disposition.get_name() { + Some("token") => { + user_id = Some(handle_token(field, db.clone()).await?); + } + Some("avatar") => { + let id = user_id.ok_or_else(|| HttpResponse::Unauthorized().finish())?; + avatar_url = Some( + crate::handlers::upload_avatar_image::handle_image( + id, + field, + disposition, + fs.clone(), + ) + .await?, + ); + } + _ => continue, + }; + } + let user_id = match user_id { + Some(id) => id, + _ => return Ok(HttpResponse::Unauthorized().finish()), + }; + + let project_id = match block_on(db.send(CurrentUserProject { user_id })) { + Ok(Ok(user_project)) => user_project.project_id, + _ => return Ok(HttpResponse::UnprocessableEntity().finish()), + }; + + match (user_id, avatar_url) { + (user_id, Some(avatar_url)) => { + let user = update_user_avatar(user_id, avatar_url.clone(), db).await?; + ws.send(BroadcastToChannel( + project_id, + WsMsg::AvatarUrlChanged(user.id, avatar_url), + )) + .await + .map_err(|_| HttpResponse::UnprocessableEntity().finish())?; + Ok(HttpResponse::NoContent().finish()) + } + _ => Ok(HttpResponse::UnprocessableEntity().finish()), + } +} + async fn update_user_avatar( user_id: UserId, new_url: String, db: Data>, -) -> Result { +) -> Result { match db .send(UpdateAvatarUrl { user_id, @@ -96,16 +160,23 @@ async fn update_user_avatar( Ok(Err(e)) => { error!("{:?}", e); - Err(HttpResponse::Unauthorized().finish().into()) + Err(actix_web::Error::from( + HttpResponse::Unauthorized().finish(), + )) } Err(e) => { error!("{:?}", e); - Err(HttpResponse::Unauthorized().finish().into()) + Err(actix_web::Error::from( + HttpResponse::Unauthorized().finish(), + )) } } } -async fn handle_token(mut field: Field, db: Data>) -> Result { +async fn handle_token( + mut field: Field, + db: Data>, +) -> Result { let mut f: Vec = vec![]; while let Some(chunk) = field.next().await { let data = chunk.unwrap(); diff --git a/actors/websocket-actor/Cargo.toml b/actors/websocket-actor/Cargo.toml index f93066e9..81e90bcc 100644 --- a/actors/websocket-actor/Cargo.toml +++ b/actors/websocket-actor/Cargo.toml @@ -29,9 +29,9 @@ flate2 = { version = "*" } syntect = { version = "*" } lazy_static = { version = "*" } -log = "0.4" -pretty_env_logger = "0.4" -env_logger = "0.7" +log = { version = "0.4" } +pretty_env_logger = { version = "0.4" } +env_logger = { version = "0.7" } uuid = { version = "0.8.1", features = ["serde", "v4", "v5"] } diff --git a/actors/websocket-actor/src/handlers/auth.rs b/actors/websocket-actor/src/handlers/auth.rs index 31ae5076..b368e54c 100644 --- a/actors/websocket-actor/src/handlers/auth.rs +++ b/actors/websocket-actor/src/handlers/auth.rs @@ -7,10 +7,8 @@ use jirs_data::msg::WsError; use jirs_data::{Token, WsMsg}; use mail_actor::welcome::Welcome; -use crate::{ - db_or_debug_and_return, db_or_debug_or_fallback, mail_or_debug_and_return, WebSocketActor, - WsHandler, WsResult, -}; +use crate::server::InnerMsg; +use crate::{db_or_debug_and_return, db_or_debug_or_fallback, mail_or_debug_and_return, *}; pub struct Authenticate { pub name: String, @@ -69,7 +67,7 @@ impl WsHandler for WebSocketActor { self.current_user_project = self.load_user_project().ok(); self.current_project = self.load_project().ok(); - block_on(self.join_channel(ctx.address().recipient())); + block_on(self.join_channel(ctx.address().recipient::())); Ok(Some(WsMsg::AuthorizeLoaded(Ok((user, setting))))) } } diff --git a/actors/websocket-actor/src/handlers/epics.rs b/actors/websocket-actor/src/handlers/epics.rs index 633aef16..740f313f 100644 --- a/actors/websocket-actor/src/handlers/epics.rs +++ b/actors/websocket-actor/src/handlers/epics.rs @@ -3,7 +3,7 @@ use jirs_data::{ DescriptionString, EndsAt, EpicId, IssueType, NameString, StartsAt, UserProject, WsMsg, }; -use crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult}; +use crate::{db_or_debug_and_return, *}; pub struct LoadEpics; diff --git a/actors/websocket-actor/src/lib.rs b/actors/websocket-actor/src/lib.rs index 00f34ead..0c69d325 100644 --- a/actors/websocket-actor/src/lib.rs +++ b/actors/websocket-actor/src/lib.rs @@ -26,7 +26,7 @@ trait WsMessageSender { fn send_msg(&mut self, msg: &jirs_data::WsMsg); } -struct WebSocketActor { +pub struct WebSocketActor { db: Data>, mail: Data>, addr: Addr, @@ -36,13 +36,20 @@ struct WebSocketActor { current_project: Option, } -impl Actor for WebSocketActor { - type Context = ws::WebsocketContext; +pub type WsCtx = ws::WebsocketContext; + +impl actix::Actor for WebSocketActor { + type Context = WsCtx; } impl WsMessageSender for ws::WebsocketContext { fn send_msg(&mut self, msg: &WsMsg) { - self.binary(bincode::serialize(msg).unwrap()) + match bincode::serialize(msg) { + Err(err) => { + log::error!("{}", err); + } + Ok(v) => self.binary(v), + } } } @@ -328,7 +335,7 @@ impl StreamHandler> for WebSocketActor { self.addr.do_send(InnerMsg::Leave( up.project_id, user.id, - ctx.address().recipient(), + ctx.address().recipient::(), )); } ctx.stop() @@ -337,7 +344,7 @@ impl StreamHandler> for WebSocketActor { pub trait WsHandler where - Self: Actor, + Self: actix::Actor, { fn handle_msg(&mut self, msg: Message, _ctx: &mut ::Context) -> WsResult; } diff --git a/jirs-client/src/model.rs b/jirs-client/src/model.rs index cffe30ef..371c67e4 100644 --- a/jirs-client/src/model.rs +++ b/jirs-client/src/model.rs @@ -280,7 +280,7 @@ impl Model { issue_form: None, project_form: None, comment_form: None, - comments_by_project_id: Default::default(), + comments_by_project_id: HashMap::with_capacity(1_000), page_content: page.build_content(), page, host_url, @@ -291,22 +291,22 @@ impl Model { messages_tooltip_visible: false, issues: vec![], users: vec![], - users_by_id: Default::default(), + users_by_id: HashMap::with_capacity(1_000), user_settings: None, comments: vec![], - comments_by_id: Default::default(), + comments_by_id: HashMap::with_capacity(1_000), issue_statuses: vec![], - issue_statuses_by_id: Default::default(), - issue_statuses_by_name: Default::default(), + issue_statuses_by_id: HashMap::with_capacity(1_000), + issue_statuses_by_name: HashMap::with_capacity(1_000), messages: vec![], user_projects: vec![], projects: vec![], epics: vec![], - issues_by_id: Default::default(), + issues_by_id: HashMap::with_capacity(1_000), show_extras: false, - epics_by_id: Default::default(), + epics_by_id: HashMap::with_capacity(1_000), modals_stack: vec![], - modals: Default::default(), + modals: Modals::default(), key_triggers: std::rc::Rc::new(std::cell::RefCell::new(HashMap::with_capacity(20))), distinct_key_up: crate::shared::on_event::distinct(), } diff --git a/jirs-client/src/pages/epics_page/model.rs b/jirs-client/src/pages/epics_page/model.rs index 4003b4f0..d2174bd6 100644 --- a/jirs-client/src/pages/epics_page/model.rs +++ b/jirs-client/src/pages/epics_page/model.rs @@ -16,12 +16,15 @@ impl EpicsPage { } pub fn build_issues_per_epic(model: &Model) -> HashMap> { - model.issues().iter().fold(HashMap::new(), |mut h, issue| { - if let Some(epic_id) = issue.epic_id.as_ref() { - h.entry(*epic_id).or_default().push(issue.id); - } - h - }) + model.issues().iter().fold( + HashMap::with_capacity(model.issues().len()), + |mut h, issue| { + if let Some(epic_id) = issue.epic_id.as_ref() { + h.entry(*epic_id).or_default().push(issue.id); + } + h + }, + ) } pub fn issues(&self, epic_id: EpicId) -> Option<&Vec> { diff --git a/jirs-client/src/pages/profile_page/update.rs b/jirs-client/src/pages/profile_page/update.rs index cd18b449..29e3ec2b 100644 --- a/jirs-client/src/pages/profile_page/update.rs +++ b/jirs-client/src/pages/profile_page/update.rs @@ -130,7 +130,7 @@ async fn update_avatar(data: FormData, host_url: String) -> Option { let path = format!("{}/avatar/", host_url); let result = Request::new(path) .method(Method::Post) - .body(data.into()) + .body(&data) .fetch() .await; let response = match result { diff --git a/jirs-client/src/pages/project_page/model.rs b/jirs-client/src/pages/project_page/model.rs index 43badd4c..a6dc8f13 100644 --- a/jirs-client/src/pages/project_page/model.rs +++ b/jirs-client/src/pages/project_page/model.rs @@ -68,10 +68,15 @@ impl ProjectPage { issues.collect() }; - let issues_per_epic_id = issues.into_iter().fold(HashMap::new(), |mut m, issue| { - m.entry(issue.epic_id).or_insert_with(Vec::new).push(issue); - m - }); + let issues_per_epic_id = { + let issues_len = issues.len(); + issues + .into_iter() + .fold(HashMap::with_capacity(issues_len), |mut m, issue| { + m.entry(issue.epic_id).or_insert_with(Vec::new).push(issue); + m + }) + }; epics .map(|epic| { diff --git a/jirs-client/src/pages/project_settings_page/update.rs b/jirs-client/src/pages/project_settings_page/update.rs index 28fa3d02..e6b9f5db 100644 --- a/jirs-client/src/pages/project_settings_page/update.rs +++ b/jirs-client/src/pages/project_settings_page/update.rs @@ -1,7 +1,6 @@ use std::collections::HashSet; use jirs_data::{IssueStatus, IssueStatusId, ProjectFieldId, UpdateProjectPayload, WsMsg}; -use seed::error; use seed::prelude::Orders; use crate::components::styled_select::StyledSelectChanged; diff --git a/jirs-server/Cargo.toml b/jirs-server/Cargo.toml index 237ccb2f..b6c29b79 100644 --- a/jirs-server/Cargo.toml +++ b/jirs-server/Cargo.toml @@ -15,10 +15,7 @@ path = "./src/main.rs" [features] aws-s3 = ["amazon-actor"] local-storage = ["filesystem-actor"] -default = [ - "aws-s3", - "local-storage", -] +default = ["local-storage"] [dependencies] actix = { version = "0.10.0" } diff --git a/shared/jirs-config/Cargo.toml b/shared/jirs-config/Cargo.toml index c8c5c35f..917dd251 100644 --- a/shared/jirs-config/Cargo.toml +++ b/shared/jirs-config/Cargo.toml @@ -20,9 +20,10 @@ hi = [] mail = [] web = ["aws-s3", "local-storage"] websocket = [] +default = ["local-storage", "database", "hi", "mail", "web", "websocket"] [dependencies] -serde = "*" +serde = { version = "*" } toml = { version = "*" } # Amazon S3 diff --git a/shared/jirs-data/Cargo.toml b/shared/jirs-data/Cargo.toml index cf7e860a..20c4ddce 100644 --- a/shared/jirs-data/Cargo.toml +++ b/shared/jirs-data/Cargo.toml @@ -17,8 +17,8 @@ backend = ["diesel", "actix", "derive_enum_sql"] frontend = [] [dependencies] -serde = "*" -serde_json = "*" +serde = { version = "*" } +serde_json = { version = "*" } chrono = { version = "*", features = ["serde"] } uuid = { version = ">=0.7.0, <0.9.0", features = ["serde"] }