5 Commits

Author SHA1 Message Date
dependabot[bot]
7b2f9cb684 chore(deps): bump console-subscriber from 0.4.1 to 0.5.0
Bumps [console-subscriber](https://github.com/tokio-rs/console) from 0.4.1 to 0.5.0.
- [Release notes](https://github.com/tokio-rs/console/releases)
- [Changelog](https://github.com/tokio-rs/console/blob/main/release-plz.toml)
- [Commits](https://github.com/tokio-rs/console/compare/console-subscriber-v0.4.1...console-subscriber-v0.5.0)

---
updated-dependencies:
- dependency-name: console-subscriber
  dependency-version: 0.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 19:07:27 +00:00
2442641479 feat(ci): auto-deploy new version after building docker image 2025-10-29 02:31:13 +04:00
ac16c96e5e chore(schedule): add lesson type 'differentiated exam' 2025-10-29 02:16:25 +04:00
622464e4c3 feat(users): add endpoints for getting user by ids 2025-10-28 22:33:49 +04:00
39c60ef939 feat(middleware): add support of path patterns 2025-10-28 22:33:10 +04:00
9 changed files with 139 additions and 55 deletions

View File

@@ -139,4 +139,7 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
build-args: | build-args: |
"BINARY_NAME=${{ env.BINARY_NAME }}" "BINARY_NAME=${{ env.BINARY_NAME }}"
- name: Deploy
run: curl ${{ secrets.DEPLOY_URL }}

83
Cargo.lock generated
View File

@@ -534,11 +534,10 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.7.9" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871"
dependencies = [ dependencies = [
"async-trait",
"axum-core", "axum-core",
"bytes", "bytes",
"futures-util", "futures-util",
@@ -551,29 +550,26 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustversion", "serde_core",
"serde",
"sync_wrapper", "sync_wrapper",
"tower 0.5.2", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
] ]
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.4.5" version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
dependencies = [ dependencies = [
"async-trait",
"bytes", "bytes",
"futures-util", "futures-core",
"http 1.3.1", "http 1.3.1",
"http-body", "http-body",
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
"rustversion",
"sync_wrapper", "sync_wrapper",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@@ -969,22 +965,23 @@ dependencies = [
[[package]] [[package]]
name = "console-api" name = "console-api"
version = "0.8.1" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857" checksum = "e8599749b6667e2f0c910c1d0dff6901163ff698a52d5a39720f61b5be4b20d3"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"prost", "prost",
"prost-types", "prost-types",
"tonic", "tonic",
"tonic-prost",
"tracing-core", "tracing-core",
] ]
[[package]] [[package]]
name = "console-subscriber" name = "console-subscriber"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01" checksum = "fb4915b7d8dd960457a1b6c380114c2944f728e7c65294ab247ae6b6f1f37592"
dependencies = [ dependencies = [
"console-api", "console-api",
"crossbeam-channel", "crossbeam-channel",
@@ -2523,9 +2520,9 @@ dependencies = [
[[package]] [[package]]
name = "matchit" name = "matchit"
version = "0.7.3" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "md-5" name = "md-5"
@@ -3061,9 +3058,9 @@ dependencies = [
[[package]] [[package]]
name = "prost" name = "prost"
version = "0.13.5" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d"
dependencies = [ dependencies = [
"bytes", "bytes",
"prost-derive", "prost-derive",
@@ -3071,9 +3068,9 @@ dependencies = [
[[package]] [[package]]
name = "prost-derive" name = "prost-derive"
version = "0.13.5" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools", "itertools",
@@ -3084,9 +3081,9 @@ dependencies = [
[[package]] [[package]]
name = "prost-types" name = "prost-types"
version = "0.13.5" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72"
dependencies = [ dependencies = [
"prost", "prost",
] ]
@@ -3389,7 +3386,7 @@ dependencies = [
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tower 0.5.2", "tower",
"tower-http", "tower-http",
"tower-service", "tower-service",
"url", "url",
@@ -4802,11 +4799,10 @@ checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
[[package]] [[package]]
name = "tonic" name = "tonic"
version = "0.12.3" version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203"
dependencies = [ dependencies = [
"async-stream",
"async-trait", "async-trait",
"axum", "axum",
"base64 0.22.1", "base64 0.22.1",
@@ -4820,34 +4816,25 @@ dependencies = [
"hyper-util", "hyper-util",
"percent-encoding", "percent-encoding",
"pin-project", "pin-project",
"prost", "socket2 0.6.1",
"socket2 0.5.10", "sync_wrapper",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tower 0.4.13", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing", "tracing",
] ]
[[package]] [[package]]
name = "tower" name = "tonic-prost"
version = "0.4.13" version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67"
dependencies = [ dependencies = [
"futures-core", "bytes",
"futures-util", "prost",
"indexmap 1.9.3", "tonic",
"pin-project",
"pin-project-lite",
"rand 0.8.5",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
] ]
[[package]] [[package]]
@@ -4858,11 +4845,15 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"indexmap 2.12.0",
"pin-project-lite", "pin-project-lite",
"slab",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-util",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing",
] ]
[[package]] [[package]]
@@ -4878,7 +4869,7 @@ dependencies = [
"http-body", "http-body",
"iri-string", "iri-string",
"pin-project-lite", "pin-project-lite",
"tower 0.5.2", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
] ]

View File

@@ -103,6 +103,9 @@ pub enum LessonType {
/// Практическое занятие. /// Практическое занятие.
Practice, Practice,
/// Дифференцированный зачёт.
DifferentiatedExam,
} }
#[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Hash, Debug, Serialize, Deserialize, ToSchema)]

View File

@@ -187,6 +187,7 @@ fn guess_lesson_type(text: &str) -> Option<LessonType> {
("курсовой проект", LessonType::CourseProject), ("курсовой проект", LessonType::CourseProject),
("защита курсового проекта", LessonType::CourseProjectDefense), ("защита курсового проекта", LessonType::CourseProjectDefense),
("практическое занятие", LessonType::Practice), ("практическое занятие", LessonType::Practice),
("дифференцированный зачет", LessonType::DifferentiatedExam),
]) ])
}); });

View File

@@ -50,7 +50,22 @@ pub fn get_api_scope<
.service(routes::auth::sign_up_vk); .service(routes::auth::sign_up_vk);
let users_scope = utoipa_actix_web::scope("/users") let users_scope = utoipa_actix_web::scope("/users")
.wrap(JWTAuthorizationBuilder::new().build()) .wrap(
JWTAuthorizationBuilder::new()
.add_paths(
["/by/id/{id}", "/by/telegram-id/{id}"],
Some(ServiceConfig {
allow_service: true,
user_roles: Some(&[UserRole::Admin]),
}),
)
.build(),
)
.service(
utoipa_actix_web::scope("/by")
.service(routes::users::by::by_id)
.service(routes::users::by::by_telegram_id),
)
.service(routes::users::change_group) .service(routes::users::change_group)
.service(routes::users::change_username) .service(routes::users::change_username)
.service(routes::users::me); .service(routes::users::me);

View File

@@ -5,13 +5,13 @@ use actix_web::body::{BoxBody, EitherBody};
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::{web, Error, HttpRequest, ResponseError}; use actix_web::{web, Error, HttpRequest, ResponseError};
use database::entity::sea_orm_active_enums::UserRole; use database::entity::sea_orm_active_enums::UserRole;
use database::entity::UserType;
use database::query::Query; use database::query::Query;
use futures_util::future::LocalBoxFuture; use futures_util::future::LocalBoxFuture;
use std::future::{ready, Ready}; use std::future::{ready, Ready};
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use database::entity::UserType;
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct ServiceConfig { pub struct ServiceConfig {
@@ -42,7 +42,11 @@ impl JWTAuthorizationBuilder {
self self
} }
pub fn add_paths(mut self, paths: impl AsRef<[&'static str]>, config: Option<ServiceConfig>) -> Self { pub fn add_paths(
mut self,
paths: impl AsRef<[&'static str]>,
config: Option<ServiceConfig>,
) -> Self {
self.path_configs.push((Arc::from(paths.as_ref()), config)); self.path_configs.push((Arc::from(paths.as_ref()), config));
self self
} }
@@ -176,11 +180,20 @@ where
fn call(&self, req: ServiceRequest) -> Self::Future { fn call(&self, req: ServiceRequest) -> Self::Future {
let service = Rc::clone(&self.service); let service = Rc::clone(&self.service);
let Some(config) = Self::find_config( let match_info = req.match_info();
req.match_info().unprocessed(), let path = if let Some(pattern) = req.match_pattern() {
&self.path_configs, let scope_start_idx = match_info
&self.default_config, .as_str()
) else { .find(match_info.unprocessed())
.unwrap_or(0);
pattern.as_str().split_at(scope_start_idx).1.to_owned()
} else {
match_info.unprocessed().to_owned()
};
let Some(config) = Self::find_config(&path, &self.path_configs, &self.default_config)
else {
let fut = self.service.call(req); let fut = self.service.call(req);
return Box::pin(async move { Ok(fut.await?.map_into_left_body()) }); return Box::pin(async move { Ok(fut.await?.map_into_left_body()) });
}; };

View File

@@ -163,6 +163,7 @@ pub mod user {
#[schema(examples( #[schema(examples(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3ZGNjOWE5NTA3YjAwMDA3NzI3NDRhMiIsImlhdCI6IjE3NDMxMDgwOTkiLCJleHAiOiIxODY5MjUyMDk5In0.rMgXRb3JbT9AvLK4eiY9HMB5LxgUudkpQyoWKOypZFY" "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3ZGNjOWE5NTA3YjAwMDA3NzI3NDRhMiIsImlhdCI6IjE3NDMxMDgwOTkiLCJleHAiOiIxODY5MjUyMDk5In0.rMgXRb3JbT9AvLK4eiY9HMB5LxgUudkpQyoWKOypZFY"
))] ))]
#[serde(skip_serializing_if = "Option::is_none")]
pub access_token: Option<String>, pub access_token: Option<String>,
} }

56
src/routes/users/by.rs Normal file
View File

@@ -0,0 +1,56 @@
use crate::routes::schema::user::UserResponse;
use crate::routes::users::by::schema::{ErrorCode, ServiceResponse};
use crate::state::AppState;
use actix_web::{get, web};
use database::query::Query;
#[utoipa::path(responses((status = OK, body = UserResponse)))]
#[get("/id/{id}")]
pub async fn by_id(app_state: web::Data<AppState>, path: web::Path<String>) -> ServiceResponse {
let user_id = path.into_inner();
let db = app_state.get_database();
match Query::find_user_by_id(db, &user_id).await {
Ok(Some(user)) => Ok(UserResponse::from(user)),
_ => Err(ErrorCode::NotFound),
}
.into()
}
#[utoipa::path(responses((status = OK, body = UserResponse)))]
#[get("/telegram-id/{id}")]
pub async fn by_telegram_id(
app_state: web::Data<AppState>,
path: web::Path<i64>,
) -> ServiceResponse {
let telegram_id = path.into_inner();
let db = app_state.get_database();
match Query::find_user_by_telegram_id(db, telegram_id).await {
Ok(Some(user)) => Ok(UserResponse::from(user)),
_ => Err(ErrorCode::NotFound),
}
.into()
}
mod schema {
use crate::routes::schema::user::UserResponse;
use actix_macros::ErrResponse;
use derive_more::Display;
use serde::Serialize;
use utoipa::ToSchema;
pub type ServiceResponse = crate::routes::schema::Response<UserResponse, ErrorCode>;
#[derive(Clone, Serialize, Display, ToSchema, ErrResponse)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[schema(as = Users::By::ErrorCode)]
pub enum ErrorCode {
/// User not found.
#[status_code = "actix_web::http::StatusCode::NOT_FOUND"]
#[display("Required user not found.")]
NotFound,
}
}

View File

@@ -1,3 +1,4 @@
pub mod by;
mod change_group; mod change_group;
mod change_username; mod change_username;
mod me; mod me;