feat(frontend): i18n expansion, admin/profile i18n, grid/list view fix, empty state
- Add 5 new locales (hi, ar, ru, ja, ko) — now 14 total - Admin panel: 117 i18n keys, confirm modal, animated tabs, no inline handlers - Profile page: 58 i18n keys with data-i18n attributes - Fix i18n safeT() shadowing bug and translationsLoaded timing - Fix grid/list view: list header no longer shows in grid mode on login - Fix classList.toggle hidden sync for view switching across all nav functions - Revert .hidden important that broke login page rendering - Add files empty state (no_files + empty_hint) with translations - Fix language selector dropdown scroll and styling - Fix admin panel scroll with sticky tabs
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
use crate::application::dtos::app_password_dto::CreateAppPasswordRequestDto;
|
||||
use crate::common::di::AppState;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::CurrentUser;
|
||||
use crate::interfaces::middleware::auth::AuthUser;
|
||||
use axum::extract::State;
|
||||
use axum::routing::{delete, get, post};
|
||||
use axum::{Json, Router};
|
||||
@@ -26,7 +26,7 @@ pub fn app_password_routes() -> Router<Arc<AppState>> {
|
||||
/// Returns the plain-text password ONCE. The user must copy it immediately.
|
||||
async fn create_app_password(
|
||||
State(state): State<Arc<AppState>>,
|
||||
axum::Extension(user): axum::Extension<CurrentUser>,
|
||||
user: AuthUser,
|
||||
Json(request): Json<CreateAppPasswordRequestDto>,
|
||||
) -> Result<Json<crate::application::dtos::app_password_dto::AppPasswordCreatedResponseDto>, AppError>
|
||||
{
|
||||
@@ -48,7 +48,7 @@ async fn create_app_password(
|
||||
/// Never returns plain-text passwords (only prefix + metadata).
|
||||
async fn list_app_passwords(
|
||||
State(state): State<Arc<AppState>>,
|
||||
axum::Extension(user): axum::Extension<CurrentUser>,
|
||||
user: AuthUser,
|
||||
) -> Result<Json<crate::application::dtos::app_password_dto::AppPasswordListResponseDto>, AppError>
|
||||
{
|
||||
let service = state
|
||||
@@ -64,7 +64,7 @@ async fn list_app_passwords(
|
||||
/// DELETE /api/auth/app-passwords/:id — Revoke an app password.
|
||||
async fn revoke_app_password(
|
||||
State(state): State<Arc<AppState>>,
|
||||
axum::Extension(user): axum::Extension<CurrentUser>,
|
||||
user: AuthUser,
|
||||
axum::extract::Path(id): axum::extract::Path<String>,
|
||||
) -> Result<Json<crate::application::dtos::app_password_dto::AppPasswordRevokeResponseDto>, AppError>
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::application::ports::calendar_ports::CalendarUseCase;
|
||||
use crate::application::services::calendar_service::CalendarService;
|
||||
use crate::common::di::AppState;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::CurrentUser;
|
||||
use crate::interfaces::middleware::auth::{AuthUser, CurrentUser};
|
||||
|
||||
const HEADER_DAV: HeaderName = HeaderName::from_static("dav");
|
||||
|
||||
@@ -138,10 +138,11 @@ fn reject_path_traversal(path: &str) -> Result<(), AppError> {
|
||||
|
||||
// ─── Helper: extract user from request ───────────────────────────────
|
||||
|
||||
fn extract_user(req: &Request<Body>) -> Result<CurrentUser, AppError> {
|
||||
fn extract_user(req: &Request<Body>) -> Result<AuthUser, AppError> {
|
||||
req.extensions()
|
||||
.get::<Arc<CurrentUser>>()
|
||||
.map(|arc| (**arc).clone())
|
||||
.cloned()
|
||||
.map(AuthUser)
|
||||
.ok_or_else(|| AppError::unauthorized("Authentication required"))
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::application::ports::carddav_ports::{AddressBookUseCase, ContactUseCas
|
||||
use crate::common::di::AppState;
|
||||
use crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::CurrentUser;
|
||||
use crate::interfaces::middleware::auth::{AuthUser, CurrentUser};
|
||||
|
||||
const HEADER_DAV: HeaderName = HeaderName::from_static("dav");
|
||||
|
||||
@@ -126,10 +126,11 @@ fn reject_path_traversal(path: &str) -> Result<(), AppError> {
|
||||
|
||||
// ─── Helper: extract user from request ───────────────────────────────
|
||||
|
||||
fn extract_user(req: &Request<Body>) -> Result<CurrentUser, AppError> {
|
||||
fn extract_user(req: &Request<Body>) -> Result<AuthUser, AppError> {
|
||||
req.extensions()
|
||||
.get::<Arc<CurrentUser>>()
|
||||
.map(|arc| (**arc).clone())
|
||||
.cloned()
|
||||
.map(AuthUser)
|
||||
.ok_or_else(|| AppError::unauthorized("Authentication required"))
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::application::services::folder_service::FolderService;
|
||||
use crate::common::di::AppState;
|
||||
use crate::infrastructure::services::path_resolver_service::ResolvedResource;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::CurrentUser;
|
||||
use crate::interfaces::middleware::auth::{AuthUser, CurrentUser};
|
||||
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, percent_decode_str, utf8_percent_encode};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -93,10 +93,11 @@ const PROPFIND_BATCH_SIZE: i64 = 500;
|
||||
/// Every mutating or data-returning WebDAV handler **must** call this so
|
||||
/// that the real `user.id` is available for ownership checks and for the
|
||||
/// user-scoped `PathResolverService` methods.
|
||||
fn extract_user(req: &Request<Body>) -> Result<CurrentUser, AppError> {
|
||||
fn extract_user(req: &Request<Body>) -> Result<AuthUser, AppError> {
|
||||
req.extensions()
|
||||
.get::<Arc<CurrentUser>>()
|
||||
.map(|arc| (**arc).clone())
|
||||
.cloned()
|
||||
.map(AuthUser)
|
||||
.ok_or_else(|| AppError::unauthorized("Authentication required"))
|
||||
}
|
||||
|
||||
|
||||
@@ -413,14 +413,12 @@ async fn authorize_wopi_access<S: FileRetrievalUseCase>(
|
||||
/// This endpoint is behind normal auth middleware. The authenticated user
|
||||
/// requests a WOPI session for a specific file.
|
||||
pub async fn get_editor_url(
|
||||
AuthUser {
|
||||
id: user_id,
|
||||
username,
|
||||
..
|
||||
}: AuthUser,
|
||||
auth_user: AuthUser,
|
||||
Query(params): Query<EditorUrlParams>,
|
||||
State(state): State<WopiState>,
|
||||
) -> Response {
|
||||
let user_id = auth_user.id;
|
||||
let username = &auth_user.username;
|
||||
// Verify the caller owns the file (SQL-level check, no existence leak).
|
||||
let (file, can_write) = match authorize_wopi_access(
|
||||
state.app_state.applications.file_retrieval_service.as_ref(),
|
||||
|
||||
Reference in New Issue
Block a user