refactor(User): move UserDto to PublicUserDto
This commit is contained in:
@@ -207,7 +207,7 @@ pub struct User {
|
||||
/// Owned decomposition of a [`User`] (mirrors `FileParts` / `FolderParts` /
|
||||
/// `ContactParts`). Lets a consumer MOVE the heap fields out instead of cloning
|
||||
/// them through the borrowing accessors — notably `image` (a data URI up to
|
||||
/// 512 KiB) and `ui_preferences` (a JSON tree). See `UserDto::from`
|
||||
/// 512 KiB) and `ui_preferences` (a JSON tree). See `PublicUserDto::from`
|
||||
/// (benches/ROUND20.md §A2).
|
||||
pub struct UserParts {
|
||||
pub id: Uuid,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::entities::user::{User, UserRole};
|
||||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -26,72 +25,6 @@ pub enum UserRepositoryError {
|
||||
|
||||
pub type UserRepositoryResult<T> = Result<T, UserRepositoryError>;
|
||||
|
||||
/// Narrow projection for user-directory tables that do not need secrets,
|
||||
/// profile pictures, or the cross-device UI-preferences document.
|
||||
///
|
||||
/// The full [`User`] row intentionally carries all of those fields for account
|
||||
/// detail and the system address book. Reusing it for the paginated admin
|
||||
/// table made PostgreSQL detoast and transfer an avatar of up to 512 KiB per
|
||||
/// row, only for the handler to serialize it back to the browser where the
|
||||
/// table never reads it. Keeping the projection explicit prevents a future
|
||||
/// full-row field from silently returning to that hot path.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserListEntry {
|
||||
pub id: Uuid,
|
||||
pub username: Option<String>,
|
||||
pub email: String,
|
||||
pub role: UserRole,
|
||||
pub storage_quota_bytes: i64,
|
||||
pub storage_used_bytes: i64,
|
||||
pub last_login_at: Option<DateTime<Utc>>,
|
||||
pub active: bool,
|
||||
pub federation_kind: Option<String>,
|
||||
pub federation_issuer: Option<String>,
|
||||
pub is_external: bool,
|
||||
/// TRUE when `auth.users.password_hash IS NOT NULL` — user has a
|
||||
/// server-verifiable password on file (legacy or admin-set).
|
||||
/// Distinct from `opaque_registered` (which is the zero-knowledge
|
||||
/// envelope): a fully-migrated user carries BOTH — password for
|
||||
/// the fallback / operator flows, envelope for the actual login.
|
||||
/// A user with `has_password = false AND !opaque_registered AND
|
||||
/// federation_issuer IS NULL` is passwordless — the only path in is
|
||||
/// via magic-link (or, for externals, whatever grant they hold).
|
||||
pub has_password: bool,
|
||||
/// TRUE when `auth.users.opaque_envelope IS NOT NULL` — the user
|
||||
/// has completed OPAQUE registration (typically via the Phase 2
|
||||
/// silent-migration hook after a successful legacy login). Surfaced
|
||||
/// on the admin user table so operators can see rollout progress
|
||||
/// per-user. Admin-only exposure — see `AdminUserSummaryDto`.
|
||||
pub opaque_registered: bool,
|
||||
/// TRUE when `auth.users.opaque_migrated_at IS NOT NULL` — the
|
||||
/// user has completed at least one successful OPAQUE login. Distinct
|
||||
/// from `opaque_registered` because a user can have an envelope on
|
||||
/// file without having actually logged in via OPAQUE yet (e.g.
|
||||
/// admin cleared the envelope, silent-migration hasn't re-run).
|
||||
pub opaque_migrated: bool,
|
||||
/// Optional avatar payload (base64, up to 512 KiB per row). Included
|
||||
/// on the admin list projection so the SPA can seed its per-user
|
||||
/// `resolveUser` cache from the list row and skip the follow-up
|
||||
/// `/api/users/{id}` fetch UserVignette would otherwise trigger.
|
||||
/// The narrow-projection concern that motivated omitting this
|
||||
/// column originally is retired by that cache-seeding path — the
|
||||
/// bytes now do useful work per page load instead of being
|
||||
/// discarded. Deferred: moving avatar storage out of the row
|
||||
/// entirely (planned refactor); this shape is transitional.
|
||||
pub image: Option<String>,
|
||||
/// Presence signal — TRUE when the server observed a request on
|
||||
/// any of this user's non-revoked sessions within the last
|
||||
/// [`ONLINE_WINDOW`](crate::application::dtos::session_dto::ONLINE_WINDOW)
|
||||
/// (5 min). Populated via an `EXISTS(...)` subquery on
|
||||
/// `auth.sessions` in the list projection — the partial index
|
||||
/// `idx_sessions_last_seen_at WHERE revoked = FALSE` covers the
|
||||
/// scan, so per-row cost is ~μs. Surfaces to the FE via
|
||||
/// `UserDto::is_online` so both `/api/users/{id}` and the admin
|
||||
/// listing carry it, and the admin table renders a green/grey
|
||||
/// presence dot next to each vignette.
|
||||
pub is_online: bool,
|
||||
}
|
||||
|
||||
/// DB-computed booleans about a user that aren't fields on the
|
||||
/// [`User`](crate::domain::entities::user::User) entity itself —
|
||||
/// either derived from column presence (`password_hash IS NOT NULL`)
|
||||
@@ -104,10 +37,8 @@ pub struct UserListEntry {
|
||||
/// both admin AND self read. The name reflects "derived from the DB
|
||||
/// row, not intrinsic to the User entity".
|
||||
///
|
||||
/// See `docs/plan/userdto-refactor.md` for the phasing that
|
||||
/// introduces this type; it will replace [`UserListEntry`] once the
|
||||
/// list repo is switched from narrow projection to
|
||||
/// `Vec<(User, UserDerivedFlags)>` (P6 of the refactor).
|
||||
/// See `docs/plan/userdto-refactor.md` for the design; this type
|
||||
/// replaced the earlier `UserListEntry` narrow projection as of P6.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct UserDerivedFlags {
|
||||
pub has_password: bool,
|
||||
@@ -211,22 +142,6 @@ pub trait UserRepository: Send + Sync + 'static {
|
||||
include_external: bool,
|
||||
) -> UserRepositoryResult<Vec<User>>;
|
||||
|
||||
/// Lists the columns needed by compact user-management tables. Unlike
|
||||
/// [`Self::list_users`], this never fetches password hashes, OIDC subjects,
|
||||
/// avatars, names, locale state, or UI preferences.
|
||||
///
|
||||
/// **Deprecated** — [`Self::list_users_with_derived_flags`] supersedes
|
||||
/// this: it returns the full `User` entity + [`UserDerivedFlags`] so
|
||||
/// the application layer can build [`FullUserDto`](crate::application::dtos::user_dto::FullUserDto)
|
||||
/// directly. Kept only until P6 of `docs/plan/userdto-refactor.md`
|
||||
/// removes `UserListEntry` + the last remaining caller.
|
||||
async fn list_user_summaries(
|
||||
&self,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
include_external: bool,
|
||||
) -> UserRepositoryResult<Vec<UserListEntry>>;
|
||||
|
||||
/// Paginated admin user listing — full `User` entity + the derived
|
||||
/// booleans (`has_password`, OPAQUE flags, `is_online`) in one wide
|
||||
/// SELECT. Called by the admin service to build
|
||||
|
||||
Reference in New Issue
Block a user