Files
modrinth/apps/labrinth/src/routes/v3/analytics_event.rs
T
François-Xavier Talbotandaecsocket 4a6fa9fc3d feat: better api docs (#6586)
* feat: docs

* fix tombi

* chore: fix some of the routes rendering with missing /

* response schemas

* fix: restore labrinth docs routes

* Fix path parameter docs in routes

* remove utoipa-actix-web

* consistency

* improve version intros

* improve formatting, examples

* better hash examples, move openapi stuff to openapi.rs

* more utoipa param fixes

* request body docs

* chore: remove moderation route from v2, remove ingest & webhooks from v3 spec

* fixes

* chore: tweak sources titles

* fix

* fix test

* improve examples

* increase compiler spawned thread stack size

* remove unused tests & script

* test

* bro what

* fix

---------

Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
2026-07-04 14:19:21 +00:00

214 lines
5.5 KiB
Rust

use actix_web::{HttpRequest, delete, get, patch, post, web};
use chrono::{DateTime, Utc};
use eyre::eyre;
use serde::{Deserialize, Serialize};
use crate::{
auth::get_user_from_headers,
database::{
PgPool,
models::{
DBAnalyticsEvent, DBAnalyticsEventId, generate_analytics_event_id,
},
redis::RedisPool,
},
models::{
ids::AnalyticsEventId,
pats::Scopes,
v3::analytics_event::{AnalyticsEvent, AnalyticsEventMeta},
},
queue::session::AuthQueue,
routes::ApiError,
util::error::Context,
};
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(analytics_events_get)
.service(analytics_event_create)
.service(analytics_event_edit)
.service(analytics_event_delete);
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct AnalyticsEventUpsert {
#[serde(flatten)]
pub meta: AnalyticsEventMeta,
pub starts: DateTime<Utc>,
pub ends: DateTime<Utc>,
}
/// List analytics events.
#[utoipa::path(
context_path = "/v3/analytics-event",
tag = "v3 analytics", responses((status = OK, body = Vec<AnalyticsEvent>))
)]
#[get("")]
pub async fn analytics_events_get(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<web::Json<Vec<AnalyticsEvent>>, ApiError> {
let events = DBAnalyticsEvent::get_all(&**pool, &redis)
.await
.wrap_internal_err("failed to fetch analytics events")?
.into_iter()
.map(AnalyticsEvent::from)
.collect();
Ok(web::Json(events))
}
/// Create an analytics event.
#[utoipa::path(
context_path = "/v3/analytics-event",
tag = "v3 analytics", responses((status = OK, body = AnalyticsEvent))
)]
#[post("")]
pub async fn analytics_event_create(
req: HttpRequest,
event: web::Json<AnalyticsEventUpsert>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<web::Json<AnalyticsEvent>, ApiError> {
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::empty(),
)
.await?
.1;
if !user.role.is_admin() {
return Err(ApiError::Auth(eyre!(
"you do not have permission to manage analytics events"
)));
}
let mut transaction = pool
.begin()
.await
.wrap_internal_err("failed to begin transaction")?;
let id = generate_analytics_event_id(&mut transaction)
.await
.wrap_internal_err("failed to generate analytics event ID")?;
let event = DBAnalyticsEvent {
id,
meta: event.meta.clone(),
starts: event.starts,
ends: event.ends,
};
event
.insert(&mut transaction)
.await
.wrap_internal_err("failed to insert analytics event")?;
transaction
.commit()
.await
.wrap_internal_err("failed to commit transaction")?;
DBAnalyticsEvent::clear_cache(&redis)
.await
.wrap_internal_err("failed to clear analytics event cache")?;
Ok(web::Json(event.into()))
}
/// Update an analytics event.
#[utoipa::path(
context_path = "/v3/analytics-event",
tag = "v3 analytics", responses((status = OK, body = AnalyticsEvent))
)]
#[patch("/{id}")]
pub async fn analytics_event_edit(
req: HttpRequest,
id: web::Path<(AnalyticsEventId,)>,
event: web::Json<AnalyticsEventUpsert>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<web::Json<AnalyticsEvent>, ApiError> {
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::empty(),
)
.await?
.1;
if !user.role.is_admin() {
return Err(ApiError::Auth(eyre!(
"you do not have permission to manage analytics events"
)));
}
let event = DBAnalyticsEvent {
id: DBAnalyticsEventId::from(id.into_inner().0),
meta: event.meta.clone(),
starts: event.starts,
ends: event.ends,
};
let updated = event
.update(&**pool)
.await
.wrap_internal_err("failed to update analytics event")?;
if !updated {
return Err(ApiError::NotFound);
}
DBAnalyticsEvent::clear_cache(&redis)
.await
.wrap_internal_err("failed to clear analytics event cache")?;
Ok(web::Json(event.into()))
}
/// Delete an analytics event.
#[utoipa::path(
context_path = "/v3/analytics-event",
tag = "v3 analytics", responses((status = NO_CONTENT))
)]
#[delete("/{id}")]
pub async fn analytics_event_delete(
req: HttpRequest,
id: web::Path<(AnalyticsEventId,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<(), ApiError> {
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::empty(),
)
.await?
.1;
if !user.role.is_admin() {
return Err(ApiError::Auth(eyre!(
"you do not have permission to manage analytics events"
)));
}
let deleted = DBAnalyticsEvent::remove(
DBAnalyticsEventId::from(id.into_inner().0),
&**pool,
)
.await
.wrap_internal_err("failed to delete analytics event")?;
if !deleted {
return Err(ApiError::NotFound);
}
DBAnalyticsEvent::clear_cache(&redis)
.await
.wrap_internal_err("failed to clear analytics event cache")?;
Ok(())
}