目前已經完成訂閱的功能,當使用者登記的時候會收到一封驗證信,點入信中的連結便會將訂閱狀態改為啟用。
首先我們就來新增一個handler吧:
pub async fn confirm(
state: State<AppState>,
Path(token): Path<Uuid>,
) -> StatusCode {
todo!();
}
首先我們要從路由中取得token的資料,所以要用到Path
提取器,再來要對資料庫做異動,因次也要取得AppState。
pub async fn confirm(
state: State<AppState>,
Path(token): Path<Uuid>,
) -> Result<StatusCode,StatusCode> {
let subscription = subscription_tokens::Entity::find()
.find_also_related(Subscriptions)
.filter(subscription_tokens::Column::SubscriptionToken.eq(token.to_string()))
.one(&state.database)
.await
//// 處理非同步查詢的錯誤,若沒有錯誤但查不到資料會回傳None
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
//// 處理查不到資料的情況,將None轉換成Result中的Err
.ok_or(StatusCode::NOT_FOUND)?
.1
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
todo!();
}
seaORM中要將關聯的資料取出需要使用find_also_related()
,因為在資料庫有設定了關聯,便可以從token反查回原來的訂閱紀錄。
後面有一連串的調用,可以看到rust中對於錯誤處理強大的支援,在C#中我們往往會忽略掉所有可能出錯或者可能為null的edge case,假定一切都會運行正常。在rust中將錯誤與不存在顯式的以Result
和Option
型別暴露出來,強調在程式面必須處理,寫起來較為繁瑣,但確保了程式的穩定性,事實上為了處裡這些細節,rust也提供了很多的工具來處理Result
與Option
。
目前查出來的資料只是普通的Model,無法用於資料更新,這時候就要介紹到seaORM
中的重要概念ActiveModel,只有將Model轉換ActiveModel才能夠更新,透過這個設計也可以避免Rust中的資料競爭問題,將可變與不可變做出明確的界線,使用上也十分簡單:
pub async fn confirm(
state: State<AppState>,
Path(token): Path<Uuid>,
) -> Result<StatusCode,StatusCode> {
let subscription = subscription_tokens::Entity::find()
.find_also_related(Subscriptions)
.filter(subscription_tokens::Column::SubscriptionToken.eq(token.to_string()))
.one(&state.database)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::NOT_FOUND)?
.1
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?
//// 轉換成active model
.into_active_model();
subscription.status = Set(SubscriptionStatus::Active);
subscription.update(&state.database).await?;
Ok(StatusCode::OK)
}
最後再把handler加入Router中就好了
//// application.rs
pub fn build() -> Router {
let database = get_database(&config.database).await.unwrap();
let email_client = get_email_client(&config.email_client);
let app = Router::new()
.route("/", get(health_check))
.route("/subscriptions", post(subscribe))
//// 加入認證的API路徑
.route("/subscriptions/confirm/:token", get(confirm))
.merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
.with_state(AppState {
database,
email_client
});;
app
}