adminx_attachments/lib.rs
1// adminx-storage/src/lib.rs
2//
3// File attachments for adminx. Register it and any resource can declare
4// `file_fields()`; the panel then shows an upload widget on the detail page and
5// the web adapters expose attach / serve / detach routes. Leave it out and
6// adminx behaves exactly as before.
7//
8// Two layers, both swappable:
9// - `BlobStore` holds the bytes. The MVP ships `LocalFsStore` (local disk); an
10// S3 / object-store backend can slot in behind it later.
11// - the `adminx_attachments` table holds the metadata, written through
12// adminx-core's `Storage`, so it works over SeaORM (SQL) or MongoDB.
13//
14// ## Startup order
15//
16// ```ignore
17// adminx_seaorm::init(&db_url).await?; // 1. storage
18// adminx_core::seed(adminx_storage::migrate_sql()).await?; // 2. SQL table (SQL backends only)
19// adminx_storage::init_local("./adminx_uploads"); // 3. register a local-disk backend
20// configure_auth(AuthConfig { /* ... */ }); // 4. auth
21// register_resource(Box::new(MyResource)); // declares file_fields()
22// ```
23//
24// A resource opts in by returning fields from `file_fields()`:
25//
26// ```ignore
27// fn file_fields(&self) -> Vec<adminx_core::attach::FileField> {
28// vec![adminx_core::attach::FileField::new("avatar", "Avatar").images()]
29// }
30// ```
31//
32// ## What is stored where
33//
34// The bytes go to the `BlobStore` under an opaque key; the row in
35// `adminx_attachments` records the original filename, content type, size and
36// that key. Serving reads the row, then the bytes. Deleting a record purges its
37// attachments (see `adminx_core::crud::delete`).
38
39mod blobstore;
40mod schema;
41mod store;
42
43pub use blobstore::{BlobStore, LocalFsStore};
44pub use store::{AttachmentStore, TABLE};
45
46/// Register a backend built from any [`BlobStore`]. Use this to supply a custom
47/// store; most apps want [`init_local`] instead.
48pub fn init(blobs: Box<dyn BlobStore>) {
49 adminx_core::set_attachments(Box::new(AttachmentStore::new(blobs)));
50 tracing::info!("adminx-storage: attachments enabled (metadata in `{TABLE}`)");
51}
52
53/// Register a local-filesystem backend rooted at `dir` (created on first write).
54/// The simplest way to turn attachments on; swap for an object-store backend in
55/// production.
56pub fn init_local(dir: impl Into<std::path::PathBuf>) {
57 let dir = dir.into();
58 tracing::info!("adminx-storage: storing blobs under {}", dir.display());
59 init(Box::new(LocalFsStore::new(dir)));
60}
61
62/// SQL `CREATE TABLE IF NOT EXISTS` + index for the attachment table. Run once on
63/// a SQL backend via `adminx_core::seed(adminx_storage::migrate_sql())`. Mongo
64/// needs nothing.
65pub fn migrate_sql() -> &'static [&'static str] {
66 schema::SQL
67}