feat(recent): update recent list server side

initially the recent was done client side
    recent files are now directly updated on serverside when accessing a file

    note: nextcloud and webdav voluntary not included
This commit is contained in:
Edouard Vanbelle
2026-06-26 01:01:44 +02:00
parent 11e34762e3
commit f4032e9e23
12 changed files with 463 additions and 18 deletions
+1
View File
@@ -20,6 +20,7 @@ pub mod music_ports;
pub mod outbound;
pub mod plugin_ports;
pub mod recent_ports;
pub mod resource_access_hook;
pub mod share_ports;
pub mod storage_ports;
pub mod thumbnail_ports;
@@ -0,0 +1,50 @@
//! Observer notified when a caller successfully reads or mutates a file.
//!
//! Read-event sibling of [`crate::application::ports::file_lifecycle`]. The
//! lifecycle hook fires on content changes (created/copied/updated/deleted);
//! this one fires on access — every authorised file read, every successful
//! upload, every PUT/COPY — and lets cross-cutting observers (Recent list,
//! audit trail, future "last seen by" UX) react without each
//! protocol-surface handler having to remember to call them.
//!
//! Folders are deliberately out of scope: a listing fires on every UI
//! navigation, every PROPFIND, every NC sync poll, and would dominate
//! `auth.user_recent_files` with noise that no user actually opened.
//! Only file-level interactions count.
//!
//! Implementors run **after** the service layer's authZ check has passed and
//! the read/write has succeeded; a denied or 404'd request never fires the
//! hook. The method is synchronous — implementors that need to do real work
//! spawn it themselves so the user-facing request is never blocked on the
//! side-effect. The recording impl lives in
//! `infrastructure/services/recent_recording_hook.rs`.
use uuid::Uuid;
/// Fired by the application services on a successful, authorised access to a
/// file owned (or shared with) the caller.
///
/// `caller_id` is mandatory because the recording side needs to know **who**
/// touched the file — the same file accessed by two different users records
/// two separate Recent rows. Anonymous surfaces (public share downloads via
/// `/api/s/{token}`) deliberately do not call this hook: a "viewer" without
/// an authenticated identity has no Recent list to land in.
pub trait ResourceAccessHook: Send + Sync {
/// Called after a file read or successful write touched `file_id` on
/// behalf of `caller_id`. The caller has already been authorised — the
/// hook is fire-and-forget; failures are the implementor's problem and
/// must never propagate.
fn on_file_accessed(&self, caller_id: Uuid, file_id: &str);
/// Called after `caller_id` has emptied their Recent list (either by
/// clearing the whole table or removing a single row). Implementors
/// hold in-memory throttle / dedup state keyed by `(caller, item)`;
/// without this signal a freshly-cleared list would refuse to record
/// the next access until the throttle TTL expires, leaving the user
/// staring at an empty Recent and wondering why their open-then-close
/// did nothing.
///
/// Default no-op: implementations without any in-memory state — most
/// audit-trail-style observers — needn't react.
fn on_recents_cleared(&self, _caller_id: Uuid) {}
}