组织、用户等数据访问接口调整
All checks were successful
build / build-rust (push) Successful in 2m21s

获取组织、用户等接口新增或更新
This commit is contained in:
soul-walker 2024-11-12 09:27:23 +08:00
parent 61de18077e
commit 8791aee96c
7 changed files with 193 additions and 42 deletions

View File

@ -36,11 +36,6 @@ pub trait OrgAccessor {
&self, &self,
parent_id: i32, parent_id: i32,
) -> Result<Vec<OrganizationModel>, DbAccessError>; ) -> Result<Vec<OrganizationModel>, DbAccessError>;
/// 通过id列表获取组织code和name
async fn query_org_name_by_ids(
&self,
ids: Vec<i32>,
) -> Result<Vec<(i32, String)>, DbAccessError>;
/// 更新组织名称 /// 更新组织名称
async fn update_org_name( async fn update_org_name(
&self, &self,
@ -175,9 +170,14 @@ impl OrgAccessor for RtsaDbAccessor {
); );
let row = sqlx::query_as::<_, OrganizationModel>(&sql) let row = sqlx::query_as::<_, OrganizationModel>(&sql)
.bind(id) .bind(id)
.fetch_one(&self.pool) .fetch_optional(&self.pool)
.await?; .await?;
Ok(row) match row {
Some(row) => Ok(row),
None => Err(DbAccessError::RowNotExist(
OrganizationColumn::Table.name().to_string(),
)),
}
} }
async fn query_org_names(&self, ids: &[i32]) -> Result<Vec<(i32, String)>, DbAccessError> { async fn query_org_names(&self, ids: &[i32]) -> Result<Vec<(i32, String)>, DbAccessError> {
@ -204,9 +204,14 @@ impl OrgAccessor for RtsaDbAccessor {
); );
let row = sqlx::query_as::<_, OrganizationModel>(&sql) let row = sqlx::query_as::<_, OrganizationModel>(&sql)
.bind(code) .bind(code)
.fetch_one(&self.pool) .fetch_optional(&self.pool)
.await?; .await?;
Ok(row) match row {
Some(row) => Ok(row),
None => Err(DbAccessError::RowNotExist(
OrganizationColumn::Table.name().to_string(),
)),
}
} }
async fn query_org_top_page( async fn query_org_top_page(
@ -292,23 +297,6 @@ impl OrgAccessor for RtsaDbAccessor {
Ok(rows) Ok(rows)
} }
async fn query_org_name_by_ids(
&self,
ids: Vec<i32>,
) -> Result<Vec<(i32, String)>, DbAccessError> {
let table = OrganizationColumn::Table.name();
let id_column = OrganizationColumn::Id.name();
let name_column = OrganizationColumn::Name.name();
let sql = format!(
"SELECT {id_column}, {name_column} FROM {table} WHERE {id_column} = ANY($1)",
id_column = id_column,
name_column = name_column,
table = table
);
let rows: Vec<(i32, String)> = sqlx::query_as(&sql).bind(ids).fetch_all(&self.pool).await?;
Ok(rows)
}
async fn update_org_name( async fn update_org_name(
&self, &self,
id: i32, id: i32,
@ -642,7 +630,7 @@ mod tests {
} }
#[sqlx::test(migrator = "crate::MIGRATOR")] #[sqlx::test(migrator = "crate::MIGRATOR")]
async fn test_query_org_name_by_ids(pool: PgPool) -> Result<(), DbAccessError> { async fn test_query_org_names(pool: PgPool) -> Result<(), DbAccessError> {
let accessor = RtsaDbAccessor::new(pool); let accessor = RtsaDbAccessor::new(pool);
let user = init_user(&accessor).await?; let user = init_user(&accessor).await?;
@ -661,7 +649,7 @@ mod tests {
} }
// Query organization names by ids // Query organization names by ids
let names = accessor.query_org_name_by_ids(ids).await?; let names = accessor.query_org_names(&ids).await?;
assert_eq!(names.len(), 3); assert_eq!(names.len(), 3);
Ok(()) Ok(())

View File

@ -387,9 +387,14 @@ impl OrgUserAccessor for RtsaDbAccessor {
let org_user = sqlx::query_as(&query_clause) let org_user = sqlx::query_as(&query_clause)
.bind(org_id) .bind(org_id)
.bind(user_id) .bind(user_id)
.fetch_one(&self.pool) .fetch_optional(&self.pool)
.await?; .await?;
Ok(org_user) match org_user {
Some(org_user) => Ok(org_user),
None => Err(DbAccessError::RowNotExist(
OrganizationUserColumn::Table.name().to_string(),
)),
}
} }
async fn query_org_user_by_student_id( async fn query_org_user_by_student_id(
@ -528,7 +533,7 @@ mod tests {
accessor.unbind_org_user(org.id, new_user.id).await?; accessor.unbind_org_user(org.id, new_user.id).await?;
// Verify the user is unbound // Verify the user is unbound
let result = accessor.query_org_user(org.id, new_user.id).await; let result = accessor.query_org_user(org.id, new_user.id).await;
assert!(matches!(result, Err(DbAccessError::SqlxError(_)))); assert!(matches!(result, Err(DbAccessError::RowNotExist(_))));
Ok(()) Ok(())
} }

View File

@ -344,11 +344,16 @@ impl UserAccessor for RtsaDbAccessor {
let table = UserColumn::Table.name(); let table = UserColumn::Table.name();
let id_column = UserColumn::Id.name(); let id_column = UserColumn::Id.name();
let query_clause = format!("SELECT * FROM {table} WHERE {id_column} = {id}",); let query_clause = format!("SELECT * FROM {table} WHERE {id_column} = {id}",);
let user: UserModel = sqlx::query_as(&query_clause) let user = sqlx::query_as(&query_clause)
.bind(id) .bind(id)
.fetch_one(&self.pool) .fetch_optional(&self.pool)
.await?; .await?;
Ok(user) match user {
None => Err(DbAccessError::RowNotExist(
UserColumn::Table.name().to_string(),
)),
Some(user) => Ok(user),
}
} }
async fn query_user_by_username(&self, username: &str) -> Result<UserModel, DbAccessError> { async fn query_user_by_username(&self, username: &str) -> Result<UserModel, DbAccessError> {
@ -361,9 +366,14 @@ impl UserAccessor for RtsaDbAccessor {
); );
let user = sqlx::query_as(&query_clause) let user = sqlx::query_as(&query_clause)
.bind(username) .bind(username)
.fetch_one(&self.pool) .fetch_optional(&self.pool)
.await?; .await?;
Ok(user) match user {
None => Err(DbAccessError::RowNotExist(
UserColumn::Table.name().to_string(),
)),
Some(user) => Ok(user),
}
} }
async fn query_user_by_email(&self, email: &str) -> Result<UserModel, DbAccessError> { async fn query_user_by_email(&self, email: &str) -> Result<UserModel, DbAccessError> {
@ -376,9 +386,14 @@ impl UserAccessor for RtsaDbAccessor {
); );
let user = sqlx::query_as(&query_clause) let user = sqlx::query_as(&query_clause)
.bind(email) .bind(email)
.fetch_one(&self.pool) .fetch_optional(&self.pool)
.await?; .await?;
Ok(user) match user {
None => Err(DbAccessError::RowNotExist(
UserColumn::Table.name().to_string(),
)),
Some(user) => Ok(user),
}
} }
async fn query_user_by_mobile(&self, mobile: &str) -> Result<UserModel, DbAccessError> { async fn query_user_by_mobile(&self, mobile: &str) -> Result<UserModel, DbAccessError> {
@ -391,9 +406,14 @@ impl UserAccessor for RtsaDbAccessor {
); );
let user = sqlx::query_as(&query_clause) let user = sqlx::query_as(&query_clause)
.bind(mobile) .bind(mobile)
.fetch_one(&self.pool) .fetch_optional(&self.pool)
.await?; .await?;
Ok(user) match user {
None => Err(DbAccessError::RowNotExist(
UserColumn::Table.name().to_string(),
)),
Some(user) => Ok(user),
}
} }
async fn update_user_info(&self, id: i32, info: Value) -> Result<UserModel, DbAccessError> { async fn update_user_info(&self, id: i32, info: Value) -> Result<UserModel, DbAccessError> {

View File

@ -28,6 +28,17 @@ impl OrgQuery {
Ok(org.into()) Ok(org.into())
} }
/// 通过组织代码获取组织信息
async fn get_org_by_code(
&self,
ctx: &Context<'_>,
code: String,
) -> async_graphql::Result<OrgDto> {
let dba = ctx.data::<RtsaDbAccessor>()?;
let org = dba.query_org_by_code(&code).await?;
Ok(org.into())
}
/// 获取默认组织信息 /// 获取默认组织信息
async fn get_default_org(&self, ctx: &Context<'_>) -> async_graphql::Result<OrgDto> { async fn get_default_org(&self, ctx: &Context<'_>) -> async_graphql::Result<OrgDto> {
let dba = ctx.data::<RtsaDbAccessor>()?; let dba = ctx.data::<RtsaDbAccessor>()?;

View File

@ -8,6 +8,7 @@ use async_graphql::{
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use rtsa_db::prelude::*; use rtsa_db::prelude::*;
use rtsa_dto::common::Role; use rtsa_dto::common::Role;
use serde_json::json;
use super::{org::OrgId, user::UserId}; use super::{org::OrgId, user::UserId};
@ -33,6 +34,38 @@ impl OrgUserQuery {
.await?; .await?;
Ok(org_user.into()) Ok(org_user.into())
} }
/// 获取登录用户的组织用户信息
#[graphql(guard = "RoleGuard::new(Role::User)")]
async fn login_org_user_info(&self, ctx: &Context<'_>) -> async_graphql::Result<OrgUserDto> {
let claims = ctx.data::<Jwt>()?.decode()?;
let org_user = ctx
.data::<RtsaDbAccessor>()?
.query_org_user(claims.oid, claims.uid)
.await
.or_else(|e| {
match e {
DbAccessError::RowNotExist(_) => {
// 组织用户不存在,构造组织游客角色用户
let org_user = OrganizationUserModel {
id: 0,
organization_id: claims.oid,
user_id: claims.uid,
student_id: None,
roles: json!([Role::OrgGuest]),
info: None,
creator_id: claims.uid,
created_at: chrono::Local::now(),
updater_id: claims.uid,
updated_at: chrono::Local::now(),
};
Ok(org_user)
}
_ => Err(e),
}
})?;
Ok(org_user.into())
}
} }
#[Object] #[Object]

