use crate::add_template;
use crate::content::add_obj;
use anyhow::bail;
use fs_err::File;
use ordinary_config::TemplateRefFieldBind::Segment;
use ordinary_config::{
AssetsConfig, Content, ContentDefinition, ContentObjectLifecycle, FragmentsConfig, Global,
LifecycleBeforeAfterScripts, OrdinaryConfig, TemplateConfig, TemplateFfi,
TemplateFfiSerialization, TemplateFfiVersion, TemplateRef, TemplateRefField, TopLevelLifecycle,
};
use ordinary_types::{ContentField, ContentObject, Field, Kind, TimeUnit};
use serde_json::Value;
use std::io::Write;
use std::path::Path;
use std::time::SystemTime;
use tracing::instrument;
const GITIGNORE: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/.gitignore"
));
const README: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/README.md"
));
const HOOKS_POSTS_SRC_MAIN_RS: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/hooks/posts/src/main.rs"
));
const HOOKS_POSTS_CARGO_TOML: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/hooks/posts/Cargo.toml"
));
const TMP_CSS: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/assets/themes/tmp.css"
));
const FAVICON: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/assets/favicon.svg"
));
const INDEX: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/templates/index.html"
));
const POST: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/templates/post.html"
));
const SITEMAP: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/templates/sitemap.xml"
));
const ROBOTS: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/templates/robots.txt"
));
const RSS: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/templates/rss.xml"
));
const HEADER: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/fragments/header.html"
));
const FOOTER: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/project/fragments/footer.html"
));
#[allow(clippy::too_many_lines)]
#[instrument(skip_all, err)]
pub fn new(path: &str, domain: &str) -> anyhow::Result<()> {
let path = Path::new(path).join(domain);
if path.exists() {
bail!("project already exists");
}
tracing::info!("creating project at {:?}", path);
fs_err::create_dir_all(&path)?;
let hooks_posts_path = path.join("hooks").join("posts");
fs_err::create_dir_all(hooks_posts_path.join("src"))?;
let mut file = File::create(hooks_posts_path.join("src").join("main.rs"))?;
file.write_all(HOOKS_POSTS_SRC_MAIN_RS)?;
file.flush()?;
let mut file = File::create(hooks_posts_path.join("Cargo.toml"))?;
file.write_all(HOOKS_POSTS_CARGO_TOML)?;
file.flush()?;
let app_config = OrdinaryConfig {
lifecycle: Some(TopLevelLifecycle {
before_all: Some(vec![
[
"cargo",
"build",
"--release",
"--manifest-path",
"hooks/posts/Cargo.toml",
]
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>(),
]),
build: Some(LifecycleBeforeAfterScripts {
before: Some(vec![
[
"hooks/posts/target/release/posts",
"before_build",
"content/posts",
]
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>(),
]),
after: None,
}),
}),
domain: domain.to_string(),
version: "0.1.0".into(),
contacts: Some(vec![]),
storage_size: Some(5_000_000),
port: Some(4433),
redirect_port: Some(8080),
globals: Some(vec![Global {
name: "author".to_string(),
kind: Kind::String,
value: "Your Name".into(),
}]),
fragments: Some(FragmentsConfig {
dir_path: "fragments".to_string(),
}),
error: None,
assets: Some(AssetsConfig {
dir_path: Some("assets".to_string()),
..Default::default()
}),
content: Some(Content {
file_path: "./content.json".to_string(),
definitions: vec![ContentDefinition {
idx: 0,
name: "post".to_string(),
fields: vec![
Field {
idx: 0,
name: "slug".to_string(),
kind: Kind::String,
indexed: Some(true),
..Default::default()
},
Field {
idx: 1,
name: "title".to_string(),
kind: Kind::String,
..Default::default()
},
Field {
idx: 2,
name: "date".to_string(),
kind: Kind::Timestamp {
unit: TimeUnit::Seconds,
},
..Default::default()
},
Field {
idx: 3,
name: "body".to_string(),
kind: Kind::Markdown,
..Default::default()
},
],
lifecycle: Some(ContentObjectLifecycle {
before_all: None,
on_add: Some(LifecycleBeforeAfterScripts {
after: Some(vec![
[
"hooks/posts/target/release/posts",
"after_add",
"content/posts",
]
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>(),
]),
before: None,
}),
on_edit: Some(LifecycleBeforeAfterScripts {
after: Some(vec![
[
"hooks/posts/target/release/posts",
"after_edit",
"content/posts",
]
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>(),
]),
before: None,
}),
on_delete: Some(LifecycleBeforeAfterScripts {
after: Some(vec![
[
"hooks/posts/target/release/posts",
"after_delete",
"content/posts",
]
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>(),
]),
before: None,
}),
}),
}],
..Default::default()
}),
templates: Some(vec![
TemplateConfig {
ffi: TemplateFfi {
version: TemplateFfiVersion::V1,
serialization: TemplateFfiSerialization::FlexBufferVector,
},
idx: 0,
name: "index".to_string(),
mime: "text/html".to_string(),
path: Some("./templates/index.html".to_string()),
route: "/".to_string(),
globals: Some(vec!["author".to_string()]),
content: Some(vec![TemplateRef {
idx: 0,
name: "post".to_string(),
fields: vec![
TemplateRefField {
idx: 0,
name: "slug".to_string(),
..Default::default()
},
TemplateRefField {
idx: 1,
name: "title".to_string(),
..Default::default()
},
TemplateRefField {
idx: 2,
name: "date".to_string(),
..Default::default()
},
],
all: Some("posts".to_string()),
}]),
..Default::default()
},
TemplateConfig {
ffi: TemplateFfi {
version: TemplateFfiVersion::V1,
serialization: TemplateFfiSerialization::FlexBufferVector,
},
idx: 1,
name: "post".to_string(),
mime: "text/html".to_string(),
path: Some("./templates/post.html".to_string()),
route: "/posts/{slug}".to_string(),
globals: Some(vec!["author".to_string()]),
content: Some(vec![TemplateRef {
idx: 0,
name: "post".to_string(),
fields: vec![
TemplateRefField {
idx: 0,
name: "slug".to_string(),
bind: Some(Segment {
name: "slug".to_string(),
expression: None,
}),
..Default::default()
},
TemplateRefField {
idx: 1,
name: "title".to_string(),
..Default::default()
},
TemplateRefField {
idx: 2,
name: "date".to_string(),
..Default::default()
},
TemplateRefField {
idx: 3,
name: "body".to_string(),
..Default::default()
},
],
all: None,
}]),
..Default::default()
},
]),
..Default::default()
};
let ordinary_json = serde_json::to_string_pretty(&app_config)?;
let mut file = File::create(path.join("ordinary.json"))?;
file.write_all(ordinary_json.as_bytes())?;
file.flush()?;
let mut file = File::create(path.join("content.json"))?;
file.write_all(b"[]")?;
file.flush()?;
let first_post_json = serde_json::to_string_pretty(&ContentObject {
uuid: uuid::Uuid::new_v4().to_string(),
instance_of: "post".to_string(),
fields: vec.".to_string(),
),
},
],
})?;
if let Some(path_str) = path.to_str() {
add_obj(path_str, &first_post_json)?;
}
let mut file = File::create(path.join(".gitignore"))?;
file.write_all(GITIGNORE)?;
file.flush()?;
let mut file = File::create(path.join("README.md"))?;
file.write_all(README)?;
file.flush()?;
let assets_dir = path.join("assets");
let themes_dir = assets_dir.join("themes");
fs_err::create_dir_all(&themes_dir)?;
let mut file = File::create(themes_dir.join("tmp.css"))?;
file.write_all(TMP_CSS)?;
file.flush()?;
let mut file = File::create(assets_dir.join("favicon.svg"))?;
file.write_all(FAVICON)?;
file.flush()?;
let fragments_dir = path.join("fragments");
fs_err::create_dir_all(&fragments_dir)?;
let mut header_file = File::create(fragments_dir.join("header.html"))?;
header_file.write_all(HEADER)?;
header_file.flush()?;
let mut footer_file = File::create(fragments_dir.join("footer.html"))?;
footer_file.write_all(FOOTER)?;
footer_file.flush()?;
let html_globals = Some(vec!["author".to_string()]);
if let Some(path_str) = path.to_str() {
let templates_dir = path.join("templates");
fs_err::create_dir_all(&templates_dir)?;
let mut index_file = File::create(templates_dir.join("index.html"))?;
index_file.write_all(INDEX)?;
index_file.flush()?;
let mut post_file = File::create(templates_dir.join("post.html"))?;
post_file.write_all(POST)?;
post_file.flush()?;
add_template(
path_str,
"error",
"/error",
"text/html",
r#"
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
<link rel="stylesheet" href="/assets/themes/tmp.css">
"#,
r#"{% include "fragments/header.html" %}"#,
r#"{% include "fragments/footer.html" %}"#,
true,
html_globals,
None,
)?;
add_template(
path_str,
"sitemap",
"/sitemap.xml",
"text/xml",
"",
"",
"",
false,
None,
Some(vec![TemplateRef {
idx: 0,
name: "post".to_string(),
fields: vec![
TemplateRefField {
idx: 0,
name: "slug".to_string(),
..Default::default()
},
TemplateRefField {
idx: 1,
name: "date".to_string(),
..Default::default()
},
],
all: Some("posts".to_string()),
}]),
)?;
let mut sitemap_file = File::create(templates_dir.join("sitemap.xml"))?;
sitemap_file.write_all(SITEMAP)?;
sitemap_file.flush()?;
add_template(
path_str,
"robots",
"/robots.txt",
"text/plain",
"",
"",
"",
false,
None,
None,
)?;
let mut robots_file = File::create(templates_dir.join("robots.txt"))?;
robots_file.write_all(ROBOTS)?;
robots_file.flush()?;
add_template(
path_str,
"rss",
"/rss",
"application/rss+xml",
"",
"",
"",
false,
Some(vec!["author".to_string()]),
Some(vec![TemplateRef {
idx: 0,
name: "post".to_string(),
fields: vec![
TemplateRefField {
idx: 0,
name: "slug".to_string(),
..Default::default()
},
TemplateRefField {
idx: 1,
name: "title".to_string(),
..Default::default()
},
TemplateRefField {
idx: 2,
name: "date".to_string(),
..Default::default()
},
],
all: Some("posts".to_string()),
}]),
)?;
let mut robots_file = File::create(templates_dir.join("rss.xml"))?;
robots_file.write_all(RSS)?;
robots_file.flush()?;
}
Ok(())
}