feat(database)!: switch from diesel to sea-orm

This commit is contained in:
2025-09-06 20:08:46 +04:00
parent e729d84c93
commit dbc800fef1
54 changed files with 2519 additions and 665 deletions

View File

@@ -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]

View File

@@ -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",

View File

@@ -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 {
}
}
}
}
}

View File

@@ -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()
}

View File

@@ -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),

View File

@@ -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,
}
}
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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")]