Skip to main content

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}