1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2025-01-14 14:37:44 +00:00

Create base request handler

This commit is contained in:
Pierre HUBERT 2021-02-05 09:11:30 +01:00
parent 1d4dc87b00
commit 62a73b25d3
22 changed files with 668 additions and 601 deletions

View File

@ -9,6 +9,7 @@ use crate::api_data::res_check_security_questions_exists::ResCheckSecurityQuesti
use crate::api_data::res_get_security_questions::ResGetSecurityQuestions;
use crate::constants::PASSWORD_RESET_TOKEN_LENGTH;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::error::ResultBoxError;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::new_account::NewAccount;

View File

@ -5,6 +5,7 @@
use std::collections::HashMap;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::http_request_handler::HttpRequestHandler;
/// Get legacy call configuration

View File

@ -13,6 +13,7 @@ use crate::data::post::PostAccessLevel;
use crate::helpers::{comments_helper, notifications_helper, posts_helper};
use crate::utils::date_utils::time;
use crate::utils::string_utils::remove_html_nodes;
use crate::data::base_request_handler::BaseRequestHandler;
/// Create a new comment
pub fn create(r: &mut HttpRequestHandler) -> RequestResult {

View File

@ -12,6 +12,7 @@ use crate::api_data::res_count_unread_conversations::ResultCountUnreadConversati
use crate::api_data::res_create_conversation::ResCreateConversation;
use crate::api_data::res_find_private_conversations::ResFindPrivateConversations;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::new_conversation::NewConversation;
use crate::data::new_conversation_message::NewConversationMessage;
@ -184,7 +185,7 @@ pub fn refresh_list(r: &mut HttpRequestHandler) -> RequestResult {
r.forbidden(format!("Your do not belongs to conversation {} !", conv_id))?;
}
let list_conv = conversations_helper::get_last_messages(conv_id , 10)?;
let list_conv = conversations_helper::get_last_messages(conv_id, 10)?;
list.insert(conv_id as u64, list_conv);
conversations_helper::mark_user_seen(conv_id as u64, &r.user_id()?)?;

View File

@ -5,6 +5,7 @@
use crate::api_data::friend_api::FriendAPI;
use crate::api_data::friendship_status_api::FriendshipStatusAPI;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::notification::NotifEventType;
use crate::helpers::{account_helper, friends_helper, notifications_helper, user_helper};

View File

@ -11,6 +11,7 @@ use crate::api_data::res_change_group_logo::ResChangeGroupLogo;
use crate::api_data::res_create_group::GroupCreationResult;
use crate::constants::{DEFAULT_GROUP_LOGO, PATH_GROUPS_LOGOS};
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::group::{Group, GroupAccessLevel, GroupPostsCreationLevel, GroupRegistrationLevel, GroupVisibilityLevel};
use crate::data::group_id::GroupID;
use crate::data::group_member::{GroupMember, GroupMembershipLevel};

View File

@ -3,6 +3,7 @@
//! @author Pierre Hubert
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::error::ExecError;
use crate::data::group::GroupAccessLevel;
use crate::data::http_request_handler::HttpRequestHandler;

View File

@ -4,6 +4,7 @@
use crate::api_data::movie_api::MovieAPI;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::helpers::movies_helper;

View File

@ -6,6 +6,7 @@ use crate::api_data::notification_api::NotificationAPI;
use crate::api_data::res_count_all_unreads::ResCountAllUnread;
use crate::api_data::res_number_unread_notifications::ResNumberUnreadNotifications;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::error::ResultBoxError;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::notification::Notification;

View File

@ -7,6 +7,7 @@ use crate::api_data::posts_targets_api::PostsTargets;
use crate::api_data::res_create_post::ResCreatePost;
use crate::constants::{PATH_POST_IMAGES, PATH_POST_PDF};
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::error::{ExecError, ResultBoxError};
use crate::data::group::GroupAccessLevel;
use crate::data::http_request_handler::HttpRequestHandler;

View File

@ -4,6 +4,7 @@
use crate::api_data::global_search_result_api::GlobalSearchResultAPI;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::global_search_result::GlobalSearchResult;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::helpers::{groups_helper, user_helper};

View File

@ -16,8 +16,9 @@ use crate::api_data::http_error::HttpError;
use crate::constants::MAX_REQUEST_SIZE;
use crate::controllers::routes::{get_routes, RequestResult, Route};
use crate::controllers::routes::Method::{GET, POST};
use crate::data::base_request_handler::{BaseRequestHandler, PostFile, RequestValue};
use crate::data::config::Config;
use crate::data::http_request_handler::{HttpRequestHandler, PostFile, RequestValue};
use crate::data::http_request_handler::HttpRequestHandler;
use crate::helpers::requests_limit_helper;
/// Main server functions

View File

@ -1,11 +1,12 @@
use crate::data::http_request_handler::HttpRequestHandler;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::http_request_handler::HttpRequestHandler;
/// Main server controller
///
/// @author Pierre Hubert
/// Root server index
pub fn main_index(request: &mut HttpRequestHandler) -> RequestResult {
pub fn main_index(request: &mut HttpRequestHandler) -> RequestResult {
request.success("Comunic API server V3. (c) Pierre Hubert 2020")
}

View File

@ -9,6 +9,7 @@ use crate::api_data::res_create_custom_emoji::ResCreateCustomEmoji;
use crate::api_data::security_settings_api::SecuritySettingsAPI;
use crate::constants::SUPPORTED_LANGUAGES;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::general_settings::GeneralSettings;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::lang_settings::LangSettings;

View File

@ -5,6 +5,7 @@
use crate::api_data::survey_api::SurveyAPI;
use crate::constants::MAXIMUM_NUMBER_SURVEY_CHOICES;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::error::ResultBoxError;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::post::PostAccessLevel;

View File

@ -8,6 +8,7 @@ use std::collections::HashMap;
use crate::api_data::user_info::APIUserInfo;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::user::UserID;
use crate::helpers::user_helper;

View File

@ -2,11 +2,12 @@
//!
//! @author Pierre Hubert
use crate::data::http_request_handler::HttpRequestHandler;
use crate::controllers::routes::RequestResult;
use crate::helpers::{user_helper, groups_helper};
use crate::api_data::res_find_user_by_virtual_directory::FindUserByVirtualDirectoryAPIResult;
use crate::api_data::res_find_virtual_directory::ResultFindVirtualDirectory;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::helpers::{groups_helper, user_helper};
/// Find a user by its virtual directory
pub fn find_user(r: &mut HttpRequestHandler) -> RequestResult {
@ -31,9 +32,7 @@ pub fn find(r: &mut HttpRequestHandler) -> RequestResult {
println!("Find virtual directory errors:\n* User: {}\n* Group: {}",
user.unwrap_err(), group.unwrap_err());
r.not_found("Specified user / group virtual directory not found !".to_string())
}
else {
} else {
r.set_response(ResultFindVirtualDirectory::new(user, group))
}
}

View File

@ -4,6 +4,7 @@
use crate::api_data::user_membership_api::UserMembershipAPI;
use crate::controllers::routes::RequestResult;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::helpers::webapp_helper;

View File

@ -0,0 +1,601 @@
//! # Base Request Handler
//!
//! Base handling code for all user requests
use std::error::Error;
use exif::In;
use image::{GenericImageView, ImageFormat};
use serde::Serialize;
use crate::api_data::http_error::HttpError;
use crate::constants::PASSWORD_MIN_LENGTH;
use crate::controllers::routes::RequestResult;
use crate::data::comment::Comment;
use crate::data::custom_emoji::CustomEmoji;
use crate::data::error::{ExecError, ResultBoxError};
use crate::data::group::GroupAccessLevel;
use crate::data::group_id::GroupID;
use crate::data::post::{Post, PostAccessLevel};
use crate::data::user::UserID;
use crate::helpers::{account_helper, comments_helper, conversations_helper, custom_emojies_helper, friends_helper, groups_helper, movies_helper, posts_helper, user_helper, virtual_directory_helper};
use crate::helpers::virtual_directory_helper::VirtualDirType;
use crate::utils::pdf_utils::is_valid_pdf;
use crate::utils::string_utils::{check_emoji_code, check_string_before_insert, check_url, remove_html_nodes};
use crate::utils::user_data_utils::{generate_new_user_data_file_name, prepare_file_creation, user_data_path};
use crate::utils::virtual_directories_utils;
#[derive(Serialize)]
struct SuccessMessage {
success: String
}
pub struct PostFile {
pub name: String,
pub buff: bytes::Bytes,
}
/// Single request body value
pub enum RequestValue {
String(String),
File(PostFile),
}
pub trait BaseRequestHandler {
/// Get a parameter of the request
fn post_parameter_opt(&self, name: &str) -> Option<&RequestValue>;
/// Set response
fn set_response<T: Serialize>(&mut self, data: T) -> RequestResult;
/// Set an error
fn set_error(&mut self, error: HttpError);
/// Get remote IP address
fn remote_ip(&self) -> String;
/// Current user ID
fn user_id_opt_ref(&self) -> Option<&UserID>;
/// Success message
fn success(&mut self, message: &str) -> RequestResult {
self.set_response(SuccessMessage {
success: message.to_string()
})
}
/// Internal error response (500)
fn internal_error(&mut self, error: Box<dyn Error>) -> RequestResult {
self.set_error(HttpError::internal_error("Internal server error."));
Err(error)
}
/// Bad request (400)
fn bad_request(&mut self, message: String) -> RequestResult {
self.set_error(HttpError::bad_request(&message));
Err(Box::new(ExecError::new(&message)))
}
/// Forbidden (401)
///
/// I did not the HTTP official specs when I made this choice of using Unauthorized instead
/// of Forbidden. Today it would be really complicated to come back...
fn forbidden(&mut self, message: String) -> RequestResult {
self.set_error(HttpError::forbidden(&message));
Err(Box::new(ExecError::new(&message)))
}
/// Not found (404)
fn not_found(&mut self, message: String) -> RequestResult {
self.set_error(HttpError::not_found(&message));
Err(Box::new(ExecError::new(&message)))
}
/// Conflict (409)
fn conflict(&mut self, message: String) -> RequestResult {
self.set_error(HttpError::new(409, &message));
Err(Box::new(ExecError::new(&message)))
}
/// Too many requests (429)
fn too_many_requests(&mut self, message: &str) -> RequestResult {
self.set_error(HttpError::new(429, message));
Err(Box::new(ExecError::new(message)))
}
/// If result is not OK, return a bad request
fn ok_or_bad_request<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
match res {
Ok(e) => Ok(e),
Err(err) => {
println!("Error leading to bad request: {}", err);
self.bad_request(msg.to_string())?;
unreachable!()
}
}
}
/// If result is not OK, return a bad request
fn ok_or_forbidden<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
match res {
Ok(e) => Ok(e),
Err(err) => {
println!("Error leading to access forbidden: {}", err);
self.forbidden(msg.to_string())?;
unreachable!()
}
}
}
/// If result is not OK, return a 404 not found error
fn ok_or_not_found<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
match res {
Ok(e) => Ok(e),
Err(err) => {
println!("Error leading to 404 not found: {}", err);
self.not_found(msg.to_string())?;
unreachable!()
}
}
}
/// Get a user ID, if available
fn user_id_opt(&self) -> Option<UserID> {
self.user_id_opt_ref().map(|u| u.clone())
}
/// Get user ID. This function assess that a user ID is available to continue
fn user_id(&self) -> ResultBoxError<UserID> {
if let Some(id) = self.user_id_opt() {
return Ok(id.clone());
}
Err(ExecError::boxed_new("Could not get required user ID!"))
}
/// Get current user ID, return invalid user id value if there is none
fn user_id_or_invalid(&self) -> UserID {
self.user_id_opt().unwrap_or(UserID::invalid())
}
/// Get user ID as a reference
fn user_id_ref(&self) -> ResultBoxError<&UserID> {
self.user_id_opt_ref().ok_or(ExecError::boxed_new("Could not get required user ID!"))
}
/// Check if a POST parameter was present in the request or not
fn has_post_parameter(&self, name: &str) -> bool {
self.post_parameter_opt(name).is_some()
}
/// Get a post parameter
fn post_parameter(&mut self, name: &str) -> ResultBoxError<&RequestValue> {
if !self.has_post_parameter(name) {
self.bad_request(format!("POST parameter '{}' not found in request!", name))?;
}
Ok(self.post_parameter_opt(name).unwrap())
}
/// Get a post string
fn post_string(&mut self, name: &str) -> ResultBoxError<String> {
self.post_string_opt(name, 1, true)
}
/// Get a post string, specifying minimum length
fn post_string_opt(&mut self, name: &str, min_length: usize, required: bool)
-> ResultBoxError<String> {
let param = self.post_parameter(name)?;
match (&param, required) {
(RequestValue::String(s), _) => {
if s.len() >= min_length {
Ok(s.to_string())
} else {
Err(self.bad_request(format!("'{}' is too short!", name)).unwrap_err())
}
}
(_, false) => Ok(String::new()),
(_, true) =>
Err(self.bad_request(format!("'{}' is not a string!", name)).unwrap_err()),
}
}
/// Check out whether a file was included in the request or not
fn has_file(&self, name: &str) -> bool {
self.post_parameter_opt(name)
.map(|f| matches!(f, RequestValue::File(_)))
.unwrap_or(false)
}
/// Get a file included in the request
fn post_file(&mut self, name: &str) -> ResultBoxError<&PostFile> {
if self.has_file(name) {
if let RequestValue::File(f) = self.post_parameter(name)? {
return Ok(f);
}
} else {
self.bad_request(format!("File {} not included in request!", name))?;
}
unreachable!();
}
/// Save an image in user data directory
fn save_post_image(&mut self, name: &str, folder: &str, max_w: u32, max_h: u32) -> ResultBoxError<String> {
// Load image
let file = self.post_file(name)?;
let mut image = image::load_from_memory(file.buff.as_ref())?;
if image.width() > max_w || image.height() > max_h {
image = image.resize(max_w, max_h, image::imageops::FilterType::Nearest);
}
// Read EXIF information in case of JPEG image, if possible
if let Ok(ImageFormat::Jpeg) = image::guess_format(file.buff.as_ref()) {
let mut reader = std::io::BufReader::new(file.buff.as_ref());
if let Ok(exif_attr) = exif::get_exif_attr_from_jpeg(&mut reader) {
let exif_reader = exif::Reader::new();
let exif = exif_reader.read_raw(exif_attr)?;
if let Some(v) = exif.get_field(exif::Tag::Orientation, In::PRIMARY) {
match v.value.get_uint(0) {
Some(1) => { /* row 0 is top and column 0 is left */ }
//Some(2) => println!("row 0 at top and column 0 at right"),
Some(3) => {
/* row 0 at bottom and column 0 at right */
image = image.rotate180()
}
//Some(4) => println!("row 0 at bottom and column 0 at left"),
//Some(5) => println!("row 0 at left and column 0 at top"),
Some(6) => {
/* row 0 is right and column 0 is top */
image = image.rotate90();
}
//Some(7) => println!("row 0 at right and column 0 at bottom"),
Some(8) => {
/* row 0 is left and column 0 is bottom */
image = image.rotate270();
}
v => println!("Unhandled EXIF Orientation: {:?}", v),
};
}
}
}
// Determine image destination
let target_user_data_folder = prepare_file_creation(&self.user_id()?, folder)?;
let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), "png")?;
let target_sys_path = user_data_path(target_file_path.as_path());
// Save image
image.save_with_format(target_sys_path, ImageFormat::Png)?;
Ok(target_file_path.to_string_lossy().to_string())
}
/// Save a pdf included in the request
fn save_post_pdf(&mut self, name: &str, folder: &str) -> ResultBoxError<String> {
let file = self.post_file(name)?;
if !is_valid_pdf(&file.buff)? {
self.bad_request(format!("Invalid PDF specified in {} !", name))?;
unreachable!();
}
// Avoid memory warnings
let copied_buff = file.buff.clone();
// Determine pdf file destination
let target_user_data_folder = prepare_file_creation(self.user_id_ref()?, folder)?;
let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), "pdf")?;
let target_sys_path = user_data_path(target_file_path.as_path());
std::fs::write(target_sys_path, &copied_buff.as_ref())?;
Ok(target_file_path.to_string_lossy().to_string())
}
/// Get an integer included in the POST request
fn post_i64(&mut self, name: &str) -> ResultBoxError<i64> {
Ok(self.post_string(name)?.parse::<i64>()?)
}
/// Get an optional number in the request. If none found, return a default value
fn post_u64_opt(&mut self, name: &str, default: u64) -> ResultBoxError<u64> {
if self.has_post_parameter(name) {
Ok(self.post_string(name)?.parse::<u64>()?)
} else {
Ok(default)
}
}
fn post_u64(&mut self, name: &str) -> ResultBoxError<u64> {
Ok(self.post_string(name)?.parse::<u64>()?)
}
/// Get a boolean included in a POST request
fn post_bool(&mut self, name: &str) -> ResultBoxError<bool> {
Ok(self.post_string(name)?.eq("true"))
}
/// Get an optional boolean included in post request
fn post_bool_opt(&mut self, name: &str, default: bool) -> bool {
self.post_bool(name).unwrap_or(default)
}
/// Get an email included in the request
fn post_email(&mut self, name: &str) -> ResultBoxError<String> {
let mail = self.post_string(name)?;
if !mailchecker::is_valid(&mail) {
self.bad_request("Invalid email address!".to_string())?;
}
Ok(mail)
}
/// Get a list of integers included in the request
fn post_numbers_list(&mut self, name: &str, min_len: usize) -> ResultBoxError<Vec<u64>> {
let param = self.post_string_opt(name, min_len, min_len != 0)?;
let mut list = vec![];
for split in param.split::<&str>(",") {
if split.is_empty() {
continue;
}
list.push(split.parse::<u64>()?);
}
if list.len() < min_len {
self.bad_request(format!("Not enough entries in '{}'!", name))?;
}
Ok(list)
}
/// Get the ID of a user included in a POST request
fn post_user_id(&mut self, name: &str) -> ResultBoxError<UserID> {
let user_id = UserID::new(self.post_u64(name)?);
if user_id.id() < 1 {
self.bad_request(format!("Invalid user specified in '{}'!", name))?;
}
if !user_helper::exists(&user_id)? {
self.not_found(format!("User with ID {} not found!", user_id.id()))?;
}
Ok(user_id)
}
/// Get the ID of a friend included in a POST request
///
/// *Note :* This function does not check whether the user exists or not before checking if the
/// two users are friend because as it is not possible to be friend with a non existent person
/// A single check is enough
fn post_friend_id(&mut self, name: &str) -> ResultBoxError<UserID> {
let friend_id = UserID::new(self.post_u64(name)?);
if !friends_helper::are_friend(&friend_id, self.user_id_ref()?)? {
self.forbidden("You are not friend with this person!".to_string())?;
}
Ok(friend_id)
}
/// Get a virtual directory included in a POST request
fn post_virtual_directory(&mut self, name: &str) -> ResultBoxError<String> {
let dir = self.post_string(name)?;
if !virtual_directories_utils::check_virtual_directory(&dir) {
self.bad_request(format!("Invalid virtual directory specified in '{}' !", name))?;
}
Ok(dir)
}
/// Get a string included in the request, with HTML codes removed
fn post_string_without_html(&mut self, name: &str, min_length: usize, required: bool) -> ResultBoxError<String> {
Ok(remove_html_nodes(self.post_string_opt(name, min_length, required)?.as_str()))
}
/// Get an optional string included in the request, with HTML codes removed
fn post_string_without_html_opt(&mut self, name: &str, min_length: usize) -> ResultBoxError<Option<String>> {
if !self.has_post_parameter(name) {
Ok(None)
} else {
Ok(Some(remove_html_nodes(self.post_string_opt(name, min_length, true)?.as_str())))
}
}
/// Get & return the ID of the conversation included in the POST request
fn post_conv_id(&mut self, name: &str) -> ResultBoxError<u64> {
let conv_id = self.post_u64(name)?;
if !conversations_helper::does_user_belongs_to(&self.user_id()?, conv_id)? {
self.forbidden(format!("You do not belong to conversation {} !", conv_id))?;
}
Ok(conv_id)
}
/// Get the ID
fn post_group_id(&mut self, name: &str) -> ResultBoxError<GroupID> {
let group_id = GroupID::new(self.post_u64(name)?);
if !groups_helper::exists(&group_id)? {
self.not_found("Specified group not found!".to_string())?;
}
Ok(group_id)
}
/// Get the ID of a group included in a request with a check for access level of current user
fn post_group_id_with_access(&mut self, name: &str, min_level: GroupAccessLevel) -> ResultBoxError<GroupID> {
let group_id = self.post_group_id(name)?;
let access_level = groups_helper::get_access_level(&group_id, self.user_id_opt())?;
if access_level == GroupAccessLevel::NO_ACCESS {
self.not_found("Specified group not found!".to_string())?;
}
if access_level < min_level {
self.forbidden("You do not have enough rights to perform what you intend to do on this group!".to_string())?;
}
Ok(group_id)
}
/// Get an URL included in the request
fn post_url_opt(&mut self, name: &str, required: bool) -> ResultBoxError<Option<String>> {
let url = self.post_string_opt(name, 0, required)?;
if url.is_empty() && !required {
Ok(None)
} else {
if !check_url(&url) {
self.bad_request(format!("Invalid url specified in {} !", name))?;
}
Ok(Some(url))
}
}
/// Get an optional virtual directory included in the request
fn post_checked_virtual_directory_opt(&mut self, name: &str, target_id: u64, target_type: VirtualDirType) -> ResultBoxError<Option<String>> {
let dir = self.post_string_opt(name, 0, false)?;
if dir.is_empty() {
return Ok(None);
}
if !virtual_directory_helper::check_availability(&dir, target_id, target_type)? {
self.forbidden("Requested virtual directory is not available!".to_string())?;
}
Ok(Some(dir))
}
/// Get information about a post whose ID was specified in the request
fn post_post_with_access(&mut self, name: &str, min_level: PostAccessLevel) -> ResultBoxError<Post> {
let post_id = self.post_u64(name)?;
let post = self.ok_or_not_found(
posts_helper::get_single(post_id),
"Requested post not found!",
)?;
if posts_helper::get_access_level(&post, &self.user_id_opt())? < min_level {
self.forbidden("You are not allowed to access this post information!".to_string())?;
}
Ok(post)
}
/// Get information about a comment whose ID is specified in the request
fn post_comment_with_access(&mut self, name: &str) -> ResultBoxError<Comment> {
let comment_id = self.post_u64(name)?;
let comment = self.ok_or_not_found(
comments_helper::get_single(comment_id),
"Specified comment not found!",
)?;
if comment.user_id != self.user_id_or_invalid() {
let post = posts_helper::get_single(comment.post_id)?;
if posts_helper::get_access_level(&post, &self.user_id_opt())? == PostAccessLevel::NO_ACCESS {
self.forbidden("You are not allowed to access this post information !".to_string())?;
}
}
Ok(comment)
}
/// Get information about a comment specified in the request for which user has full access
fn post_comment_with_full_access(&mut self, name: &str) -> ResultBoxError<Comment> {
let comment = self.post_comment_with_access(name)?;
if comment.user_id != self.user_id()? {
self.forbidden("You are not the owner of this comment!".to_string())?;
}
Ok(comment)
}
/// Get the ID of a movie included in the request
fn post_movie_id(&mut self, name: &str) -> ResultBoxError<u64> {
let movie_id = self.post_u64(name)?;
if !movies_helper::does_user_has(self.user_id_ref()?, movie_id)? {
self.forbidden("You are not authorized to use this movie!".to_string())?;
}
Ok(movie_id)
}
/// Get a content of a post and satinize it
fn post_content(&mut self, name: &str, min_len: usize, required: bool) -> ResultBoxError<String> {
let content = self.post_string_opt(name, min_len, required)?;
if content.contains("data:image") {
self.forbidden("Please do not include inline images!".to_string())?;
}
if min_len > 0 && required && !check_string_before_insert(&content) {
self.forbidden(format!("The content inside {} was rejected!", name))?;
}
Ok(remove_html_nodes(&content))
}
/// Check the password of the current user
fn need_user_password(&mut self, field: &str) -> ResultBoxError {
let password = self.post_string_opt(field, PASSWORD_MIN_LENGTH, true)?;
if !account_helper::check_user_password(self.user_id_ref()?, &password)? {
self.forbidden("Invalid password!".to_string())?;
}
Ok(())
}
/// Get an emoji shortcut included in a POST request
fn post_emoji_shortcut(&mut self, field: &str) -> ResultBoxError<String> {
let emoji_shortcut = self.post_string(field)?;
if !check_emoji_code(&emoji_shortcut) {
self.bad_request("Invalid emoji shortcut code!".to_string())?;
}
Ok(emoji_shortcut)
}
/// Get information about an emoji included in a POST request
fn post_emoji_id(&mut self, field: &str) -> ResultBoxError<CustomEmoji> {
let emoji_id = self.post_u64(field)?;
let info = self.ok_or_not_found(
custom_emojies_helper::get_single(emoji_id),
"Requested emoji not found!",
)?;
if &info.user_id != self.user_id_ref()? {
self.forbidden("You do not own this emoji!".to_string())?;
}
Ok(info)
}
}

