codoseo_web/routes/
rankorg.rs1use axum::Router;
6use axum::extract::{OriginalUri, Query, State};
7use axum::response::{IntoResponse, Redirect, Response};
8use axum::routing::get;
9use codoseo_store::events::{self, EventKind};
10use serde::Deserialize;
11use serde_json::json;
12use uuid::Uuid;
13
14use super::quick::require_cloud;
15use crate::auth::{CurrentUser, urlencode};
16use crate::error::AppError;
17use crate::rankorg;
18use crate::state::AppState;
19
20const TOP_PAGES: i64 = 10;
22
23pub fn routes() -> Router<AppState> {
24 Router::new().route("/go/rankorg", get(go))
25}
26
27#[derive(Deserialize)]
28pub struct GoQuery {
29 src: Option<String>,
30 audit: Option<String>,
31 site: Option<String>,
32}
33
34fn parse_id(raw: Option<&str>) -> Result<Uuid, AppError> {
35 raw.and_then(|r| Uuid::parse_str(r).ok())
36 .ok_or(AppError::NotFound)
37}
38
39async fn go(
40 State(state): State<AppState>,
41 user: Option<CurrentUser>,
42 OriginalUri(uri): OriginalUri,
43 Query(q): Query<GoQuery>,
44) -> Result<Response, AppError> {
45 require_cloud(&state)?;
46 let pool = &state.pool;
47 let (domain, pages, medium, account_id, site_id, payload) = match q.src.as_deref() {
48 Some("audit") => {
50 let id = parse_id(q.audit.as_deref())?;
51 let audit = codoseo_store::quick::get(pool, id)
52 .await?
53 .ok_or(AppError::NotFound)?;
54 let pages = codoseo_store::reports::top_pages_by_inlinks(pool, id, TOP_PAGES).await?;
55 (
56 audit.domain,
57 pages,
58 "audit_preview",
59 None,
60 Some(audit.site_id),
61 json!({ "src": "audit", "crawl_id": id }),
62 )
63 }
64 Some("explorer") => {
66 let id = parse_id(q.site.as_deref())?;
67 let Some(user) = user else {
68 let back = uri.path_and_query().map_or("/", |p| p.as_str());
69 return Ok(
70 Redirect::to(&format!("/login?next={}", urlencode(back))).into_response()
71 );
72 };
73 let site = codoseo_store::sites::get_for_account(pool, user.id(), id)
74 .await?
75 .ok_or(AppError::NotFound)?;
76 let pages = match codoseo_store::crawls::latest_done(pool, site.id).await? {
77 Some(crawl) => {
78 codoseo_store::reports::top_pages_by_inlinks(pool, crawl.id, TOP_PAGES).await?
79 }
80 None => Vec::new(),
81 };
82 (
83 site.domain,
84 pages,
85 "explorer",
86 Some(user.id()),
87 Some(site.id),
88 json!({ "src": "explorer" }),
89 )
90 }
91 _ => return Err(AppError::NotFound),
92 };
93
94 events::record(
95 pool,
96 EventKind::RankorgClick,
97 account_id,
98 site_id,
99 Some(payload),
100 )
101 .await?;
102 let to = rankorg::link(&state.config.rankorg_url, &domain, &pages, medium);
103 Ok(Redirect::to(to.as_str()).into_response())
104}