-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
40 changed files
with
983 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"recommendations": [ | ||
"rust-lang.rust-analyzer", | ||
"fill-labs.dependi", | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,5 +76,6 @@ | |
}, | ||
"accessibility.signals.positionHasError": { | ||
"sound": "off" | ||
} | ||
}, | ||
"editor.tabSize": 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Examples | ||
|
||
## 服务依赖 | ||
|
||
使用 docker compose 启动并初始化 PG 数据库 | ||
|
||
```sh | ||
# 当前目录下有容器,先删除容器及数据卷(一般在 sql 有变更时需要) | ||
#docker compose down --volumes --remove-orphans | ||
|
||
docker compose up -d --build | ||
``` | ||
|
||
## api-example | ||
|
||
### 启动服务 | ||
|
||
```sh | ||
cargo run --bin api-example | ||
``` | ||
|
||
### 测试服务 | ||
|
||
#### 使用密码登录 | ||
|
||
```sh | ||
curl -v --location 'http://localhost:8888/auth/login/pwd' \ | ||
--header 'Content-Type: application/json' \ | ||
--data-raw '{ | ||
"email": "[email protected]", | ||
"pwd": "2024.Ultimate" | ||
}' | python -m json.tool | ||
``` | ||
|
||
登录成功返回 token | ||
|
||
```sh | ||
{ | ||
"token": "eyJ0eXAiOiJKV1QiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..EZwETCBq1CNs8yO5Zec09Q.g3JoMryHoq01ZO3TQ2Ja_ppJZb9SYdon-LfB6OGyH7s.sBCGn14NuoxujmAgRpkYPg", | ||
"token_type": "Bearer" | ||
} | ||
``` | ||
|
||
#### 用户-分页查询 | ||
|
||
```sh | ||
curl -v --location 'http://localhost:8888/v1/user/page' \ | ||
--header 'Content-Type: application/json' \ | ||
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..EZwETCBq1CNs8yO5Zec09Q.g3JoMryHoq01ZO3TQ2Ja_ppJZb9SYdon-LfB6OGyH7s.sBCGn14NuoxujmAgRpkYPg' \ | ||
--data '{}' | python -m json.tool --no-ensure-ascii | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
[package] | ||
name = "api-example" | ||
version.workspace = true | ||
edition.workspace = true | ||
rust-version.workspace = true | ||
description.workspace = true | ||
license-file.workspace = true | ||
repository.workspace = true | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[dependencies] | ||
ultimate-common = { workspace = true } | ||
ultimate = { workspace = true, features = ["ulid"] } | ||
ultimate-web = { workspace = true } | ||
ultimate-db = { workspace = true } | ||
thiserror.workspace = true | ||
tokio.workspace = true | ||
tower-http.workspace = true | ||
axum.workspace = true | ||
typed-builder.workspace = true | ||
derive-getters.workspace = true | ||
derive_more.workspace = true | ||
serde.workspace = true | ||
serde_json.workspace = true | ||
serde_repr.workspace = true | ||
sqlx.workspace = true | ||
sea-query.workspace = true | ||
sea-query-binder.workspace = true | ||
modql.workspace = true | ||
enum-iterator.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# 使用 Rust 开发高效能的 API 服务 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[ultimate.app] | ||
run_mode = "DEV" | ||
name = "app-example" | ||
|
||
[ultimate.security.pwd] | ||
expires_in = 604800 | ||
default_pwd = "2024.Ultimate" | ||
|
||
[ultimate.web] | ||
enable = true | ||
server_addr = "0.0.0.0:8888" | ||
|
||
[ultimate.db] | ||
enable = true | ||
host = "localhost" | ||
port = 15432 | ||
database = "ultimate" | ||
username = "ultimate" | ||
password = "2024.Ultimate" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use axum::{ | ||
async_trait, | ||
extract::FromRequestParts, | ||
http::{request::Parts, StatusCode}, | ||
}; | ||
use derive_more::derive::Constructor; | ||
use ultimate::{security::pwd::verify_pwd, Result}; | ||
use ultimate_web::AppError; | ||
|
||
use crate::{ | ||
state::AppState, | ||
user::{UserFilter, UserServ}, | ||
util::make_token, | ||
}; | ||
|
||
use super::{LoginByPwdReq, LoginResp, TokenType}; | ||
|
||
#[derive(Constructor)] | ||
pub struct AuthServ { | ||
app: AppState, | ||
} | ||
|
||
impl AuthServ { | ||
pub async fn login_by_pwd(&self, req: LoginByPwdReq) -> Result<LoginResp> { | ||
let user_serv = UserServ::new(self.app.clone(), self.app.create_super_admin_ctx()); | ||
|
||
let (u, uc) = user_serv.get_fetch_credential(UserFilter::from(&req)).await?; | ||
verify_pwd(&req.pwd, &uc.encrypted_pwd).await?; | ||
|
||
let token = make_token(self.app.ultimate_config().security(), u.id)?; | ||
Ok(LoginResp { token, token_type: TokenType::Bearer }) | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl FromRequestParts<AppState> for AuthServ { | ||
type Rejection = (StatusCode, AppError); | ||
|
||
async fn from_request_parts(_parts: &mut Parts, state: &AppState) -> core::result::Result<Self, Self::Rejection> { | ||
Ok(AuthServ::new(state.clone())) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
mod auth_serv; | ||
mod model; | ||
mod web; | ||
|
||
use auth_serv::AuthServ; | ||
use model::*; | ||
pub use web::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use modql::filter::{FilterNodes, OpValString, OpValsString}; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::user::UserFilter; | ||
|
||
#[derive(FilterNodes)] | ||
pub struct LoginFilter { | ||
pub email: Option<OpValsString>, | ||
pub phone: Option<OpValsString>, | ||
} | ||
|
||
impl From<&LoginByPwdReq> for LoginFilter { | ||
fn from(req: &LoginByPwdReq) -> Self { | ||
Self { | ||
email: req.email.as_deref().map(|s| OpValString::Eq(s.to_string()).into()), | ||
phone: req.phone.as_deref().map(|s| OpValString::Eq(s.to_string()).into()), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Deserialize)] | ||
pub struct LoginByPwdReq { | ||
pub email: Option<String>, | ||
pub phone: Option<String>, | ||
#[serde(skip_serializing)] | ||
pub pwd: String, | ||
} | ||
|
||
impl From<&LoginByPwdReq> for UserFilter { | ||
fn from(value: &LoginByPwdReq) -> Self { | ||
UserFilter { | ||
email: value.email.as_deref().map(|s| OpValString::Eq(s.to_string()).into()), | ||
phone: value.phone.as_deref().map(|s| OpValString::Eq(s.to_string()).into()), | ||
..Default::default() | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct LoginResp { | ||
pub token: String, | ||
pub token_type: TokenType, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub enum TokenType { | ||
Bearer, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
use axum::{routing::post, Json, Router}; | ||
use ultimate_web::{ok, AppResult}; | ||
|
||
use crate::state::AppState; | ||
|
||
use super::{AuthServ, LoginByPwdReq, LoginResp}; | ||
|
||
pub fn auth_routes() -> Router<AppState> { | ||
Router::new().route("/login/pwd", post(login_pwd)) | ||
} | ||
|
||
async fn login_pwd(auth_serv: AuthServ, Json(req): Json<LoginByPwdReq>) -> AppResult<LoginResp> { | ||
let resp = auth_serv.login_by_pwd(req).await?; | ||
ok(resp) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
use std::sync::Arc; | ||
|
||
use axum::{ | ||
async_trait, | ||
extract::FromRequestParts, | ||
http::{request::Parts, HeaderMap, StatusCode}, | ||
Json, | ||
}; | ||
use derive_getters::Getters; | ||
use ultimate::ctx::Ctx; | ||
use ultimate_db::ModelManager; | ||
use ultimate_web::{extract_session, AppError}; | ||
|
||
use crate::state::AppState; | ||
|
||
static X_APP_VERSION: &str = "X-APP-VARSION"; | ||
static X_DEVICE_ID: &str = "X-DEVICE-ID"; | ||
|
||
#[derive(Clone, Getters)] | ||
pub struct CtxW { | ||
ctx: Ctx, | ||
mm: ModelManager, | ||
req_meta: Arc<RequestMetadata>, | ||
} | ||
impl CtxW { | ||
pub fn new(state: &AppState, ctx: Ctx, req_meta: Arc<RequestMetadata>) -> Self { | ||
let mm = state.mm().clone().with_ctx(ctx.clone()); | ||
Self { ctx, mm, req_meta } | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl FromRequestParts<AppState> for CtxW { | ||
type Rejection = (StatusCode, Json<AppError>); | ||
|
||
async fn from_request_parts(parts: &mut Parts, state: &AppState) -> core::result::Result<Self, Self::Rejection> { | ||
match extract_session(parts, state.ultimate_config().security()) { | ||
Ok(ctx) => Ok(CtxW::new(state, ctx, Arc::new(RequestMetadata::from(&parts.headers)))), | ||
Err(e) => Err((StatusCode::UNAUTHORIZED, Json(e.into()))), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone, Default)] | ||
pub struct RequestMetadata { | ||
app_ver: String, | ||
dev_id: String, | ||
} | ||
|
||
impl RequestMetadata { | ||
pub fn app_ver(&self) -> &str { | ||
self.app_ver.as_str() | ||
} | ||
|
||
pub fn dev_id(&self) -> &str { | ||
self.dev_id.as_str() | ||
} | ||
} | ||
|
||
impl From<&HeaderMap> for RequestMetadata { | ||
fn from(headers: &HeaderMap) -> Self { | ||
let app_ver = headers.get(X_APP_VERSION).map(|v| v.to_str().unwrap_or("").to_string()).unwrap_or_default(); | ||
let dev_id = headers.get(X_DEVICE_ID).map(|v| v.to_str().unwrap_or("").to_string()).unwrap_or_default(); | ||
Self { app_ver, dev_id } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
mod auth; | ||
pub mod ctx; | ||
pub mod router; | ||
pub mod state; | ||
mod user; | ||
mod util; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
use api_example::{router::new_api_router, state::new_app_state}; | ||
use ultimate_web::server::init_server; | ||
|
||
#[tokio::main] | ||
async fn main() -> ultimate::Result<()> { | ||
let state = new_app_state().await?; | ||
let conf = state.ultimate_config(); | ||
let router = new_api_router(state.clone()); | ||
|
||
init_server(conf, router).await?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
use axum::Router; | ||
|
||
use crate::{auth::auth_routes, state::AppState, user::user_routes}; | ||
|
||
pub fn new_api_router(app_state: AppState) -> Router { | ||
Router::new().nest("/v1/user", user_routes()).nest("/auth", auth_routes()).with_state(app_state) | ||
} |
Oops, something went wrong.