Обновление документации.

This commit is contained in:
2025-04-15 22:09:10 +04:00
parent 2fd6d787a0
commit 5068fe3069
26 changed files with 370 additions and 235 deletions

View File

@@ -50,7 +50,7 @@ impl Schedule {
} }
} }
/// Общие данные передаваемые в эндпоинты /// Common data provided to endpoints.
pub struct AppState { pub struct AppState {
pub downloader: Mutex<BasicXlsDownloader>, pub downloader: Mutex<BasicXlsDownloader>,
pub schedule: Mutex<Option<Schedule>>, pub schedule: Mutex<Option<Schedule>>,
@@ -76,7 +76,7 @@ impl AppState {
} }
} }
/// Создание нового объекта web::Data<AppState> /// Create a new object web::Data<AppState>.
pub async fn app_state() -> web::Data<AppState> { pub async fn app_state() -> web::Data<AppState> {
web::Data::new(AppState::new().await) web::Data::new(AppState::new().await)
} }

View File

@@ -31,28 +31,28 @@ pub enum UserRole {
#[diesel(table_name = crate::database::schema::users)] #[diesel(table_name = crate::database::schema::users)]
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
pub struct User { pub struct User {
/// UUID аккаунта /// Account UUID.
pub id: String, pub id: String,
/// Имя пользователя /// User name.
pub username: String, pub username: String,
/// BCrypt хеш пароля /// BCrypt password hash.
pub password: String, pub password: String,
/// Идентификатор привязанного аккаунта VK /// ID of the linked VK account.
pub vk_id: Option<i32>, pub vk_id: Option<i32>,
/// JWT токен доступа /// JWT access token.
pub access_token: String, pub access_token: String,
/// Группа /// Group.
pub group: String, pub group: String,
/// Роль /// Role.
pub role: UserRole, pub role: UserRole,
/// Версия установленного приложения Polytechnic+ /// Version of the installed Polytechnic+ application.
pub version: String, pub version: String,
} }
@@ -73,12 +73,12 @@ pub struct User {
#[diesel(table_name = crate::database::schema::fcm)] #[diesel(table_name = crate::database::schema::fcm)]
#[diesel(primary_key(user_id))] #[diesel(primary_key(user_id))]
pub struct FCM { pub struct FCM {
/// UUID аккаунта. /// Account UUID.
pub user_id: String, pub user_id: String,
/// FCM токен. /// FCM token.
pub token: String, pub token: String,
/// Список топиков, на которые подписан пользователь. /// List of topics subscribed to by the user.
pub topics: Vec<Option<String>>, pub topics: Vec<Option<String>>,
} }

View File

@@ -16,19 +16,19 @@ use std::fmt::Debug;
#[status_code = "actix_web::http::StatusCode::UNAUTHORIZED"] #[status_code = "actix_web::http::StatusCode::UNAUTHORIZED"]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Error { pub enum Error {
/// В запросе отсутствует заголовок Authorization /// There is no Authorization header in the request.
#[display("No Authorization header found")] #[display("No Authorization header found")]
NoHeader, NoHeader,
/// Неизвестный тип авторизации, отличающийся от Bearer /// Unknown authorization type other than Bearer.
#[display("Bearer token is required")] #[display("Bearer token is required")]
UnknownAuthorizationType, UnknownAuthorizationType,
/// Токен не действителен /// Invalid or expired access token.
#[display("Invalid or expired access token")] #[display("Invalid or expired access token")]
InvalidAccessToken, InvalidAccessToken,
/// Пользователь привязанный к токену не найден в базе данных /// The user bound to the token is not found in the database.
#[display("No user associated with access token")] #[display("No user associated with access token")]
NoUser, NoUser,
} }
@@ -39,7 +39,7 @@ impl Error {
} }
} }
/// Экстрактор пользователя из запроса с токеном /// User extractor from request with Bearer access token.
impl FromRequestSync for User { impl FromRequestSync for User {
type Error = actix_web::Error; type Error = actix_web::Error;
@@ -87,7 +87,7 @@ impl<const FCM: bool> UserExtractor<{ FCM }> {
} }
} }
/// Экстрактор пользователя и дополнительных параметров из запроса с токеном /// Extractor of user and additional parameters from request with Bearer token.
impl<const FCM: bool> FromRequestSync for UserExtractor<{ FCM }> { impl<const FCM: bool> FromRequestSync for UserExtractor<{ FCM }> {
type Error = actix_web::Error; type Error = actix_web::Error;

View File

@@ -2,26 +2,64 @@ use actix_web::dev::Payload;
use actix_web::{FromRequest, HttpRequest}; use actix_web::{FromRequest, HttpRequest};
use futures_util::future::LocalBoxFuture; use futures_util::future::LocalBoxFuture;
use std::future::{Ready, ready}; use std::future::{Ready, ready};
use std::ops;
/// Асинхронный экстрактор объектов из запроса /// # Async extractor.
/// Asynchronous object extractor from a query.
pub struct AsyncExtractor<T>(T); pub struct AsyncExtractor<T>(T);
impl<T> AsyncExtractor<T> { impl<T> AsyncExtractor<T> {
#[allow(dead_code)] #[allow(dead_code)]
/// Получение объекта, извлечённого с помощью экстрактора /// Retrieve the object extracted with the extractor.
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0 self.0
} }
} }
impl<T> ops::Deref for AsyncExtractor<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> ops::DerefMut for AsyncExtractor<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub trait FromRequestAsync: Sized { pub trait FromRequestAsync: Sized {
type Error: Into<actix_web::Error>; type Error: Into<actix_web::Error>;
/// Асинхронная функция для извлечения данных из запроса /// Asynchronous function for extracting data from a query.
///
/// returns: Result<Self, Self::Error>
///
/// # Examples
///
/// ```
/// struct User {
/// pub id: String,
/// pub username: String,
/// }
///
/// // TODO: Я вообще этот экстрактор не использую, нахуя мне тогда писать пример, если я не ебу как его использовать. Я забыл.
///
/// #[get("/")]
/// fn get_user_async(
/// user: web::AsyncExtractor<User>,
/// ) -> web::Json<User> {
/// let user = user.into_inner();
///
/// web::Json(user)
/// }
/// ```
async fn from_request_async(req: HttpRequest, payload: Payload) -> Result<Self, Self::Error>; async fn from_request_async(req: HttpRequest, payload: Payload) -> Result<Self, Self::Error>;
} }
/// Реализация треита FromRequest для всех асинхронных экстракторов
impl<T: FromRequestAsync> FromRequest for AsyncExtractor<T> { impl<T: FromRequestAsync> FromRequest for AsyncExtractor<T> {
type Error = T::Error; type Error = T::Error;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
@@ -37,24 +75,72 @@ impl<T: FromRequestAsync> FromRequest for AsyncExtractor<T> {
} }
} }
/// Синхронный экстрактор объектов из запроса /// # Sync extractor.
/// Synchronous object extractor from a query.
pub struct SyncExtractor<T>(T); pub struct SyncExtractor<T>(T);
impl<T> SyncExtractor<T> { impl<T> SyncExtractor<T> {
/// Получение объекта, извлечённого с помощью экстрактора /// Retrieving an object extracted with the extractor.
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0 self.0
} }
} }
impl<T> ops::Deref for SyncExtractor<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> ops::DerefMut for SyncExtractor<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub trait FromRequestSync: Sized { pub trait FromRequestSync: Sized {
type Error: Into<actix_web::Error>; type Error: Into<actix_web::Error>;
/// Синхронная функция для извлечения данных из запроса /// Synchronous function for extracting data from a query.
///
/// returns: Result<Self, Self::Error>
///
/// # Examples
///
/// ```
/// struct User {
/// pub id: String,
/// pub username: String,
/// }
///
/// impl FromRequestSync for User {
/// type Error = actix_web::Error;
///
/// fn from_request_sync(req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
/// // do magic here.
///
/// Ok(User {
/// id: "qwerty".to_string(),
/// username: "n08i40k".to_string()
/// })
/// }
/// }
///
/// #[get("/")]
/// fn get_user_sync(
/// user: web::SyncExtractor<User>,
/// ) -> web::Json<User> {
/// let user = user.into_inner();
///
/// web::Json(user)
/// }
/// ```
fn from_request_sync(req: &HttpRequest, payload: &mut Payload) -> Result<Self, Self::Error>; fn from_request_sync(req: &HttpRequest, payload: &mut Payload) -> Result<Self, Self::Error>;
} }
/// Реализация треита FromRequest для всех синхронных экстракторов
impl<T: FromRequestSync> FromRequest for SyncExtractor<T> { impl<T: FromRequestSync> FromRequest for SyncExtractor<T> {
type Error = T::Error; type Error = T::Error;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self, Self::Error>>;

View File

@@ -7,7 +7,7 @@ use actix_web::{Error, HttpRequest, ResponseError};
use futures_util::future::LocalBoxFuture; use futures_util::future::LocalBoxFuture;
use std::future::{Ready, ready}; use std::future::{Ready, ready};
/// Middleware guard работающий с токенами JWT /// Middleware guard working with JWT tokens.
pub struct JWTAuthorization; pub struct JWTAuthorization;
impl<S, B> Transform<S, ServiceRequest> for JWTAuthorization impl<S, B> Transform<S, ServiceRequest> for JWTAuthorization
@@ -31,13 +31,13 @@ pub struct JWTAuthorizationMiddleware<S> {
service: S, service: S,
} }
/// Функция для проверки наличия и действительности токена в запросе, а так же существования пользователя к которому он привязан
impl<S, B> JWTAuthorizationMiddleware<S> impl<S, B> JWTAuthorizationMiddleware<S>
where where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>, S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static, S::Future: 'static,
B: 'static, B: 'static,
{ {
/// Checking the validity of the token.
pub fn check_authorization( pub fn check_authorization(
&self, &self,
req: &HttpRequest, req: &HttpRequest,

View File

@@ -14,39 +14,37 @@ use std::sync::LazyLock;
pub mod schema; pub mod schema;
/// Данные ячейке хранящей строку /// Data cell storing the line.
struct InternalId { struct InternalId {
/// Индекс строки /// Line index.
row: u32, row: u32,
/// Индекс столбца /// Column index.
column: u32, column: u32,
/** /// Text in the cell.
* Текст в ячейке
*/
name: String, name: String,
} }
/// Данные о времени проведения пар из второй колонки расписания /// Data on the time of lessons from the second column of the schedule.
struct InternalTime { struct InternalTime {
/// Временной отрезок проведения пары /// Temporary segment of the lesson.
time_range: LessonTime, time_range: LessonTime,
/// Тип пары /// Type of lesson.
lesson_type: LessonType, lesson_type: LessonType,
/// Индекс пары /// The lesson index.
default_index: Option<u32>, default_index: Option<u32>,
/// Рамка ячейки /// The frame of the cell.
xls_range: ((u32, u32), (u32, u32)), xls_range: ((u32, u32), (u32, u32)),
} }
/// Сокращение типа рабочего листа /// Working sheet type alias.
type WorkSheet = calamine::Range<calamine::Data>; type WorkSheet = calamine::Range<calamine::Data>;
/// Получение строки из требуемой ячейки /// Getting a line from the required cell.
fn get_string_from_cell(worksheet: &WorkSheet, row: u32, col: u32) -> Option<String> { fn get_string_from_cell(worksheet: &WorkSheet, row: u32, col: u32) -> Option<String> {
let cell_data = if let Some(data) = worksheet.get((row as usize, col as usize)) { let cell_data = if let Some(data) = worksheet.get((row as usize, col as usize)) {
data.to_string() data.to_string()
@@ -74,7 +72,7 @@ fn get_string_from_cell(worksheet: &WorkSheet, row: u32, col: u32) -> Option<Str
} }
} }
/// Получение границ ячейки по её верхней левой координате /// Obtaining the boundaries of the cell along its upper left coordinate.
fn get_merge_from_start(worksheet: &WorkSheet, row: u32, column: u32) -> ((u32, u32), (u32, u32)) { fn get_merge_from_start(worksheet: &WorkSheet, row: u32, column: u32) -> ((u32, u32), (u32, u32)) {
let worksheet_end = worksheet.end().unwrap(); let worksheet_end = worksheet.end().unwrap();
@@ -109,7 +107,7 @@ fn get_merge_from_start(worksheet: &WorkSheet, row: u32, column: u32) -> ((u32,
((row, column), (row_end, column_end)) ((row, column), (row_end, column_end))
} }
/// Получение "скелета" расписания из рабочего листа /// Obtaining a "skeleton" schedule from the working sheet.
fn parse_skeleton(worksheet: &WorkSheet) -> Result<(Vec<InternalId>, Vec<InternalId>), ParseError> { fn parse_skeleton(worksheet: &WorkSheet) -> Result<(Vec<InternalId>, Vec<InternalId>), ParseError> {
let range = &worksheet; let range = &worksheet;
@@ -167,19 +165,20 @@ fn parse_skeleton(worksheet: &WorkSheet) -> Result<(Vec<InternalId>, Vec<Interna
Ok((days, groups)) Ok((days, groups))
} }
/// Результат получения пары из ячейки /// The result of obtaining a lesson from the cell.
enum LessonParseResult { enum LessonParseResult {
/// Список пар длинной от одного до двух /// List of lessons long from one to two.
/// ///
/// Количество пар будет равно одному, если пара первая за день, иначе будет возвращен список из шаблона перемены и самой пары /// The number of lessons will be equal to one if the couple is the first in the day,
/// otherwise the list from the change template and the lesson itself will be returned.
Lessons(Vec<Lesson>), Lessons(Vec<Lesson>),
/// Улица на которой находится корпус политехникума /// Street on which the Polytechnic Corps is located.
Street(String), Street(String),
} }
trait StringInnerSlice { trait StringInnerSlice {
/// Получения отрезка строки из строки по начальному и конечному индексу /// Obtaining a line from the line on the initial and final index.
fn inner_slice(&self, from: usize, to: usize) -> Self; fn inner_slice(&self, from: usize, to: usize) -> Self;
} }
@@ -192,7 +191,8 @@ impl StringInnerSlice for String {
} }
} }
/// Получение нестандартного типа пары по названию // noinspection GrazieInspection
/// Obtaining a non-standard type of lesson by name.
fn guess_lesson_type(name: &String) -> Option<(String, LessonType)> { fn guess_lesson_type(name: &String) -> Option<(String, LessonType)> {
let map: HashMap<String, LessonType> = HashMap::from([ let map: HashMap<String, LessonType> = HashMap::from([
("(консультация)".to_string(), LessonType::Consultation), ("(консультация)".to_string(), LessonType::Consultation),
@@ -234,7 +234,7 @@ fn guess_lesson_type(name: &String) -> Option<(String, LessonType)> {
} }
} }
/// Получение пары или улицы из ячейки /// Getting a pair or street from a cell.
fn parse_lesson( fn parse_lesson(
worksheet: &WorkSheet, worksheet: &WorkSheet,
day: &mut Day, day: &mut Day,
@@ -369,7 +369,7 @@ fn parse_lesson(
]))) ])))
} }
/// Получение списка кабинетов справа от ячейки пары /// Obtaining a list of cabinets to the right of the lesson cell.
fn parse_cabinets(worksheet: &WorkSheet, row: u32, column: u32) -> Vec<String> { fn parse_cabinets(worksheet: &WorkSheet, row: u32, column: u32) -> Vec<String> {
let mut cabinets: Vec<String> = Vec::new(); let mut cabinets: Vec<String> = Vec::new();
@@ -387,7 +387,7 @@ fn parse_cabinets(worksheet: &WorkSheet, row: u32, column: u32) -> Vec<String> {
cabinets cabinets
} }
/// Получение "чистого" названия пары и списка преподавателей из текста ячейки пары /// Getting the "pure" name of the lesson and list of teachers from the text of the lesson cell.
fn parse_name_and_subgroups(name: &String) -> Result<(String, Vec<LessonSubGroup>), ParseError> { fn parse_name_and_subgroups(name: &String) -> Result<(String, Vec<LessonSubGroup>), ParseError> {
static LESSON_RE: LazyLock<Regex, fn() -> Regex> = static LESSON_RE: LazyLock<Regex, fn() -> Regex> =
LazyLock::new(|| Regex::new(r"(?:[А-Я][а-я]+[А-Я]{2}(?:\([0-9][а-я]+\))?)+$").unwrap()); LazyLock::new(|| Regex::new(r"(?:[А-Я][а-я]+[А-Я]{2}(?:\([0-9][а-я]+\))?)+$").unwrap());
@@ -479,7 +479,7 @@ fn parse_name_and_subgroups(name: &String) -> Result<(String, Vec<LessonSubGroup
Ok((lesson_name, subgroups)) Ok((lesson_name, subgroups))
} }
/// Конвертация списка пар групп в список пар преподавателей /// Conversion of the list of couples of groups in the list of lessons of teachers.
fn convert_groups_to_teachers( fn convert_groups_to_teachers(
groups: &HashMap<String, ScheduleEntry>, groups: &HashMap<String, ScheduleEntry>,
) -> HashMap<String, ScheduleEntry> { ) -> HashMap<String, ScheduleEntry> {
@@ -556,7 +556,24 @@ fn convert_groups_to_teachers(
teachers teachers
} }
/// Чтение XLS документа из буфера и преобразование его в готовые к использованию расписания /// Reading XLS Document from the buffer and converting it into the schedule ready to use.
///
/// # Arguments
///
/// * `buffer`: XLS data containing schedule.
///
/// returns: Result<ParseResult, ParseError>
///
/// # Examples
///
/// ```
/// let result = parse_xls(&include_bytes!("../../schedule.xls").to_vec());
///
/// assert!(result.is_ok());
///
/// assert_ne!(result.as_ref().unwrap().groups.len(), 0);
/// assert_ne!(result.as_ref().unwrap().teachers.len(), 0);
/// ```
pub fn parse_xls(buffer: &Vec<u8>) -> Result<ParseResult, ParseError> { pub fn parse_xls(buffer: &Vec<u8>) -> Result<ParseResult, ParseError> {
let cursor = Cursor::new(&buffer); let cursor = Cursor::new(&buffer);
let mut workbook: Xls<_> = let mut workbook: Xls<_> =

View File

@@ -6,137 +6,139 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use utoipa::ToSchema; use utoipa::ToSchema;
/// The beginning and end of the lesson.
#[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)]
pub struct LessonTime { pub struct LessonTime {
/// Начало пары /// The beginning of a lesson.
pub start: DateTime<Utc>, pub start: DateTime<Utc>,
/// Конец пары /// The end of the lesson.
pub end: DateTime<Utc>, pub end: DateTime<Utc>,
} }
/// Type of lesson.
#[derive(Clone, Hash, PartialEq, Debug, Serialize_repr, Deserialize_repr, ToSchema)] #[derive(Clone, Hash, PartialEq, Debug, Serialize_repr, Deserialize_repr, ToSchema)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u8)] #[repr(u8)]
pub enum LessonType { pub enum LessonType {
/// Обычная /// Обычная.
Default = 0, Default = 0,
/// Допы /// Допы.
Additional, Additional,
/// Перемена /// Перемена.
Break, Break,
/// Консультация /// Консультация.
Consultation, Consultation,
/// Самостоятельная работа /// Самостоятельная работа.
IndependentWork, IndependentWork,
/// Зачёт /// Зачёт.
Exam, Exam,
/// Зачет с оценкой /// Зачёт с оценкой.
ExamWithGrade, ExamWithGrade,
/// Экзамен /// Экзамен.
ExamDefault, ExamDefault,
} }
#[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)]
pub struct LessonSubGroup { pub struct LessonSubGroup {
/// Номер подгруппы /// Index of subgroup.
pub number: u8, pub number: u8,
/// Кабинет, если присутствует /// Cabinet, if present.
pub cabinet: Option<String>, pub cabinet: Option<String>,
/// Фио преподавателя /// Full name of the teacher.
pub teacher: String, pub teacher: String,
} }
#[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Lesson { pub struct Lesson {
/// Тип занятия /// Type.
#[serde(rename = "type")] #[serde(rename = "type")]
pub lesson_type: LessonType, pub lesson_type: LessonType,
/// Индексы пар, если присутствуют /// Lesson indexes, if present.
pub default_range: Option<[u8; 2]>, pub default_range: Option<[u8; 2]>,
/// Название занятия /// Name.
pub name: Option<String>, pub name: Option<String>,
/// Начало и конец занятия /// The beginning and end.
pub time: LessonTime, pub time: LessonTime,
/// Список подгрупп /// List of subgroups.
#[serde(rename = "subGroups")] #[serde(rename = "subGroups")]
pub subgroups: Option<Vec<LessonSubGroup>>, pub subgroups: Option<Vec<LessonSubGroup>>,
/// Группа, если это расписание для преподавателей /// Group name, if this is a schedule for teachers.
pub group: Option<String>, pub group: Option<String>,
} }
#[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)]
pub struct Day { pub struct Day {
/// День недели /// Day of the week.
pub name: String, pub name: String,
/// Адрес другого корпуса /// Address of another corps.
pub street: Option<String>, pub street: Option<String>,
/// Дата /// Date.
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
/// Список пар в этот день /// List of lessons on this day.
pub lessons: Vec<Lesson>, pub lessons: Vec<Lesson>,
} }
#[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)]
pub struct ScheduleEntry { pub struct ScheduleEntry {
/// Название группы или ФИО преподавателя /// The name of the group or name of the teacher.
pub name: String, pub name: String,
/// Список из шести дней /// List of six days.
pub days: Vec<Day>, pub days: Vec<Day>,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct ParseResult { pub struct ParseResult {
/// Список групп /// List of groups.
pub groups: HashMap<String, ScheduleEntry>, pub groups: HashMap<String, ScheduleEntry>,
/// Список преподавателей /// List of teachers.
pub teachers: HashMap<String, ScheduleEntry>, pub teachers: HashMap<String, ScheduleEntry>,
} }
#[derive(Debug, Display, Clone, ToSchema)] #[derive(Debug, Display, Clone, ToSchema)]
pub enum ParseError { pub enum ParseError {
/// Ошибки связанные с чтением XLS файла. /// Errors related to reading XLS file.
#[display("{}: Failed to read XLS file.", "_0")] #[display("{}: Failed to read XLS file.", "_0")]
#[schema(value_type = String)] #[schema(value_type = String)]
BadXLS(Arc<calamine::XlsError>), BadXLS(Arc<calamine::XlsError>),
/// Не найдено ни одного листа /// Not a single sheet was found.
#[display("No work sheets found.")] #[display("No work sheets found.")]
NoWorkSheets, NoWorkSheets,
/// Отсутствуют данные об границах листа /// There are no data on the boundaries of the sheet.
#[display("There is no data on work sheet boundaries.")] #[display("There is no data on work sheet boundaries.")]
UnknownWorkSheetRange, UnknownWorkSheetRange,
/// Не удалось прочитать начало и конец пары из строки /// Failed to read the beginning and end of the lesson from the line
#[display("Failed to read lesson start and end times from string.")] #[display("Failed to read lesson start and end times from string.")]
GlobalTime, GlobalTime,
/// Не найдены начало и конец соответствующее паре /// Not found the beginning and the end corresponding to the lesson.
#[display("No start and end times matching the lesson was found.")] #[display("No start and end times matching the lesson was found.")]
LessonTimeNotFound, LessonTimeNotFound,
/// Не удалось прочитать индекс подгруппы /// Failed to read the subgroup index.
#[display("Failed to read subgroup index.")] #[display("Failed to read subgroup index.")]
SubgroupIndexParsingFailed, SubgroupIndexParsingFailed,
} }

View File

@@ -86,11 +86,11 @@ mod schema {
#[derive(Deserialize, Serialize, ToSchema)] #[derive(Deserialize, Serialize, ToSchema)]
#[schema(as = SignIn::Request)] #[schema(as = SignIn::Request)]
pub struct Request { pub struct Request {
/// Имя пользователя /// User name.
#[schema(examples("n08i40k"))] #[schema(examples("n08i40k"))]
pub username: String, pub username: String,
/// Пароль /// Password.
pub password: String, pub password: String,
} }
@@ -102,7 +102,7 @@ mod schema {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[schema(as = SignInVk::Request)] #[schema(as = SignInVk::Request)]
pub struct Request { pub struct Request {
/// Токен VK ID /// VK ID token.
pub access_token: String, pub access_token: String,
} }
} }
@@ -114,21 +114,21 @@ mod schema {
#[schema(as = SignIn::ErrorCode)] #[schema(as = SignIn::ErrorCode)]
#[status_code = "actix_web::http::StatusCode::NOT_ACCEPTABLE"] #[status_code = "actix_web::http::StatusCode::NOT_ACCEPTABLE"]
pub enum ErrorCode { pub enum ErrorCode {
/// Некорректное имя пользователя или пароль /// Incorrect username or password.
IncorrectCredentials, IncorrectCredentials,
/// Недействительный токен VK ID /// Invalid VK ID token.
InvalidVkAccessToken, InvalidVkAccessToken,
} }
/// Internal /// Internal
/// Тип авторизации /// Type of authorization.
pub enum SignInData { pub enum SignInData {
/// Имя пользователя и пароль /// User and password name and password.
Default(Request), Default(Request),
/// Идентификатор привязанного аккаунта VK /// Identifier of the attached account VK.
Vk(i32), Vk(i32),
} }
} }

View File

@@ -50,10 +50,7 @@ async fn sign_up_combined(
(status = NOT_ACCEPTABLE, body = ResponseError<ErrorCode>) (status = NOT_ACCEPTABLE, body = ResponseError<ErrorCode>)
))] ))]
#[post("/sign-up")] #[post("/sign-up")]
pub async fn sign_up( pub async fn sign_up(data_json: Json<Request>, app_state: web::Data<AppState>) -> ServiceResponse {
data_json: Json<Request>,
app_state: web::Data<AppState>,
) -> ServiceResponse {
let data = data_json.into_inner(); let data = data_json.into_inner();
sign_up_combined( sign_up_combined(
@@ -124,21 +121,21 @@ mod schema {
#[derive(Serialize, Deserialize, utoipa::ToSchema)] #[derive(Serialize, Deserialize, utoipa::ToSchema)]
#[schema(as = SignUp::Request)] #[schema(as = SignUp::Request)]
pub struct Request { pub struct Request {
/// Имя пользователя /// User name.
#[schema(examples("n08i40k"))] #[schema(examples("n08i40k"))]
pub username: String, pub username: String,
/// Пароль /// Password.
pub password: String, pub password: String,
/// Группа /// Group.
#[schema(examples("ИС-214/23"))] #[schema(examples("ИС-214/23"))]
pub group: String, pub group: String,
/// Роль /// Role.
pub role: UserRole, pub role: UserRole,
/// Версия установленного приложения Polytechnic+ /// Version of the installed Polytechnic+ application.
#[schema(examples("3.0.0"))] #[schema(examples("3.0.0"))]
pub version: String, pub version: String,
} }
@@ -151,21 +148,21 @@ mod schema {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[schema(as = SignUpVk::Request)] #[schema(as = SignUpVk::Request)]
pub struct Request { pub struct Request {
/// Токен VK ID /// VK ID token.
pub access_token: String, pub access_token: String,
/// Имя пользователя /// User name.
#[schema(examples("n08i40k"))] #[schema(examples("n08i40k"))]
pub username: String, pub username: String,
/// Группа /// Group.
#[schema(examples("ИС-214/23"))] #[schema(examples("ИС-214/23"))]
pub group: String, pub group: String,
/// Роль /// Role.
pub role: UserRole, pub role: UserRole,
/// Версия установленного приложения Polytechnic+ /// Version of the installed Polytechnic+ application.
#[schema(examples("3.0.0"))] #[schema(examples("3.0.0"))]
pub version: String, pub version: String,
} }
@@ -178,44 +175,44 @@ mod schema {
#[schema(as = SignUp::ErrorCode)] #[schema(as = SignUp::ErrorCode)]
#[status_code = "actix_web::http::StatusCode::NOT_ACCEPTABLE"] #[status_code = "actix_web::http::StatusCode::NOT_ACCEPTABLE"]
pub enum ErrorCode { pub enum ErrorCode {
/// Передана роль ADMIN /// Conveyed the role of Admin.
DisallowedRole, DisallowedRole,
/// Неизвестное название группы /// Unknown name of the group.
InvalidGroupName, InvalidGroupName,
/// Пользователь с таким именем уже зарегистрирован /// User with this name is already registered.
UsernameAlreadyExists, UsernameAlreadyExists,
/// Недействительный токен VK ID /// Invalid VK ID token.
InvalidVkAccessToken, InvalidVkAccessToken,
/// Пользователь с таким аккаунтом VK уже зарегистрирован /// User with such an account VK is already registered.
VkAlreadyExists, VkAlreadyExists,
} }
/// Internal /// Internal
/// Данные для регистрации /// Data for registration.
pub struct SignUpData { pub struct SignUpData {
/// Имя пользователя /// User name.
pub username: String, pub username: String,
/// Пароль /// Password.
/// ///
/// Должен присутствовать даже если регистрация происходит с помощью токена VK ID /// Should be present even if registration occurs using the VK ID token.
pub password: String, pub password: String,
/// Идентификатор аккаунта VK /// Account identifier VK.
pub vk_id: Option<i32>, pub vk_id: Option<i32>,
/// Группа /// Group.
pub group: String, pub group: String,
/// Роль /// Role.
pub role: UserRole, pub role: UserRole,
/// Версия установленного приложения Polytechnic+ /// Version of the installed Polytechnic+ application.
pub version: String, pub version: String,
} }
@@ -302,10 +299,7 @@ mod tests {
test_env(); test_env();
let app_state = static_app_state().await; let app_state = static_app_state().await;
driver::users::delete_by_username( driver::users::delete_by_username(&app_state, &"test::sign_up_multiple".to_string());
&app_state,
&"test::sign_up_multiple".to_string(),
);
let create = sign_up_client(SignUpPartial { let create = sign_up_client(SignUpPartial {
username: "test::sign_up_multiple".to_string(), username: "test::sign_up_multiple".to_string(),

View File

@@ -25,10 +25,7 @@ use actix_web::{get, web};
), ),
))] ))]
#[get("/group")] #[get("/group")]
pub async fn group( pub async fn group(user: SyncExtractor<User>, app_state: web::Data<AppState>) -> ServiceResponse {
user: SyncExtractor<User>,
app_state: web::Data<AppState>,
) -> ServiceResponse {
// Prevent thread lock // Prevent thread lock
let schedule_lock = app_state.schedule.lock().unwrap(); let schedule_lock = app_state.schedule.lock().unwrap();
@@ -55,18 +52,18 @@ mod schema {
#[schema(as = GetGroup::Response)] #[schema(as = GetGroup::Response)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Response { pub struct Response {
/// Расписание группы /// Group schedule.
pub group: ScheduleEntry, pub group: ScheduleEntry,
/// Устаревшая переменная /// ## Outdated variable.
/// ///
/// По умолчанию возвращается пустой список /// By default, an empty list is returned.
#[deprecated = "Will be removed in future versions"] #[deprecated = "Will be removed in future versions"]
pub updated: Vec<i32>, pub updated: Vec<i32>,
/// Устаревшая переменная /// ## Outdated variable.
/// ///
/// По умолчанию начальная дата по Unix /// By default, the initial date for unix.
#[deprecated = "Will be removed in future versions"] #[deprecated = "Will be removed in future versions"]
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
} }
@@ -86,12 +83,12 @@ mod schema {
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[schema(as = GroupSchedule::ErrorCode)] #[schema(as = GroupSchedule::ErrorCode)]
pub enum ErrorCode { pub enum ErrorCode {
/// Расписания ещё не получены /// Schedules have not yet been parsed.
#[status_code = "actix_web::http::StatusCode::SERVICE_UNAVAILABLE"] #[status_code = "actix_web::http::StatusCode::SERVICE_UNAVAILABLE"]
#[display("Schedule not parsed yet.")] #[display("Schedule not parsed yet.")]
NoSchedule, NoSchedule,
/// Группа не найдена /// Group not found.
#[status_code = "actix_web::http::StatusCode::NOT_FOUND"] #[status_code = "actix_web::http::StatusCode::NOT_FOUND"]
#[display("Required group not found.")] #[display("Required group not found.")]
NotFound, NotFound,

View File

@@ -35,7 +35,7 @@ mod schema {
#[derive(Serialize, ToSchema)] #[derive(Serialize, ToSchema)]
#[schema(as = GetGroupNames::Response)] #[schema(as = GetGroupNames::Response)]
pub struct Response { pub struct Response {
/// Список названий групп отсортированный в алфавитном порядке /// List of group names sorted in alphabetical order.
#[schema(examples(json!(["ИС-214/23"])))] #[schema(examples(json!(["ИС-214/23"])))]
pub names: Vec<String>, pub names: Vec<String>,
} }

View File

@@ -8,23 +8,23 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use utoipa::ToSchema; use utoipa::ToSchema;
/// Ответ от сервера с расписаниями /// Response from schedule server.
#[derive(Serialize, ToSchema)] #[derive(Serialize, ToSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ScheduleView { pub struct ScheduleView {
/// ETag расписания на сервере политехникума /// ETag schedules on polytechnic server.
etag: String, etag: String,
/// Дата обновления расписания на сайте политехникума /// Schedule update date on polytechnic website.
uploaded_at: DateTime<Utc>, uploaded_at: DateTime<Utc>,
/// Дата последнего скачивания расписания с сервера политехникума /// Date last downloaded from the Polytechnic server.
downloaded_at: DateTime<Utc>, downloaded_at: DateTime<Utc>,
/// Расписание групп /// Groups schedule.
groups: HashMap<String, ScheduleEntry>, groups: HashMap<String, ScheduleEntry>,
/// Расписание преподавателей /// Teachers schedule.
teachers: HashMap<String, ScheduleEntry>, teachers: HashMap<String, ScheduleEntry>,
} }
@@ -33,7 +33,7 @@ pub struct ScheduleView {
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[schema(as = ScheduleShared::ErrorCode)] #[schema(as = ScheduleShared::ErrorCode)]
pub enum ErrorCode { pub enum ErrorCode {
/// Расписания ещё не получены /// Schedules not yet parsed.
#[display("Schedule not parsed yet.")] #[display("Schedule not parsed yet.")]
NoSchedule, NoSchedule,
} }
@@ -56,22 +56,22 @@ impl TryFrom<&web::Data<AppState>> for ScheduleView {
} }
} }
/// Статус кешированного расписаний /// Cached schedule status.
#[derive(Serialize, Deserialize, ToSchema, ResponderJson)] #[derive(Serialize, Deserialize, ToSchema, ResponderJson)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CacheStatus { pub struct CacheStatus {
/// Хеш расписаний /// Schedule hash.
pub cache_hash: String, pub cache_hash: String,
/// Требуется ли обновить ссылку на расписание /// Whether the schedule reference needs to be updated.
pub cache_update_required: bool, pub cache_update_required: bool,
/// Дата последнего обновления кеша /// Last cache update date.
pub last_cache_update: i64, pub last_cache_update: i64,
/// Дата обновления кешированного расписания /// Cached schedule update date.
/// ///
/// Определяется сервером политехникума /// Determined by the polytechnic's server.
pub last_schedule_update: i64, pub last_schedule_update: i64,
} }

View File

@@ -53,18 +53,18 @@ mod schema {
#[schema(as = GetTeacher::Response)] #[schema(as = GetTeacher::Response)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Response { pub struct Response {
/// Расписание преподавателя /// Teacher's schedule.
pub teacher: ScheduleEntry, pub teacher: ScheduleEntry,
/// Устаревшая переменная /// ## Deprecated variable.
/// ///
/// По умолчанию возвращается пустой список /// By default, an empty list is returned.
#[deprecated = "Will be removed in future versions"] #[deprecated = "Will be removed in future versions"]
pub updated: Vec<i32>, pub updated: Vec<i32>,
/// Устаревшая переменная /// ## Deprecated variable.
/// ///
/// По умолчанию начальная дата по Unix /// Defaults to the Unix start date.
#[deprecated = "Will be removed in future versions"] #[deprecated = "Will be removed in future versions"]
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
} }
@@ -84,12 +84,12 @@ mod schema {
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[schema(as = TeacherSchedule::ErrorCode)] #[schema(as = TeacherSchedule::ErrorCode)]
pub enum ErrorCode { pub enum ErrorCode {
/// Расписания ещё не получены /// Schedules have not yet been parsed.
#[status_code = "actix_web::http::StatusCode::SERVICE_UNAVAILABLE"] #[status_code = "actix_web::http::StatusCode::SERVICE_UNAVAILABLE"]
#[display("Schedule not parsed yet.")] #[display("Schedule not parsed yet.")]
NoSchedule, NoSchedule,
/// Преподаватель не найден /// Teacher not found.
#[status_code = "actix_web::http::StatusCode::NOT_FOUND"] #[status_code = "actix_web::http::StatusCode::NOT_FOUND"]
#[display("Required teacher not found.")] #[display("Required teacher not found.")]
NotFound, NotFound,

View File

@@ -35,7 +35,7 @@ mod schema {
#[derive(Serialize, ToSchema)] #[derive(Serialize, ToSchema)]
#[schema(as = GetTeacherNames::Response)] #[schema(as = GetTeacherNames::Response)]
pub struct Response { pub struct Response {
/// Список имён преподавателей отсортированный в алфавитном порядке /// List of teacher names sorted alphabetically.
#[schema(examples(json!(["Хомченко Н.Е."])))] #[schema(examples(json!(["Хомченко Н.Е."])))]
pub names: Vec<String>, pub names: Vec<String>,
} }

View File

@@ -84,7 +84,7 @@ mod schema {
#[derive(Serialize, Deserialize, ToSchema)] #[derive(Serialize, Deserialize, ToSchema)]
pub struct Request { pub struct Request {
/// Ссылка на расписание /// Schedule link.
pub url: String, pub url: String,
} }
@@ -92,25 +92,26 @@ mod schema {
#[status_code = "actix_web::http::StatusCode::NOT_ACCEPTABLE"] #[status_code = "actix_web::http::StatusCode::NOT_ACCEPTABLE"]
#[schema(as = SetDownloadUrl::ErrorCode)] #[schema(as = SetDownloadUrl::ErrorCode)]
pub enum ErrorCode { pub enum ErrorCode {
/// Передана ссылка с хостом отличающимся от politehnikum-eng.ru /// Transferred link with host different from politehnikum-eng.ru.
#[display("URL with unknown host provided. Provide url with politehnikum-eng.ru host.")] #[display("URL with unknown host provided. Provide url with politehnikum-eng.ru host.")]
NonWhitelistedHost, NonWhitelistedHost,
/// Не удалось получить мета-данные файла /// Failed to retrieve file metadata.
#[display("Unable to retrieve metadata from the specified URL.")] #[display("Unable to retrieve metadata from the specified URL.")]
FetchFailed, FetchFailed,
/// Не удалось скачать файл /// Failed to download the file.
#[display("Unable to retrieve data from the specified URL.")] #[display("Unable to retrieve data from the specified URL.")]
DownloadFailed, DownloadFailed,
/// Ссылка ведёт на устаревшее расписание /// The link leads to an outdated schedule.
/// ///
/// Под устаревшим расписанием подразумевается расписание, которое было опубликовано раньше, чем уже имеется на данный момент /// An outdated schedule refers to a schedule that was published earlier
/// than is currently available.
#[display("The schedule is older than it already is.")] #[display("The schedule is older than it already is.")]
OutdatedSchedule, OutdatedSchedule,
/// Не удалось преобразовать расписание /// Failed to parse the schedule.
#[display("{}", "_0.display()")] #[display("{}", "_0.display()")]
InvalidSchedule(ParseError), InvalidSchedule(ParseError),
} }

View File

@@ -113,6 +113,7 @@ pub mod user {
use actix_macros::ResponderJson; use actix_macros::ResponderJson;
use serde::Serialize; use serde::Serialize;
//noinspection SpellCheckingInspection
/// Используется для скрытия чувствительных полей, таких как хеш пароля или FCM /// Используется для скрытия чувствительных полей, таких как хеш пароля или FCM
#[derive(Serialize, utoipa::ToSchema, ResponderJson)] #[derive(Serialize, utoipa::ToSchema, ResponderJson)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -132,7 +133,7 @@ pub mod user {
/// Роль /// Роль
role: UserRole, role: UserRole,
/// Идентификатор прявязанного аккаунта VK /// Идентификатор привязанного аккаунта VK
#[schema(examples(498094647, json!(null)))] #[schema(examples(498094647, json!(null)))]
vk_id: Option<i32>, vk_id: Option<i32>,

View File

@@ -54,7 +54,7 @@ mod schema {
#[derive(Serialize, Deserialize, ToSchema)] #[derive(Serialize, Deserialize, ToSchema)]
#[schema(as = ChangeGroup::Request)] #[schema(as = ChangeGroup::Request)]
pub struct Request { pub struct Request {
/// Название группы. /// Group name.
pub group: String, pub group: String,
} }
@@ -63,21 +63,21 @@ mod schema {
#[schema(as = ChangeGroup::ErrorCode)] #[schema(as = ChangeGroup::ErrorCode)]
#[status_code = "actix_web::http::StatusCode::CONFLICT"] #[status_code = "actix_web::http::StatusCode::CONFLICT"]
pub enum ErrorCode { pub enum ErrorCode {
/// Расписания ещё не получены. /// Schedules have not yet been received.
#[display("Schedule not parsed yet.")] #[display("Schedule not parsed yet.")]
#[status_code = "actix_web::http::StatusCode::SERVICE_UNAVAILABLE"] #[status_code = "actix_web::http::StatusCode::SERVICE_UNAVAILABLE"]
NoSchedule, NoSchedule,
/// Передано то же название группы, что есть на данный момент. /// Passed the same group name that is currently there.
#[display("Passed the same group name as it is at the moment.")] #[display("Passed the same group name as it is at the moment.")]
SameGroup, SameGroup,
/// Требуемая группа не существует. /// The required group does not exist.
#[display("The required group does not exist.")] #[display("The required group does not exist.")]
#[status_code = "actix_web::http::StatusCode::NOT_FOUND"] #[status_code = "actix_web::http::StatusCode::NOT_FOUND"]
NotFound, NotFound,
/// Ошибка на стороне сервера. /// Server-side error.
#[display("Internal server error.")] #[display("Internal server error.")]
#[status_code = "actix_web::http::StatusCode::INTERNAL_SERVER_ERROR"] #[status_code = "actix_web::http::StatusCode::INTERNAL_SERVER_ERROR"]
InternalServerError, InternalServerError,

View File

@@ -45,7 +45,7 @@ mod schema {
#[derive(Serialize, Deserialize, ToSchema)] #[derive(Serialize, Deserialize, ToSchema)]
#[schema(as = ChangeUsername::Request)] #[schema(as = ChangeUsername::Request)]
pub struct Request { pub struct Request {
/// Новое имя. /// User name.
pub username: String, pub username: String,
} }
@@ -54,15 +54,15 @@ mod schema {
#[schema(as = ChangeUsername::ErrorCode)] #[schema(as = ChangeUsername::ErrorCode)]
#[status_code = "actix_web::http::StatusCode::CONFLICT"] #[status_code = "actix_web::http::StatusCode::CONFLICT"]
pub enum ErrorCode { pub enum ErrorCode {
/// Передано то же имя, что есть на данный момент. /// The same name that is currently present is passed.
#[display("Passed the same name as it is at the moment.")] #[display("Passed the same name as it is at the moment.")]
SameUsername, SameUsername,
/// Пользователь с таким именем уже существует. /// A user with this name already exists.
#[display("A user with this name already exists.")] #[display("A user with this name already exists.")]
AlreadyExists, AlreadyExists,
/// Ошибка на стороне сервера. /// Server-side error.
#[display("Internal server error.")] #[display("Internal server error.")]
#[status_code = "actix_web::http::StatusCode::INTERNAL_SERVER_ERROR"] #[status_code = "actix_web::http::StatusCode::INTERNAL_SERVER_ERROR"]
InternalServerError, InternalServerError,

View File

@@ -84,13 +84,13 @@ mod schema {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[schema(as = VkIdOAuth::Request)] #[schema(as = VkIdOAuth::Request)]
pub struct Request { pub struct Request {
/// Код подтверждения authorization_code /// Код подтверждения authorization_code.
pub code: String, pub code: String,
/// Параметр для защиты передаваемых данных /// Parameter to protect transmitted data.
pub code_verifier: String, pub code_verifier: String,
/// Идентификатор устройства /// Device ID.
pub device_id: String, pub device_id: String,
} }
@@ -98,7 +98,7 @@ mod schema {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[schema(as = VkIdOAuth::Response)] #[schema(as = VkIdOAuth::Response)]
pub struct Response { pub struct Response {
/// ID токен /// ID token.
pub access_token: String, pub access_token: String,
} }
@@ -107,7 +107,7 @@ mod schema {
#[schema(as = VkIdOAuth::ErrorCode)] #[schema(as = VkIdOAuth::ErrorCode)]
#[status_code = "actix_web::http::StatusCode::NOT_ACCEPTABLE"] #[status_code = "actix_web::http::StatusCode::NOT_ACCEPTABLE"]
pub enum ErrorCode { pub enum ErrorCode {
/// Сервер VK вернул ошибку /// VK server returned an error.
#[display("VK server returned an error")] #[display("VK server returned an error")]
VkIdError, VkIdError,
} }

View File

@@ -2,7 +2,7 @@ use std::fmt::{Write};
use std::fmt::Display; use std::fmt::Display;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Ответ от сервера при ошибках внутри Middleware /// Server response to errors within Middleware.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ResponseErrorMessage<T: Display> { pub struct ResponseErrorMessage<T: Display> {
code: T, code: T,

View File

@@ -1,7 +1,7 @@
use sha1::Digest; use sha1::Digest;
use std::hash::Hasher; use std::hash::Hasher;
/// Хешер возвращающий хеш из алгоритма реализующего Digest /// Hesher returning hash from the algorithm implementing Digest
pub struct DigestHasher<D: Digest> { pub struct DigestHasher<D: Digest> {
digest: D, digest: D,
} }
@@ -10,7 +10,7 @@ impl<D> DigestHasher<D>
where where
D: Digest, D: Digest,
{ {
/// Получение хеша /// Obtain hash.
pub fn finalize(self) -> String { pub fn finalize(self) -> String {
hex::encode(self.digest.finalize().0) hex::encode(self.digest.finalize().0)
} }
@@ -20,14 +20,14 @@ impl<D> From<D> for DigestHasher<D>
where where
D: Digest, D: Digest,
{ {
/// Создания хешера из алгоритма реализующего Digest /// Creating a hash from an algorithm implementing Digest.
fn from(digest: D) -> Self { fn from(digest: D) -> Self {
DigestHasher { digest } DigestHasher { digest }
} }
} }
impl<D: Digest> Hasher for DigestHasher<D> { impl<D: Digest> Hasher for DigestHasher<D> {
/// Заглушка для предотвращения вызова стандартного результата Hasher /// Stopper to prevent calling the standard Hasher result.
fn finish(&self) -> u64 { fn finish(&self) -> u64 {
unimplemented!("Do not call finish()"); unimplemented!("Do not call finish()");
} }

View File

@@ -9,31 +9,31 @@ use std::env;
use std::mem::discriminant; use std::mem::discriminant;
use std::sync::LazyLock; use std::sync::LazyLock;
/// Ключ для верификации токена /// Key for token verification.
static DECODING_KEY: LazyLock<DecodingKey> = LazyLock::new(|| { static DECODING_KEY: LazyLock<DecodingKey> = LazyLock::new(|| {
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set"); let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
DecodingKey::from_secret(secret.as_bytes()) DecodingKey::from_secret(secret.as_bytes())
}); });
/// Ключ для создания подписанного токена /// Key for creating a signed token.
static ENCODING_KEY: LazyLock<EncodingKey> = LazyLock::new(|| { static ENCODING_KEY: LazyLock<EncodingKey> = LazyLock::new(|| {
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set"); let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
EncodingKey::from_secret(secret.as_bytes()) EncodingKey::from_secret(secret.as_bytes())
}); });
/// Ошибки верификации токена /// Token verification errors.
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Токен имеет другую подпись /// The token has a different signature.
InvalidSignature, InvalidSignature,
/// Ошибка чтения токена /// Token reading error.
InvalidToken(ErrorKind), InvalidToken(ErrorKind),
/// Токен просрочен /// Token expired.
Expired, Expired,
} }
@@ -43,26 +43,26 @@ impl PartialEq for Error {
} }
} }
/// Данные, которые хранит в себе токен /// The data the token holds.
#[serde_as] #[serde_as]
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct Claims { struct Claims {
/// UUID аккаунта пользователя /// User account UUID.
id: String, id: String,
/// Дата создания токена /// Token creation date.
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
iat: u64, iat: u64,
/// Дата окончания действия токена /// Token expiry date.
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
exp: u64, exp: u64,
} }
/// Алгоритм подписи токенов /// Token signing algorithm.
pub(crate) const DEFAULT_ALGORITHM: Algorithm = Algorithm::HS256; pub(crate) const DEFAULT_ALGORITHM: Algorithm = Algorithm::HS256;
/// Проверка токена и извлечение из него UUID аккаунта пользователя /// Checking the token and extracting the UUID of the user account from it.
pub fn verify_and_decode(token: &String) -> Result<String, Error> { pub fn verify_and_decode(token: &String) -> Result<String, Error> {
let mut validation = Validation::new(DEFAULT_ALGORITHM); let mut validation = Validation::new(DEFAULT_ALGORITHM);
@@ -87,7 +87,7 @@ pub fn verify_and_decode(token: &String) -> Result<String, Error> {
} }
} }
/// Создание токена пользователя /// Creating a user token.
pub fn encode(id: &String) -> String { pub fn encode(id: &String) -> String {
let header = Header { let header = Header {
typ: Some(String::from("JWT")), typ: Some(String::from("JWT")),
@@ -132,6 +132,7 @@ mod tests {
); );
} }
//noinspection SpellCheckingInspection
#[test] #[test]
fn test_decode_invalid_signature() { fn test_decode_invalid_signature() {
test_env(); test_env();
@@ -143,6 +144,7 @@ mod tests {
assert_eq!(result.err().unwrap(), Error::InvalidSignature); assert_eq!(result.err().unwrap(), Error::InvalidSignature);
} }
//noinspection SpellCheckingInspection
#[test] #[test]
fn test_decode_expired() { fn test_decode_expired() {
test_env(); test_env();
@@ -154,6 +156,7 @@ mod tests {
assert_eq!(result.err().unwrap(), Error::Expired); assert_eq!(result.err().unwrap(), Error::Expired);
} }
//noinspection SpellCheckingInspection
#[test] #[test]
fn test_decode_ok() { fn test_decode_ok() {
test_env(); test_env();

View File

@@ -5,6 +5,22 @@ pub trait MutexScope<T, ScopeFn, ScopeFnOutput>
where where
ScopeFn: FnOnce(&mut T) -> ScopeFnOutput, ScopeFn: FnOnce(&mut T) -> ScopeFnOutput,
{ {
/// Replaces manually creating a mutex lock to perform operations on the data it manages.
///
/// # Arguments
///
/// * `f`: Function (mostly lambda) to which a reference to the mutable object stored in the mutex will be passed.
///
/// returns: Return value of `f` function.
///
/// # Examples
///
/// ```
/// let mtx: Mutex<i32> = Mutex::new(10);
///
/// let res = mtx.scope(|x| { *x = *x * 2; *x });
/// assert_eq!(res, *mtx.lock().unwrap());
/// ```
fn scope(&self, f: ScopeFn) -> ScopeFnOutput; fn scope(&self, f: ScopeFn) -> ScopeFnOutput;
} }
@@ -21,6 +37,24 @@ where
} }
pub trait MutexScopeAsync<T> { pub trait MutexScopeAsync<T> {
/// ## Asynchronous variant of [MutexScope::scope][MutexScope::scope].
///
/// Replaces manually creating a mutex lock to perform operations on the data it manages.
///
/// # Arguments
///
/// * `f`: Asynchronous function (mostly lambda) to which a reference to the mutable object stored in the mutex will be passed.
///
/// returns: Return value of `f` function.
///
/// # Examples
///
/// ```
/// let mtx: Mutex<i32> = Mutex::new(10);
///
/// let res = mtx.async_scope(async |x| { *x = *x * 2; *x }).await;
/// assert_eq!(res, *mtx.lock().unwrap());
/// ```
async fn async_scope<'a, F, FnFut, FnOut>(&'a self, f: F) -> FnOut async fn async_scope<'a, F, FnFut, FnOut>(&'a self, f: F) -> FnOut
where where
FnFut: Future<Output = FnOut>, FnFut: Future<Output = FnOut>,

View File

@@ -1,41 +1,41 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
/// Ошибки получения данных XLS /// XLS data retrieval errors.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum FetchError { pub enum FetchError {
/// Не установлена ссылка на файл /// File url is not set.
NoUrlProvided, NoUrlProvided,
/// Неизвестная ошибка /// Unknown error.
Unknown, Unknown,
/// Сервер вернул статус код отличающийся от 200 /// Server returned a status code different from 200.
BadStatusCode, BadStatusCode,
/// Ссылка ведёт на файл другого типа /// The url leads to a file of a different type.
BadContentType, BadContentType,
/// Сервер не вернул ожидаемые заголовки /// Server doesn't return expected headers.
BadHeaders, BadHeaders,
} }
/// Результат получения данных XLS /// Result of XLS data retrieval.
pub struct FetchOk { pub struct FetchOk {
/// ETag объекта /// ETag object.
pub etag: String, pub etag: String,
/// Дата загрузки файла /// File upload date.
pub uploaded_at: DateTime<Utc>, pub uploaded_at: DateTime<Utc>,
/// Дата получения данных /// Date data received.
pub requested_at: DateTime<Utc>, pub requested_at: DateTime<Utc>,
/// Данные файла /// File data.
pub data: Option<Vec<u8>>, pub data: Option<Vec<u8>>,
} }
impl FetchOk { impl FetchOk {
/// Результат без контента файла /// Result without file content.
pub fn head(etag: String, uploaded_at: DateTime<Utc>) -> Self { pub fn head(etag: String, uploaded_at: DateTime<Utc>) -> Self {
FetchOk { FetchOk {
etag, etag,
@@ -45,7 +45,7 @@ impl FetchOk {
} }
} }
/// Полный результат /// Full result.
pub fn get(etag: String, uploaded_at: DateTime<Utc>, data: Vec<u8>) -> Self { pub fn get(etag: String, uploaded_at: DateTime<Utc>, data: Vec<u8>) -> Self {
FetchOk { FetchOk {
etag, etag,
@@ -59,9 +59,9 @@ impl FetchOk {
pub type FetchResult = Result<FetchOk, FetchError>; pub type FetchResult = Result<FetchOk, FetchError>;
pub trait XLSDownloader { pub trait XLSDownloader {
/// Получение данных о файле, и, опционально, его контент /// Get data about the file, and optionally its content.
async fn fetch(&self, head: bool) -> FetchResult; async fn fetch(&self, head: bool) -> FetchResult;
/// Установка ссылки на файл /// Setting the file link.
async fn set_url(&mut self, url: String) -> FetchResult; async fn set_url(&mut self, url: String) -> FetchResult;
} }