#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
mod handlers;
mod store;
use cratefield_core::{
Config, ConfigError, Migrations, Module, ModuleConfig, ModuleContext, Port, SqlMigration,
};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub(crate) enum Collections {
List(Vec<String>),
Any,
}
#[derive(Debug, Clone)]
pub(crate) struct Settings {
pub collections: Collections,
}
const MIGRATION_INIT: SqlMigration = SqlMigration {
id: "0001",
name: "init",
sql: include_str!("../migrations/sqlite/0001_init.sql"),
};
pub struct Cms {
settings: Settings,
}
impl Default for Cms {
fn default() -> Self {
Self::new()
}
}
impl Cms {
#[must_use]
pub fn new() -> Self {
Self {
settings: Settings {
collections: Collections::List(Vec::new()),
},
}
}
#[must_use]
pub fn collections(mut self, collections: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.settings.collections =
Collections::List(collections.into_iter().map(Into::into).collect());
self
}
#[must_use]
pub fn any_collection(mut self) -> Self {
self.settings.collections = Collections::Any;
self
}
}
fn is_slug(s: &str) -> bool {
!s.is_empty()
&& s.len() <= 64
&& s.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
&& !s.starts_with('-')
&& !s.ends_with('-')
&& !s.contains("--")
}
impl Module for Cms {
fn name(&self) -> &'static str {
"cms"
}
fn version(&self) -> &'static str {
env!("CARGO_PKG_VERSION")
}
fn requires(&self) -> &'static [Port] {
&[Port::Db]
}
fn tables(&self) -> &'static [&'static str] {
&["cms_item", "cms_revision"]
}
fn migrations(&self) -> Migrations {
const MIGRATIONS: [SqlMigration; 1] = [MIGRATION_INIT];
Migrations {
sqlite: &MIGRATIONS,
postgres: &[],
}
}
fn validate_config(&self, cfg: &dyn Config) -> Result<(), ConfigError> {
let module = ModuleConfig::new("cms", cfg);
let mut errors = ConfigError::default();
if let Some(list) = cfg.get(&module.key("COLLECTIONS"))
&& list != "*"
&& list.split(',').map(str::trim).all(|slug| !is_slug(slug))
{
errors.push(format!(
"cms: {} must be a comma-separated list of kebab-case slugs or `*`",
module.key("COLLECTIONS")
));
}
errors.into_result()
}
fn router(&self, ctx: ModuleContext) -> axum::Router {
handlers::router(Arc::new(ctx), self.settings.clone())
}
fn surface(&self) -> cratefield_core::Surface {
handlers::surface(&self.settings)
}
}