Allow anonymous jump-hop recommend play
This commit is contained in:
@@ -59,17 +59,44 @@ pub async fn require_bearer_auth(
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AppError> {
|
||||
let Some(authenticated) = authenticate_request(&state, &request)? else {
|
||||
return Err(AppError::from_status(StatusCode::UNAUTHORIZED));
|
||||
};
|
||||
request.extensions_mut().insert(authenticated.clone());
|
||||
|
||||
let mut response = next.run(request).await;
|
||||
response.extensions_mut().insert(authenticated);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn attach_optional_bearer_auth(
|
||||
State(state): State<AppState>,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AppError> {
|
||||
if let Some(authenticated) = authenticate_request(&state, &request)? {
|
||||
request.extensions_mut().insert(authenticated.clone());
|
||||
let mut response = next.run(request).await;
|
||||
response.extensions_mut().insert(authenticated);
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
Ok(next.run(request).await)
|
||||
}
|
||||
|
||||
fn authenticate_request(
|
||||
state: &AppState,
|
||||
request: &Request,
|
||||
) -> Result<Option<AuthenticatedAccessToken>, AppError> {
|
||||
if allows_internal_forwarded_auth(request.uri().path())
|
||||
&& let Some(claims) = try_build_internal_forwarded_claims(&state, request.headers())
|
||||
{
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(AuthenticatedAccessToken::new(claims.clone()));
|
||||
let mut response = next.run(request).await;
|
||||
response
|
||||
.extensions_mut()
|
||||
.insert(AuthenticatedAccessToken::new(claims));
|
||||
return Ok(response);
|
||||
return Ok(Some(AuthenticatedAccessToken::new(claims)));
|
||||
}
|
||||
|
||||
if !request.headers().contains_key(AUTHORIZATION) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let bearer_token = extract_bearer_token(request.headers())?;
|
||||
@@ -145,16 +172,7 @@ pub async fn require_bearer_auth(
|
||||
.with_message("当前登录态已失效,请重新登录"));
|
||||
}
|
||||
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(AuthenticatedAccessToken::new(claims.clone()));
|
||||
|
||||
let mut response = next.run(request).await;
|
||||
response
|
||||
.extensions_mut()
|
||||
.insert(AuthenticatedAccessToken::new(claims));
|
||||
|
||||
Ok(response)
|
||||
Ok(Some(AuthenticatedAccessToken::new(claims)))
|
||||
}
|
||||
|
||||
pub async fn inspect_auth_claims(
|
||||
|
||||
@@ -17,8 +17,12 @@ use spacetime_client::SpacetimeClientError;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::{
|
||||
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
|
||||
request_context::RequestContext, state::AppState,
|
||||
api_response::json_success_body,
|
||||
auth::AuthenticatedAccessToken,
|
||||
http_error::AppError,
|
||||
request_context::RequestContext,
|
||||
state::AppState,
|
||||
work_play_tracking::{record_work_play_start_after_success, WorkPlayTrackingDraft},
|
||||
};
|
||||
|
||||
const JUMP_HOP_PROVIDER: &str = "jump-hop";
|
||||
@@ -26,6 +30,8 @@ const JUMP_HOP_CREATION_PROVIDER: &str = "jump-hop-creation";
|
||||
const JUMP_HOP_RUNTIME_PROVIDER: &str = "jump-hop-runtime";
|
||||
const JUMP_HOP_TEMPLATE_ID: &str = "jump-hop";
|
||||
const JUMP_HOP_TEMPLATE_NAME: &str = "跳一跳";
|
||||
const JUMP_HOP_ANONYMOUS_RUNTIME_OWNER_ID: &str = "anonymous-runtime";
|
||||
const JUMP_HOP_RUNTIME_RUNS_ROUTE: &str = "/api/runtime/jump-hop/runs";
|
||||
|
||||
pub async fn create_jump_hop_session(
|
||||
State(state): State<AppState>,
|
||||
@@ -170,14 +176,18 @@ pub async fn get_jump_hop_runtime_work(
|
||||
pub async fn start_jump_hop_run(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
maybe_authenticated: Option<Extension<AuthenticatedAccessToken>>,
|
||||
payload: Result<Json<JumpHopStartRunRequest>, JsonRejection>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let Json(payload) = jump_hop_json(payload, &request_context, JUMP_HOP_RUNTIME_PROVIDER)?;
|
||||
ensure_non_empty(&request_context, &payload.profile_id, "profileId")?;
|
||||
let authenticated = maybe_authenticated.as_ref().map(|Extension(authenticated)| authenticated);
|
||||
let owner_user_id = authenticated
|
||||
.map(|authenticated| authenticated.claims().user_id().to_string())
|
||||
.unwrap_or_else(|| JUMP_HOP_ANONYMOUS_RUNTIME_OWNER_ID.to_string());
|
||||
let run = state
|
||||
.spacetime_client()
|
||||
.start_jump_hop_run(payload, authenticated.claims().user_id().to_string())
|
||||
.start_jump_hop_run(payload, owner_user_id.clone())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
jump_hop_error_response(
|
||||
@@ -187,6 +197,24 @@ pub async fn start_jump_hop_run(
|
||||
)
|
||||
})?;
|
||||
|
||||
record_work_play_start_after_success(
|
||||
&state,
|
||||
&request_context,
|
||||
build_jump_hop_work_play_tracking_draft(
|
||||
authenticated,
|
||||
run.profile_id.clone(),
|
||||
JUMP_HOP_RUNTIME_RUNS_ROUTE,
|
||||
)
|
||||
.owner_user_id(run.owner_user_id.clone())
|
||||
.run_id(run.run_id.clone())
|
||||
.profile_id(run.profile_id.clone())
|
||||
.extra(json!({
|
||||
"runStatus": run.status,
|
||||
"isAnonymous": maybe_authenticated.is_none(),
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
JumpHopRunResponse { run },
|
||||
@@ -197,18 +225,18 @@ pub async fn jump_hop_run_jump(
|
||||
State(state): State<AppState>,
|
||||
Path(run_id): Path<String>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
maybe_authenticated: Option<Extension<AuthenticatedAccessToken>>,
|
||||
payload: Result<Json<JumpHopJumpRequest>, JsonRejection>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
ensure_non_empty(&request_context, &run_id, "runId")?;
|
||||
let Json(payload) = jump_hop_json(payload, &request_context, JUMP_HOP_RUNTIME_PROVIDER)?;
|
||||
let owner_user_id = maybe_authenticated
|
||||
.as_ref()
|
||||
.map(|Extension(authenticated)| authenticated.claims().user_id().to_string())
|
||||
.unwrap_or_else(|| JUMP_HOP_ANONYMOUS_RUNTIME_OWNER_ID.to_string());
|
||||
let run = state
|
||||
.spacetime_client()
|
||||
.jump_hop_run_jump(
|
||||
run_id,
|
||||
authenticated.claims().user_id().to_string(),
|
||||
payload,
|
||||
)
|
||||
.jump_hop_run_jump(run_id, owner_user_id, payload)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
jump_hop_error_response(
|
||||
@@ -228,18 +256,18 @@ pub async fn restart_jump_hop_run(
|
||||
State(state): State<AppState>,
|
||||
Path(run_id): Path<String>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
maybe_authenticated: Option<Extension<AuthenticatedAccessToken>>,
|
||||
payload: Result<Json<JumpHopRestartRunRequest>, JsonRejection>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
ensure_non_empty(&request_context, &run_id, "runId")?;
|
||||
let Json(payload) = jump_hop_json(payload, &request_context, JUMP_HOP_RUNTIME_PROVIDER)?;
|
||||
let owner_user_id = maybe_authenticated
|
||||
.as_ref()
|
||||
.map(|Extension(authenticated)| authenticated.claims().user_id().to_string())
|
||||
.unwrap_or_else(|| JUMP_HOP_ANONYMOUS_RUNTIME_OWNER_ID.to_string());
|
||||
let run = state
|
||||
.spacetime_client()
|
||||
.restart_jump_hop_run(
|
||||
run_id,
|
||||
authenticated.claims().user_id().to_string(),
|
||||
payload,
|
||||
)
|
||||
.restart_jump_hop_run(run_id, owner_user_id, payload)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
jump_hop_error_response(
|
||||
@@ -298,6 +326,19 @@ pub async fn get_jump_hop_gallery_detail(
|
||||
))
|
||||
}
|
||||
|
||||
fn build_jump_hop_work_play_tracking_draft(
|
||||
authenticated: Option<&AuthenticatedAccessToken>,
|
||||
work_id: impl Into<String>,
|
||||
source_route: &'static str,
|
||||
) -> WorkPlayTrackingDraft {
|
||||
match authenticated {
|
||||
Some(authenticated) => {
|
||||
WorkPlayTrackingDraft::new("jump-hop", work_id, authenticated, source_route)
|
||||
}
|
||||
None => WorkPlayTrackingDraft::anonymous("jump-hop", work_id, source_route),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_jump_hop_draft(payload: &JumpHopWorkspaceCreateRequest) -> JumpHopDraftResponse {
|
||||
JumpHopDraftResponse {
|
||||
template_id: JUMP_HOP_TEMPLATE_ID.to_string(),
|
||||
|
||||
@@ -4,7 +4,7 @@ use axum::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
auth::require_bearer_auth,
|
||||
auth::{attach_optional_bearer_auth, require_bearer_auth},
|
||||
jump_hop::{
|
||||
create_jump_hop_session, execute_jump_hop_action, get_jump_hop_gallery_detail,
|
||||
get_jump_hop_runtime_work, get_jump_hop_session, jump_hop_run_jump, list_jump_hop_gallery,
|
||||
@@ -51,21 +51,21 @@ pub fn router(state: AppState) -> Router<AppState> {
|
||||
"/api/runtime/jump-hop/runs",
|
||||
post(start_jump_hop_run).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
attach_optional_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/jump-hop/runs/{run_id}/jump",
|
||||
post(jump_hop_run_jump).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
attach_optional_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/jump-hop/runs/{run_id}/restart",
|
||||
post(restart_jump_hop_run).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
attach_optional_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route("/api/runtime/jump-hop/gallery", get(list_jump_hop_gallery))
|
||||
|
||||
@@ -13,7 +13,7 @@ pub(crate) const WORK_PLAY_START_EVENT_KEY: &str = "work_play_start";
|
||||
pub(crate) struct WorkPlayTrackingDraft {
|
||||
pub play_type: &'static str,
|
||||
pub work_id: String,
|
||||
pub user_id: String,
|
||||
pub user_id: Option<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub run_id: Option<String>,
|
||||
@@ -28,7 +28,28 @@ impl WorkPlayTrackingDraft {
|
||||
authenticated: &AuthenticatedAccessToken,
|
||||
source_route: &'static str,
|
||||
) -> Self {
|
||||
let user_id = authenticated.claims().user_id().to_string();
|
||||
Self::with_user_id(
|
||||
play_type,
|
||||
work_id,
|
||||
Some(authenticated.claims().user_id().to_string()),
|
||||
source_route,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn anonymous(
|
||||
play_type: &'static str,
|
||||
work_id: impl Into<String>,
|
||||
source_route: &'static str,
|
||||
) -> Self {
|
||||
Self::with_user_id(play_type, work_id, None, source_route)
|
||||
}
|
||||
|
||||
fn with_user_id(
|
||||
play_type: &'static str,
|
||||
work_id: impl Into<String>,
|
||||
user_id: Option<String>,
|
||||
source_route: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
play_type,
|
||||
work_id: work_id.into(),
|
||||
@@ -91,7 +112,11 @@ async fn record_work_play_start_input_after_success(
|
||||
"workId": draft.work_id,
|
||||
"sourceRoute": draft.source_route,
|
||||
});
|
||||
metadata["userId"] = json!(draft.user_id);
|
||||
if let Some(user_id) = draft.user_id.as_deref() {
|
||||
metadata["userId"] = json!(user_id);
|
||||
} else {
|
||||
metadata["userKind"] = json!("anonymous");
|
||||
}
|
||||
if let Some(owner_user_id) = draft.owner_user_id.as_deref() {
|
||||
metadata["ownerUserId"] = json!(owner_user_id);
|
||||
}
|
||||
@@ -108,7 +133,7 @@ async fn record_work_play_start_input_after_success(
|
||||
let mut tracking = TrackingEventDraft::new(WORK_PLAY_START_EVENT_KEY, draft.play_type);
|
||||
tracking.scope_kind = RuntimeTrackingScopeKind::Work;
|
||||
tracking.scope_id = draft.work_id;
|
||||
tracking.user_id = Some(draft.user_id);
|
||||
tracking.user_id = draft.user_id;
|
||||
tracking.owner_user_id = draft.owner_user_id;
|
||||
tracking.profile_id = draft.profile_id;
|
||||
tracking.metadata = metadata;
|
||||
|
||||
Reference in New Issue
Block a user