//! The HTTP wire contract of filebrowser-ng in one place. //! //! Both the server (axum) and the web frontend (wasm `fetch`) import these //! endpoint paths, query params and serde types, so the two sides cannot //! drift apart. Serde only — no axum, no wasm dependencies. use serde::{Deserialize, Serialize}; // --------------------------------------------------------------------------- // Endpoint paths (single source of truth for the route table and the client) // --------------------------------------------------------------------------- pub const AUTH_LOGIN: &str = "/api/auth/login"; pub const AUTH_LOGOUT: &str = "/api/auth/logout"; pub const AUTH_ME: &str = "/api/auth/me"; pub const AUTH_SETUP: &str = "/api/auth/setup"; /// Change or set the signed-in user's password (`POST`), or remove it /// (`DELETE`, passkey-only accounts). pub const AUTH_PASSWORD: &str = "/api/auth/password"; /// `PUT` the signed-in user's sign-in requirement ([`AuthMode`]). pub const AUTH_MODE: &str = "/api/auth/mode"; /// The signed-in user's passkeys: `GET {AUTH_PASSKEYS}` lists them, /// `DELETE {AUTH_PASSKEYS}/{id}` removes one. pub const AUTH_PASSKEYS: &str = "/api/auth/passkeys"; /// Start registering a new passkey (`POST`). Finished at /// `{AUTH_PASSKEYS_REGISTER}{FINISH_SUFFIX}`. pub const AUTH_PASSKEYS_REGISTER: &str = "/api/auth/passkeys/register"; /// Start a passkey sign-in (`POST`, no session needed). Finished at /// `{AUTH_PASSKEY_LOGIN}{FINISH_SUFFIX}`. pub const AUTH_PASSKEY_LOGIN: &str = "/api/auth/passkey/login"; /// The signed-in user's WebDAV app passwords: `GET {AUTH_APP_PASSWORDS}` /// lists them, `POST` creates one, `DELETE {AUTH_APP_PASSWORDS}/{id}` revokes /// one. pub const AUTH_APP_PASSWORDS: &str = "/api/auth/app-passwords"; /// Second leg of both WebAuthn ceremonies: the browser's answer goes to the /// begin path plus this suffix. pub const FINISH_SUFFIX: &str = "/finish"; /// File operations: `{FILES}/{root_id}` and `{FILES}/{root_id}/{path...}`. pub const FILES: &str = "/api/files"; /// Share management (authenticated): `{SHARES}` and `{SHARES}/{id}`. pub const SHARES: &str = "/api/shares"; /// Public share resolve (no login): `{SHARE}/{token}`. pub const SHARE: &str = "/api/share"; /// Suffix on `{SHARE}/{token}`: submit the password of a protected share. pub const SHARE_UNLOCK_SUFFIX: &str = "/unlock"; /// `GET /api/search` — name and/or content search, streamed as SSE. pub const SEARCH: &str = "/api/search"; /// WebDAV mount of the signed-in user's roots: `{DAV}` and `{DAV}/{path...}`. pub const DAV: &str = "/dav"; /// WebDAV mount of one public share: `{DAV_SHARE}/{token}/{path...}`. /// /// A separate top-level path, not a segment under [`DAV`]: there, the first /// segment is a root's display name, which a reserved word could collide with. pub const DAV_SHARE: &str = "/dav-share"; /// CalDAV and CardDAV: principals, calendars and address books. /// /// Not under [`DAV`] for the same reason as [`DAV_SHARE`]. pub const PIM: &str = "/pim"; /// RFC 6764 discovery. Both redirect to [`PIM`]. pub const WELL_KNOWN_CALDAV: &str = "/.well-known/caldav"; pub const WELL_KNOWN_CARDDAV: &str = "/.well-known/carddav"; /// Admin user management: `{ADMIN_USERS}` and `{ADMIN_USERS}/{id}`. pub const ADMIN_USERS: &str = "/api/admin/users"; /// Admin view of every share on the server: `{ADMIN_SHARES}` and /// `{ADMIN_SHARES}/{id}`. [`SHARES`] is the same data scoped to the caller. pub const ADMIN_SHARES: &str = "/api/admin/shares"; pub const ADMIN_SETTINGS: &str = "/api/admin/settings"; /// Admin management of rooms and resources: `{ADMIN_ROOMS}` and /// `{ADMIN_ROOMS}/{id}`. pub const ADMIN_ROOMS: &str = "/api/admin/rooms"; /// Admin view of every public calendar and address book feed: /// `{ADMIN_PIM_LINKS}` and `{ADMIN_PIM_LINKS}/{id}`. pub const ADMIN_PIM_LINKS: &str = "/api/admin/pim-links"; /// The signed-in user's calendars and address books, own and lent to them /// (`GET`). `{PIM_COLLECTIONS}/{id}{SHARES_SUFFIX}` lists (`GET`) and lends /// (`POST`) an own one; `DELETE` on `.../{user_id}` below it ends a loan. pub const PIM_COLLECTIONS: &str = "/api/pim/collections"; pub const SHARES_SUFFIX: &str = "/shares"; /// `{PIM_COLLECTIONS}/{id}{LINKS_SUFFIX}`: the public feeds of an own /// collection (`GET`, `POST`); `DELETE` on `.../{link_id}` below it. pub const LINKS_SUFFIX: &str = "/links"; /// `POST {PIM_COLLECTIONS}/{id}{IMPORT_SUFFIX}`: an `.ics` or `.vcf` body, or /// a JSON [`PimRootFile`] naming a file in a root. pub const IMPORT_SUFFIX: &str = "/import"; /// `{PIM_COLLECTIONS}/{id}{EXPORT_SUFFIX}`: `GET` downloads the collection as /// one file; `POST` with a [`PimRootFile`] saves it into a root. pub const EXPORT_SUFFIX: &str = "/export"; /// The system address book, which has no collection id: `GET` downloads it, /// `POST` with a [`PimRootFile`] saves it into a root. pub const PIM_SYSTEM_EXPORT: &str = "/api/pim/system/export"; /// `GET {PIM_COLLECTIONS}/{id}{OBJECTS_SUFFIX}/{name}{PHOTO_SUFFIX}`: a /// contact's photo as a WebP thumbnail. pub const OBJECTS_SUFFIX: &str = "/objects"; pub const PHOTO_SUFFIX: &str = "/photo"; /// Public feed of one calendar or address book: `{FEED}/{token}.ics` or /// `.vcf`. The extension is optional. pub const FEED: &str = "/feed"; /// Pseudo root id every signed-in admin has on the files API: the whole /// server root, read-only (the admin folder picker browses it). Real root /// ids are positive database ids. Not listed in `/me`. pub const ADMIN_ROOT: i64 = -1; // --------------------------------------------------------------------------- // Query params // --------------------------------------------------------------------------- /// `?action=...` on file URLs; without it the route lists the directory. pub const P_ACTION: &str = "action"; pub const ACTION_DOWNLOAD: &str = "download"; pub const ACTION_PREVIEW: &str = "preview"; pub const ACTION_CONTENT: &str = "content"; pub const ACTION_THUMB: &str = "thumb"; /// `POST {FILES}/...?action=mkdir` — create a folder. Explicit, because the /// POST route also carries uploads and mutations. pub const ACTION_MKDIR: &str = "mkdir"; /// `POST {FILES}/...?action=create-file` — create an empty file. Explicit /// like `mkdir`, for the same reason. pub const ACTION_CREATE_FILE: &str = "create-file"; /// `POST {FILES}/...?action=exists` with an [`ExistsReq`] body — read-only /// pre-check for an upload: which of the given targets already exist. pub const ACTION_EXISTS: &str = "exists"; /// `?format=...` for folder downloads (values: see `server::archive::ArchiveFormat`). pub const P_FORMAT: &str = "format"; /// `?share=` — authenticate file calls with a public share token. pub const P_SHARE: &str = "share"; /// Search query text (`GET {SEARCH}`). pub const P_Q: &str = "q"; /// Which index to search: `name`, `content` or `both`. pub const P_SCOPE: &str = "scope"; /// Root id to search; omitted = the caller's first root. pub const P_ROOT: &str = "root"; /// Folder inside the root to start a search in (relative to the root); /// omitted or empty = the whole root. pub const P_PATH: &str = "path"; /// `?overwrite=true|1` on mutations and uploads. pub const P_OVERWRITE: &str = "overwrite"; /// Listing order ([`SortKey`]); omitted = by name. pub const P_SORT: &str = "sort"; /// `?desc=true` reverses the listing order. Folders still come first. pub const P_DESC: &str = "desc"; /// First listing entry to return, in the sorted order. pub const P_OFFSET: &str = "offset"; /// Listing entries to return, capped at [`MAX_LIST_ENTRIES`]; omitted = the cap. pub const P_LIMIT: &str = "limit"; /// `?dirs=true` lists only the subfolders (the folder picker). pub const P_DIRS: &str = "dirs"; /// `?around=name` returns the page that holds this entry, instead of the /// one at the offset. A missing name falls back to the offset. pub const P_AROUND: &str = "around"; // --------------------------------------------------------------------------- // Wire enums // --------------------------------------------------------------------------- /// Access mode of a root or a share. /// /// The serde names are also the values stored in the SQLite `mode` columns, /// so renaming a variant would break existing databases. The round-trip test /// below pins them. #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum Mode { Rw, Ro, } impl Mode { /// The only question callers ask: may this root be written to? pub fn is_writable(self) -> bool { matches!(self, Mode::Rw) } /// The wire/database spelling, for `