View File

@ -2,7 +2,10 @@ use std::{collections::HashMap, sync::Arc};
use async_graphql::{dataloader::Loader, Context, InputObject, Object, SimpleObject}; use async_graphql::{dataloader::Loader, Context, InputObject, Object, SimpleObject};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use rtsa_db::{model::UserModel, DbAccessError, RtsaDbAccessor, UserAccessor}; use rtsa_db::{
model::{OrganizationModel, OrganizationUserModel, UserModel},
DbAccessError, RtsaDbAccessor, UserAccessor,
};
use serde_json::json; use serde_json::json;
use crate::{ use crate::{
@ -35,8 +38,8 @@ impl UserQuery {
/// 获取用户信息 /// 获取用户信息
#[graphql(guard = "RoleGuard::new(Role::User)")] #[graphql(guard = "RoleGuard::new(Role::User)")]
async fn login_user_info(&self, ctx: &Context<'_>) -> async_graphql::Result<UserDto> { async fn login_user_info(&self, ctx: &Context<'_>) -> async_graphql::Result<UserDto> {
let claims = ctx.data::<Jwt>()?.decode()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?; let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let claims = ctx.data::<Jwt>()?.decode()?;
let user = db_accessor.query_user(claims.uid).await?; let user = db_accessor.query_user(claims.uid).await?;
Ok(user.into()) Ok(user.into())
} }
@ -170,6 +173,46 @@ impl From<UserQueryDto> for rtsa_db::UserPageFilter {
} }
} }
#[derive(Debug, SimpleObject)]
pub struct LoginUserInfoDto {
pub id: i32,
pub username: String,
pub nickname: String,
pub mobile: Option<String>,
pub email: Option<String>,
/// 用户角色
pub roles: Vec<Role>,
/// 组织
pub org: LoginUserOrgInfoDto,
}
#[derive(Debug, SimpleObject)]
pub struct LoginUserOrgInfoDto {
pub org_id: i32,
pub org_code: Option<String>,
pub org_name: String,
pub roles: Vec<Role>,
}
impl From<(UserModel, OrganizationUserModel, OrganizationModel)> for LoginUserInfoDto {
fn from(value: (UserModel, OrganizationUserModel, OrganizationModel)) -> Self {
Self {
id: value.0.id,
username: value.0.username.clone(),
nickname: value.0.nickname.clone(),
mobile: value.0.mobile.clone(),
email: value.0.email.clone(),
roles: serde_json::from_value(value.0.roles).unwrap(),
org: LoginUserOrgInfoDto {
org_id: value.2.id,
org_code: value.2.code,
org_name: value.2.name,
roles: serde_json::from_value(value.1.roles).unwrap(),
},
}
}
}
#[derive(Debug, SimpleObject)] #[derive(Debug, SimpleObject)]
pub struct UserDto { pub struct UserDto {
pub id: i32, pub id: i32,

View File

@ -2,6 +2,7 @@ use async_graphql::Guard;
use rtsa_db::prelude::*; use rtsa_db::prelude::*;
use rtsa_dto::common::Role; use rtsa_dto::common::Role;
use rtsa_log::tracing::{info, warn}; use rtsa_log::tracing::{info, warn};
use serde_json::json;
mod jwt_auth; mod jwt_auth;
use crate::{apis::UserLoginDto, error::BusinessError, sys_init::DEFAULT_ORG_CODE}; use crate::{apis::UserLoginDto, error::BusinessError, sys_init::DEFAULT_ORG_CODE};
@ -120,6 +121,36 @@ pub(crate) async fn handle_login(
} }
} }
/// 查询登录用户、组织等信息
#[allow(dead_code)]
pub(crate) async fn query_login_user_info(
db_accessor: &RtsaDbAccessor,
claims: Claims,
) -> Result<(UserModel, OrganizationUserModel, OrganizationModel), BusinessError> {
let user = db_accessor.query_user(claims.uid).await?;
let org = db_accessor.query_org(claims.oid).await?;
let org_user = db_accessor.query_org_user(claims.oid, claims.uid).await;
match org_user {
Ok(org_user) => Ok((user, org_user, org)),
Err(_) => {
// 组织用户不存在,构造组织游客角色用户
let org_user = OrganizationUserModel {
id: 0,
organization_id: claims.oid,
user_id: claims.uid,
student_id: None,
roles: json!([Role::OrgGuest]),
info: None,
creator_id: 0,
created_at: chrono::Local::now(),
updater_id: 0,
updated_at: chrono::Local::now(),
};
Ok((user, org_user, org))
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::sys_init::{init_default_user_and_org, ADMIN_USER_NAME, ADMIN_USER_PASSWORD}; use crate::sys_init::{init_default_user_and_org, ADMIN_USER_NAME, ADMIN_USER_PASSWORD};
@ -154,4 +185,24 @@ mod tests {
assert_eq!(claims.oid, org.id); assert_eq!(claims.oid, org.id);
Ok(()) Ok(())
} }
#[sqlx::test(migrator = "rtsa_db::MIGRATOR")]
async fn test_query_login_user_info(pool: PgPool) -> anyhow::Result<()> {
Logging::default().with_level(Level::DEBUG).init();
let accessor = RtsaDbAccessor::new(pool);
rtsa_db::set_default_db_accessor(accessor.clone());
init_default_user_and_org().await.unwrap();
let info = UserLoginDto {
username: ADMIN_USER_NAME.to_string(),
password: ADMIN_USER_PASSWORD.to_string(),
org_id: None,
};
let jwt = handle_login(&accessor, info).await.unwrap();
let claims = jwt.decode().unwrap();
let (user, org_user, org) = query_login_user_info(&accessor, claims).await.unwrap();
assert_eq!(user.username, ADMIN_USER_NAME);
assert_eq!(org_user.organization_id, org.id);
Ok(())
}
} }