mirror of
https://github.com/n08i40k/schedule-parser-rusted.git
synced 2025-12-06 09:47:50 +03:00
feat(database)!: switch from diesel to sea-orm
This commit is contained in:
@@ -1,148 +0,0 @@
|
||||
pub mod users {
|
||||
use crate::database::models::User;
|
||||
use crate::database::schema::users::dsl::users;
|
||||
use crate::database::schema::users::dsl::*;
|
||||
use crate::state::AppState;
|
||||
use actix_web::web;
|
||||
use diesel::{insert_into, ExpressionMethods, QueryResult};
|
||||
use diesel::{QueryDsl, RunQueryDsl};
|
||||
use diesel::{SaveChangesDsl, SelectableHelper};
|
||||
use std::ops::DerefMut;
|
||||
|
||||
pub async fn get(state: &web::Data<AppState>, _id: &String) -> QueryResult<User> {
|
||||
users
|
||||
.filter(id.eq(_id))
|
||||
.select(User::as_select())
|
||||
.first(state.get_database().await.deref_mut())
|
||||
}
|
||||
|
||||
pub async fn get_by_username(
|
||||
state: &web::Data<AppState>,
|
||||
_username: &String,
|
||||
) -> QueryResult<User> {
|
||||
users
|
||||
.filter(username.eq(_username))
|
||||
.select(User::as_select())
|
||||
.first(state.get_database().await.deref_mut())
|
||||
}
|
||||
|
||||
//noinspection RsTraitObligations
|
||||
pub async fn get_by_vk_id(state: &web::Data<AppState>, _vk_id: i32) -> QueryResult<User> {
|
||||
users
|
||||
.filter(vk_id.eq(_vk_id))
|
||||
.select(User::as_select())
|
||||
.first(state.get_database().await.deref_mut())
|
||||
}
|
||||
|
||||
//noinspection RsTraitObligations
|
||||
pub async fn get_by_telegram_id(
|
||||
state: &web::Data<AppState>,
|
||||
_telegram_id: i64,
|
||||
) -> QueryResult<User> {
|
||||
users
|
||||
.filter(telegram_id.eq(_telegram_id))
|
||||
.select(User::as_select())
|
||||
.first(state.get_database().await.deref_mut())
|
||||
}
|
||||
|
||||
//noinspection DuplicatedCode
|
||||
pub async fn contains_by_username(state: &web::Data<AppState>, _username: &String) -> bool {
|
||||
// и как это нахуй сократить блять примеров нихуя нет, нихуя не работает
|
||||
// как меня этот раст заебал уже
|
||||
|
||||
match users
|
||||
.filter(username.eq(_username))
|
||||
.count()
|
||||
.get_result::<i64>(state.get_database().await.deref_mut())
|
||||
{
|
||||
Ok(count) => count > 0,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection DuplicatedCode
|
||||
//noinspection RsTraitObligations
|
||||
pub async fn contains_by_vk_id(state: &web::Data<AppState>, _vk_id: i32) -> bool {
|
||||
match users
|
||||
.filter(vk_id.eq(_vk_id))
|
||||
.count()
|
||||
.get_result::<i64>(state.get_database().await.deref_mut())
|
||||
{
|
||||
Ok(count) => count > 0,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert(state: &web::Data<AppState>, user: &User) -> QueryResult<usize> {
|
||||
insert_into(users)
|
||||
.values(user)
|
||||
.execute(state.get_database().await.deref_mut())
|
||||
}
|
||||
|
||||
/// Function declaration [User::save][UserSave::save].
|
||||
pub trait UserSave {
|
||||
/// Saves the user's changes to the database.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state`: The state of the actix-web application that stores the mutex of the [connection][diesel::PgConnection].
|
||||
///
|
||||
/// returns: `QueryResult<User>`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use crate::database::driver::users;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Params {
|
||||
/// pub username: String,
|
||||
/// }
|
||||
///
|
||||
/// #[patch("/")]
|
||||
/// async fn patch_user(
|
||||
/// app_state: web::Data<AppState>,
|
||||
/// user: SyncExtractor<User>,
|
||||
/// web::Query(params): web::Query<Params>,
|
||||
/// ) -> web::Json<User> {
|
||||
/// let mut user = user.into_inner();
|
||||
///
|
||||
/// user.username = params.username;
|
||||
///
|
||||
/// match user.save(&app_state) {
|
||||
/// Ok(user) => web::Json(user),
|
||||
/// Err(e) => {
|
||||
/// eprintln!("Failed to save user: {e}");
|
||||
/// panic!();
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
async fn save(&self, state: &web::Data<AppState>) -> QueryResult<User>;
|
||||
}
|
||||
|
||||
/// Implementation of [UserSave][UserSave] trait.
|
||||
impl UserSave for User {
|
||||
async fn save(&self, state: &web::Data<AppState>) -> QueryResult<User> {
|
||||
self.save_changes::<Self>(state.get_database().await.deref_mut())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub async fn delete_by_username(state: &web::Data<AppState>, _username: &String) -> bool {
|
||||
match diesel::delete(users.filter(username.eq(_username)))
|
||||
.execute(state.get_database().await.deref_mut())
|
||||
{
|
||||
Ok(count) => count > 0,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub async fn insert_or_ignore(state: &web::Data<AppState>, user: &User) -> QueryResult<usize> {
|
||||
insert_into(users)
|
||||
.values(user)
|
||||
.on_conflict_do_nothing()
|
||||
.execute(state.get_database().await.deref_mut())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod driver;
|
||||
pub mod models;
|
||||
pub mod schema;
|
||||
@@ -1,87 +0,0 @@
|
||||
use actix_macros::ResponderJson;
|
||||
use diesel::QueryId;
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, PartialEq, Debug, Serialize, Deserialize, diesel_derive_enum::DbEnum, ToSchema,
|
||||
)]
|
||||
#[ExistingTypePath = "crate::database::schema::sql_types::UserRole"]
|
||||
#[DbValueStyle = "UPPERCASE"]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum UserRole {
|
||||
Student,
|
||||
Teacher,
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Identifiable,
|
||||
AsChangeset,
|
||||
Queryable,
|
||||
QueryId,
|
||||
Selectable,
|
||||
Serialize,
|
||||
Insertable,
|
||||
Debug,
|
||||
ToSchema,
|
||||
ResponderJson,
|
||||
)]
|
||||
#[diesel(table_name = crate::database::schema::users)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
pub struct User {
|
||||
/// Account UUID.
|
||||
pub id: String,
|
||||
|
||||
/// User name.
|
||||
pub username: String,
|
||||
|
||||
/// BCrypt password hash.
|
||||
pub password: Option<String>,
|
||||
|
||||
/// ID of the linked VK account.
|
||||
pub vk_id: Option<i32>,
|
||||
|
||||
/// JWT access token.
|
||||
pub access_token: Option<String>,
|
||||
|
||||
/// Group.
|
||||
pub group: Option<String>,
|
||||
|
||||
/// Role.
|
||||
pub role: UserRole,
|
||||
|
||||
/// Version of the installed Polytechnic+ application.
|
||||
pub android_version: Option<String>,
|
||||
|
||||
/// ID of the linked Telegram account.
|
||||
pub telegram_id: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Serialize,
|
||||
Identifiable,
|
||||
Queryable,
|
||||
Selectable,
|
||||
Insertable,
|
||||
AsChangeset,
|
||||
Associations,
|
||||
ToSchema,
|
||||
ResponderJson,
|
||||
)]
|
||||
#[diesel(belongs_to(User))]
|
||||
#[diesel(table_name = crate::database::schema::fcm)]
|
||||
#[diesel(primary_key(user_id))]
|
||||
pub struct FCM {
|
||||
/// Account UUID.
|
||||
pub user_id: String,
|
||||
|
||||
/// FCM token.
|
||||
pub token: String,
|
||||
|
||||
/// List of topics subscribed to by the user.
|
||||
pub topics: Vec<Option<String>>,
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
pub mod sql_types {
|
||||
#[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "user_role"))]
|
||||
pub struct UserRole;
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
fcm (user_id) {
|
||||
user_id -> Text,
|
||||
token -> Text,
|
||||
topics -> Array<Nullable<Text>>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use super::sql_types::UserRole;
|
||||
|
||||
users (id) {
|
||||
id -> Text,
|
||||
username -> Text,
|
||||
password -> Nullable<Text>,
|
||||
vk_id -> Nullable<Int4>,
|
||||
access_token -> Nullable<Text>,
|
||||
group -> Nullable<Text>,
|
||||
role -> UserRole,
|
||||
android_version -> Nullable<Text>,
|
||||
telegram_id -> Nullable<Int8>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(fcm -> users (user_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
fcm,
|
||||
users,
|
||||
);
|
||||
@@ -1,5 +1,3 @@
|
||||
use crate::database::driver;
|
||||
use crate::database::models::User;
|
||||
use crate::extractors::base::FromRequestAsync;
|
||||
use crate::state::AppState;
|
||||
use crate::utility::jwt;
|
||||
@@ -8,9 +6,12 @@ use actix_web::body::BoxBody;
|
||||
use actix_web::dev::Payload;
|
||||
use actix_web::http::header;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use database::entity::User;
|
||||
use database::query::Query;
|
||||
use derive_more::Display;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Display, MiddlewareError)]
|
||||
#[status_code = "actix_web::http::StatusCode::UNAUTHORIZED"]
|
||||
@@ -88,10 +89,20 @@ impl FromRequestAsync for User {
|
||||
let user_id = jwt::verify_and_decode(&access_token)
|
||||
.map_err(|_| Error::InvalidAccessToken.into_err())?;
|
||||
|
||||
let app_state = req.app_data::<web::Data<AppState>>().unwrap();
|
||||
let db = req
|
||||
.app_data::<web::Data<AppState>>()
|
||||
.unwrap()
|
||||
.get_database();
|
||||
|
||||
driver::users::get(app_state, &user_id)
|
||||
Query::find_user_by_id(db, &user_id)
|
||||
.await
|
||||
.map_err(|_| Error::NoUser.into())
|
||||
.and_then(|user| {
|
||||
if let Some(user) = user {
|
||||
Ok(user)
|
||||
} else {
|
||||
Err(actix_web::Error::from(Error::NoUser))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ use utoipa_rapidoc::RapiDoc;
|
||||
|
||||
mod state;
|
||||
|
||||
mod database;
|
||||
|
||||
mod extractors;
|
||||
mod middlewares;
|
||||
mod routes;
|
||||
@@ -72,7 +70,7 @@ pub fn get_api_scope<
|
||||
async fn async_main() -> io::Result<()> {
|
||||
info!("Запуск сервера...");
|
||||
|
||||
let app_state = new_app_state().await.unwrap();
|
||||
let app_state = new_app_state(None).await.unwrap();
|
||||
|
||||
HttpServer::new(move || {
|
||||
let (app, api) = App::new()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::database::models::User;
|
||||
use crate::extractors::authorized_user;
|
||||
use crate::extractors::base::FromRequestAsync;
|
||||
use actix_web::body::{BoxBody, EitherBody};
|
||||
@@ -7,6 +6,7 @@ use actix_web::{Error, HttpRequest, ResponseError};
|
||||
use futures_util::future::LocalBoxFuture;
|
||||
use std::future::{Ready, ready};
|
||||
use std::rc::Rc;
|
||||
use database::entity::User;
|
||||
|
||||
/// Middleware guard working with JWT tokens.
|
||||
pub struct JWTAuthorization {
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
use self::schema::*;
|
||||
use crate::database::driver;
|
||||
use crate::database::driver::users::UserSave;
|
||||
use crate::routes::auth::shared::parse_vk_id;
|
||||
use crate::routes::auth::sign_in::schema::SignInData::{Default, VkOAuth};
|
||||
use crate::routes::schema::ResponseError;
|
||||
use crate::routes::schema::user::UserResponse;
|
||||
use crate::{AppState, utility};
|
||||
use crate::routes::schema::ResponseError;
|
||||
use crate::{utility, AppState};
|
||||
use actix_web::{post, web};
|
||||
use database::query::Query;
|
||||
use web::Json;
|
||||
|
||||
async fn sign_in_combined(
|
||||
data: SignInData,
|
||||
app_state: &web::Data<AppState>,
|
||||
) -> Result<UserResponse, ErrorCode> {
|
||||
let db = app_state.get_database();
|
||||
|
||||
let user = match &data {
|
||||
Default(data) => driver::users::get_by_username(&app_state, &data.username).await,
|
||||
VkOAuth(id) => driver::users::get_by_vk_id(&app_state, *id).await,
|
||||
};
|
||||
Default(data) => Query::find_user_by_username(db, &data.username).await,
|
||||
VkOAuth(id) => Query::find_user_by_vk_id(db, *id).await,
|
||||
}
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
match user {
|
||||
Ok(mut user) => {
|
||||
Some(user) => {
|
||||
if let Default(data) = data {
|
||||
if user.password.is_none() {
|
||||
return Err(ErrorCode::IncorrectCredentials);
|
||||
@@ -37,14 +40,11 @@ async fn sign_in_combined(
|
||||
}
|
||||
}
|
||||
|
||||
user.access_token = Some(utility::jwt::encode(&user.id));
|
||||
|
||||
user.save(&app_state).await.expect("Failed to update user");
|
||||
|
||||
Ok(user.into())
|
||||
let access_token = utility::jwt::encode(&user.id);
|
||||
Ok(UserResponse::from_user_with_token(user, access_token))
|
||||
}
|
||||
|
||||
Err(_) => Err(ErrorCode::IncorrectCredentials),
|
||||
None => Err(ErrorCode::IncorrectCredentials),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,16 +139,16 @@ mod schema {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::schema::*;
|
||||
use crate::database::driver;
|
||||
use crate::database::models::{User, UserRole};
|
||||
use crate::routes::auth::sign_in::sign_in;
|
||||
use crate::test_env::tests::{static_app_state, test_app_state, test_env};
|
||||
use crate::utility;
|
||||
use actix_test::test_app;
|
||||
use actix_web::dev::ServiceResponse;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
use database::entity::sea_orm_active_enums::UserRole;
|
||||
use database::entity::ActiveUser;
|
||||
use database::sea_orm::{ActiveModelTrait, Set};
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::fmt::Write;
|
||||
|
||||
@@ -182,22 +182,24 @@ mod tests {
|
||||
test_env();
|
||||
|
||||
let app_state = static_app_state().await;
|
||||
driver::users::insert_or_ignore(
|
||||
&app_state,
|
||||
&User {
|
||||
id: id.clone(),
|
||||
username,
|
||||
password: Some(bcrypt::hash("example".to_string(), bcrypt::DEFAULT_COST).unwrap()),
|
||||
vk_id: None,
|
||||
telegram_id: None,
|
||||
access_token: Some(utility::jwt::encode(&id)),
|
||||
group: Some("ИС-214/23".to_string()),
|
||||
role: UserRole::Student,
|
||||
android_version: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let active_user = ActiveUser {
|
||||
id: Set(id.clone()),
|
||||
username: Set(username),
|
||||
password: Set(Some(
|
||||
bcrypt::hash("example".to_string(), bcrypt::DEFAULT_COST).unwrap(),
|
||||
)),
|
||||
vk_id: Set(None),
|
||||
telegram_id: Set(None),
|
||||
group: Set(Some("ИС-214/23".to_string())),
|
||||
role: Set(UserRole::Student),
|
||||
android_version: Set(None),
|
||||
};
|
||||
|
||||
active_user
|
||||
.save(app_state.get_database())
|
||||
.await
|
||||
.expect("Failed to save user");
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use self::schema::*;
|
||||
use crate::AppState;
|
||||
use crate::database::driver;
|
||||
use crate::database::models::UserRole;
|
||||
use crate::routes::auth::shared::parse_vk_id;
|
||||
use crate::routes::schema::ResponseError;
|
||||
use crate::routes::schema::user::UserResponse;
|
||||
use crate::routes::schema::ResponseError;
|
||||
use crate::{utility, AppState};
|
||||
use actix_web::{post, web};
|
||||
use database::entity::sea_orm_active_enums::UserRole;
|
||||
use database::entity::ActiveUser;
|
||||
use database::query::Query;
|
||||
use database::sea_orm::ActiveModelTrait;
|
||||
use std::ops::Deref;
|
||||
use web::Json;
|
||||
|
||||
async fn sign_up_combined(
|
||||
@@ -28,22 +31,31 @@ async fn sign_up_combined(
|
||||
return Err(ErrorCode::InvalidGroupName);
|
||||
}
|
||||
|
||||
// If user with specified username already exists.
|
||||
if driver::users::contains_by_username(&app_state, &data.username).await {
|
||||
let db = app_state.get_database();
|
||||
|
||||
// If user with specified username already exists.O
|
||||
if Query::find_user_by_username(db, &data.username)
|
||||
.await
|
||||
.is_ok_and(|user| user.is_some())
|
||||
{
|
||||
return Err(ErrorCode::UsernameAlreadyExists);
|
||||
}
|
||||
|
||||
// If user with specified VKID already exists.
|
||||
if let Some(id) = data.vk_id {
|
||||
if driver::users::contains_by_vk_id(&app_state, id).await {
|
||||
if Query::find_user_by_vk_id(db, id)
|
||||
.await
|
||||
.is_ok_and(|user| user.is_some())
|
||||
{
|
||||
return Err(ErrorCode::VkAlreadyExists);
|
||||
}
|
||||
}
|
||||
|
||||
let user = data.into();
|
||||
driver::users::insert(&app_state, &user).await.unwrap();
|
||||
let active_user: ActiveUser = data.into();
|
||||
let user = active_user.insert(db).await.unwrap();
|
||||
let access_token = utility::jwt::encode(&user.id);
|
||||
|
||||
Ok(UserResponse::from(&user)).into()
|
||||
Ok(UserResponse::from_user_with_token(user, access_token))
|
||||
}
|
||||
|
||||
#[utoipa::path(responses(
|
||||
@@ -101,10 +113,11 @@ pub async fn sign_up_vk(
|
||||
}
|
||||
|
||||
mod schema {
|
||||
use crate::database::models::{User, UserRole};
|
||||
use crate::routes::schema::user::UserResponse;
|
||||
use crate::utility;
|
||||
use actix_macros::ErrResponse;
|
||||
use database::entity::sea_orm_active_enums::UserRole;
|
||||
use database::entity::ActiveUser;
|
||||
use database::sea_orm::Set;
|
||||
use derive_more::Display;
|
||||
use objectid::ObjectId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -134,7 +147,7 @@ mod schema {
|
||||
}
|
||||
|
||||
pub mod vk {
|
||||
use crate::database::models::UserRole;
|
||||
use database::entity::sea_orm_active_enums::UserRole;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
@@ -215,25 +228,21 @@ mod schema {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<User> for SignUpData {
|
||||
fn into(self) -> User {
|
||||
impl Into<ActiveUser> for SignUpData {
|
||||
fn into(self) -> ActiveUser {
|
||||
assert_ne!(self.password.is_some(), self.vk_id.is_some());
|
||||
|
||||
let id = ObjectId::new().unwrap().to_string();
|
||||
let access_token = Some(utility::jwt::encode(&id));
|
||||
|
||||
User {
|
||||
id,
|
||||
username: self.username,
|
||||
password: self
|
||||
ActiveUser {
|
||||
id: Set(ObjectId::new().unwrap().to_string()),
|
||||
username: Set(self.username),
|
||||
password: Set(self
|
||||
.password
|
||||
.map(|x| bcrypt::hash(x, bcrypt::DEFAULT_COST).unwrap()),
|
||||
vk_id: self.vk_id,
|
||||
telegram_id: None,
|
||||
access_token,
|
||||
group: Some(self.group),
|
||||
role: self.role,
|
||||
android_version: Some(self.version),
|
||||
.map(|x| bcrypt::hash(x, bcrypt::DEFAULT_COST).unwrap())),
|
||||
vk_id: Set(self.vk_id),
|
||||
telegram_id: Set(None),
|
||||
group: Set(Some(self.group)),
|
||||
role: Set(self.role),
|
||||
android_version: Set(Some(self.version)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,8 +250,6 @@ mod schema {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::database::driver;
|
||||
use crate::database::models::UserRole;
|
||||
use crate::routes::auth::sign_up::schema::Request;
|
||||
use crate::routes::auth::sign_up::sign_up;
|
||||
use crate::test_env::tests::{static_app_state, test_app_state, test_env};
|
||||
@@ -251,6 +258,11 @@ mod tests {
|
||||
use actix_web::http::Method;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
use database::entity::sea_orm_active_enums::UserRole;
|
||||
use database::entity::{UserColumn, UserEntity};
|
||||
use database::sea_orm::ColumnTrait;
|
||||
use database::sea_orm::{EntityTrait, QueryFilter};
|
||||
use std::ops::Deref;
|
||||
|
||||
struct SignUpPartial<'a> {
|
||||
username: &'a str,
|
||||
@@ -282,7 +294,12 @@ mod tests {
|
||||
test_env();
|
||||
|
||||
let app_state = static_app_state().await;
|
||||
driver::users::delete_by_username(&app_state, &"test::sign_up_valid".to_string()).await;
|
||||
|
||||
UserEntity::delete_many()
|
||||
.filter(UserColumn::Username.eq("test::sign_up_valid"))
|
||||
.exec(app_state.get_database())
|
||||
.await
|
||||
.expect("Failed to delete user");
|
||||
|
||||
// test
|
||||
|
||||
@@ -303,7 +320,12 @@ mod tests {
|
||||
test_env();
|
||||
|
||||
let app_state = static_app_state().await;
|
||||
driver::users::delete_by_username(&app_state, &"test::sign_up_multiple".to_string()).await;
|
||||
|
||||
UserEntity::delete_many()
|
||||
.filter(UserColumn::Username.eq("test::sign_up_multiple"))
|
||||
.exec(app_state.get_database())
|
||||
.await
|
||||
.expect("Failed to delete user");
|
||||
|
||||
let create = sign_up_client(SignUpPartial {
|
||||
username: "test::sign_up_multiple",
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use self::schema::*;
|
||||
use crate::database::driver;
|
||||
use crate::database::driver::users::UserSave;
|
||||
use crate::database::models::{User, UserRole};
|
||||
use crate::routes::schema::ResponseError;
|
||||
use crate::utility::telegram::{WebAppInitDataMap, WebAppUser};
|
||||
use crate::{AppState, utility};
|
||||
use crate::{utility, AppState};
|
||||
use actix_web::{post, web};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use database::entity::sea_orm_active_enums::UserRole;
|
||||
use database::entity::ActiveUser;
|
||||
use database::query::Query;
|
||||
use database::sea_orm::{ActiveModelTrait, Set};
|
||||
use objectid::ObjectId;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use web::Json;
|
||||
|
||||
@@ -48,39 +50,28 @@ pub async fn telegram_auth(
|
||||
let web_app_user =
|
||||
serde_json::from_str::<WebAppUser>(init_data.data_map.get("user").unwrap()).unwrap();
|
||||
|
||||
let mut user = {
|
||||
match driver::users::get_by_telegram_id(&app_state, web_app_user.id).await {
|
||||
Ok(value) => Ok(value),
|
||||
Err(_) => {
|
||||
let new_user = User {
|
||||
id: ObjectId::new().unwrap().to_string(),
|
||||
username: format!("telegram_{}", web_app_user.id), // можно оставить, а можно поменять
|
||||
password: None, // ибо нехуй
|
||||
vk_id: None,
|
||||
telegram_id: Some(web_app_user.id),
|
||||
access_token: None, // установится ниже
|
||||
group: None,
|
||||
role: UserRole::Student, // TODO: при реге проверять данные
|
||||
android_version: None,
|
||||
let user =
|
||||
match Query::find_user_by_telegram_id(app_state.get_database(), web_app_user.id).await {
|
||||
Ok(Some(value)) => Ok(value),
|
||||
_ => {
|
||||
let new_user = ActiveUser {
|
||||
id: Set(ObjectId::new().unwrap().to_string()),
|
||||
username: Set(format!("telegram_{}", web_app_user.id)), // можно оставить, а можно поменять
|
||||
password: Set(None), // ибо нехуй
|
||||
vk_id: Set(None),
|
||||
telegram_id: Set(Some(web_app_user.id)),
|
||||
group: Set(None),
|
||||
role: Set(UserRole::Student), // TODO: при реге проверять данные
|
||||
android_version: Set(None),
|
||||
};
|
||||
|
||||
driver::users::insert(&app_state, &new_user)
|
||||
.await
|
||||
.map(|_| new_user)
|
||||
new_user.insert(app_state.get_database()).await
|
||||
}
|
||||
}
|
||||
.expect("Failed to get or add user")
|
||||
};
|
||||
.expect("Failed to get or add user");
|
||||
|
||||
user.access_token = Some(utility::jwt::encode(&user.id));
|
||||
|
||||
user.save(&app_state).await.expect("Failed to update user");
|
||||
|
||||
Ok(Response::new(
|
||||
&*user.access_token.unwrap(),
|
||||
user.group.is_some(),
|
||||
))
|
||||
.into()
|
||||
let access_token = utility::jwt::encode(&user.id);
|
||||
Ok(Response::new(&access_token, user.group.is_some())).into()
|
||||
}
|
||||
|
||||
mod schema {
|
||||
@@ -89,9 +80,9 @@ mod schema {
|
||||
use crate::utility::telegram::VerifyError;
|
||||
use actix_macros::ErrResponse;
|
||||
use actix_web::body::EitherBody;
|
||||
use actix_web::cookie::CookieBuilder;
|
||||
use actix_web::cookie::time::OffsetDateTime;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::cookie::CookieBuilder;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use derive_more::Display;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use std::ops::Add;
|
||||
@@ -176,4 +167,4 @@ mod schema {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use self::schema::*;
|
||||
use crate::AppState;
|
||||
use crate::database::driver;
|
||||
use crate::database::driver::users::UserSave;
|
||||
use crate::database::models::User;
|
||||
use crate::extractors::base::AsyncExtractor;
|
||||
use crate::routes::schema::ResponseError;
|
||||
use crate::AppState;
|
||||
use actix_web::{post, web};
|
||||
use database::entity::User;
|
||||
use database::query::Query;
|
||||
use database::sea_orm::{ActiveModelTrait, IntoActiveModel, Set};
|
||||
use std::ops::Deref;
|
||||
use web::Json;
|
||||
|
||||
#[utoipa::path(responses(
|
||||
@@ -20,7 +21,7 @@ pub async fn telegram_complete(
|
||||
app_state: web::Data<AppState>,
|
||||
user: AsyncExtractor<User>,
|
||||
) -> ServiceResponse {
|
||||
let mut user = user.into_inner();
|
||||
let user = user.into_inner();
|
||||
|
||||
// проверка на перезапись уже имеющихся данных
|
||||
if user.group.is_some() {
|
||||
@@ -29,13 +30,19 @@ pub async fn telegram_complete(
|
||||
|
||||
let data = data.into_inner();
|
||||
|
||||
let db = app_state.get_database();
|
||||
let mut active_user = user.clone().into_active_model();
|
||||
|
||||
// замена существующего имени, если оно отличается
|
||||
if user.username != data.username {
|
||||
if driver::users::contains_by_username(&app_state, &data.username).await {
|
||||
if Query::is_user_exists_by_username(db, &data.username)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
return Err(ErrorCode::UsernameAlreadyExists).into();
|
||||
}
|
||||
|
||||
user.username = data.username;
|
||||
active_user.username = Set(data.username);
|
||||
}
|
||||
|
||||
// проверка на существование группы
|
||||
@@ -50,9 +57,12 @@ pub async fn telegram_complete(
|
||||
return Err(ErrorCode::InvalidGroupName).into();
|
||||
}
|
||||
|
||||
user.group = Some(data.group);
|
||||
active_user.group = Set(Some(data.group));
|
||||
|
||||
user.save(&app_state).await.expect("Failed to update user");
|
||||
active_user
|
||||
.update(db)
|
||||
.await
|
||||
.expect("Failed to update user");
|
||||
|
||||
Ok(()).into()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use self::schema::*;
|
||||
use crate::AppState;
|
||||
use crate::database::models::User;
|
||||
use crate::extractors::base::AsyncExtractor;
|
||||
use crate::routes::schedule::schema::ScheduleEntryResponse;
|
||||
use crate::routes::schema::ResponseError;
|
||||
use actix_web::{get, web};
|
||||
use database::entity::User;
|
||||
|
||||
#[utoipa::path(responses(
|
||||
(status = OK, body = ScheduleEntryResponse),
|
||||
|
||||
@@ -126,8 +126,9 @@ where
|
||||
}
|
||||
|
||||
pub mod user {
|
||||
use crate::database::models::{User, UserRole};
|
||||
use actix_macros::{OkResponse, ResponderJson};
|
||||
use database::entity::sea_orm_active_enums::UserRole;
|
||||
use database::entity::User;
|
||||
use serde::Serialize;
|
||||
|
||||
//noinspection SpellCheckingInspection
|
||||
@@ -165,17 +166,31 @@ pub mod user {
|
||||
pub access_token: Option<String>,
|
||||
}
|
||||
|
||||
/// Create UserResponse from User ref.
|
||||
impl From<&User> for UserResponse {
|
||||
fn from(user: &User) -> Self {
|
||||
UserResponse {
|
||||
impl UserResponse {
|
||||
pub fn from_user_with_token(user: User, access_token: String) -> Self {
|
||||
Self {
|
||||
id: user.id.clone(),
|
||||
username: user.username.clone(),
|
||||
group: user.group.clone(),
|
||||
role: user.role.clone(),
|
||||
vk_id: user.vk_id.clone(),
|
||||
telegram_id: user.telegram_id.clone(),
|
||||
access_token: user.access_token.clone(),
|
||||
access_token: Some(access_token),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create UserResponse from User ref.
|
||||
impl From<&User> for UserResponse {
|
||||
fn from(user: &User) -> Self {
|
||||
Self {
|
||||
id: user.id.clone(),
|
||||
username: user.username.clone(),
|
||||
group: user.group.clone(),
|
||||
role: user.role.clone(),
|
||||
vk_id: user.vk_id.clone(),
|
||||
telegram_id: user.telegram_id.clone(),
|
||||
access_token: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,14 +198,14 @@ pub mod user {
|
||||
/// Transform User to UserResponse.
|
||||
impl From<User> for UserResponse {
|
||||
fn from(user: User) -> Self {
|
||||
UserResponse {
|
||||
Self {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
group: user.group,
|
||||
role: user.role,
|
||||
vk_id: user.vk_id,
|
||||
telegram_id: user.telegram_id,
|
||||
access_token: user.access_token,
|
||||
access_token: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use self::schema::*;
|
||||
use crate::database::driver::users::UserSave;
|
||||
use crate::database::models::User;
|
||||
use crate::extractors::base::AsyncExtractor;
|
||||
use crate::state::AppState;
|
||||
use actix_web::{post, web};
|
||||
use database::entity::User;
|
||||
use database::sea_orm::{ActiveModelTrait, IntoActiveModel, Set};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[utoipa::path(responses((status = OK)))]
|
||||
#[post("/change-group")]
|
||||
@@ -12,9 +13,13 @@ pub async fn change_group(
|
||||
user: AsyncExtractor<User>,
|
||||
data: web::Json<Request>,
|
||||
) -> ServiceResponse {
|
||||
let mut user = user.into_inner();
|
||||
let user = user.into_inner();
|
||||
|
||||
if user.group.is_some_and(|group| group == data.group) {
|
||||
if user
|
||||
.group
|
||||
.as_ref()
|
||||
.is_some_and(|group| group.eq(&data.group))
|
||||
{
|
||||
return Ok(()).into();
|
||||
}
|
||||
|
||||
@@ -28,10 +33,12 @@ pub async fn change_group(
|
||||
{
|
||||
return Err(ErrorCode::NotFound).into();
|
||||
}
|
||||
|
||||
user.group = Some(data.into_inner().group);
|
||||
user.save(&app_state).await.unwrap();
|
||||
|
||||
|
||||
let mut active_user = user.clone().into_active_model();
|
||||
active_user.group = Set(Some(data.into_inner().group));
|
||||
|
||||
active_user.update(app_state.get_database()).await.unwrap();
|
||||
|
||||
Ok(()).into()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use self::schema::*;
|
||||
use crate::database::driver;
|
||||
use crate::database::driver::users::UserSave;
|
||||
use crate::database::models::User;
|
||||
use crate::extractors::base::AsyncExtractor;
|
||||
use crate::state::AppState;
|
||||
use actix_web::{post, web};
|
||||
use database::entity::User;
|
||||
use database::query::Query;
|
||||
use database::sea_orm::{ActiveModelTrait, IntoActiveModel, Set};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[utoipa::path(responses((status = OK)))]
|
||||
#[post("/change-username")]
|
||||
@@ -13,21 +14,24 @@ pub async fn change_username(
|
||||
user: AsyncExtractor<User>,
|
||||
data: web::Json<Request>,
|
||||
) -> ServiceResponse {
|
||||
let mut user = user.into_inner();
|
||||
let user = user.into_inner();
|
||||
|
||||
if user.username == data.username {
|
||||
return Ok(()).into();
|
||||
}
|
||||
|
||||
if driver::users::get_by_username(&app_state, &data.username)
|
||||
let db = app_state.get_database();
|
||||
|
||||
if Query::is_user_exists_by_username(db, &data.username)
|
||||
.await
|
||||
.is_ok()
|
||||
.unwrap()
|
||||
{
|
||||
return Err(ErrorCode::AlreadyExists).into();
|
||||
}
|
||||
|
||||
user.username = data.into_inner().username;
|
||||
user.save(&app_state).await.unwrap();
|
||||
let mut active_user = user.into_active_model();
|
||||
active_user.username = Set(data.into_inner().username);
|
||||
active_user.update(db).await.unwrap();
|
||||
|
||||
Ok(()).into()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::database::models::User;
|
||||
use crate::extractors::base::AsyncExtractor;
|
||||
use crate::routes::schema::user::UserResponse;
|
||||
use actix_web::get;
|
||||
use database::entity::User;
|
||||
|
||||
#[utoipa::path(responses((status = OK, body = UserResponse)))]
|
||||
#[get("/me")]
|
||||
|
||||
@@ -2,25 +2,24 @@ mod env;
|
||||
|
||||
pub use crate::state::env::AppEnv;
|
||||
use actix_web::web;
|
||||
use diesel::{Connection, PgConnection};
|
||||
use database::sea_orm::{Database, DatabaseConnection};
|
||||
use providers::base::{ScheduleProvider, ScheduleSnapshot};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
/// Common data provided to endpoints.
|
||||
pub struct AppState {
|
||||
cancel_token: CancellationToken,
|
||||
database: Mutex<PgConnection>,
|
||||
database: DatabaseConnection,
|
||||
providers: HashMap<String, Arc<dyn ScheduleProvider>>,
|
||||
env: AppEnv,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
|
||||
pub async fn new(
|
||||
database: Option<DatabaseConnection>,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let env = AppEnv::default();
|
||||
let providers: HashMap<String, Arc<dyn ScheduleProvider>> = HashMap::from([(
|
||||
"eng_polytechnic".to_string(),
|
||||
@@ -52,10 +51,14 @@ impl AppState {
|
||||
|
||||
let this = Self {
|
||||
cancel_token: CancellationToken::new(),
|
||||
database: Mutex::new(
|
||||
PgConnection::establish(&database_url)
|
||||
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url)),
|
||||
),
|
||||
database: if let Some(database) = database {
|
||||
database
|
||||
} else {
|
||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
Database::connect(&database_url)
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||
},
|
||||
env,
|
||||
providers,
|
||||
};
|
||||
@@ -80,8 +83,8 @@ impl AppState {
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn get_database(&'_ self) -> MutexGuard<'_, PgConnection> {
|
||||
self.database.lock().await
|
||||
pub fn get_database(&'_ self) -> &DatabaseConnection {
|
||||
&self.database
|
||||
}
|
||||
|
||||
pub fn get_env(&self) -> &AppEnv {
|
||||
@@ -90,6 +93,6 @@ impl AppState {
|
||||
}
|
||||
|
||||
/// Create a new object web::Data<AppState>.
|
||||
pub async fn new_app_state() -> Result<web::Data<AppState>, Box<dyn std::error::Error>> {
|
||||
Ok(web::Data::new(AppState::new().await?))
|
||||
pub async fn new_app_state(database: Option<DatabaseConnection>) -> Result<web::Data<AppState>, Box<dyn std::error::Error>> {
|
||||
Ok(web::Data::new(AppState::new(database).await?))
|
||||
}
|
||||
|
||||
@@ -7,20 +7,31 @@ pub(crate) mod tests {
|
||||
|
||||
pub fn test_env() {
|
||||
info!("Loading test environment file...");
|
||||
|
||||
dotenvy::from_filename(".env.test.local")
|
||||
.or_else(|_| dotenvy::from_filename(".env.test"))
|
||||
.expect("Failed to load test environment file");
|
||||
}
|
||||
|
||||
pub async fn test_app_state() -> web::Data<AppState> {
|
||||
let state = new_app_state().await.unwrap();
|
||||
let state = new_app_state(Some(static_app_state().await.get_database().clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
state.clone()
|
||||
}
|
||||
|
||||
pub async fn static_app_state() -> web::Data<AppState> {
|
||||
static STATE: OnceCell<web::Data<AppState>> = OnceCell::const_new();
|
||||
|
||||
STATE.get_or_init(|| test_app_state()).await.clone()
|
||||
|
||||
STATE
|
||||
.get_or_init(async || -> web::Data<AppState> {
|
||||
#[cfg(all(test, tokio_unstable))]
|
||||
console_subscriber::init();
|
||||
|
||||
new_app_state(None).await.unwrap()
|
||||
})
|
||||
.await
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user