refactor(User): move UserDto to PublicUserDto
This commit is contained in:
@@ -22,7 +22,7 @@ use crate::application::dtos::settings_dto::{
|
||||
TestOidcConnectionDto, TestStorageConnectionDto, UpdateUserActiveDto, UpdateUserQuotaDto,
|
||||
UpdateUserRoleDto,
|
||||
};
|
||||
use crate::application::dtos::user_dto::{FullUserDto, UserDto};
|
||||
use crate::application::dtos::user_dto::{FullUserDto, PublicUserDto};
|
||||
use crate::application::ports::authorization_ports::AuthorizationEngine;
|
||||
use crate::application::ports::plugin_ports::{LogQuery, PluginManagementPort, PluginMgmtError};
|
||||
// JobStoreProvider is used only by the storage-migration shims below,
|
||||
@@ -42,10 +42,10 @@ use uuid::Uuid;
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
enum AdminUsersPayload {
|
||||
/// Fat-`UserDto` per row. Emitted when `?summary=false` — legacy
|
||||
/// Fat-`PublicUserDto` per row. Emitted when `?summary=false` — legacy
|
||||
/// path retained until the FE drops the `summary=false` query
|
||||
/// (rare; the SPA uses `summary=true` for the paginated table).
|
||||
Full(Vec<UserDto>),
|
||||
Full(Vec<PublicUserDto>),
|
||||
/// `FullUserDto` per row — same shape one row of the /me
|
||||
/// response's embedded `full` carries. Emitted when
|
||||
/// `?summary=true`. The FE seeds `resolveUser` cache from
|
||||
@@ -1571,7 +1571,7 @@ pub async fn reset_user_password(
|
||||
path = "/api/admin/users/{id}/promote-to-internal",
|
||||
params(("id" = String, Path, description = "Target user id")),
|
||||
responses(
|
||||
(status = 200, description = "User promoted", body = UserDto),
|
||||
(status = 200, description = "User promoted", body = PublicUserDto),
|
||||
(status = 400, description = "Magic-link login is disabled on this deployment"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 403, description = "Admin required (or target is OIDC-linked)"),
|
||||
|
||||
@@ -61,7 +61,7 @@ async fn create_app_password(
|
||||
}
|
||||
|
||||
// Require a claimed username. NextCloud Basic Auth resolves users by
|
||||
// username; an app password is unusable without one. UserDto carries
|
||||
// username; an app password is unusable without one. PublicUserDto carries
|
||||
// an empty string when the underlying `users.username` is NULL — the
|
||||
// entity rejects empty strings on construction, so empty here is an
|
||||
// unambiguous signal that the column is NULL.
|
||||
|
||||
@@ -12,8 +12,8 @@ use uuid::Uuid;
|
||||
|
||||
use crate::application::dtos::user_dto::{
|
||||
AuthResponseDto, ChangePasswordDto, FullUserDto, LoginDto, OidcCallbackQueryDto,
|
||||
OidcExchangeDto, OidcProviderInfoDto, RefreshTokenDto, RegisterDto, SelfUserDto, SetupAdminDto,
|
||||
UpgradeToInternalDto, UserDto,
|
||||
OidcExchangeDto, OidcProviderInfoDto, PublicUserDto, RefreshTokenDto, RegisterDto, SelfUserDto,
|
||||
SetupAdminDto, UpgradeToInternalDto,
|
||||
};
|
||||
use crate::application::services::auth_application_service::{OidcCallbackResult, RegisterResult};
|
||||
use crate::common::di::AppState;
|
||||
@@ -89,7 +89,7 @@ pub fn setup_route() -> Router<Arc<AppState>> {
|
||||
/// the `audit` channel as `auth.register` with `reason` one of
|
||||
/// `created`, `email_taken`, `username_taken`.
|
||||
/// - **SMTP not configured**: there is no welcome-mail cover story, so
|
||||
/// the classic `201 + UserDto` on success and `409` on collision
|
||||
/// the classic `201 + PublicUserDto` on success and `409` on collision
|
||||
/// apply. Anti-enumeration would just be misleading UX (telling the
|
||||
/// user to check an email that will never arrive). Email-only
|
||||
/// signup is **503** in this mode because the user would otherwise
|
||||
@@ -106,7 +106,7 @@ pub fn setup_route() -> Router<Arc<AppState>> {
|
||||
request_body = RegisterDto,
|
||||
responses(
|
||||
(status = 200, description = "Uniform registration response (SMTP configured, anti-enumeration mode)"),
|
||||
(status = 201, description = "User registered successfully (SMTP not configured)", body = UserDto),
|
||||
(status = 201, description = "User registered successfully (SMTP not configured)", body = PublicUserDto),
|
||||
(status = 400, description = "Validation error (malformed request body)"),
|
||||
(status = 403, description = "Registration disabled (admin setting or OIDC-only mode)"),
|
||||
(status = 409, description = "Username or email already taken (SMTP not configured)"),
|
||||
@@ -280,7 +280,7 @@ pub async fn register(
|
||||
}
|
||||
Ok(resp)
|
||||
} else {
|
||||
// Classic mode: clear 201 + UserDto so the frontend can
|
||||
// Classic mode: clear 201 + PublicUserDto so the frontend can
|
||||
// log the user in directly with the password they just
|
||||
// submitted. Unbox the DTO for the JSON serialisation.
|
||||
Ok((StatusCode::CREATED, Json(*user)).into_response())
|
||||
@@ -659,7 +659,7 @@ pub async fn get_current_user(
|
||||
// `User` entity + `UserDerivedFlags` (has_password / OPAQUE flags /
|
||||
// is_online) in one round-trip. That collapses what used to be a
|
||||
// `get_user_by_id` + separate credential lookups into one wire trip,
|
||||
// AND populates the OPAQUE flags on `/me` which the fat-UserDto path
|
||||
// AND populates the OPAQUE flags on `/me` which the fat-PublicUserDto path
|
||||
// never did (it left them at false — the "quiet lie" that motivated
|
||||
// this refactor, see `docs/plan/userdto-refactor.md`).
|
||||
let (user, flags) = auth_service
|
||||
@@ -860,14 +860,14 @@ pub async fn change_password(
|
||||
/// self-registration policy. Refused with 403
|
||||
/// `error_type = "RegistrationDomainNotAllowed"`.
|
||||
///
|
||||
/// Response: the updated `UserDto` (post-upgrade view — `is_external`
|
||||
/// Response: the updated `PublicUserDto` (post-upgrade view — `is_external`
|
||||
/// is false, `storage_quota_bytes` is set).
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/auth/upgrade-to-internal",
|
||||
request_body = UpgradeToInternalDto,
|
||||
responses(
|
||||
(status = 200, description = "Upgrade succeeded", body = UserDto),
|
||||
(status = 200, description = "Upgrade succeeded", body = PublicUserDto),
|
||||
(status = 400, description = "Password missing / too short"),
|
||||
(status = 401, description = "Not authenticated"),
|
||||
(status = 403, description = "OIDC user, or domain not in allowlist"),
|
||||
@@ -965,7 +965,7 @@ pub async fn upgrade_to_internal(
|
||||
path = "/api/auth/me/profile",
|
||||
request_body = crate::application::dtos::user_dto::UpdateProfileDto,
|
||||
responses(
|
||||
(status = 200, description = "Updated profile (UserDto)", body = UserDto),
|
||||
(status = 200, description = "Updated profile (PublicUserDto)", body = PublicUserDto),
|
||||
(status = 400, description = "Validation error (e.g. invalid handle format, empty given_name)"),
|
||||
(status = 401, description = "Not authenticated"),
|
||||
(status = 403, description = "OIDC-managed profile — edit at the IdP"),
|
||||
@@ -1249,7 +1249,7 @@ pub struct BackchannelLogoutForm {
|
||||
path = "/api/setup",
|
||||
request_body = SetupAdminDto,
|
||||
responses(
|
||||
(status = 201, description = "First admin created and system initialized", body = UserDto),
|
||||
(status = 201, description = "First admin created and system initialized", body = PublicUserDto),
|
||||
(status = 403, description = "System already initialized"),
|
||||
(status = 503, description = "Auth service not configured"),
|
||||
),
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::application::dtos::contact_dto::{
|
||||
AddressDto, ContactDto, ContactGroupDto, CreateContactDto, CreateContactGroupDto, EmailDto,
|
||||
GroupMembershipDto, PhoneDto, UpdateContactDto, UpdateContactGroupDto,
|
||||
};
|
||||
use crate::application::dtos::user_dto::UserDto;
|
||||
use crate::application::dtos::user_dto::PublicUserDto;
|
||||
use crate::application::ports::carddav_ports::{AddressBookUseCase, ContactUseCase};
|
||||
use crate::application::services::auth_application_service::AuthApplicationService;
|
||||
use crate::application::services::contact_service::ContactService;
|
||||
@@ -185,14 +185,14 @@ fn if_match_passes(if_match: Option<&str>, stored_etag: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a `UserDto` to a `ContactDto` so OxiCloud users appear as contacts
|
||||
/// Map a `PublicUserDto` to a `ContactDto` so OxiCloud users appear as contacts
|
||||
/// inside the virtual system address book.
|
||||
///
|
||||
/// `given_name`/`family_name` come from OIDC standard claims at JIT
|
||||
/// provisioning (or NULL for password-only or pre-OIDC users). When
|
||||
/// they're present, prefer a "First Last" full name; otherwise fall
|
||||
/// back to the username (which is always present).
|
||||
fn user_to_contact(user: UserDto) -> ContactDto {
|
||||
fn user_to_contact(user: PublicUserDto) -> ContactDto {
|
||||
// Display fallback chain: given+family name → username → email.
|
||||
// Username is `Option<String>` post PR 16; externals start with None.
|
||||
let full_name = match (user.given_name.as_deref(), user.family_name.as_deref()) {
|
||||
@@ -222,8 +222,19 @@ fn user_to_contact(user: UserDto) -> ContactDto {
|
||||
photo_url: user.image.clone(),
|
||||
birthday: None,
|
||||
anniversary: None,
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
// System-book contacts are VIRTUAL projections of the user
|
||||
// directory — they have no independent creation history. Stamp
|
||||
// both timestamps with `Utc::now()` so the ContactDto shape is
|
||||
// satisfied; CardDAV clients ETag on the vCard content (see
|
||||
// `etag` below, keyed on the stable user id), not on these
|
||||
// wrapper timestamps.
|
||||
//
|
||||
// Previously read `user.created_at` / `user.updated_at` from the
|
||||
// fat `UserDto`; those fields moved to `FullUserDto` under the
|
||||
// three-layer refactor (docs/plan/userdto-refactor.md) and are
|
||||
// not exposed on the slim `PublicUserDto` this function receives.
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
etag: user.id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! User-profile lookup for the frontend.
|
||||
//!
|
||||
//! `GET /api/users/{id}` returns a [`UserDto`] for the target user iff
|
||||
//! `GET /api/users/{id}` returns a [`PublicUserDto`] for the target user iff
|
||||
//! the authenticated caller has a legitimate relationship with them.
|
||||
//! The visibility rule lives in
|
||||
//! [`AuthApplicationService::get_user_profile`] — handlers never embed
|
||||
|
||||
@@ -46,7 +46,7 @@ use crate::application::dtos::trash_dto::{
|
||||
};
|
||||
use crate::application::dtos::user_dto::{
|
||||
AuthResponseDto, ChangePasswordDto, LoginDto, OidcExchangeDto, OidcProviderInfoDto,
|
||||
RefreshTokenDto, RegisterDto, SetupAdminDto, UserDto,
|
||||
PublicUserDto, RefreshTokenDto, RegisterDto, SetupAdminDto,
|
||||
};
|
||||
use crate::application::ports::chunked_upload_ports::{
|
||||
ChunkUploadResponseDto, CreateUploadResponseDto, UploadStatusResponseDto,
|
||||
@@ -367,7 +367,7 @@ use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
|
||||
PaginationDto,
|
||||
PaginationRequestDto,
|
||||
// User / Auth schemas
|
||||
UserDto,
|
||||
PublicUserDto,
|
||||
LoginDto,
|
||||
RegisterDto,
|
||||
SetupAdminDto,
|
||||
@@ -583,7 +583,7 @@ mod tests {
|
||||
"FolderDto",
|
||||
"ShareDto",
|
||||
"TrashedItemDto",
|
||||
"UserDto",
|
||||
"PublicUserDto",
|
||||
] {
|
||||
assert!(schemas.contains_key(name), "missing schema: {name}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user