mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
feat-webgarages-121 (#129)
### Description Closes #121 ### Please confirm - [x] I am the legal owner or represent the legal owner of all work submitted (including LLM-generated code, if any) - [x] I consent to my changes being added to this FOSS project - [x] I have confirmed that this does not add new errors or warnings with `utils/clippy.sh` - [ ] This PR used LLMs to generate some or all of the code changes Reviewed-on: https://git.ngram.ca/OpenJam/rc-servers/pulls/129
This commit is contained in:
@@ -11,6 +11,7 @@ readme.workspace = true
|
||||
actix-web = { workspace = true, features = [ "secure-cookies" ] }
|
||||
actix-session = { version = "0.11", features = [ "cookie-session" ], default-features = false }
|
||||
actix-identity = "0.9"
|
||||
actix-multipart = "0.7"
|
||||
cookie = "0.18"
|
||||
actix-files.workspace = true
|
||||
#actix-ws = "0.3"
|
||||
@@ -21,8 +22,11 @@ tokio = { version = "1.43", features = [ "rt-multi-thread" ] }
|
||||
clap.workspace = true
|
||||
handlebars = { version = "6", features = ["dir_source"] }
|
||||
oj_rc_core = { version = "*", path = "../rc_core" }
|
||||
oj_rc_plugins = { version = "*", path = "../rc_plugins" }
|
||||
oj_convert.workspace = true
|
||||
libfj.workspace = true
|
||||
git-version.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
base64.workspace = true
|
||||
chrono.workspace = true
|
||||
|
||||
1
rc_society/src/api/garage/mod.rs
Normal file
1
rc_society/src/api/garage/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod plugins;
|
||||
124
rc_society/src/api/garage/plugins/bobocraft.rs
Normal file
124
rc_society/src/api/garage/plugins/bobocraft.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use oj_convert::{Bobocraft, BobocraftBlock};
|
||||
|
||||
pub struct RexLike {
|
||||
convert: std::sync::Arc<oj_rc_core::cubes::CubeConversionParser>,
|
||||
}
|
||||
|
||||
impl RexLike {
|
||||
pub fn new(convert: std::sync::Arc<oj_rc_core::cubes::CubeConversionParser>) -> Self {
|
||||
Self {
|
||||
convert,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl oj_rc_plugins::vehicle_import::VehicleImportPlugin for RexLike {
|
||||
fn file_ext(&self) -> &'static str {
|
||||
"bobo"
|
||||
}
|
||||
|
||||
fn import(&self, upload: &[u8]) -> Result<oj_rc_plugins::vehicle_import::VehicleImportData, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
let data = Bobocraft::parse(upload).map_err(|e| {
|
||||
log::error!("Failed to parse bobocraft import: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let cubes = data.cubes.iter()
|
||||
.map(|b| oj_rc_core::cubes::Cube {
|
||||
id: b.cube_id,
|
||||
x: b.x,
|
||||
y: b.y,
|
||||
z: b.z,
|
||||
orientation: b.orientation,
|
||||
}).collect();
|
||||
let cube_data = oj_rc_core::cubes::Cube::dump_list(cubes)
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to write cube data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let colours = data.cubes.iter()
|
||||
.map(|b| oj_rc_core::cubes::Colour {
|
||||
colour: b.color.unwrap_or(0),
|
||||
x: b.x,
|
||||
y: b.y,
|
||||
z: b.z,
|
||||
}).collect();
|
||||
let colour_data = oj_rc_core::cubes::Colour::dump_list(colours)
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to write colour data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let upgraded = self.convert.upgrade_to_modern(
|
||||
&mut std::io::Cursor::new(&cube_data),
|
||||
&mut std::io::Cursor::new(&colour_data),
|
||||
).map_err(|e| {
|
||||
log::warn!("Failed to upgrade bobo import data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Unsupported
|
||||
})?;
|
||||
Ok(oj_rc_plugins::vehicle_import::VehicleImportData {
|
||||
cube_data: upgraded.cube_data,
|
||||
colour_data: upgraded.colour_data,
|
||||
vehicle_name: Some(data.item_name),
|
||||
vehicle_author: Some(data.added_by_display_name),
|
||||
})
|
||||
}
|
||||
|
||||
fn export(&self, data: &oj_rc_plugins::vehicle_import::VehicleImportData) -> Result<Vec<u8>, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
let cubes = oj_rc_core::cubes::Cube::parse_list(&mut std::io::Cursor::new(&data.cube_data))
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to read cube data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let colours = oj_rc_core::cubes::Colour::parse_list(&mut std::io::Cursor::new(&data.colour_data))
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to read colour data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
if colours.len() != cubes.len() {
|
||||
log::error!("Failed to parse the same amount of cubes and colours (aborting)");
|
||||
return Err(oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid);
|
||||
}
|
||||
let mut placements = Vec::with_capacity(colours.len());
|
||||
for i in 0..cubes.len() {
|
||||
let colour = &colours[i];
|
||||
let cube = &cubes[i];
|
||||
// assumption: cube and colours are in the same order
|
||||
let placement = BobocraftBlock {
|
||||
cube_id: cube.id,
|
||||
x: cube.x,
|
||||
y: cube.y,
|
||||
z: cube.z,
|
||||
orientation: cube.orientation,
|
||||
color: if (cube.x, cube.y, cube.z) == (colour.x, colour.y, colour.z) { Some(colour.colour) } else { None },
|
||||
};
|
||||
placements.push(placement);
|
||||
}
|
||||
let bobo = Bobocraft {
|
||||
item_id: 0,
|
||||
item_name: data.vehicle_name.clone().unwrap_or_else(|| "Exported vehicle".to_owned()),
|
||||
item_description: format!("Exported by OpenJam {} {}", env!("CARGO_CRATE_NAME"), env!("CARGO_PKG_VERSION")),
|
||||
thumbnail: String::default(),
|
||||
added_by: data.vehicle_author.clone().unwrap_or_default(),
|
||||
added_by_display_name: data.vehicle_author.clone().unwrap_or_default(),
|
||||
added_date: chrono::Utc::now().naive_utc(),
|
||||
expiry_date: chrono::Utc::now().naive_utc(),
|
||||
cpu: 0, // TODO?
|
||||
total_robot_ranking: None, // TODO?
|
||||
buyable: true,
|
||||
buy_count: 0,
|
||||
unknown_count: 0,
|
||||
combat_rating: 5.0,
|
||||
cosmetic_rating: 5.0,
|
||||
featured: false,
|
||||
banner_message: None,
|
||||
offset: (0, 0, 0),
|
||||
cubes: placements,
|
||||
};
|
||||
bobo.dump()
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to write bobocraft format: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl oj_rc_plugins::Plugin for RexLike {}
|
||||
103
rc_society/src/api/garage/plugins/classic.rs
Normal file
103
rc_society/src/api/garage/plugins/classic.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use oj_convert::{Classic, ClassicBlock};
|
||||
|
||||
pub struct FifteenLike {
|
||||
convert: std::sync::Arc<oj_rc_core::cubes::CubeConversionParser>,
|
||||
base_image: std::sync::Arc<Vec<u8>>,
|
||||
cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>,
|
||||
}
|
||||
|
||||
impl FifteenLike {
|
||||
pub fn new(
|
||||
convert: std::sync::Arc<oj_rc_core::cubes::CubeConversionParser>,
|
||||
base_image: Vec<u8>,
|
||||
cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>,
|
||||
) -> Self {
|
||||
Self {
|
||||
convert,
|
||||
base_image: std::sync::Arc::new(base_image),
|
||||
cpu_counter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl oj_rc_plugins::vehicle_import::VehicleImportPlugin for FifteenLike {
|
||||
fn file_ext(&self) -> &'static str {
|
||||
"png"
|
||||
}
|
||||
|
||||
fn import(&self, upload: &[u8]) -> Result<oj_rc_plugins::vehicle_import::VehicleImportData, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
let data = Classic::parse(upload).map_err(|e| {
|
||||
log::error!("Failed to parse RC15 import: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let cubes = data.cubes.iter()
|
||||
.map(|b| oj_rc_core::cubes::Cube {
|
||||
id: b.cube_id,
|
||||
x: b.x,
|
||||
y: b.y,
|
||||
z: b.z,
|
||||
orientation: b.orientation,
|
||||
}).collect();
|
||||
let cube_data = oj_rc_core::cubes::Cube::dump_list(cubes)
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to write cube data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let converted = self.convert.convert_to_modern(
|
||||
&mut std::io::Cursor::new(&cube_data),
|
||||
).map_err(|e| {
|
||||
log::warn!("Failed to modernize RC15 import data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Unsupported
|
||||
})?;
|
||||
Ok(oj_rc_plugins::vehicle_import::VehicleImportData {
|
||||
cube_data: converted.cube_data,
|
||||
colour_data: converted.colour_data,
|
||||
vehicle_name: Some(data.item_name),
|
||||
vehicle_author: data.item_author,
|
||||
})
|
||||
}
|
||||
|
||||
fn export(&self, data: &oj_rc_plugins::vehicle_import::VehicleImportData) -> Result<Vec<u8>, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
let cpu_count = self.cpu_counter.calculate_cpu(&mut std::io::Cursor::new(&data.cube_data)).total;
|
||||
let classified = self.convert.convert_to_classic(
|
||||
&mut std::io::Cursor::new(&data.cube_data),
|
||||
&mut std::io::Cursor::new(&data.colour_data),
|
||||
).map_err(|e| {
|
||||
log::warn!("Failed to classic-ify RC15 export data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Unsupported
|
||||
})?;
|
||||
let cubes = oj_rc_core::cubes::Cube::parse_list(&mut std::io::Cursor::new(&classified))
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to read cube data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let mut placements = Vec::with_capacity(cubes.len());
|
||||
for cube in cubes.iter() {
|
||||
let placement = ClassicBlock {
|
||||
cube_id: cube.id,
|
||||
x: cube.x,
|
||||
y: cube.y,
|
||||
z: cube.z,
|
||||
orientation: cube.orientation,
|
||||
};
|
||||
placements.push(placement);
|
||||
}
|
||||
let classic = Classic {
|
||||
item_author: data.vehicle_author.clone(),
|
||||
game_version: Some(1), // once told me
|
||||
export_time: Some(chrono::Utc::now().to_rfc3339()),
|
||||
item_name: data.vehicle_name.clone().unwrap_or_else(|| "Exported vehicle".to_owned()),
|
||||
item_description: Some(format!("Exported by OpenJam {} {}", env!("CARGO_CRATE_NAME"), env!("CARGO_PKG_VERSION"))),
|
||||
item_tier: 1, // TODO
|
||||
item_cpu: cpu_count,
|
||||
cubes: placements,
|
||||
};
|
||||
classic.dump(&self.base_image)
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to dump the RC15 PNG data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Unsupported
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl oj_rc_plugins::Plugin for FifteenLike {}
|
||||
102
rc_society/src/api/garage/plugins/factory.rs
Normal file
102
rc_society/src/api/garage/plugins/factory.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use libfj::robocraft::{FactoryRobotGetInfo, FactoryInfo};
|
||||
|
||||
pub struct FactoryLike {
|
||||
convert: std::sync::Arc<oj_rc_core::cubes::CubeConversionParser>,
|
||||
}
|
||||
|
||||
impl FactoryLike {
|
||||
pub fn new(convert: std::sync::Arc<oj_rc_core::cubes::CubeConversionParser>,) -> Self {
|
||||
Self {
|
||||
convert,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_guess_json_format(upload: &[u8]) -> Option<FactoryRobotGetInfo> {
|
||||
let reader = Cursor::new(upload);
|
||||
match serde_json::from_reader::<_, FactoryRobotGetInfo>(reader) {
|
||||
Ok(data) => Some(data),
|
||||
Err(e) => {
|
||||
let reader = Cursor::new(upload);
|
||||
match serde_json::from_reader::<_, FactoryInfo<FactoryRobotGetInfo>>(reader) {
|
||||
Ok(data) => Some(data.response),
|
||||
Err(e2) => {
|
||||
log::error!("Failed to parse json for factory-like vehicle import: (1){} (2){}", e, e2);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl oj_rc_plugins::vehicle_import::VehicleImportPlugin for FactoryLike {
|
||||
fn file_ext(&self) -> &'static str {
|
||||
"rcbup"
|
||||
}
|
||||
|
||||
fn import(&self, upload: &[u8]) -> Result<oj_rc_plugins::vehicle_import::VehicleImportData, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
if let Some(upload_data) = Self::try_guess_json_format(upload) {
|
||||
use base64::Engine;
|
||||
let cube_data = base64::engine::general_purpose::STANDARD.decode(&upload_data.cube_data).map_err(|e| {
|
||||
log::error!("Bad base64 encoding of cube data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let colour_data = base64::engine::general_purpose::STANDARD.decode(&upload_data.cube_data).map_err(|e| {
|
||||
log::error!("Bad base64 encoding of colour data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
let upgraded = self.convert.upgrade_to_modern(
|
||||
&mut std::io::Cursor::new(&cube_data),
|
||||
&mut std::io::Cursor::new(&colour_data),
|
||||
).map_err(|e| {
|
||||
log::warn!("Failed to upgrade rcbup import data: {}", e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Unsupported
|
||||
})?;
|
||||
Ok(oj_rc_plugins::vehicle_import::VehicleImportData {
|
||||
cube_data: upgraded.cube_data,
|
||||
colour_data: upgraded.colour_data,
|
||||
vehicle_name: Some(upload_data.item_name),
|
||||
vehicle_author: Some(upload_data.added_by_display_name),
|
||||
})
|
||||
} else {
|
||||
Err(oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid)
|
||||
}
|
||||
}
|
||||
|
||||
fn export(&self, data: &oj_rc_plugins::vehicle_import::VehicleImportData) -> Result<Vec<u8>, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
use base64::Engine;
|
||||
let json_data = FactoryRobotGetInfo {
|
||||
item_id: 0,
|
||||
item_name: data.vehicle_name.clone().unwrap_or_default(),
|
||||
item_description: String::default(),
|
||||
thumbnail: String::default(),
|
||||
added_by: data.vehicle_author.clone().unwrap_or_default(),
|
||||
added_by_display_name: data.vehicle_author.clone().unwrap_or_default(),
|
||||
added_date: "1970-01-01T00:00:01".to_owned(),
|
||||
expiry_date: "2100-01-01T00:00:01".to_owned(),
|
||||
rent_count: 0,
|
||||
buy_count: 0,
|
||||
cpu: 0, // TODO
|
||||
total_robot_ranking: 0, // TODO
|
||||
buyable: true,
|
||||
removed_date: None,
|
||||
ban_date: None,
|
||||
featured: false,
|
||||
banner_message: None,
|
||||
combat_rating: 5.0,
|
||||
cosmetic_rating: 5.0,
|
||||
cube_data: base64::engine::general_purpose::STANDARD.encode(&data.cube_data),
|
||||
colour_data: base64::engine::general_purpose::STANDARD.encode(&data.colour_data),
|
||||
cube_amounts: String::default(),
|
||||
};
|
||||
let output = serde_json::to_vec_pretty(&json_data).map_err(|e| {
|
||||
log::error!("Failed to convert vehicle `{:?}` to JSON: {}", json_data.item_name, e);
|
||||
oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid
|
||||
})?;
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl oj_rc_plugins::Plugin for FactoryLike {}
|
||||
61
rc_society/src/api/garage/plugins/mod.rs
Normal file
61
rc_society/src/api/garage/plugins/mod.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
mod factory;
|
||||
pub use factory::FactoryLike;
|
||||
|
||||
mod bobocraft;
|
||||
pub use bobocraft::RexLike;
|
||||
|
||||
mod classic;
|
||||
pub use classic::FifteenLike;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ImportPlugins {
|
||||
map: std::collections::HashMap<String, Box<dyn oj_rc_plugins::vehicle_import::VehicleImportPlugin>>,
|
||||
}
|
||||
|
||||
impl ImportPlugins {
|
||||
pub fn standard(
|
||||
assets_path: impl AsRef<std::path::Path>,
|
||||
parsers: &oj_rc_core::cubes::CubeParsers,
|
||||
) -> Self {
|
||||
let mut map = std::collections::HashMap::with_capacity(3);
|
||||
map.insert("rcbup".to_owned(), Box::new(FactoryLike::new(parsers.converter())) as _);
|
||||
map.insert("bobo".to_owned(), Box::new(RexLike::new(parsers.converter())) as _);
|
||||
let image_path = assets_path.as_ref().join("rc_export.png");
|
||||
log::debug!("Loading PNG for RC15 export steganography from {}", image_path.display());
|
||||
let image_data = std::fs::read(image_path).expect("Failed to read rc_export.png file required for exporting RC15 vehicles");
|
||||
map.insert("rc15".to_owned(), Box::new(FifteenLike::new(parsers.converter(), image_data, parsers.cpu_counter())) as _);
|
||||
Self {
|
||||
map,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_names(&self) -> impl std::iter::Iterator<Item=&'_ String> {
|
||||
let mut to_sort = self.map.keys().collect::<Vec<_>>();
|
||||
to_sort.sort_by_key(|x| x.to_lowercase());
|
||||
to_sort.into_iter()
|
||||
}
|
||||
|
||||
pub fn file_ext(&self, name: &str) -> Result<&'static str, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
if let Some(plugin) = self.map.get(name) {
|
||||
Ok(plugin.file_ext())
|
||||
} else {
|
||||
Err(oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Unsupported)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn import_by_name(&self, name: &str, upload: &[u8]) -> Result<oj_rc_plugins::vehicle_import::VehicleImportData, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
if let Some(plugin) = self.map.get(name) {
|
||||
plugin.import(upload)
|
||||
} else {
|
||||
Err(oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Unsupported)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_by_name(&self, name: &str, data: &oj_rc_plugins::vehicle_import::VehicleImportData) -> Result<Vec<u8>, oj_rc_plugins::vehicle_import::VehicleImportErrorCode> {
|
||||
if let Some(plugin) = self.map.get(name) {
|
||||
plugin.export(data)
|
||||
} else {
|
||||
Err(oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Unsupported)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod garage;
|
||||
|
||||
@@ -27,6 +27,14 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
let server_settings = actix_web::web::Data::new(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::server_config(&config));
|
||||
|
||||
let parsers = oj_rc_core::cubes::CubeParsers::new(&config);
|
||||
|
||||
let vehicle_importers = crate::api::garage::plugins::ImportPlugins::standard(&cli_args.assets_robocraft, &parsers);
|
||||
log::info!("Loaded {} vehicle import/export plugins", vehicle_importers.plugin_names().count());
|
||||
let importers_ref = actix_web::web::Data::new(vehicle_importers);
|
||||
|
||||
let parsers_ref = actix_web::web::Data::new(parsers);
|
||||
|
||||
let users = oj_rc_core::UserImpl::load(&cli_args.data_robocraft, &config).await.expect("Bad user data");
|
||||
let auth_ref = actix_web::web::Data::new(Box::new(users));
|
||||
|
||||
@@ -65,12 +73,22 @@ async fn main() -> std::io::Result<()> {
|
||||
.app_data(handlebars_ref.clone())
|
||||
.app_data(server_settings.clone())
|
||||
.app_data(auth_ref.clone())
|
||||
.app_data(importers_ref.clone())
|
||||
.app_data(parsers_ref.clone())
|
||||
.service(version_info)
|
||||
.service(web::login::form_submit)
|
||||
.service(web::login::form_load)
|
||||
.service(web::favicon::favicon_standard)
|
||||
.service(web::dashboard::get)
|
||||
.service(web::dashboard::post) // for login redirect
|
||||
.service(web::index::get)
|
||||
.service(web::garage::list::get)
|
||||
.service(web::garage::info::get)
|
||||
.service(web::garage::export::get)
|
||||
.service(web::garage::import::get_existing)
|
||||
.service(web::garage::import::get_new)
|
||||
.service(web::garage::import::post)
|
||||
.service(web::garage::selected::get)
|
||||
})
|
||||
.bind((cli_args.ip, cli_args.port))?
|
||||
.run()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use actix_web::{get, web::Data, Responder, HttpRequest};
|
||||
use actix_web::{get, post, web::Data, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
@@ -29,12 +29,12 @@ struct PermissionData {
|
||||
banned: bool,
|
||||
}
|
||||
|
||||
#[get("/dashboard")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match super::try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
super::LoginReturn::Success(user) => {
|
||||
// TODO
|
||||
log::debug!("Rendering user's dashboard");
|
||||
let creation_time = user.creation();
|
||||
let creation_time_chrono = chrono::DateTime::<chrono::Utc>::from_timestamp_secs(creation_time).unwrap_or_default();
|
||||
Ok(super::render_ok(
|
||||
@@ -63,3 +63,13 @@ pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Bo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/dashboard")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
dashboard_impl(handlebars_ref, auth, user_opt, req).await
|
||||
}
|
||||
|
||||
#[post("/dashboard")]
|
||||
pub async fn post(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
dashboard_impl(handlebars_ref, auth, user_opt, req).await
|
||||
}
|
||||
|
||||
52
rc_society/src/web/garage/export.rs
Normal file
52
rc_society/src/web/garage/export.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use actix_web::{HttpRequest, HttpResponse, Responder, get, http::StatusCode, web::{Data, Path, Query}};
|
||||
use actix_identity::Identity;
|
||||
|
||||
use oj_rc_plugins::vehicle_import::VehicleImportData;
|
||||
|
||||
use crate::web::{LoginReturn, try_auth_user};
|
||||
use crate::api::garage::plugins::ImportPlugins;
|
||||
|
||||
#[get("/garages/{id}/export")]
|
||||
pub async fn get(id: Path<i32>, query: Query<super::PortQuery>, exporter: Data<ImportPlugins>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
match user.garage_by_id(*id).await {
|
||||
Ok(Some(garage)) => {
|
||||
let data = VehicleImportData {
|
||||
cube_data: garage.robot_data,
|
||||
colour_data: garage.colour_data,
|
||||
vehicle_name: garage.name,
|
||||
vehicle_author: Some(user.display_name().to_owned()),
|
||||
};
|
||||
match exporter.export_by_name(&query.plugin, &data) {
|
||||
Ok(export) => {
|
||||
//export.push(b'\n');
|
||||
use actix_web::http::header::{ContentDisposition, TryIntoHeaderPair};
|
||||
let ext = exporter.file_ext(&query.plugin).unwrap();
|
||||
// TODO sanitise vehicle name and include it in the filename
|
||||
let dispo = ContentDisposition::attachment(format!("export-{}-{}.{}", &query.plugin, *id, ext));
|
||||
let (key, val) = dispo.try_into_pair().unwrap();
|
||||
let mut resp = HttpResponse::with_body(StatusCode::OK, export);
|
||||
resp.headers_mut().append(key, val);
|
||||
Ok(resp.map_into_boxed_body())
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed export of vehicle {} for user {}: {}", id, user.public_id(), e);
|
||||
Err(super::PluginPortError::from(e).into())
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
Ok(HttpResponse::NotFound()
|
||||
.finish())
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to load garage ID for user {}: {}", user.public_id(), e);
|
||||
Ok(HttpResponse::InternalServerError()
|
||||
.finish())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
221
rc_society/src/web/garage/import.rs
Normal file
221
rc_society/src/web/garage/import.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
use actix_web::{get, post, web::{Data, Path}, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::web::{LoginReturn, try_auth_user, render_ok, render_err};
|
||||
use crate::api::garage::plugins::ImportPlugins;
|
||||
|
||||
const FORM_NAME: &str = "garage_import";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
display_name: String,
|
||||
public_id: String,
|
||||
garage: Option<GarageData>,
|
||||
importers: Vec<String>,
|
||||
selected_importer: Option<String>,
|
||||
success: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct GarageData {
|
||||
garage_id: i32,
|
||||
slot: i32,
|
||||
}
|
||||
|
||||
async fn import_impl(id: Option<i32>, handlebars_ref: Data<handlebars::Handlebars<'_>>, importer: Data<ImportPlugins>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let html = if let Some(id) = id {
|
||||
match user.garage_by_id(id).await {
|
||||
Ok(Some(garage)) => {
|
||||
render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: Some(GarageData {
|
||||
garage_id: id,
|
||||
slot: garage.slot,
|
||||
}),
|
||||
importers: importer.plugin_names()
|
||||
.map(|x| x.to_owned())
|
||||
.collect(),
|
||||
selected_importer: None,
|
||||
success: None,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
},
|
||||
Ok(None) => {
|
||||
log::error!("Failed to find vehicle {} for user {}", id, user.public_id());
|
||||
render_err(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: Some(GarageData {
|
||||
garage_id: -1,
|
||||
slot: -1,
|
||||
}),
|
||||
importers: Vec::new(),
|
||||
selected_importer: None,
|
||||
success: None,
|
||||
},
|
||||
"Vehicle not found".to_owned(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to retrieve vehicle {} for user {}: {}", id, user.public_id(), e);
|
||||
render_err(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: Some(GarageData {
|
||||
garage_id: -1,
|
||||
slot: -1,
|
||||
}),
|
||||
importers: Vec::new(),
|
||||
selected_importer: None,
|
||||
success: None,
|
||||
},
|
||||
e.to_string(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: None,
|
||||
importers: importer.plugin_names()
|
||||
.map(|x| x.to_owned())
|
||||
.collect(),
|
||||
selected_importer: None,
|
||||
success: None,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
};
|
||||
Ok(
|
||||
html
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/garages/{id}/import")]
|
||||
pub async fn get_existing(id: Path<i32>, handlebars_ref: Data<handlebars::Handlebars<'_>>, importer: Data<ImportPlugins>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
import_impl(Some(*id), handlebars_ref, importer, auth, user_opt, req).await
|
||||
}
|
||||
|
||||
#[get("/garages/import")]
|
||||
pub async fn get_new(handlebars_ref: Data<handlebars::Handlebars<'_>>, importer: Data<ImportPlugins>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
import_impl(None, handlebars_ref, importer, auth, user_opt, req).await
|
||||
}
|
||||
|
||||
#[derive(Debug, actix_multipart::form::MultipartForm)]
|
||||
struct ImportForm {
|
||||
//#[multipart]
|
||||
files: Vec<actix_multipart::form::bytes::Bytes>,
|
||||
garage_id: actix_multipart::form::text::Text<i32>,
|
||||
plugin: actix_multipart::form::text::Text<String>,
|
||||
|
||||
}
|
||||
|
||||
#[post("/garages/import")]
|
||||
pub async fn post(form: actix_multipart::form::MultipartForm<ImportForm>, handlebars_ref: Data<handlebars::Handlebars<'_>>, importer: Data<ImportPlugins>, parsers: Data<oj_rc_core::cubes::CubeParsers>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let total_size: usize = form.files.iter().map(|f| f.data.len()).sum();
|
||||
if form.files.is_empty() {
|
||||
return Err(super::PluginPortError {
|
||||
code: oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid,
|
||||
}.into());
|
||||
}
|
||||
if *form.garage_id != -1 && form.files.len() != 1 {
|
||||
return Err(super::PluginPortError {
|
||||
code: oj_rc_plugins::vehicle_import::VehicleImportErrorCode::Invalid,
|
||||
}.into());
|
||||
}
|
||||
for (i, file) in form.files.iter().enumerate() {
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!("import {} file {} data: {:?}", &*form.plugin, i, &file.data[..]);
|
||||
let import_data = match importer.import_by_name(&form.plugin, &file.data)
|
||||
.map_err(|e| super::PluginPortError { code: e }) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
let html = render_err(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: None,
|
||||
importers: importer.plugin_names()
|
||||
.map(|x| x.to_owned())
|
||||
.collect(),
|
||||
selected_importer: Some(form.plugin.0.clone()),
|
||||
success: None,
|
||||
},
|
||||
format!("Failed to import file {}: {}", i, e),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
);
|
||||
return Ok(
|
||||
html
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
);
|
||||
},
|
||||
};
|
||||
let vehicle_data = oj_rc_core::persist::user::VehicleData {
|
||||
name: import_data.vehicle_name,
|
||||
slot: -1, // will be overriden in oj_rc_core
|
||||
robot_data: import_data.cube_data,
|
||||
colour_data: import_data.colour_data,
|
||||
weapon_order: Vec::default(), // will be overriden in oj_rc_core
|
||||
crf_id: None, // irrelevant
|
||||
was_rated: None, // irrelevant
|
||||
};
|
||||
user.save_garage(
|
||||
vehicle_data,
|
||||
if *form.garage_id < 0 { None } else { Some(*form.garage_id) }, // multipart crate silliness
|
||||
parsers.cpu_counter().as_ref(),
|
||||
parsers.weapon_order().as_ref(),
|
||||
).await?;
|
||||
}
|
||||
let html = render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: None,
|
||||
importers: importer.plugin_names()
|
||||
.map(|x| x.to_owned())
|
||||
.collect(),
|
||||
selected_importer: Some(form.plugin.0.clone()),
|
||||
success: Some(format!(
|
||||
"Imported {} vehicle{} ({:.2}KiB)",
|
||||
form.files.len(),
|
||||
if form.files.len() > 1 { "s" } else { "" },
|
||||
total_size as f64 / 1024.0,
|
||||
)),
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
);
|
||||
Ok(
|
||||
html
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
93
rc_society/src/web/garage/info.rs
Normal file
93
rc_society/src/web/garage/info.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use actix_web::{get, web::{Data, Path}, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::web::{LoginReturn, try_auth_user, render_ok, render_err};
|
||||
use crate::api::garage::plugins::ImportPlugins;
|
||||
|
||||
const FORM_NAME: &str = "garage_info";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
display_name: String,
|
||||
public_id: String,
|
||||
garage: GarageData,
|
||||
exporters: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct GarageData {
|
||||
garage_id: i32,
|
||||
slot: i32,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[get("/garages/{id}/info")]
|
||||
pub async fn get(id: Path<i32>, handlebars_ref: Data<handlebars::Handlebars<'_>>, exporter: Data<ImportPlugins>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let html = match user.garage_by_id(*id).await {
|
||||
Ok(Some(garage)) => {
|
||||
render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: GarageData {
|
||||
garage_id: *id,
|
||||
slot: garage.slot,
|
||||
name: garage.name.unwrap_or_default(),
|
||||
},
|
||||
exporters: exporter.plugin_names()
|
||||
.map(|x| x.to_owned())
|
||||
.collect(),
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
},
|
||||
Ok(None) => {
|
||||
log::error!("Failed to find vehicle {} for user {}", id, user.public_id());
|
||||
render_err(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: GarageData {
|
||||
garage_id: -1,
|
||||
slot: -1,
|
||||
name: String::default(),
|
||||
},
|
||||
exporters: Vec::new(),
|
||||
},
|
||||
"Vehicle not found".to_owned(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to retrieve vehicle {} for user {}: {}", id, user.public_id(), e);
|
||||
render_err(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garage: GarageData {
|
||||
garage_id: -1,
|
||||
slot: -1,
|
||||
name: String::default(),
|
||||
},
|
||||
exporters: Vec::new(),
|
||||
},
|
||||
e.to_string(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
html
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
76
rc_society/src/web/garage/list.rs
Normal file
76
rc_society/src/web/garage/list.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use actix_web::{get, web::Data, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::web::{LoginReturn, try_auth_user, render_ok, render_err};
|
||||
|
||||
const FORM_NAME: &str = "garage_list";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
display_name: String,
|
||||
public_id: String,
|
||||
garages: Vec<GarageData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct GarageData {
|
||||
garage_id: i32,
|
||||
slot: i32,
|
||||
name: String,
|
||||
cpu: i32,
|
||||
max_cpu: i32,
|
||||
creation_time_unix: i64,
|
||||
creation_time_iso: String,
|
||||
}
|
||||
|
||||
#[get("/garages/list")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let html = match user.garages().await {
|
||||
Ok(mut garage_infos) => {
|
||||
garage_infos.sort_by_key(|g| g.slot);
|
||||
render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garages: garage_infos.into_iter()
|
||||
.map(|g| GarageData {
|
||||
garage_id: g.id,
|
||||
slot: g.slot,
|
||||
name: g.name,
|
||||
cpu: g.total_robot_cpu,
|
||||
max_cpu: g.bay_cpu,
|
||||
creation_time_unix: g.creation_time,
|
||||
creation_time_iso: chrono::DateTime::<chrono::Utc>::from_timestamp_secs(g.creation_time).unwrap_or_default().to_rfc3339(),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to retrieve garages for user {}: {}", user.public_id(), e);
|
||||
render_err(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
garages: Vec::new(),
|
||||
},
|
||||
e.to_string(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok(
|
||||
html
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
37
rc_society/src/web/garage/mod.rs
Normal file
37
rc_society/src/web/garage/mod.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
pub mod list;
|
||||
pub mod export;
|
||||
pub mod import;
|
||||
pub mod info;
|
||||
pub mod selected;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct PortQuery {
|
||||
pub plugin: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PluginPortError {
|
||||
code: oj_rc_plugins::vehicle_import::VehicleImportErrorCode,
|
||||
}
|
||||
|
||||
impl std::convert::From<oj_rc_plugins::vehicle_import::VehicleImportErrorCode> for PluginPortError {
|
||||
fn from(value: oj_rc_plugins::vehicle_import::VehicleImportErrorCode) -> Self {
|
||||
Self {
|
||||
code: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for PluginPortError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.code)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for PluginPortError {}
|
||||
|
||||
impl actix_web::error::ResponseError for PluginPortError {
|
||||
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||
actix_web::http::StatusCode::UNPROCESSABLE_ENTITY
|
||||
}
|
||||
}
|
||||
29
rc_society/src/web/garage/selected.rs
Normal file
29
rc_society/src/web/garage/selected.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use actix_web::{get, web::{Data, Redirect}, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
|
||||
use crate::web::{LoginReturn, try_auth_user};
|
||||
|
||||
#[get("/garages/selected")]
|
||||
pub async fn get(auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let resp = if let Some(selected_garage) = user.garage_id_selected().await? {
|
||||
Redirect::to(format!("/garages/{}/info", selected_garage))
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
|
||||
} else {
|
||||
log::error!("User {} has no garage selected (bad database state?)", user.display_name());
|
||||
Redirect::to("/garages/list".to_owned())
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
};
|
||||
Ok(
|
||||
resp
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub mod dashboard;
|
||||
pub mod login;
|
||||
pub mod favicon;
|
||||
pub mod index;
|
||||
pub mod garage;
|
||||
|
||||
use serde::Serialize;
|
||||
use actix_web::{web::{Html, Redirect}, Responder};
|
||||
@@ -51,19 +52,25 @@ async fn try_auth_user(user_opt: Option<actix_identity::Identity>, auth: &oj_rc_
|
||||
if let Some(user) = user_opt {
|
||||
let user_id = user.id()?;
|
||||
match <oj_rc_core::UserImpl as oj_rc_core::UserProvider<()>>::web_authenticate(auth, user_id.clone()).await {
|
||||
Ok(user) => Ok(LoginReturn::Success(user)),
|
||||
Ok(user) => {
|
||||
//log::debug!("Web auth success");
|
||||
Ok(LoginReturn::Success(user))
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to login with token {}: {} ({:?})", user_id, e.message, e.code);
|
||||
Ok(LoginReturn::AuthFail(
|
||||
Redirect::to("/login")
|
||||
.temporary()
|
||||
.respond_to(req)
|
||||
.map_into_boxed_body()
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("No user identity, redirecting to login page");
|
||||
Ok(LoginReturn::AuthFail(
|
||||
Redirect::to("/login")
|
||||
.temporary()
|
||||
.respond_to(req)
|
||||
.map_into_boxed_body()
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user