Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c8b71dc
feat(routes): start of bot reports
ValDesign22 Mar 27, 2026
ecf8876
feat(openapi): add admin field to user response
ValDesign22 Mar 27, 2026
92d2a42
feat(routes): finish `GET /bots/{id}/reports` route
ValDesign22 Mar 27, 2026
1884284
refactor(auth): use an update_many query instead of a loop
ValDesign22 Mar 28, 2026
6517a14
feat(auth): add current field to session response
ValDesign22 Mar 28, 2026
be83f8c
feat(services): remove user from teams when deleting user
ValDesign22 Mar 28, 2026
68136f4
feat(routes): bot reports done
ValDesign22 Mar 28, 2026
0baf08d
fix(clippy): cleaned up the code
ValDesign22 Mar 28, 2026
6b8e152
refactor(models): Frequency now lowercase instead of camel
ValDesign22 Mar 28, 2026
995f6fa
refactor(models): use enum instead of String
ValDesign22 Mar 28, 2026
ac62fce
refactor(routes): cleanup up code and reduce db calls
ValDesign22 Mar 28, 2026
9a25bcf
refactor(routes): return subscription object when subscribing to reports
ValDesign22 Mar 28, 2026
680368d
fix(routes): check if user_id is authenticated user user_id when using
ValDesign22 Mar 28, 2026
e80174e
fix(mails): disable mail sending in debug mode
ValDesign22 Mar 29, 2026
2b36ed6
fix(openapi): missing camelCase
ValDesign22 Mar 29, 2026
0a91c39
feat(reports): add reports task
ValDesign22 Mar 29, 2026
d30e750
fix(reports): data at 0 on previous data range
ValDesign22 Apr 1, 2026
69bde4f
chore(deps): bump s3 from 0.1.22 to 0.1.24
ValDesign22 Apr 1, 2026
d8ebc74
feat(repositories): add max_key to reduce request load time in r2 ping
ValDesign22 Apr 1, 2026
34ab8ca
fix: rustfmt issue
ValDesign22 Apr 1, 2026
91dc3fd
fix(mails): fixed weird title for stats report mail
ValDesign22 Apr 2, 2026
7972e4b
refactor(tasks): use unwrap_or_else instead of match
ValDesign22 Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,267 changes: 1,167 additions & 100 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ actix-web = { version = "4.13.0", default-features = false }
actix-ws = { version = "0.4.0", default-features = false }
anyhow = { version = "1.0.102", default-features = false }
apistos = { version = "0.6.0", default-features = false }
charts-rs = { version = "0.3.28", default-features = false, features = [
"image-encoder",
] }
chrono = { version = "0.4.44", default-features = false, features = [
"clock",
"now",
"serde",
] }
Expand All @@ -41,7 +45,7 @@ jsonwebtoken = { version = "10.3.0", default-features = false, features = [
"hmac",
"sha2",
] }
lettre = { version = "0.11.19", optional = true, default-features = false, features = [
lettre = { version = "0.11.20", optional = true, default-features = false, features = [
"builder",
"hostname",
"smtp-transport",
Expand Down Expand Up @@ -74,7 +78,7 @@ reqwest = { version = "0.13.2", default-features = false, features = [
"rustls",
] }
ring = { version = "0.17.14", default-features = false }
s3 = { version = "0.1.22", optional = true, features = ["providers"] }
s3 = { version = "0.1.24", optional = true, features = ["providers"] }
schemars = { package = "apistos-schemars", version = "0.8.22", default-features = false }
serde = { version = "1.0.228", default-features = false }
serde_json = { version = "1.0.149", default-features = false }
Expand Down
31 changes: 6 additions & 25 deletions src/api/middleware/extractors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use std::ops::Deref;
use actix_web::{
Error, FromRequest, HttpMessage, HttpRequest,
dev::Payload,
error::{ErrorForbidden, ErrorInternalServerError, ErrorUnauthorized},
web::Data,
error::{ErrorForbidden, ErrorUnauthorized},
};
use apistos::{ApiComponent, ApiSecurity};
use futures::{
Expand All @@ -13,9 +12,9 @@ use futures::{
};
use schemars::JsonSchema;

use crate::{
domain::{auth::AuthContext, error::ApiError},
services::Services,
use crate::domain::{
auth::{AuthContext, is_admin},
error::ApiError,
};

#[derive(Clone, JsonSchema, ApiSecurity)]
Expand Down Expand Up @@ -43,17 +42,8 @@ impl FromRequest for Authenticated {
let ctx = context.clone();

if ctx.is_admin() {
let services = match req.app_data::<Data<Services>>() {
Some(services) => services,
None => {
return ready(Err(ErrorInternalServerError(ApiError::InternalError(
"Services not available".to_string(),
))));
}
};

if let Some(user_id) = ctx.user_id.as_deref() {
if !services.auth.is_admin(user_id) {
if !is_admin(user_id) {
return ready(Err(ErrorForbidden(ApiError::Forbidden)));
}
} else {
Expand Down Expand Up @@ -118,17 +108,8 @@ impl FromRequest for RequireAdmin {
None => return ready(Err(ErrorUnauthorized(ApiError::Unauthorized))),
};

let services = match req.app_data::<Data<Services>>() {
Some(services) => services,
None => {
return ready(Err(ErrorInternalServerError(ApiError::InternalError(
"Services not available".to_string(),
))));
}
};

let is_admin = match ctx.user_id.as_deref() {
Some(user_id) => ctx.is_admin() && services.auth.is_admin(user_id),
Some(user_id) => ctx.is_admin() && is_admin(user_id),
None => false,
};

Expand Down
18 changes: 11 additions & 7 deletions src/api/routes/auth/sessions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ async fn list_sessions(
repos: Data<Repositories>,
) -> ApiResult<Json<Vec<SessionResponse>>> {
let user_id = auth.user_id.as_ref().ok_or(ApiError::Unauthorized)?;
let session_id = auth.session_id.as_ref().ok_or(ApiError::Unauthorized)?;

info!(
code = %LogCode::Request,
Expand All @@ -38,7 +39,12 @@ async fn list_sessions(

let session_responses = sessions
.into_iter()
.map(SessionResponse::try_from)
.map(|s| {
SessionResponse::try_from(s).map(|mut r| {
r.current = r.session_id == *session_id;
r
})
})
.collect::<Result<Vec<_>, _>>()?;

info!(
Expand Down Expand Up @@ -69,12 +75,10 @@ async fn revoke_all_sessions(
"Revoking all sessions",
);

let sessions = repos.sessions.find_by_user_id(user_id).await?;
for session in sessions {
if session.session_id != *current_session_id {
repos.sessions.revoke(&session.session_id).await?;
}
}
repos
.sessions
.revoke_many_for_user(user_id, current_session_id)
.await?;

info!(
code = %LogCode::Request,
Expand Down
4 changes: 1 addition & 3 deletions src/api/routes/bots/bot/achievements/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::{
DeleteAchievementQuery, MessageResponse,
},
repository::{AchievementUpdate, Repositories},
services::Services,
utils::{discord::Snowflake, logger::LogCode},
};

Expand All @@ -29,7 +28,6 @@ use crate::{
)]
async fn get_bot_achievements(
auth: Authenticated,
services: Data<Services>,
repos: Data<Repositories>,
id: Path<String>,
) -> ApiResult<Json<Vec<AchievementResponse>>> {
Expand Down Expand Up @@ -67,7 +65,7 @@ async fn get_bot_achievements(
return Err(ApiError::Forbidden);
} else if ctx.is_user() {
let user_id = ctx.user_id.as_deref().ok_or(ApiError::Unauthorized)?;
if !services.auth.user_has_bot_access(user_id, &bot_id).await? {
if !bot.has_access(user_id) {
warn!(
code = %LogCode::Forbidden,
bot_id = %bot_id,
Expand Down
10 changes: 3 additions & 7 deletions src/api/routes/bots/bot/events/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::{
domain::error::{ApiError, ApiResult},
openapi::schemas::{CustomEventResponse, CustomEventUpdatePayload, MessageResponse},
repository::{CustomEventUpdate, Repositories},
services::Services,
utils::{discord::Snowflake, logger::LogCode},
};

Expand All @@ -21,7 +20,6 @@ use crate::{
)]
async fn get_event(
auth: Authenticated,
services: Data<Services>,
repos: Data<Repositories>,
path: Path<(String, String)>,
) -> ApiResult<Json<CustomEventResponse>> {
Expand Down Expand Up @@ -65,7 +63,7 @@ async fn get_event(
return Err(ApiError::Forbidden);
} else if ctx.is_user() {
let user_id = ctx.user_id.as_deref().ok_or(ApiError::Unauthorized)?;
if !services.auth.user_has_bot_access(user_id, &bot_id).await? {
if !bot.has_access(user_id) {
warn!(
code = %LogCode::Forbidden,
user_id = %user_id,
Expand Down Expand Up @@ -112,7 +110,6 @@ async fn get_event(
)]
async fn update_event(
auth: Authenticated,
services: Data<Services>,
repos: Data<Repositories>,
body: Json<CustomEventUpdatePayload>,
path: Path<(String, String)>,
Expand Down Expand Up @@ -157,7 +154,7 @@ async fn update_event(
return Err(ApiError::Forbidden);
} else if ctx.is_user() {
let user_id = ctx.user_id.as_deref().ok_or(ApiError::Unauthorized)?;
if !services.auth.user_has_bot_access(user_id, &bot_id).await? {
if !bot.has_access(user_id) {
warn!(
code = %LogCode::Forbidden,
user_id = %user_id,
Expand Down Expand Up @@ -232,7 +229,6 @@ async fn update_event(
)]
async fn delete_event(
auth: Authenticated,
services: Data<Services>,
repos: Data<Repositories>,
path: Path<(String, String)>,
) -> ApiResult<Json<MessageResponse>> {
Expand Down Expand Up @@ -276,7 +272,7 @@ async fn delete_event(
return Err(ApiError::Forbidden);
} else if ctx.is_user() {
let user_id = ctx.user_id.as_deref().ok_or(ApiError::Unauthorized)?;
if !services.auth.user_has_bot_access(user_id, &bot_id).await? {
if !bot.has_access(user_id) {
warn!(
code = %LogCode::Forbidden,
user_id = %user_id,
Expand Down
7 changes: 2 additions & 5 deletions src/api/routes/bots/bot/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use crate::{
},
openapi::schemas::{CustomEventBody, CustomEventResponse},
repository::Repositories,
services::Services,
utils::{discord::Snowflake, logger::LogCode},
};

Expand All @@ -26,7 +25,6 @@ use crate::{
)]
async fn get_all_events(
auth: Authenticated,
services: Data<Services>,
repos: Data<Repositories>,
id: Path<String>,
) -> ApiResult<Json<Vec<CustomEventResponse>>> {
Expand Down Expand Up @@ -73,7 +71,7 @@ async fn get_all_events(
return Err(ApiError::Forbidden);
} else if ctx.is_user() {
let user_id = ctx.user_id.as_deref().ok_or(ApiError::Unauthorized)?;
if !services.auth.user_has_bot_access(user_id, &bot_id).await? {
if !bot.has_access(user_id) {
warn!(
code = %LogCode::Forbidden,
bot_id = %bot_id,
Expand Down Expand Up @@ -111,7 +109,6 @@ async fn get_all_events(
)]
async fn create_event(
auth: Authenticated,
services: Data<Services>,
repos: Data<Repositories>,
event: Json<CustomEventBody>,
id: Path<String>,
Expand Down Expand Up @@ -153,7 +150,7 @@ async fn create_event(
return Err(ApiError::Forbidden);
} else if ctx.is_user() {
let user_id = ctx.user_id.as_deref().ok_or(ApiError::Unauthorized)?;
if !services.auth.user_has_bot_access(user_id, &bot_id).await? {
if !bot.has_access(user_id) {
warn!(
code = %LogCode::Forbidden,
bot_id = %bot_id,
Expand Down
27 changes: 14 additions & 13 deletions src/api/routes/bots/bot/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod achievements;
mod events;
#[cfg(feature = "reports")]
mod reports;
mod settings;
mod stats;
mod suspend;
Expand Down Expand Up @@ -39,7 +41,6 @@ use crate::{
)]
async fn get_bot(
auth: Authenticated,
services: Data<Services>,
repos: Data<Repositories>,
id: Path<String>,
) -> ApiResult<Json<BotResponse>> {
Expand Down Expand Up @@ -79,7 +80,7 @@ async fn get_bot(
return Err(ApiError::Forbidden);
} else if ctx.is_user() {
let user_id = ctx.user_id.as_deref().ok_or(ApiError::Unauthorized)?;
if !services.auth.user_has_bot_access(user_id, &bot_id).await? {
if !bot.has_access(user_id) {
warn!(
code = %LogCode::Forbidden,
bot_id = %bot_id,
Expand Down Expand Up @@ -362,7 +363,6 @@ async fn delete_bot(
"Attempting to delete bot",
);

#[cfg_attr(not(feature = "mails"), allow(unused_variables))]
let bot = repos.bots.find_by_id(&bot_id).await?.ok_or_else(|| {
info!(
code = %LogCode::Request,
Expand All @@ -389,7 +389,7 @@ async fn delete_bot(
return Err(ApiError::Forbidden);
} else if ctx.is_user() {
let user_id = ctx.user_id.as_deref().ok_or(ApiError::Unauthorized)?;
if !services.auth.user_owns_bot(user_id, &bot_id).await? {
if !bot.is_owner(user_id) {
warn!(
code = %LogCode::Forbidden,
bot_id = %bot_id,
Expand All @@ -407,15 +407,6 @@ async fn delete_bot(
return Err(ApiError::Forbidden);
}

repos.bots.find_by_id(&bot_id).await?.ok_or_else(|| {
info!(
code = %LogCode::Request,
bot_id = %bot_id,
"Bot not found for deletion",
);
ApiError::NotFound(format!("Bot with ID {} not found", bot_id))
})?;

services.bots.delete_bot(&bot_id).await?;

if ctx.is_admin() {
Expand Down Expand Up @@ -504,6 +495,16 @@ pub fn configure(cfg: &mut ServiceConfig) {
)
.configure(achievements::configure)
.configure(events::configure)
.configure(
#[cfg(feature = "reports")]
{
reports::configure
},
#[cfg(not(feature = "reports"))]
{
|_cfg| {}
},
)
.configure(settings::configure)
.configure(stats::configure)
.configure(suspend::configure)
Expand Down
Loading