Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions adapters/src/incoming/http_axum/auth/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub struct User {
pub email: String,
pub username: String,
pub email_verified_at: Option<time::OffsetDateTime>,
pub available_charges: i32,
pub charges_updated_at: time::OffsetDateTime,
pub roles: Vec<Role>,
}

Expand All @@ -25,6 +27,8 @@ impl From<UserPublic> for User {
email: user_public.email,
username: user_public.username,
email_verified_at: user_public.email_verified_at,
available_charges: user_public.available_charges,
charges_updated_at: user_public.charges_updated_at,
roles: user_public.roles,
}
}
Expand All @@ -37,8 +41,8 @@ impl From<User> for UserPublic {
email: user.email,
username: user.username,
email_verified_at: user.email_verified_at,
available_charges: 0,
charges_updated_at: time::OffsetDateTime::now_utc(),
available_charges: user.available_charges,
charges_updated_at: user.charges_updated_at,
roles: user.roles,
}
}
Expand Down
17 changes: 12 additions & 5 deletions adapters/src/incoming/http_axum/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ use dto::responses::{
BanResponse, PaintOkEnvelope, PaintPixelResponse, PixelHistoryEntry, PixelInfoResponse,
TileImageResponse, UserResponse,
};
use handlers::palette::{PaletteEntry, PaletteResponse, SpecialColorEntry};
use handlers::canvas::CanvasConfigResponse;
use handlers::worlds::{CreateWorldRequest, PaletteEntry, WorldResponse};
use utoipa::OpenApi;

