use jsondb::JsonDb; pub mod prelude { pub use std::collections::HashMap; pub use jsondb::Schema; pub use serde::{Deserialize, Serialize}; pub use serde_with::serde_as; pub use serde_with::skip_serializing_none; pub use time::OffsetDateTime; pub trait SizedFile { fn size(&self) -> u64; fn formatted_size(&self) -> String { bytesize::to_string(self.size(), false).replace(' ', "") } } } mod v0; pub mod v1 { //! Schema version 1 //! //! Changes: //! //! * Represent times in RFC3339 format instead of epoch seconds //! * Ditch some pre-JsonDb ad-hoc migrations //! * Stored files now have optional passwords for //! deleting/extending them, stored in plaintext because the //! threat model doesn't warrant anything stronger use super::prelude::*; #[serde_as] #[derive(Debug, Clone, Deserialize, Serialize)] pub struct UploadedFile { pub name: String, pub size: u64, #[serde_as(as = "time::format_description::well_known::Rfc3339")] pub modtime: OffsetDateTime, } impl From for UploadedFile { fn from(old: super::v0::UploadedFile) -> Self { UploadedFile { name: old.name, size: old.size, modtime: old.modtime, } } } impl SizedFile for UploadedFile { fn size(&self) -> u64 { self.size } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct FileSet { pub files: Vec, // Optional for backwards compatibility only pub directory_name: Option, } impl From for FileSet { fn from(old: super::v0::FileSet) -> Self { FileSet { files: old.files.into_iter().map(UploadedFile::from).collect(), directory_name: old.directory_name, } } } #[serde_as] #[skip_serializing_none] #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StoredFile { pub name: String, pub size: u64, #[serde_as(as = "time::format_description::well_known::Rfc3339")] pub modtime: OffsetDateTime, #[serde_as(as = "time::format_description::well_known::Rfc3339")] pub expiry: OffsetDateTime, pub contents: Option, } impl SizedFile for StoredFile { fn size(&self) -> u64 { self.size } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StoredFileWithPassword { #[serde(flatten)] pub file: StoredFile, /// None password means the admin page can't be accessed pub password: Option, } impl From for StoredFileWithPassword { fn from(old: super::v0::StoredFile) -> Self { StoredFileWithPassword { file: StoredFile { name: old.name, size: old.size, modtime: old.modtime, expiry: old.expiry, contents: old.contents.map(FileSet::from), }, password: None, } } } #[derive(Debug, Default, Deserialize, Serialize)] pub struct StoredFiles(pub HashMap); pub type State = StoredFiles; impl Schema for State { type Prev = super::v0::State; } impl From for State { fn from(old: super::v0::State) -> Self { StoredFiles( old.0 .into_iter() .map(|(k, v)| (k, StoredFileWithPassword::from(v))) .collect(), ) } } } pub use v1::State; pub type StateDb = JsonDb;