Update user avatar
This commit is contained in:
parent
5b871a3332
commit
fe06c1e63e
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ db.toml
|
|||||||
db.test.toml
|
db.test.toml
|
||||||
pkg
|
pkg
|
||||||
jirs-client/pkg
|
jirs-client/pkg
|
||||||
|
tmp
|
||||||
|
52
README.md
52
README.md
@ -14,20 +14,68 @@ https://git.sr.ht/~tsumanu/jirs
|
|||||||
|
|
||||||
### Config files
|
### Config files
|
||||||
|
|
||||||
|
#### WEB
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# web.toml
|
# web.toml
|
||||||
concurrency = 2
|
concurrency = 2
|
||||||
port = "5000"
|
port = "5000"
|
||||||
bind = "0.0.0.0"
|
bind = "0.0.0.0"
|
||||||
ssl = false
|
ssl = false
|
||||||
|
tmp_dir = "./tmp"
|
||||||
|
|
||||||
|
[s3]
|
||||||
|
access_key_id = ""
|
||||||
|
secret_access_key = ""
|
||||||
|
bucket = ""
|
||||||
|
region_name = "eu-central-1"
|
||||||
|
|
||||||
|
[filesystem]
|
||||||
|
store_path = ""
|
||||||
|
client_path = "/img"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### Upload local storage
|
||||||
|
|
||||||
|
If default feature `"local-storage"` is on your uploaded files will be stored on your machine.
|
||||||
|
This requires additional configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[filesystem]
|
||||||
|
store_path = "/var/jirs/uploads"
|
||||||
|
client_path = "/img"
|
||||||
|
```
|
||||||
|
|
||||||
|
* `store_path` is your local machine path. Files will be saved there. This can be relative to `CWD` path or absolute path.
|
||||||
|
* `client_path` is web path
|
||||||
|
|
||||||
|
Both must be set and non-empty
|
||||||
|
|
||||||
|
##### Upload to AWS S3
|
||||||
|
|
||||||
|
If default feature `"aws-s3"` is on your uploaded files will be send to AWS S3 service.
|
||||||
|
This requires additional configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[s3]
|
||||||
|
access_key_id = ""
|
||||||
|
secret_access_key = ""
|
||||||
|
bucket = ""
|
||||||
|
region_name = "eu-central-1"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Database
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# db.toml
|
# db.toml
|
||||||
concurrency = 2
|
concurrency = 2
|
||||||
database_url = "postgres://postgres@localhost:5432/jirs"
|
database_url = "postgres://postgres@localhost:5432/jirs"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Mail Service
|
||||||
|
|
||||||
|
You can send e-mail only via service which will handle this. This application was build using sendgrid.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# mail.toml
|
# mail.toml
|
||||||
concurrency = 2
|
concurrency = 2
|
||||||
@ -52,6 +100,9 @@ NODE_ENV=development
|
|||||||
DEBUG=true
|
DEBUG=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Client and Server bind/port must be provided. Client will be build using those variables and will send requests only using this address.
|
||||||
|
`DATABASE_URL` is required only to setup database. Runtime will use `db.toml`.
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
@ -60,6 +111,7 @@ Requirements:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo install diesel_cli --no-default-features --features postgres
|
cargo install diesel_cli --no-default-features --features postgres
|
||||||
|
export DATABASE_URL=postgres://postgres@localhost/jirs
|
||||||
diesel setup
|
diesel setup
|
||||||
diesel migration run
|
diesel migration run
|
||||||
|
|
||||||
|
@ -67,6 +67,18 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Msg::WsMsg(WsMsg::AvatarUrlChanged(user_id, avatar_url)) => {
|
||||||
|
for user in model.users.iter_mut() {
|
||||||
|
if user.id == *user_id {
|
||||||
|
user.avatar_url = Some(avatar_url.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(me) = model.user.as_mut() {
|
||||||
|
if me.id == *user_id {
|
||||||
|
me.avatar_url = Some(avatar_url.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
orders.render();
|
orders.render();
|
||||||
|
@ -16,7 +16,7 @@ if (process.env.NODE_ENV === "production") {
|
|||||||
});
|
});
|
||||||
execSync("rm -Rf ./dist");
|
execSync("rm -Rf ./dist");
|
||||||
execSync("mkdir -p ./dist");
|
execSync("mkdir -p ./dist");
|
||||||
execSync('./target/debug/jirs-css -O ./jirs-client/dist/styles.css', {
|
execSync('./target/release/jirs-css -O ./jirs-client/dist/styles.css', {
|
||||||
cwd: jirDir,
|
cwd: jirDir,
|
||||||
});
|
});
|
||||||
console.log("CSS combined");
|
console.log("CSS combined");
|
||||||
|
@ -767,4 +767,7 @@ pub enum WsMsg {
|
|||||||
UpdateComment(UpdateCommentPayload),
|
UpdateComment(UpdateCommentPayload),
|
||||||
CommentDeleteRequest(CommentId),
|
CommentDeleteRequest(CommentId),
|
||||||
CommentDeleted(CommentId),
|
CommentDeleted(CommentId),
|
||||||
|
|
||||||
|
// users
|
||||||
|
AvatarUrlChanged(UserId, String),
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,19 @@ license = "MPL-2.0"
|
|||||||
name = "jirs_server"
|
name = "jirs_server"
|
||||||
path = "./src/main.rs"
|
path = "./src/main.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
aws-s3 = [
|
||||||
|
"rusoto_s3",
|
||||||
|
"rusoto_core"
|
||||||
|
]
|
||||||
|
local-storage = [
|
||||||
|
"actix-files"
|
||||||
|
]
|
||||||
|
default = [
|
||||||
|
"aws-s3",
|
||||||
|
"local-storage",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "*", features = ["derive"] }
|
serde = { version = "*", features = ["derive"] }
|
||||||
actix = { version = "*" }
|
actix = { version = "*" }
|
||||||
@ -21,7 +34,6 @@ actix-service = { version = "*" }
|
|||||||
actix-rt = "1"
|
actix-rt = "1"
|
||||||
actix-web-actors = "*"
|
actix-web-actors = "*"
|
||||||
actix-multipart = { version = "*" }
|
actix-multipart = { version = "*" }
|
||||||
actix-files = { version = "0.2.1" }
|
|
||||||
|
|
||||||
dotenv = { version = "*" }
|
dotenv = { version = "*" }
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
@ -50,10 +62,6 @@ futures = { version = "*" }
|
|||||||
lettre = { version = "*" }
|
lettre = { version = "*" }
|
||||||
lettre_email = { version = "*" }
|
lettre_email = { version = "*" }
|
||||||
|
|
||||||
# Amazon S3
|
|
||||||
rusoto_s3 = "0.43.0"
|
|
||||||
rusoto_core = "0.43.0"
|
|
||||||
|
|
||||||
[dependencies.diesel]
|
[dependencies.diesel]
|
||||||
version = "1.4.4"
|
version = "1.4.4"
|
||||||
features = [ "unstable", "postgres", "numeric", "extras", "uuidv07" ]
|
features = [ "unstable", "postgres", "numeric", "extras", "uuidv07" ]
|
||||||
@ -61,3 +69,17 @@ features = [ "unstable", "postgres", "numeric", "extras", "uuidv07" ]
|
|||||||
[dependencies.jirs-data]
|
[dependencies.jirs-data]
|
||||||
path = "../jirs-data"
|
path = "../jirs-data"
|
||||||
features = [ "backend" ]
|
features = [ "backend" ]
|
||||||
|
|
||||||
|
# Amazon S3
|
||||||
|
[dependencies.rusoto_s3]
|
||||||
|
optional = true
|
||||||
|
version = "0.43.0"
|
||||||
|
|
||||||
|
[dependencies.rusoto_core]
|
||||||
|
optional = true
|
||||||
|
version = "0.43.0"
|
||||||
|
|
||||||
|
# Local storage
|
||||||
|
[dependencies.actix-files]
|
||||||
|
optional = true
|
||||||
|
version = "0.2.1"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
|
use diesel::pg::Pg;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -27,17 +28,19 @@ impl Handler<AuthorizeUser> for DbExecutor {
|
|||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
let token = tokens
|
let token = tokens
|
||||||
.filter(access_token.eq(msg.access_token))
|
.filter(access_token.eq(msg.access_token))
|
||||||
.first::<Token>(conn)
|
.first::<Token>(conn)
|
||||||
.map_err(|_e| {
|
.map_err(|_e| {
|
||||||
ServiceErrors::RecordNotFound(format!("token for {}", msg.access_token))
|
ServiceErrors::RecordNotFound(format!("token for {}", msg.access_token))
|
||||||
})?;
|
})?;
|
||||||
let user = users
|
|
||||||
.filter(id.eq(token.user_id))
|
let user_query = users.filter(id.eq(token.user_id));
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
||||||
|
user_query
|
||||||
.first::<User>(conn)
|
.first::<User>(conn)
|
||||||
.map_err(|_e| ServiceErrors::RecordNotFound(format!("user {}", token.user_id)))?;
|
.map_err(|_e| ServiceErrors::RecordNotFound(format!("user {}", token.user_id)))
|
||||||
Ok(user)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,10 +58,11 @@ impl SyncQuery for AuthorizeUser {
|
|||||||
.filter(access_token.eq(self.access_token))
|
.filter(access_token.eq(self.access_token))
|
||||||
.first::<Token>(&conn)
|
.first::<Token>(&conn)
|
||||||
.map_err(|_| crate::errors::ServiceErrors::Unauthorized)?;
|
.map_err(|_| crate::errors::ServiceErrors::Unauthorized)?;
|
||||||
let user = users
|
|
||||||
.filter(id.eq(token.user_id))
|
let user_query = users.filter(id.eq(token.user_id));
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
||||||
|
user_query
|
||||||
.first::<User>(&conn)
|
.first::<User>(&conn)
|
||||||
.map_err(|_| crate::errors::ServiceErrors::Unauthorized)?;
|
.map_err(|_| crate::errors::ServiceErrors::Unauthorized)
|
||||||
Ok(user)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
|
use diesel::pg::Pg;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -26,12 +27,12 @@ impl Handler<LoadIssueComments> for DbExecutor {
|
|||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
let rows: Vec<Comment> = comments
|
|
||||||
.distinct_on(id)
|
let comments_query = comments.distinct_on(id).filter(issue_id.eq(msg.issue_id));
|
||||||
.filter(issue_id.eq(msg.issue_id))
|
debug!("{}", diesel::debug_query::<Pg, _>(&comments_query));
|
||||||
|
comments_query
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))
|
||||||
Ok(rows)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,11 +64,12 @@ impl Handler<CreateComment> for DbExecutor {
|
|||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
let row: Comment = diesel::insert_into(comments)
|
|
||||||
.values(form)
|
let comment_query = diesel::insert_into(comments).values(form);
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&comment_query));
|
||||||
|
comment_query
|
||||||
.get_result::<Comment>(conn)
|
.get_result::<Comment>(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))
|
||||||
Ok(row)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +100,7 @@ impl Handler<UpdateComment> for DbExecutor {
|
|||||||
.find(msg.comment_id),
|
.find(msg.comment_id),
|
||||||
)
|
)
|
||||||
.set(body.eq(msg.body));
|
.set(body.eq(msg.body));
|
||||||
info!("{}", diesel::debug_query::<diesel::pg::Pg, _>(&query));
|
info!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||||
let row: Comment = query
|
let row: Comment = query
|
||||||
.get_result::<Comment>(conn)
|
.get_result::<Comment>(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
||||||
@ -126,11 +128,14 @@ impl Handler<DeleteComment> for DbExecutor {
|
|||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
diesel::delete(
|
|
||||||
|
let comment_query = diesel::delete(
|
||||||
comments
|
comments
|
||||||
.filter(user_id.eq(msg.user_id))
|
.filter(user_id.eq(msg.user_id))
|
||||||
.find(msg.comment_id),
|
.find(msg.comment_id),
|
||||||
)
|
);
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&comment_query));
|
||||||
|
comment_query
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
|
use diesel::pg::Pg;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -26,9 +27,11 @@ impl Handler<LoadAssignees> for DbExecutor {
|
|||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
issue_assignees
|
let issue_assignees_query = issue_assignees
|
||||||
.distinct_on(id)
|
.distinct_on(id)
|
||||||
.filter(issue_id.eq(msg.issue_id))
|
.filter(issue_id.eq(msg.issue_id));
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&issue_assignees_query));
|
||||||
|
issue_assignees_query
|
||||||
.load::<IssueAssignee>(conn)
|
.load::<IssueAssignee>(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
||||||
}
|
}
|
||||||
|
@ -162,11 +162,10 @@ impl Handler<UpdateIssue> for DbExecutor {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = issues
|
issues
|
||||||
.find(msg.issue_id)
|
.find(msg.issue_id)
|
||||||
.first::<Issue>(conn)
|
.first::<Issue>(conn)
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)
|
||||||
Ok(row)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
|
use diesel::pg::Pg;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -63,19 +64,21 @@ impl Handler<UpdateProject> for DbExecutor {
|
|||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
diesel::update(projects.find(msg.project_id))
|
let update_query = diesel::update(projects.find(msg.project_id)).set((
|
||||||
.set((
|
|
||||||
msg.name.map(|v| name.eq(v)),
|
msg.name.map(|v| name.eq(v)),
|
||||||
msg.url.map(|v| url.eq(v)),
|
msg.url.map(|v| url.eq(v)),
|
||||||
msg.description.map(|v| description.eq(v)),
|
msg.description.map(|v| description.eq(v)),
|
||||||
msg.category.map(|v| category.eq(v)),
|
msg.category.map(|v| category.eq(v)),
|
||||||
msg.time_tracking.map(|v| time_tracking.eq(v)),
|
msg.time_tracking.map(|v| time_tracking.eq(v)),
|
||||||
))
|
));
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&update_query));
|
||||||
|
update_query
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||||
|
|
||||||
projects
|
let project_query = projects.find(msg.project_id);
|
||||||
.filter(id.eq(msg.project_id))
|
debug!("{}", diesel::debug_query::<Pg, _>(&project_query));
|
||||||
|
project_query
|
||||||
.first::<Project>(conn)
|
.first::<Project>(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))
|
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))
|
||||||
}
|
}
|
||||||
|
@ -72,10 +72,10 @@ impl Handler<CreateBindToken> for DbExecutor {
|
|||||||
refresh_token,
|
refresh_token,
|
||||||
bind_token,
|
bind_token,
|
||||||
};
|
};
|
||||||
let row: Token = diesel::insert_into(tokens)
|
|
||||||
|
diesel::insert_into(tokens)
|
||||||
.values(form)
|
.values(form)
|
||||||
.get_result(conn)
|
.get_result(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))
|
||||||
Ok(row)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use diesel::pg::Pg;
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use jirs_data::{IssueAssignee, Project, User, UserId};
|
use jirs_data::{Project, User, UserId};
|
||||||
|
|
||||||
use crate::db::{DbExecutor, DbPooledConn};
|
use crate::db::{DbExecutor, DbPooledConn};
|
||||||
use crate::errors::ServiceErrors;
|
use crate::errors::ServiceErrors;
|
||||||
@ -29,15 +29,15 @@ impl Handler<FindUser> for DbExecutor {
|
|||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
let row: User = users
|
|
||||||
|
let users_query = users
|
||||||
.distinct_on(id)
|
.distinct_on(id)
|
||||||
.filter(email.eq(msg.email.as_str()))
|
.filter(email.eq(msg.email.as_str()))
|
||||||
.filter(name.eq(msg.name.as_str()))
|
.filter(name.eq(msg.name.as_str()));
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&users_query));
|
||||||
|
users_query
|
||||||
.first(conn)
|
.first(conn)
|
||||||
.map_err(|_| {
|
.map_err(|_| ServiceErrors::RecordNotFound(format!("user {} {}", msg.name, msg.email)))
|
||||||
ServiceErrors::RecordNotFound(format!("user {} {}", msg.name, msg.email))
|
|
||||||
})?;
|
|
||||||
Ok(row)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,12 +60,12 @@ impl Handler<LoadProjectUsers> for DbExecutor {
|
|||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
let rows: Vec<User> = users
|
|
||||||
.distinct_on(id)
|
let users_query = users.distinct_on(id).filter(project_id.eq(msg.project_id));
|
||||||
.filter(project_id.eq(msg.project_id))
|
debug!("{}", diesel::debug_query::<Pg, _>(&users_query));
|
||||||
|
users_query
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("project users".to_string()))?;
|
.map_err(|_| ServiceErrors::RecordNotFound("project users".to_string()))
|
||||||
Ok(rows)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,17 +89,16 @@ impl Handler<LoadIssueAssignees> for DbExecutor {
|
|||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
let rows: Vec<(User, IssueAssignee)> = users
|
|
||||||
|
let users_query = users
|
||||||
.distinct_on(id)
|
.distinct_on(id)
|
||||||
.inner_join(issue_assignees.on(user_id.eq(id)))
|
.inner_join(issue_assignees.on(user_id.eq(id)))
|
||||||
.filter(issue_id.eq(msg.issue_id))
|
.filter(issue_id.eq(msg.issue_id))
|
||||||
|
.select(users::all_columns());
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&users_query));
|
||||||
|
users_query
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))?;
|
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
||||||
let mut vec: Vec<User> = vec![];
|
|
||||||
for row in rows {
|
|
||||||
vec.push(row.0);
|
|
||||||
}
|
|
||||||
Ok(vec)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,21 +130,17 @@ impl Handler<Register> for DbExecutor {
|
|||||||
return Err(ServiceErrors::RegisterCollision);
|
return Err(ServiceErrors::RegisterCollision);
|
||||||
}
|
}
|
||||||
|
|
||||||
let project: Project = match projects.first(conn) {
|
|
||||||
Ok(project) => project,
|
|
||||||
_ => {
|
|
||||||
let form = CreateProjectForm {
|
let form = CreateProjectForm {
|
||||||
name: "initial".to_string(),
|
name: "initial".to_string(),
|
||||||
url: "".to_string(),
|
url: "".to_string(),
|
||||||
description: "".to_string(),
|
description: "".to_string(),
|
||||||
category: Default::default(),
|
category: Default::default(),
|
||||||
};
|
};
|
||||||
diesel::insert_into(projects)
|
let insert_query = diesel::insert_into(projects).values(form);
|
||||||
.values(form)
|
debug!("{}", diesel::debug_query::<Pg, _>(&insert_query));
|
||||||
|
let project: Project = insert_query
|
||||||
.get_result(conn)
|
.get_result(conn)
|
||||||
.map_err(|_| ServiceErrors::RegisterCollision)?
|
.map_err(|_| ServiceErrors::RegisterCollision)?;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let form = UserForm {
|
let form = UserForm {
|
||||||
name: msg.name,
|
name: msg.name,
|
||||||
@ -154,7 +149,9 @@ impl Handler<Register> for DbExecutor {
|
|||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
match diesel::insert_into(users).values(form).execute(conn) {
|
let insert_user_query = diesel::insert_into(users).values(form);
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&insert_user_query));
|
||||||
|
match insert_user_query.execute(conn) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
_ => return Err(ServiceErrors::RegisterCollision),
|
_ => return Err(ServiceErrors::RegisterCollision),
|
||||||
};
|
};
|
||||||
@ -188,12 +185,10 @@ impl Handler<LoadInvitedUsers> for DbExecutor {
|
|||||||
.inner_join(invitations.on(i_email.eq(u_email)))
|
.inner_join(invitations.on(i_email.eq(u_email)))
|
||||||
.filter(invited_by_id.eq(msg.user_id))
|
.filter(invited_by_id.eq(msg.user_id))
|
||||||
.select(users::all_columns());
|
.select(users::all_columns());
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||||
|
query
|
||||||
let res: Vec<User> = query
|
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,15 +200,46 @@ fn count_matching_users(name: &str, email: &str, conn: &DbPooledConn) -> i64 {
|
|||||||
.or_filter(dsl::email.ne(email).and(dsl::name.eq(name)))
|
.or_filter(dsl::email.ne(email).and(dsl::name.eq(name)))
|
||||||
.or_filter(dsl::email.eq(email).and(dsl::name.eq(name)))
|
.or_filter(dsl::email.eq(email).and(dsl::name.eq(name)))
|
||||||
.count();
|
.count();
|
||||||
|
info!("{}", diesel::debug_query::<diesel::pg::Pg, _>(&query));
|
||||||
info!(
|
|
||||||
"{}",
|
|
||||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
query.get_result::<i64>(conn).unwrap_or(1)
|
query.get_result::<i64>(conn).unwrap_or(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct UpdateAvatarUrl {
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub avatar_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for UpdateAvatarUrl {
|
||||||
|
type Result = Result<User, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<UpdateAvatarUrl> for DbExecutor {
|
||||||
|
type Result = Result<User, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: UpdateAvatarUrl, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
use crate::schema::users::dsl::{avatar_url, id, users};
|
||||||
|
|
||||||
|
let conn = &self
|
||||||
|
.pool
|
||||||
|
.get()
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
let update_query = diesel::update(users)
|
||||||
|
.set(avatar_url.eq(msg.avatar_url))
|
||||||
|
.filter(id.eq(msg.user_id));
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&update_query));
|
||||||
|
update_query
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||||
|
|
||||||
|
let user_query = users.find(msg.user_id);
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
||||||
|
user_query
|
||||||
|
.first(conn)
|
||||||
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::db::build_pool;
|
use crate::db::build_pool;
|
||||||
|
@ -7,6 +7,7 @@ extern crate log;
|
|||||||
|
|
||||||
use actix::Actor;
|
use actix::Actor;
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
|
#[cfg(feature = "local-storage")]
|
||||||
use actix_files as fs;
|
use actix_files as fs;
|
||||||
use actix_web::{App, HttpServer};
|
use actix_web::{App, HttpServer};
|
||||||
|
|
||||||
@ -29,6 +30,13 @@ async fn main() -> Result<(), String> {
|
|||||||
|
|
||||||
let web_config = web::Configuration::read();
|
let web_config = web::Configuration::read();
|
||||||
|
|
||||||
|
std::fs::create_dir_all(web_config.tmp_dir.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
#[cfg(feature = "local-storage")]
|
||||||
|
if !web_config.filesystem.is_empty() {
|
||||||
|
let filesystem = &web_config.filesystem;
|
||||||
|
std::fs::create_dir_all(filesystem.store_path.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
let db_addr = actix::SyncArbiter::start(
|
let db_addr = actix::SyncArbiter::start(
|
||||||
crate::db::Configuration::read().concurrency,
|
crate::db::Configuration::read().concurrency,
|
||||||
crate::db::DbExecutor::default,
|
crate::db::DbExecutor::default,
|
||||||
@ -41,8 +49,7 @@ async fn main() -> Result<(), String> {
|
|||||||
let ws_server = WsServer::default().start();
|
let ws_server = WsServer::default().start();
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let web_config = web::Configuration::read();
|
let app = App::new()
|
||||||
let mut app = App::new()
|
|
||||||
.wrap(actix_web::middleware::Logger::default())
|
.wrap(actix_web::middleware::Logger::default())
|
||||||
.wrap(Cors::default())
|
.wrap(Cors::default())
|
||||||
.data(ws_server.clone())
|
.data(ws_server.clone())
|
||||||
@ -51,12 +58,19 @@ async fn main() -> Result<(), String> {
|
|||||||
.data(crate::db::build_pool())
|
.data(crate::db::build_pool())
|
||||||
.service(crate::ws::index)
|
.service(crate::ws::index)
|
||||||
.service(actix_web::web::scope("/avatar").service(crate::web::avatar::upload));
|
.service(actix_web::web::scope("/avatar").service(crate::web::avatar::upload));
|
||||||
if let Some(file_system) = web_config.filesystem.as_ref() {
|
|
||||||
app = app.service(fs::Files::new(
|
#[cfg(feature = "local-storage")]
|
||||||
file_system.client_path.as_str(),
|
let web_config = web::Configuration::read();
|
||||||
file_system.store_path.as_str(),
|
#[cfg(feature = "local-storage")]
|
||||||
));
|
let app = if !web_config.filesystem.is_empty() {
|
||||||
}
|
let filesystem = &web_config.filesystem;
|
||||||
|
app.service(fs::Files::new(
|
||||||
|
filesystem.client_path.as_str(),
|
||||||
|
filesystem.store_path.as_str(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
app
|
||||||
|
};
|
||||||
app
|
app
|
||||||
})
|
})
|
||||||
.workers(web_config.concurrency)
|
.workers(web_config.concurrency)
|
||||||
|
@ -1,19 +1,37 @@
|
|||||||
|
#[cfg(feature = "aws-s3")]
|
||||||
|
use std::fs::File;
|
||||||
|
#[cfg(feature = "aws-s3")]
|
||||||
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_multipart::{Field, Multipart};
|
use actix_multipart::{Field, Multipart};
|
||||||
use actix_web::http::header::ContentDisposition;
|
use actix_web::http::header::ContentDisposition;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{get, post, web, Error, HttpResponse, Responder};
|
use actix_web::{post, web, Error, HttpResponse};
|
||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
|
#[cfg(feature = "aws-s3")]
|
||||||
|
use rusoto_s3::{PutObjectRequest, S3Client, S3};
|
||||||
|
|
||||||
|
use jirs_data::{User, UserId, WsMsg};
|
||||||
|
|
||||||
|
use crate::db::authorize_user::AuthorizeUser;
|
||||||
|
use crate::db::users::UpdateAvatarUrl;
|
||||||
use crate::db::DbExecutor;
|
use crate::db::DbExecutor;
|
||||||
|
#[cfg(feature = "aws-s3")]
|
||||||
|
use crate::web::AmazonS3Storage;
|
||||||
|
use crate::ws::InnerMsg::BroadcastToChannel;
|
||||||
|
use crate::ws::WsServer;
|
||||||
|
|
||||||
#[post("/")]
|
#[post("/")]
|
||||||
pub async fn upload(
|
pub async fn upload(
|
||||||
mut payload: Multipart,
|
mut payload: Multipart,
|
||||||
db: Data<Addr<DbExecutor>>,
|
db: Data<Addr<DbExecutor>>,
|
||||||
|
ws: Data<Addr<WsServer>>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let mut user_id: Option<UserId> = None;
|
||||||
|
let mut avatar_url: Option<String> = None;
|
||||||
|
|
||||||
while let Ok(Some(field)) = payload.try_next().await {
|
while let Ok(Some(field)) = payload.try_next().await {
|
||||||
let disposition: ContentDisposition = match field.content_disposition() {
|
let disposition: ContentDisposition = match field.content_disposition() {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
@ -22,47 +40,163 @@ pub async fn upload(
|
|||||||
if !disposition.is_form_data() {
|
if !disposition.is_form_data() {
|
||||||
return Ok(HttpResponse::BadRequest().finish());
|
return Ok(HttpResponse::BadRequest().finish());
|
||||||
}
|
}
|
||||||
let _name = disposition.get_name().as_ref().cloned().unwrap_or_default();
|
|
||||||
match disposition.get_name() {
|
match disposition.get_name() {
|
||||||
Some("token") => handle_token(field, disposition, db.clone()).await?,
|
Some("token") => {
|
||||||
Some("avatar") => handle_image(field, disposition, db.clone()).await?,
|
user_id = Some(handle_token(field, db.clone()).await?);
|
||||||
|
}
|
||||||
|
Some("avatar") => {
|
||||||
|
let id = user_id.ok_or_else(|| HttpResponse::Unauthorized().finish())?;
|
||||||
|
avatar_url = Some(handle_image(id, field, disposition, db.clone()).await?);
|
||||||
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(HttpResponse::Ok().json(""))
|
match (user_id, avatar_url) {
|
||||||
|
(Some(user_id), Some(avatar_url)) => {
|
||||||
|
let user = update_user_avatar(user_id, avatar_url.clone(), db).await?;
|
||||||
|
ws.send(BroadcastToChannel(
|
||||||
|
user.project_id,
|
||||||
|
WsMsg::AvatarUrlChanged(user.id, avatar_url),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.map_err(|_| HttpResponse::UnprocessableEntity().finish())?;
|
||||||
|
Ok(HttpResponse::NoContent().finish())
|
||||||
|
}
|
||||||
|
_ => Ok(HttpResponse::UnprocessableEntity().finish()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_token(
|
async fn update_user_avatar(
|
||||||
mut field: Field,
|
user_id: UserId,
|
||||||
_disposition: ContentDisposition,
|
new_url: String,
|
||||||
_db: Data<Addr<DbExecutor>>,
|
db: Data<Addr<DbExecutor>>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<User, Error> {
|
||||||
Ok(())
|
match db
|
||||||
|
.send(UpdateAvatarUrl {
|
||||||
|
user_id,
|
||||||
|
avatar_url: Some(new_url),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Ok(user)) => Ok(user),
|
||||||
|
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
error!("{:?}", e);
|
||||||
|
Err(HttpResponse::Unauthorized().finish().into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("{:?}", e);
|
||||||
|
Err(HttpResponse::Unauthorized().finish().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_token(mut field: Field, db: Data<Addr<DbExecutor>>) -> Result<UserId, Error> {
|
||||||
|
let mut f: Vec<u8> = vec![];
|
||||||
|
while let Some(chunk) = field.next().await {
|
||||||
|
let data = chunk.unwrap();
|
||||||
|
f = web::block(move || f.write_all(&data).map(|_| f)).await?;
|
||||||
|
}
|
||||||
|
let access_token = String::from_utf8(f)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.parse::<uuid::Uuid>()
|
||||||
|
.map_err(|_| HttpResponse::Unauthorized().finish())?;
|
||||||
|
match db.send(AuthorizeUser { access_token }).await {
|
||||||
|
Ok(Ok(user)) => Ok(user.id),
|
||||||
|
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
error!("{:?}", e);
|
||||||
|
Err(HttpResponse::Unauthorized().finish().into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("{:?}", e);
|
||||||
|
Err(HttpResponse::Unauthorized().finish().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_image(
|
async fn handle_image(
|
||||||
|
user_id: UserId,
|
||||||
mut field: Field,
|
mut field: Field,
|
||||||
disposition: ContentDisposition,
|
disposition: ContentDisposition,
|
||||||
_db: Data<Addr<DbExecutor>>,
|
_db: Data<Addr<DbExecutor>>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<String, Error> {
|
||||||
let web_config = crate::web::Configuration::read();
|
let web_config = crate::web::Configuration::read();
|
||||||
|
|
||||||
|
let mut new_link = None;
|
||||||
let filename = disposition.get_filename().unwrap();
|
let filename = disposition.get_filename().unwrap();
|
||||||
let filepath = format!("./tmp/{}", filename);
|
let tmp_file_path = format!("{}/{}-{}", web_config.tmp_dir, user_id, filename);
|
||||||
// File::create is blocking operation, use threadpool
|
let mut f = web::block(move || std::fs::File::create(tmp_file_path))
|
||||||
let mut f = web::block(|| std::fs::File::create(filepath))
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Field in turn is stream of *Bytes* object
|
|
||||||
|
// Write temp file
|
||||||
while let Some(chunk) = field.next().await {
|
while let Some(chunk) = field.next().await {
|
||||||
let data = chunk.unwrap();
|
let data = chunk.unwrap();
|
||||||
// filesystem operations are blocking, we have to use thread pool
|
|
||||||
f = web::block(move || f.write_all(&data).map(|_| f)).await?;
|
f = web::block(move || f.write_all(&data).map(|_| f)).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
// Write public visible file
|
||||||
|
#[cfg(feature = "local-storage")]
|
||||||
|
if !web_config.filesystem.is_empty() {
|
||||||
|
let filesystem = &web_config.filesystem;
|
||||||
|
std::fs::copy(
|
||||||
|
format!("{}/{}-{}", web_config.tmp_dir, user_id, filename),
|
||||||
|
format!("{}/{}-{}", filesystem.store_path, user_id, filename),
|
||||||
|
)
|
||||||
|
.map_err(|_| HttpResponse::InsufficientStorage().finish())?;
|
||||||
|
|
||||||
|
new_link = Some(format!(
|
||||||
|
"{proto}://{bind}{port}{client_path}/{user_id}-{filename}",
|
||||||
|
proto = if web_config.ssl { "https" } else { "http" },
|
||||||
|
bind = web_config.bind,
|
||||||
|
port = match web_config.port.as_str() {
|
||||||
|
"80" | "443" => "".to_string(),
|
||||||
|
p @ _ => format!(":{}", p),
|
||||||
|
},
|
||||||
|
client_path = filesystem.client_path,
|
||||||
|
user_id = user_id,
|
||||||
|
filename = filename
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/{id}")]
|
// Upload to AWS S3
|
||||||
async fn download(_id: web::Path<i32>) -> impl Responder {
|
#[cfg(feature = "aws-s3")]
|
||||||
HttpResponse::Ok().json("")
|
if !web_config.s3.is_empty() {
|
||||||
|
let s3 = &web_config.s3;
|
||||||
|
s3.set_variables();
|
||||||
|
let key = format!("{}-{}", user_id, filename);
|
||||||
|
let mut tmp_file = File::open(format!("{}/{}-{}", web_config.tmp_dir, user_id, filename))
|
||||||
|
.map_err(|_| HttpResponse::InternalServerError())?;
|
||||||
|
let mut buffer: Vec<u8> = vec![];
|
||||||
|
tmp_file
|
||||||
|
.read_to_end(&mut buffer)
|
||||||
|
.map_err(|_| HttpResponse::InternalServerError())?;
|
||||||
|
|
||||||
|
let client = S3Client::new(s3.region());
|
||||||
|
let put_object = PutObjectRequest {
|
||||||
|
bucket: s3.bucket.clone(),
|
||||||
|
key: key.clone(),
|
||||||
|
body: Some(buffer.into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let _id = client
|
||||||
|
.put_object(put_object)
|
||||||
|
.await
|
||||||
|
.map_err(|_| HttpResponse::InternalServerError())?;
|
||||||
|
new_link = Some(aws_s3_url(key.as_str(), s3));
|
||||||
|
}
|
||||||
|
std::fs::remove_file(format!("{}/{}-{}", web_config.tmp_dir, user_id, filename).as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
Ok(new_link.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "aws-s3")]
|
||||||
|
fn aws_s3_url(key: &str, config: &AmazonS3Storage) -> String {
|
||||||
|
format!(
|
||||||
|
"https://{bucket}.s3.{region}.amazonaws.com/{key}",
|
||||||
|
bucket = config.bucket,
|
||||||
|
region = config.region_name,
|
||||||
|
key = key
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ use std::fs::*;
|
|||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{HttpRequest, HttpResponse};
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
|
#[cfg(feature = "aws-s3")]
|
||||||
|
use rusoto_core::Region;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use jirs_data::User;
|
use jirs_data::User;
|
||||||
@ -40,24 +42,44 @@ pub enum Protocol {
|
|||||||
Https,
|
Https,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "local-storage")]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct FileSystemStorage {
|
pub struct FileSystemStorage {
|
||||||
pub store_path: String,
|
pub store_path: String,
|
||||||
pub client_path: String,
|
pub client_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "local-storage")]
|
||||||
|
impl FileSystemStorage {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.store_path.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "aws-s3")]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct AmazonS3Storage {
|
pub struct AmazonS3Storage {
|
||||||
pub access_key_id: String,
|
pub access_key_id: String,
|
||||||
pub secret_access_key: String,
|
pub secret_access_key: String,
|
||||||
pub bucket: String,
|
pub bucket: String,
|
||||||
pub region: String,
|
pub region_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[cfg(feature = "aws-s3")]
|
||||||
pub enum Storage {
|
impl AmazonS3Storage {
|
||||||
FileSystem,
|
pub fn is_empty(&self) -> bool {
|
||||||
AmazonS3,
|
self.access_key_id.is_empty() || self.secret_access_key.is_empty() || self.bucket.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn region(&self) -> Region {
|
||||||
|
self.region_name.parse::<Region>().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_variables(&self) {
|
||||||
|
std::env::set_var("AWS_ACCESS_KEY_ID", self.access_key_id.as_str());
|
||||||
|
std::env::set_var("AWS_SECRET_ACCESS_KEY", self.secret_access_key.as_str());
|
||||||
|
std::env::set_var("AWS_S3_BUCKET_NAME", self.region_name.as_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -66,24 +88,77 @@ pub struct Configuration {
|
|||||||
pub port: String,
|
pub port: String,
|
||||||
pub bind: String,
|
pub bind: String,
|
||||||
pub ssl: bool,
|
pub ssl: bool,
|
||||||
pub storage: Storage,
|
pub tmp_dir: String,
|
||||||
pub filesystem: Option<FileSystemStorage>,
|
#[cfg(feature = "aws-s3")]
|
||||||
pub s3: Option<AmazonS3Storage>,
|
pub s3: AmazonS3Storage,
|
||||||
|
#[cfg(feature = "local-storage")]
|
||||||
|
pub filesystem: FileSystemStorage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Configuration {
|
impl Default for Configuration {
|
||||||
|
#[cfg(all(feature = "local-storage", feature = "aws-s3"))]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
concurrency: 2,
|
concurrency: 2,
|
||||||
port: "5000".to_string(),
|
port: "5000".to_string(),
|
||||||
bind: "0.0.0.0".to_string(),
|
bind: "0.0.0.0".to_string(),
|
||||||
ssl: false,
|
ssl: false,
|
||||||
storage: Storage::FileSystem,
|
|
||||||
filesystem: Some(FileSystemStorage {
|
tmp_dir: "./tmp".to_string(),
|
||||||
store_path: "./tmp".to_string(),
|
filesystem: FileSystemStorage {
|
||||||
|
store_path: "".to_string(),
|
||||||
client_path: "/img".to_string(),
|
client_path: "/img".to_string(),
|
||||||
}),
|
},
|
||||||
s3: None,
|
s3: AmazonS3Storage {
|
||||||
|
access_key_id: "".to_string(),
|
||||||
|
secret_access_key: "".to_string(),
|
||||||
|
bucket: "".to_string(),
|
||||||
|
region_name: Region::default().name().to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "local-storage", not(feature = "aws-s3")))]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
concurrency: 2,
|
||||||
|
port: "5000".to_string(),
|
||||||
|
bind: "0.0.0.0".to_string(),
|
||||||
|
ssl: false,
|
||||||
|
|
||||||
|
tmp_dir: "./tmp".to_string(),
|
||||||
|
filesystem: FileSystemStorage {
|
||||||
|
store_path: "./img".to_string(),
|
||||||
|
client_path: "/img".to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "aws-s3", not(feature = "local-storage")))]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
concurrency: 2,
|
||||||
|
port: "5000".to_string(),
|
||||||
|
bind: "0.0.0.0".to_string(),
|
||||||
|
ssl: false,
|
||||||
|
|
||||||
|
tmp_dir: "./tmp".to_string(),
|
||||||
|
s3: AmazonS3Storage {
|
||||||
|
access_key_id: "".to_string(),
|
||||||
|
secret_access_key: "".to_string(),
|
||||||
|
bucket: "".to_string(),
|
||||||
|
region_name: Region::default().name().to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "aws-s3"), not(feature = "local-storage")))]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
concurrency: 2,
|
||||||
|
port: "5000".to_string(),
|
||||||
|
bind: "0.0.0.0".to_string(),
|
||||||
|
ssl: false,
|
||||||
|
tmp_dir: "./tmp".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,6 +271,7 @@ impl Handler<InnerMsg> for WsServer {
|
|||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
_ => return debug!(" channel not found, aborting..."),
|
_ => return debug!(" channel not found, aborting..."),
|
||||||
};
|
};
|
||||||
|
let _s = set.len();
|
||||||
for r in set {
|
for r in set {
|
||||||
let recipient = match self.sessions.get(r) {
|
let recipient = match self.sessions.get(r) {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
|
Loading…
Reference in New Issue
Block a user