#[derive(OpenApi)]
Expand All @@ -32,7 +33,11 @@ use utoipa::OpenApi;
handlers::tiles::serve_tile,
handlers::tiles::serve_tile_head,
handlers::tiles::paint_pixels_batch,
handlers::palette::get_palette,
handlers::canvas::get_canvas_config,
handlers::worlds::list_worlds,
handlers::worlds::get_world_by_id,
handlers::worlds::get_world_by_name,
handlers::worlds::create_world,
handlers::pixel_info::get_pixel_info,
handlers::health::health_check,
handlers::auth::register_handler,
Expand All @@ -58,9 +63,10 @@ use utoipa::OpenApi;
ApiResponseUser,
PaintOkEnvelope,
PaintPixelResponse,
CanvasConfigResponse,
WorldResponse,
CreateWorldRequest,
PaletteEntry,
PaletteResponse,
SpecialColorEntry,
RegisterRequest,
LoginRequest,
UpdateUsernameRequest,
Expand Down Expand Up @@ -93,7 +99,8 @@ use utoipa::OpenApi;
tags(
(name = "tiles", description = "Tile management operations - serve WebP tile images with caching and rate limiting"),
(name = "painting", description = "Pixel painting operations - place pixels on tiles with rate limiting and backoff guidance"),
(name = "palette", description = "Color palette management - retrieve available colors for pixel painting"),
(name = "canvas", description = "Canvas configuration - retrieve default world ID, and other global settings in the future"),
(name = "worlds", description = "World management - list, retrieve, and create worlds with palette configuration, tile size, and pixel size"),
(name = "pixel", description = "Pixel information operations - retrieve metadata about individual pixels"),
(name = "auth", description = "Authentication and user management - register, login, logout, and user profile operations"),
(name = "admin", description = "Admin operations - role management and user administration (requires admin privileges)"),
Expand Down
4 changes: 2 additions & 2 deletions adapters/src/incoming/http_axum/dto/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub struct PaintRequest {
#[cfg_attr(feature = "docs", schema(example = 256, minimum = 0))]
pub py: usize,
#[cfg_attr(feature = "docs", schema(example = 2))]
pub color_id: u8,
pub color_id: i16,
}

#[cfg_attr(feature = "docs", derive(ToSchema))]
Expand All @@ -49,7 +49,7 @@ pub struct BatchPixelPaint {
#[cfg_attr(feature = "docs", schema(example = 256, minimum = 0))]
pub py: usize,
#[cfg_attr(feature = "docs", schema(example = 2))]
pub color_id: u8,
pub color_id: i16,
}

impl BatchPixelPaint {
Expand Down
4 changes: 2 additions & 2 deletions adapters/src/incoming/http_axum/dto/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ pub struct PixelHistoryEntry {
#[cfg_attr(feature = "docs", schema(example = 64))]
pub py: usize,
#[cfg_attr(feature = "docs", schema(example = 5))]
pub color_id: u8,
pub color_id: i16,
#[cfg_attr(feature = "docs", schema(example = "2023-01-01T12:00:00Z"))]
pub timestamp: String,
}
Expand All @@ -273,7 +273,7 @@ pub struct PixelInfoResponse {
#[cfg_attr(feature = "docs", schema(example = "johndoe"))]
pub username: String,
#[cfg_attr(feature = "docs", schema(example = 5))]
pub color_id: u8,
pub color_id: i16,
#[cfg_attr(feature = "docs", schema(example = "2023-01-01T12:00:00Z"))]
pub timestamp: String,
}
Expand Down
2 changes: 2 additions & 0 deletions adapters/src/incoming/http_axum/error_mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ impl IntoResponse for HttpError {

AppError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden".to_string()),

AppError::NotFound { message } => (StatusCode::NOT_FOUND, message.clone()),

AppError::EmailNotVerified => (
StatusCode::FORBIDDEN,
"Email verification is required".to_string(),
Expand Down
14 changes: 6 additions & 8 deletions adapters/src/incoming/http_axum/handlers/auth_user_response.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use time::{OffsetDateTime, format_description::well_known::Rfc3339};

use domain::auth::UserPublic;
use domain::credits::{CreditBalance, CreditConfig};
use domain::{auth::UserPublic, credits::CreditConfig};
use fedi_wplace_application::error::AppError;

use crate::{incoming::http_axum::dto::responses::UserResponse, shared::app_state::AppState};
Expand All @@ -11,16 +10,15 @@ pub async fn build_user_response(
state: &AppState,
now: OffsetDateTime,
) -> Result<UserResponse, AppError> {
let balance = state.credit_store.get_user_credits(&user_public.id).await?;

let credit_config = CreditConfig::new(
state.config.credits.max_charges,
state.config.credits.charge_cooldown_seconds,
);
let credit_balance = CreditBalance::new(
user_public.available_charges,
user_public.charges_updated_at,
);
let current_charges = credit_balance.calculate_current_balance(now, &credit_config);
let seconds_until_next_charge = credit_balance.seconds_until_next_charge(now, &credit_config);

let current_charges = balance.calculate_current_balance(now, &credit_config);
let seconds_until_next_charge = balance.seconds_until_next_charge(now, &credit_config);

let roles = user_public
.roles
Expand Down
78 changes: 78 additions & 0 deletions adapters/src/incoming/http_axum/handlers/canvas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use axum::{extract::State, Json};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[cfg(feature = "docs")]
use utoipa::ToSchema;

use crate::incoming::http_axum::dto::responses::ApiResponse;
#[cfg(feature = "docs")]
use crate::incoming::http_axum::dto::responses::ApiResponseValue;
use crate::shared::app_state::AppState;

#[cfg_attr(feature = "docs", derive(ToSchema))]
#[cfg_attr(
feature = "docs",
schema(
description = "Canvas configuration with default world ID",
example = json!({
"default_world_id": "550e8400-e29b-41d4-a716-446655440000"
})
)
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CanvasConfigResponse {
pub default_world_id: Uuid,
}

#[cfg_attr(
feature = "docs",
utoipa::path(
get,
path = "/canvas/config",
responses(
(status = 200, description = "Canvas configuration retrieved successfully",
body = ApiResponseValue,
example = json!({
"ok": true,
"data": {
"default_world_id": "550e8400-e29b-41d4-a716-446655440000"
}
})
),
(status = 500, description = "Internal server error", body = ApiResponseValue)
),
tag = "canvas"
)
)]
pub async fn get_canvas_config(
State(state): State<AppState>,
) -> Json<ApiResponse<serde_json::Value>> {
let canvas_config = match state.canvas_config_service.get_canvas_config().await {
Ok(config) => config,
Err(e) => {
return Json(ApiResponse::<serde_json::Value> {
ok: false,
error: Some(format!("Failed to retrieve canvas config: {e}")),
data: None,
});
}
};

let response = CanvasConfigResponse {
default_world_id: *canvas_config.default_world_id.as_uuid(),
};

let response_data = match serde_json::to_value(response) {
Ok(data) => Some(data),
Err(_) => {
return Json(ApiResponse::<serde_json::Value> {
ok: false,
error: Some("Failed to serialize canvas config data".to_string()),
data: None,
});
}
};

Json(ApiResponse::success_with_data(response_data))
}
19 changes: 17 additions & 2 deletions adapters/src/incoming/http_axum/handlers/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use axum::{Json, extract::State};
use crate::incoming::http_axum::dto::responses::ApiResponseValue;
use crate::incoming::http_axum::{dto::responses::ApiResponse, error_mapper::HttpError};
use crate::shared::app_state::AppState;
use fedi_wplace_application::ports::incoming::tiles::MetricsQueryUseCase;
use fedi_wplace_application::{
error::AppError,
ports::incoming::tiles::MetricsQueryUseCase,
};

#[cfg_attr(feature = "docs", utoipa::path(
get,
Expand Down Expand Up @@ -36,8 +39,20 @@ use fedi_wplace_application::ports::incoming::tiles::MetricsQueryUseCase;
pub async fn health_check(
State(state): State<AppState>,
) -> Result<Json<ApiResponse<serde_json::Value>>, HttpError> {
let default_world = state
.world_service
.get_default_world()
.await
.map_err(HttpError)?
.ok_or_else(|| HttpError(AppError::NotFound {
message: "Default world not found".to_string(),
}))?;

let metrics_uc: &dyn MetricsQueryUseCase = &*state.metrics_query_service;
let metrics = metrics_uc.get_metrics().await.map_err(HttpError)?;
let metrics = metrics_uc
.get_metrics(&default_world.id)
.await
.map_err(HttpError)?;

Ok(Json(ApiResponse::success_with_data(Some(
serde_json::json!({
Expand Down
4 changes: 2 additions & 2 deletions adapters/src/incoming/http_axum/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pub(crate) mod auth_user_response;
pub(crate) mod palette;

// keep public for OpenAPI docs
pub mod admin;
pub mod auth;
pub mod ban;
pub mod canvas;
pub mod health;
pub mod pixel_info;
pub mod tiles;
pub mod worlds;
Loading