View File

@ -1,53 +1,24 @@
use std::collections::HashMap;
use std::error::Error;
use std::str::FromStr;
use actix_web::{HttpRequest, HttpResponse, web};
use actix_web::http::{HeaderName, HeaderValue};
use bytes::Bytes;
use exif::In;
use image::{GenericImageView, ImageFormat};
use actix_web::dev::HttpResponseBuilder;
use actix_web::http::{HeaderName, HeaderValue, StatusCode};
use serde::Serialize;
use crate::api_data::http_error::HttpError;
use crate::constants::PASSWORD_MIN_LENGTH;
use crate::controllers::routes::RequestResult;
use crate::data::api_client::APIClient;
use crate::data::comment::Comment;
use crate::data::base_request_handler::{BaseRequestHandler, RequestValue};
use crate::data::config::conf;
use crate::data::custom_emoji::CustomEmoji;
use crate::data::error::{ExecError, ResultBoxError};
use crate::data::group::GroupAccessLevel;
use crate::data::group_id::GroupID;
use crate::data::post::{Post, PostAccessLevel};
use crate::data::error::ResultBoxError;
use crate::data::user::UserID;
use crate::helpers::{account_helper, api_helper, comments_helper, conversations_helper, custom_emojies_helper, friends_helper, groups_helper, movies_helper, posts_helper, user_helper, virtual_directory_helper};
use crate::helpers::virtual_directory_helper::VirtualDirType;
use crate::utils::pdf_utils::is_valid_pdf;
use crate::utils::string_utils::{check_emoji_code, check_string_before_insert, check_url, remove_html_nodes};
use crate::utils::user_data_utils::{generate_new_user_data_file_name, prepare_file_creation, user_data_path};
use crate::utils::virtual_directories_utils::check_virtual_directory;
use crate::helpers::{account_helper, api_helper};
/// Http request handler
///
/// @author Pierre Hubert
pub struct PostFile {
pub name: String,
pub buff: Bytes,
}
/// Single request body value
pub enum RequestValue {
String(String),
File(PostFile),
}
#[derive(Serialize)]
struct SuccessMessage {
success: String
}
pub struct HttpRequestHandler {
request: web::HttpRequest,
body: HashMap<String, RequestValue>,
@ -94,100 +65,6 @@ impl HttpRequestHandler {
Ok(response)
}
/// Set request response
pub fn set_response<T: Serialize>(&mut self, data: T) -> RequestResult {
self.response = Some(HttpResponse::Ok().json(data));
Ok(())
}
/// Success message
pub fn success(&mut self, message: &str) -> RequestResult {
self.response = Some(HttpResponse::Ok().json(SuccessMessage {
success: message.to_string()
}));
Ok(())
}
/// Internal error response (500)
pub fn internal_error(&mut self, error: Box<dyn Error>) -> RequestResult {
self.response = Some(HttpResponse::InternalServerError().json(
HttpError::internal_error("Internal server error.")));
Err(error)
}
/// Bad request (400)
pub fn bad_request(&mut self, message: String) -> RequestResult {
self.response = Some(HttpResponse::BadRequest().json(
HttpError::bad_request(&message)));
Err(Box::new(ExecError::new(&message)))
}
/// Forbidden (401)
///
/// I did not the HTTP official specs when I made this choice of using Unauthorized instead
/// of Forbidden. Today it would be really complicated to come back...
pub fn forbidden(&mut self, message: String) -> RequestResult {
self.response = Some(HttpResponse::Unauthorized().json(
HttpError::forbidden(&message)));
Err(Box::new(ExecError::new(&message)))
}
/// Not found (404)
pub fn not_found(&mut self, message: String) -> RequestResult {
self.response = Some(HttpResponse::NotFound().json(
HttpError::not_found(&message)));
Err(Box::new(ExecError::new(&message)))
}
/// Conflict (409)
pub fn conflict(&mut self, message: String) -> RequestResult {
self.response = Some(HttpResponse::Conflict().json(
HttpError::new(409, &message)));
Err(Box::new(ExecError::new(&message)))
}
/// Too many requests (429)
pub fn too_many_requests(&mut self, message: &str) -> RequestResult {
self.response = Some(HttpResponse::TooManyRequests().json(
HttpError::new(429, message)));
Err(Box::new(ExecError::new(message)))
}
/// If result is not OK, return a bad request
pub fn ok_or_bad_request<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
match res {
Ok(e) => Ok(e),
Err(err) => {
println!("Error leading to bad request: {}", err);
self.bad_request(msg.to_string())?;
unreachable!()
}
}
}
/// If result is not OK, return a bad request
pub fn ok_or_forbidden<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
match res {
Ok(e) => Ok(e),
Err(err) => {
println!("Error leading to access forbidden: {}", err);
self.forbidden(msg.to_string())?;
unreachable!()
}
}
}
/// If result is not OK, return a 404 not found error
pub fn ok_or_not_found<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
match res {
Ok(e) => Ok(e),
Err(err) => {
println!("Error leading to 404 not found: {}", err);
self.not_found(msg.to_string())?;
unreachable!()
}
}
}
/// Get the path of the request
pub fn request_path(&self) -> String {
@ -199,71 +76,6 @@ impl HttpRequestHandler {
self.client.as_ref().unwrap()
}
/// Get the remote IP address
pub fn remote_ip(&self) -> String {
let mut ip = self.request.peer_addr().unwrap().ip().to_string();
// We check if the request comes from a trusted reverse proxy
if let Some(proxy) = conf().proxy.as_ref() {
if ip.eq(proxy) {
if let Some(header) = self.request.headers().get("X-Forwarded-For") {
let header: Vec<String> = header
.to_str()
.unwrap()
.to_string()
.split(",")
.map(|f| f.to_string())
.collect();
if header.len() > 0 {
ip = header[0].to_string();
}
}
}
}
ip
}
/// Check if a POST parameter was present in the request or not
pub fn has_post_parameter(&self, name: &str) -> bool {
self.body.contains_key(name)
}
/// Get a post parameter
pub fn post_parameter(&mut self, name: &str) -> ResultBoxError<&RequestValue> {
if !self.has_post_parameter(name) {
self.bad_request(format!("POST parameter '{}' not found in request!", name))?;
}
Ok(self.body.get(name).unwrap())
}
/// Get a post string
pub fn post_string(&mut self, name: &str) -> ResultBoxError<String> {
self.post_string_opt(name, 1, true)
}
/// Get a post string, specifying minimum length
pub fn post_string_opt(&mut self, name: &str, min_length: usize, required: bool)
-> ResultBoxError<String> {
let param = self.post_parameter(name)?;
match (&param, required) {
(RequestValue::String(s), _) => {
if s.len() >= min_length {
Ok(s.to_string())
} else {
Err(self.bad_request(format!("'{}' is too short!", name)).unwrap_err())
}
}
(_, false) => Ok(String::new()),
(_, true) =>
Err(self.bad_request(format!("'{}' is not a string!", name)).unwrap_err()),
}
}
/// Check API client tokens
pub fn check_client_token(&mut self) -> RequestResult {
let api_name = self.post_string("serviceName")?;
@ -319,417 +131,53 @@ impl HttpRequestHandler {
}
}
}
}
/// Check out whether a file was included in the request or not
pub fn has_file(&self, name: &str) -> bool {
if let Some(RequestValue::File(_)) = self.body.get(name) { true } else { false }
impl BaseRequestHandler for HttpRequestHandler {
/// Get request parameter
fn post_parameter_opt(&self, name: &str) -> Option<&RequestValue> {
self.body.get(name)
}
/// Get a file included in the request
pub fn post_file(&mut self, name: &str) -> ResultBoxError<&PostFile> {
if self.has_file(name) {
if let RequestValue::File(f) = self.post_parameter(name)? {
return Ok(f);
}
} else {
self.bad_request(format!("File {} not included in request!", name))?;
}
unreachable!();
/// Set request response
fn set_response<T: Serialize>(&mut self, data: T) -> RequestResult {
self.response = Some(HttpResponse::Ok().json(data));
Ok(())
}
/// Save an image in user data directory
pub fn save_post_image(&mut self, name: &str, folder: &str, max_w: u32, max_h: u32) -> ResultBoxError<String> {
/// Set request error
fn set_error(&mut self, error: HttpError) {
self.response = Some(HttpResponseBuilder::new(StatusCode::from_u16(error.error.code).unwrap())
.json(error));
}
// Load image
let file = self.post_file(name)?;
let mut image = image::load_from_memory(file.buff.as_ref())?;
/// Get the remote IP address
fn remote_ip(&self) -> String {
let mut ip = self.request.peer_addr().unwrap().ip().to_string();
if image.width() > max_w || image.height() > max_h {
image = image.resize(max_w, max_h, image::imageops::FilterType::Nearest);
}
// We check if the request comes from a trusted reverse proxy
if let Some(proxy) = conf().proxy.as_ref() {
if ip.eq(proxy) {
if let Some(header) = self.request.headers().get("X-Forwarded-For") {
let header: Vec<String> = header
.to_str()
.unwrap()
.to_string()
.split(",")
.map(|f| f.to_string())
.collect();
// Read EXIF information in case of JPEG image, if possible
if let Ok(ImageFormat::Jpeg) = image::guess_format(file.buff.as_ref()) {
let mut reader = std::io::BufReader::new(file.buff.as_ref());
if let Ok(exif_attr) = exif::get_exif_attr_from_jpeg(&mut reader) {
let exif_reader = exif::Reader::new();
let exif = exif_reader.read_raw(exif_attr)?;
if let Some(v) = exif.get_field(exif::Tag::Orientation, In::PRIMARY) {
match v.value.get_uint(0) {
Some(1) => { /* row 0 is top and column 0 is left */ }
//Some(2) => println!("row 0 at top and column 0 at right"),
Some(3) => {
/* row 0 at bottom and column 0 at right */
image = image.rotate180()
}
//Some(4) => println!("row 0 at bottom and column 0 at left"),
//Some(5) => println!("row 0 at left and column 0 at top"),
Some(6) => {
/* row 0 is right and column 0 is top */
image = image.rotate90();
}
//Some(7) => println!("row 0 at right and column 0 at bottom"),
Some(8) => {
/* row 0 is left and column 0 is bottom */
image = image.rotate270();
}
v => println!("Unhandled EXIF Orientation: {:?}", v),
};
if header.len() > 0 {
ip = header[0].to_string();
}
}
}
}
// Determine image destination
let target_user_data_folder = prepare_file_creation(&self.user_id()?, folder)?;
let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), "png")?;
let target_sys_path = user_data_path(target_file_path.as_path());
// Save image
image.save_with_format(target_sys_path, ImageFormat::Png)?;
Ok(target_file_path.to_string_lossy().to_string())
ip
}
/// Save a pdf included in the request
pub fn save_post_pdf(&mut self, name: &str, folder: &str) -> ResultBoxError<String> {
let file = self.post_file(name)?;
if !is_valid_pdf(&file.buff)? {
self.bad_request(format!("Invalid PDF specified in {} !", name))?;
unreachable!();
}
// Avoid memory warnings
let copied_buff = file.buff.clone();
// Determine pdf file destination
let target_user_data_folder = prepare_file_creation(self.user_id_ref()?, folder)?;
let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), "pdf")?;
let target_sys_path = user_data_path(target_file_path.as_path());
std::fs::write(target_sys_path, &copied_buff.as_ref())?;
Ok(target_file_path.to_string_lossy().to_string())
}
/// Get an integer included in the POST request
pub fn post_i64(&mut self, name: &str) -> ResultBoxError<i64> {
Ok(self.post_string(name)?.parse::<i64>()?)
}
/// Get an optional number in the request. If none found, return a default value
pub fn post_u64_opt(&mut self, name: &str, default: u64) -> ResultBoxError<u64> {
if self.has_post_parameter(name) {
Ok(self.post_string(name)?.parse::<u64>()?)
} else {
Ok(default)
}
}
pub fn post_u64(&mut self, name: &str) -> ResultBoxError<u64> {
Ok(self.post_string(name)?.parse::<u64>()?)
}
/// Get a boolean included in a POST request
pub fn post_bool(&mut self, name: &str) -> ResultBoxError<bool> {
Ok(self.post_string(name)?.eq("true"))
}
/// Get an optional boolean included in post request
pub fn post_bool_opt(&mut self, name: &str, default: bool) -> bool {
self.post_bool(name).unwrap_or(default)
}
/// Get user ID. This function assess that a user ID is available to continue
pub fn user_id(&self) -> ResultBoxError<UserID> {
match self.curr_user_id.clone() {
Some(s) => Ok(s),
None => Err(ExecError::boxed_new("Could not get required user ID!"))
}
}
/// Get a user ID, if available
pub fn user_id_opt(&self) -> Option<UserID> {
self.curr_user_id.clone()
}
/// Get current user ID, return invalid user id value if there is none
pub fn user_id_or_invalid(&self) -> UserID {
self.user_id_opt().unwrap_or(UserID::invalid())
}
/// Get user ID as a reference
pub fn user_id_ref(&self) -> ResultBoxError<&UserID> {
match self.curr_user_id.as_ref() {
Some(s) => Ok(s),
None => Err(ExecError::boxed_new("Could not get required user ID!"))
}
}
/// Get an email included in the request
pub fn post_email(&mut self, name: &str) -> ResultBoxError<String> {
let mail = self.post_string(name)?;
if !mailchecker::is_valid(&mail) {
self.bad_request("Invalid email address!".to_string())?;
}
Ok(mail)
}
/// Get a list of integers included in the request
pub fn post_numbers_list(&mut self, name: &str, min_len: usize) -> ResultBoxError<Vec<u64>> {
let param = self.post_string_opt(name, min_len, min_len != 0)?;
let mut list = vec![];
for split in param.split::<&str>(",") {
if split.is_empty() {
continue;
}
list.push(split.parse::<u64>()?);
}
if list.len() < min_len {
self.bad_request(format!("Not enough entries in '{}'!", name))?;
}
Ok(list)
}
/// Get the ID of a user included in a POST request
pub fn post_user_id(&mut self, name: &str) -> ResultBoxError<UserID> {
let user_id = UserID::new(self.post_u64(name)?);
if user_id.id() < 1 {
self.bad_request(format!("Invalid user specified in '{}'!", name))?;
}
if !user_helper::exists(&user_id)? {
self.not_found(format!("User with ID {} not found!", user_id.id()))?;
}
Ok(user_id)
}
/// Get the ID of a friend included in a POST request
///
/// *Note :* This function does not check whether the user exists or not before checking if the
/// two users are friend because as it is not possible to be friend with a non existent person
/// A single check is enough
pub fn post_friend_id(&mut self, name: &str) -> ResultBoxError<UserID> {
let friend_id = UserID::new(self.post_u64(name)?);
if !friends_helper::are_friend(&friend_id, self.user_id_ref()?)? {
self.forbidden("You are not friend with this person!".to_string())?;
}
Ok(friend_id)
}
/// Get a virtual directory included in a POST request
pub fn post_virtual_directory(&mut self, name: &str) -> ResultBoxError<String> {
let dir = self.post_string(name)?;
if !check_virtual_directory(&dir) {
self.bad_request(format!("Invalid virtual directory specified in '{}' !", name))?;
}
Ok(dir)
}
/// Get a string included in the request, with HTML codes removed
pub fn post_string_without_html(&mut self, name: &str, min_length: usize, required: bool) -> ResultBoxError<String> {
Ok(remove_html_nodes(self.post_string_opt(name, min_length, required)?.as_str()))
}
/// Get an optionnal string included in the request, with HTML codes removed
pub fn post_string_without_html_opt(&mut self, name: &str, min_length: usize) -> ResultBoxError<Option<String>> {
if !self.has_post_parameter(name) {
Ok(None)
} else {
Ok(Some(remove_html_nodes(self.post_string_opt(name, min_length, true)?.as_str())))
}
}
/// Get & return the ID of the conversation included in the POST request
pub fn post_conv_id(&mut self, name: &str) -> ResultBoxError<u64> {
let conv_id = self.post_u64(name)?;
if !conversations_helper::does_user_belongs_to(&self.user_id()?, conv_id)? {
self.forbidden(format!("You do not belong to conversation {} !", conv_id))?;
}
Ok(conv_id)
}
/// Get the ID
pub fn post_group_id(&mut self, name: &str) -> ResultBoxError<GroupID> {
let group_id = GroupID::new(self.post_u64(name)?);
if !groups_helper::exists(&group_id)? {
self.not_found("Specified group not found!".to_string())?;
}
Ok(group_id)
}
/// Get the ID of a group included in a request with a check for access level of current user
pub fn post_group_id_with_access(&mut self, name: &str, min_level: GroupAccessLevel) -> ResultBoxError<GroupID> {
let group_id = self.post_group_id(name)?;
let access_level = groups_helper::get_access_level(&group_id, self.user_id_opt())?;
if access_level == GroupAccessLevel::NO_ACCESS {
self.not_found("Specified group not found!".to_string())?;
}
if access_level < min_level {
self.forbidden("You do not have enough rights to perform what you intend to do on this group!".to_string())?;
}
Ok(group_id)
}
/// Get an URL included in the request
pub fn post_url_opt(&mut self, name: &str, required: bool) -> ResultBoxError<Option<String>> {
let url = self.post_string_opt(name, 0, required)?;
if url.is_empty() && !required {
Ok(None)
} else {
if !check_url(&url) {
self.bad_request(format!("Invalid url specified in {} !", name))?;
}
Ok(Some(url))
}
}
/// Get an optional virtual directory included in the request
pub fn post_checked_virtual_directory_opt(&mut self, name: &str, target_id: u64, target_type: VirtualDirType) -> ResultBoxError<Option<String>> {
let dir = self.post_string_opt(name, 0, false)?;
if dir.is_empty() {
return Ok(None);
}
if !virtual_directory_helper::check_availability(&dir, target_id, target_type)? {
self.forbidden("Requested virtual directory is not available!".to_string())?;
}
Ok(Some(dir))
}
/// Get information about a post whose ID was specified in the request
pub fn post_post_with_access(&mut self, name: &str, min_level: PostAccessLevel) -> ResultBoxError<Post> {
let post_id = self.post_u64(name)?;
let post = self.ok_or_not_found(
posts_helper::get_single(post_id),
"Requested post not found!",
)?;
if posts_helper::get_access_level(&post, &self.user_id_opt())? < min_level {
self.forbidden("You are not allowed to access this post information!".to_string())?;
}
Ok(post)
}
/// Get information about a comment whose ID is specified in the request
pub fn post_comment_with_access(&mut self, name: &str) -> ResultBoxError<Comment> {
let comment_id = self.post_u64(name)?;
let comment = self.ok_or_not_found(
comments_helper::get_single(comment_id),
"Specified comment not found!",
)?;
if comment.user_id != self.user_id_or_invalid() {
let post = posts_helper::get_single(comment.post_id)?;
if posts_helper::get_access_level(&post, &self.user_id_opt())? == PostAccessLevel::NO_ACCESS {
self.forbidden("You are not allowed to access this post information !".to_string())?;
}
}
Ok(comment)
}
/// Get information about a comment specified in the request for which user has full access
pub fn post_comment_with_full_access(&mut self, name: &str) -> ResultBoxError<Comment> {
let comment = self.post_comment_with_access(name)?;
if comment.user_id != self.user_id()? {
self.forbidden("You are not the owner of this comment!".to_string())?;
}
Ok(comment)
}
/// Get the ID of a movie included in the request
pub fn post_movie_id(&mut self, name: &str) -> ResultBoxError<u64> {
let movie_id = self.post_u64(name)?;
if !movies_helper::does_user_has(self.user_id_ref()?, movie_id)? {
self.forbidden("You are not authorized to use this movie!".to_string())?;
}
Ok(movie_id)
}
/// Get a content of a post and satinize it
pub fn post_content(&mut self, name: &str, min_len: usize, required: bool) -> ResultBoxError<String> {
let content = self.post_string_opt(name, min_len, required)?;
if content.contains("data:image") {
self.forbidden("Please do not include inline images!".to_string())?;
}
if min_len > 0 && required && !check_string_before_insert(&content) {
self.forbidden(format!("The content inside {} was rejected!", name))?;
}
Ok(remove_html_nodes(&content))
}
/// Check the password of the current user
pub fn need_user_password(&mut self, field: &str) -> ResultBoxError {
let password = self.post_string_opt(field, PASSWORD_MIN_LENGTH, true)?;
if !account_helper::check_user_password(self.user_id_ref()?, &password)? {
self.forbidden("Invalid password!".to_string())?;
}
Ok(())
}
/// Get an emoji shortcut included in a POST request
pub fn post_emoji_shortcut(&mut self, field: &str) -> ResultBoxError<String> {
let emoji_shortcut = self.post_string(field)?;
if !check_emoji_code(&emoji_shortcut) {
self.bad_request("Invalid emoji shortcut code!".to_string())?;
}
Ok(emoji_shortcut)
}
/// Get information about an emoji included in a POST request
pub fn post_emoji_id(&mut self, field: &str) -> ResultBoxError<CustomEmoji> {
let emoji_id = self.post_u64(field)?;
let info = self.ok_or_not_found(
custom_emojies_helper::get_single(emoji_id),
"Requested emoji not found!",
)?;
if &info.user_id != self.user_id_ref()? {
self.forbidden("You do not own this emoji!".to_string())?;
}
Ok(info)
fn user_id_opt_ref(&self) -> Option<&UserID> {
self.curr_user_id.as_ref()
}
}

View File

@ -1,6 +1,7 @@
pub mod error;
pub mod config;
pub mod base_request_handler;
pub mod http_request_handler;
pub mod api_client;

View File

@ -7,6 +7,7 @@ use std::sync::{Arc, Mutex};
use crate::constants::LIMIT_COUNTER_LIFETIME;
use crate::controllers::routes::{LimitPolicy, Route};
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::error::{ExecError, ResultBoxError};
use crate::data::http_request_handler::HttpRequestHandler;
use crate::utils::date_utils;