god this sucks
This commit is contained in:
parent
97498d3c65
commit
24f6052ed0
5 changed files with 331 additions and 288 deletions
|
@ -20,7 +20,7 @@ pub struct Page {
|
||||||
pub content: PageContent,
|
pub content: PageContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub enum PageContent {
|
pub enum PageContent {
|
||||||
Single {
|
Single {
|
||||||
content: Post,
|
content: Post,
|
||||||
|
@ -31,7 +31,7 @@ pub enum PageContent {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub enum CollectionKind {
|
pub enum CollectionKind {
|
||||||
Blog,
|
Blog,
|
||||||
Gallery,
|
Gallery,
|
||||||
|
|
133
src/main.rs
133
src/main.rs
|
@ -19,67 +19,86 @@ fn main() {
|
||||||
use outbound::services::site::SiteService;
|
use outbound::services::site::SiteService;
|
||||||
|
|
||||||
builder = builder.with_context_provider(|| {
|
builder = builder.with_context_provider(|| {
|
||||||
let mut store = InMemoryStore::new();
|
|
||||||
store.add_site(outbound::repository::site::SiteMetadata {
|
let store = tokio::runtime::Builder::new_multi_thread()
|
||||||
domain: "localhost".to_string(),
|
.enable_all()
|
||||||
title: "Test site".to_string(),
|
.build()
|
||||||
});
|
.unwrap()
|
||||||
store.add_page(
|
.block_on(async move {
|
||||||
"localhost",
|
let store = InMemoryStore::new();
|
||||||
domain::entities::site::Page {
|
store
|
||||||
info: domain::entities::site::PageInfo {
|
.add_site(outbound::repository::site::SiteMetadata {
|
||||||
title: "Home".to_string(),
|
domain: "localhost".to_string(),
|
||||||
name: "/".to_string(),
|
title: "Test site".to_string(),
|
||||||
order: 0,
|
})
|
||||||
},
|
.await;
|
||||||
content: domain::entities::site::PageContent::Single {
|
store
|
||||||
content: domain::entities::site::Post {
|
.add_page(
|
||||||
blocks: vec![domain::entities::site::Block::Text {
|
"localhost",
|
||||||
text: "Hello, world!".to_string(),
|
domain::entities::site::Page {
|
||||||
}],
|
info: domain::entities::site::PageInfo {
|
||||||
|
title: "Home".to_string(),
|
||||||
|
name: "/".to_string(),
|
||||||
|
order: 0,
|
||||||
|
},
|
||||||
|
content: domain::entities::site::PageContent::Single {
|
||||||
|
content: domain::entities::site::Post {
|
||||||
|
blocks: vec![domain::entities::site::Block::Text {
|
||||||
|
text: "Hello, world!".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
)
|
||||||
);
|
.await;
|
||||||
store.add_page(
|
store
|
||||||
"localhost",
|
.add_page(
|
||||||
domain::entities::site::Page {
|
"localhost",
|
||||||
info: domain::entities::site::PageInfo {
|
domain::entities::site::Page {
|
||||||
title: "Cool page".to_string(),
|
info: domain::entities::site::PageInfo {
|
||||||
name: "cool".to_string(),
|
title: "Cool page".to_string(),
|
||||||
order: 10,
|
name: "cool".to_string(),
|
||||||
},
|
order: 10,
|
||||||
content: domain::entities::site::PageContent::Collection {
|
|
||||||
kind: domain::entities::site::CollectionKind::Blog,
|
|
||||||
collection_id: "cool-posts".to_string(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
store.add_post(
|
|
||||||
"localhost",
|
|
||||||
"cool-posts",
|
|
||||||
"test_id",
|
|
||||||
domain::entities::site::Post {
|
|
||||||
blocks: vec![domain::entities::site::Block::Text {
|
|
||||||
text: "This is a cool post!".to_string(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
store.add_post(
|
|
||||||
"localhost",
|
|
||||||
"cool-posts",
|
|
||||||
"test_id_2",
|
|
||||||
domain::entities::site::Post {
|
|
||||||
blocks: vec![
|
|
||||||
domain::entities::site::Block::Text {
|
|
||||||
text: "This is another cool post!".to_string(),
|
|
||||||
},
|
},
|
||||||
domain::entities::site::Block::Text {
|
content: domain::entities::site::PageContent::Collection {
|
||||||
text: "With two blocks!".to_string(),
|
kind: domain::entities::site::CollectionKind::Blog,
|
||||||
|
collection_id: "cool-posts".to_string(),
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
},
|
)
|
||||||
);
|
.await;
|
||||||
|
store
|
||||||
|
.add_post(
|
||||||
|
"localhost",
|
||||||
|
"cool-posts",
|
||||||
|
"test_id",
|
||||||
|
domain::entities::site::Post {
|
||||||
|
blocks: vec![domain::entities::site::Block::Text {
|
||||||
|
text: "This is a cool post!".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
store
|
||||||
|
.add_post(
|
||||||
|
"localhost",
|
||||||
|
"cool-posts",
|
||||||
|
"test_id_2",
|
||||||
|
domain::entities::site::Post {
|
||||||
|
blocks: vec![
|
||||||
|
domain::entities::site::Block::Text {
|
||||||
|
text: "This is another cool post!".to_string(),
|
||||||
|
},
|
||||||
|
domain::entities::site::Block::Text {
|
||||||
|
text: "With two blocks!".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
store
|
||||||
|
});
|
||||||
|
|
||||||
Box::new(SiteService::new(store))
|
Box::new(SiteService::new(store))
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::entities::{
|
domain::entities::{
|
||||||
|
@ -13,103 +14,130 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct InMemoryStore {
|
struct InMemoryStoreData {
|
||||||
sites: HashMap<String, SiteMetadata>,
|
sites: HashMap<String, SiteMetadata>,
|
||||||
pages: HashMap<(String, String), Page>,
|
pages: HashMap<(String, String), Page>,
|
||||||
posts: HashMap<(String, String), HashMap<String, Post>>,
|
posts: HashMap<(String, String), HashMap<String, Post>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct InMemoryStore {
|
||||||
|
data: Arc<Mutex<InMemoryStoreData>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl InMemoryStore {
|
impl InMemoryStore {
|
||||||
pub fn new() -> InMemoryStore {
|
pub fn new() -> InMemoryStore {
|
||||||
InMemoryStore {
|
InMemoryStore {
|
||||||
sites: HashMap::new(),
|
data: Arc::new(Mutex::new(InMemoryStoreData {
|
||||||
pages: HashMap::new(),
|
sites: HashMap::new(),
|
||||||
posts: HashMap::new(),
|
pages: HashMap::new(),
|
||||||
|
posts: HashMap::new(),
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_site(&mut self, site: SiteMetadata) {
|
pub async fn add_site(&self, site: SiteMetadata) {
|
||||||
self.sites.insert(site.domain.clone(), site);
|
self.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.sites
|
||||||
|
.insert(site.domain.clone(), site);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_page(&mut self, site: &str, page: Page) {
|
pub async fn add_page(&self, site: &str, page: Page) {
|
||||||
self.pages
|
self.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.pages
|
||||||
.insert((site.to_string(), page.info.name.clone()), page);
|
.insert((site.to_string(), page.info.name.clone()), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_post(&mut self, site: &str, collection_id: &str, post_id: &str, post: Post) {
|
pub async fn add_post(&self, site: &str, collection_id: &str, post_id: &str, post: Post) {
|
||||||
self.posts
|
self.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.posts
|
||||||
.entry((site.to_string(), collection_id.to_string()))
|
.entry((site.to_string(), collection_id.to_string()))
|
||||||
.or_insert_with(HashMap::new)
|
.or_insert_with(HashMap::new)
|
||||||
.insert(post_id.to_string(), post);
|
.insert(post_id.to_string(), post);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn with_test_data() -> InMemoryStore {
|
pub async fn with_test_data() -> InMemoryStore {
|
||||||
use crate::domain::entities::site::{Block, PageContent, Post};
|
use crate::domain::entities::site::{Block, PageContent, Post};
|
||||||
|
|
||||||
let mut store = InMemoryStore::new();
|
let store = InMemoryStore::new();
|
||||||
store.add_site(SiteMetadata {
|
|
||||||
domain: "example.com".to_string(),
|
store
|
||||||
title: "Test site".to_string(),
|
.add_site(SiteMetadata {
|
||||||
});
|
domain: "example.com".to_string(),
|
||||||
store.add_page(
|
title: "Test site".to_string(),
|
||||||
"example.com",
|
})
|
||||||
Page {
|
.await;
|
||||||
info: PageInfo {
|
store
|
||||||
title: "Home".to_string(),
|
.add_page(
|
||||||
name: "/".to_string(),
|
"example.com",
|
||||||
order: 0,
|
Page {
|
||||||
},
|
info: PageInfo {
|
||||||
content: PageContent::Single {
|
title: "Home".to_string(),
|
||||||
content: Post {
|
name: "/".to_string(),
|
||||||
blocks: vec![Block::Text {
|
order: 0,
|
||||||
text: "Hello, world!".to_string(),
|
},
|
||||||
}],
|
content: PageContent::Single {
|
||||||
|
content: Post {
|
||||||
|
blocks: vec![Block::Text {
|
||||||
|
text: "Hello, world!".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
)
|
||||||
);
|
.await;
|
||||||
|
|
||||||
store.add_page(
|
store
|
||||||
"example.com",
|
.add_page(
|
||||||
Page {
|
"example.com",
|
||||||
info: PageInfo {
|
Page {
|
||||||
title: "About".to_string(),
|
info: PageInfo {
|
||||||
name: "/about".to_string(),
|
title: "About".to_string(),
|
||||||
order: 10,
|
name: "about".to_string(),
|
||||||
},
|
order: 10,
|
||||||
content: PageContent::Single {
|
},
|
||||||
content: Post {
|
content: PageContent::Single {
|
||||||
blocks: vec![Block::Text {
|
content: Post {
|
||||||
text: "This is the about page.".to_string(),
|
blocks: vec![Block::Text {
|
||||||
}],
|
text: "This is the about page.".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
)
|
||||||
);
|
.await;
|
||||||
|
|
||||||
store.add_post(
|
store
|
||||||
"example.com",
|
.add_post(
|
||||||
"home_posts",
|
"example.com",
|
||||||
"post_id",
|
"home_posts",
|
||||||
Post {
|
"post_id",
|
||||||
blocks: vec![Block::Text {
|
Post {
|
||||||
text: "Hello, world!".to_string(),
|
blocks: vec![Block::Text {
|
||||||
}],
|
text: "Hello, world!".to_string(),
|
||||||
},
|
}],
|
||||||
);
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
store.add_post(
|
store
|
||||||
"example.com",
|
.add_post(
|
||||||
"home_posts",
|
"example.com",
|
||||||
"post_id2",
|
"home_posts",
|
||||||
Post {
|
"post_id2",
|
||||||
blocks: vec![Block::Text {
|
Post {
|
||||||
text: "Hello again!".to_string(),
|
blocks: vec![Block::Text {
|
||||||
}],
|
text: "Hello again!".to_string(),
|
||||||
},
|
}],
|
||||||
);
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
store
|
store
|
||||||
}
|
}
|
||||||
|
@ -118,7 +146,10 @@ impl InMemoryStore {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl SiteRepository for InMemoryStore {
|
impl SiteRepository for InMemoryStore {
|
||||||
async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata> {
|
async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata> {
|
||||||
self.sites
|
self.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.sites
|
||||||
.get(domain)
|
.get(domain)
|
||||||
.ok_or(repository::site::Error::NotFound)
|
.ok_or(repository::site::Error::NotFound)
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -126,6 +157,9 @@ impl SiteRepository for InMemoryStore {
|
||||||
|
|
||||||
async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>> {
|
async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>> {
|
||||||
let mut pages = self
|
let mut pages = self
|
||||||
|
.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.pages
|
.pages
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|((site, _), _)| site == domain)
|
.filter(|((site, _), _)| site == domain)
|
||||||
|
@ -138,14 +172,30 @@ impl SiteRepository for InMemoryStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_page(&self, domain: &str, page: &str) -> Result<Page> {
|
async fn get_page(&self, domain: &str, page: &str) -> Result<Page> {
|
||||||
self.pages
|
self.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.pages
|
||||||
.get(&(domain.to_string(), page.to_string()))
|
.get(&(domain.to_string(), page.to_string()))
|
||||||
.ok_or(repository::site::Error::NotFound)
|
.ok_or(repository::site::Error::NotFound)
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_page(&self, domain: &str, page: Page) -> Result<()> {
|
||||||
|
self.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.pages
|
||||||
|
.insert((domain.to_string(), page.info.name.clone()), page);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_post(&self, domain: &str, collection_id: &str, post_id: &str) -> Result<Post> {
|
async fn get_post(&self, domain: &str, collection_id: &str, post_id: &str) -> Result<Post> {
|
||||||
self.posts
|
self.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.posts
|
||||||
.get(&(domain.to_string(), collection_id.to_string()))
|
.get(&(domain.to_string(), collection_id.to_string()))
|
||||||
.and_then(|posts| posts.get(post_id))
|
.and_then(|posts| posts.get(post_id))
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -159,6 +209,9 @@ impl SiteRepository for InMemoryStore {
|
||||||
cursor: CursorOptions<String>,
|
cursor: CursorOptions<String>,
|
||||||
) -> Result<Paginated<Post, String>> {
|
) -> Result<Paginated<Post, String>> {
|
||||||
let posts = self
|
let posts = self
|
||||||
|
.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.posts
|
.posts
|
||||||
.get(&(domain.to_string(), collection_id.to_string()))
|
.get(&(domain.to_string(), collection_id.to_string()))
|
||||||
.ok_or(repository::site::Error::NotFound)
|
.ok_or(repository::site::Error::NotFound)
|
||||||
|
@ -220,14 +273,10 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_site_by_domain_works() {
|
async fn get_site_by_domain_works() {
|
||||||
let mut store = InMemoryStore::new();
|
let store = InMemoryStore::with_test_data().await;
|
||||||
store.add_site(SiteMetadata {
|
let info = store.get_site_by_domain("example.com").await.unwrap();
|
||||||
domain: "test.com".to_string(),
|
assert_eq!(info.domain, "example.com");
|
||||||
title: "Some test site".to_string(),
|
assert_eq!(info.title, "Test site");
|
||||||
});
|
|
||||||
let info = store.get_site_by_domain("test.com").await.unwrap();
|
|
||||||
assert_eq!(info.domain, "test.com");
|
|
||||||
assert_eq!(info.title, "Some test site");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -243,77 +292,32 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_pages_for_site_works() {
|
async fn get_pages_for_site_works() {
|
||||||
let mut store = InMemoryStore::new();
|
let store = InMemoryStore::with_test_data().await;
|
||||||
store.add_site(SiteMetadata {
|
|
||||||
domain: "example.com".to_string(),
|
|
||||||
title: "Test site".to_string(),
|
|
||||||
});
|
|
||||||
store.add_page(
|
|
||||||
"example.com",
|
|
||||||
Page {
|
|
||||||
info: PageInfo {
|
|
||||||
title: "Home".to_string(),
|
|
||||||
name: "/".to_string(),
|
|
||||||
order: 0,
|
|
||||||
},
|
|
||||||
content: PageContent::Single {
|
|
||||||
content: Post { blocks: vec![] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
store.add_page(
|
|
||||||
"example.com",
|
|
||||||
Page {
|
|
||||||
info: PageInfo {
|
|
||||||
title: "Cool page".to_string(),
|
|
||||||
name: "cool".to_string(),
|
|
||||||
order: 10,
|
|
||||||
},
|
|
||||||
content: PageContent::Single {
|
|
||||||
content: Post { blocks: vec![] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let pages = store.get_pages_for_site("example.com").await.unwrap();
|
let pages = store.get_pages_for_site("example.com").await.unwrap();
|
||||||
assert_eq!(pages.len(), 2);
|
assert_eq!(pages.len(), 2);
|
||||||
assert_eq!(pages[0].title, "Home");
|
assert_eq!(pages[0].title, "Home");
|
||||||
assert_eq!(pages[0].name, "/");
|
assert_eq!(pages[0].name, "/");
|
||||||
assert_eq!(pages[1].title, "Cool page");
|
assert_eq!(pages[1].title, "About");
|
||||||
assert_eq!(pages[1].name, "cool");
|
assert_eq!(pages[1].name, "about");
|
||||||
assert!(pages[0].order < pages[1].order);
|
assert!(pages[0].order < pages[1].order);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_page_works() {
|
async fn get_page_works() {
|
||||||
let mut store = InMemoryStore::new();
|
let store = InMemoryStore::with_test_data().await;
|
||||||
store.add_page(
|
|
||||||
"example.com",
|
|
||||||
Page {
|
|
||||||
info: PageInfo {
|
|
||||||
title: "Home".to_string(),
|
|
||||||
name: "/".to_string(),
|
|
||||||
order: 0,
|
|
||||||
},
|
|
||||||
content: PageContent::Single {
|
|
||||||
content: Post {
|
|
||||||
blocks: vec![Block::Text {
|
|
||||||
text: "Hello, world!".to_string(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let page = store.get_page("example.com", "/").await.unwrap();
|
let page = store.get_page("example.com", "/").await.unwrap();
|
||||||
let PageContent::Single { content } = page.content else {
|
assert_eq!(page.info.title, "Home");
|
||||||
panic!("page content must be a single text block")
|
assert_eq!(page.info.name, "/");
|
||||||
};
|
assert_eq!(
|
||||||
let Post { blocks } = content;
|
page.content,
|
||||||
assert_eq!(blocks.len(), 1);
|
PageContent::Single {
|
||||||
|
content: Post {
|
||||||
let Block::Text { text } = &blocks[0] else {
|
blocks: vec![Block::Text {
|
||||||
panic!("page content must be a single text block")
|
text: "Hello, world!".to_string()
|
||||||
};
|
}]
|
||||||
assert_eq!(text, "Hello, world!");
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -329,29 +333,26 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_post_works() {
|
async fn get_post_works() {
|
||||||
let mut store = InMemoryStore::new();
|
let expected_post = Post {
|
||||||
store.add_post(
|
blocks: vec![Block::Text {
|
||||||
"example.com",
|
text: "Hello, world!".to_string(),
|
||||||
"home_posts",
|
}],
|
||||||
"test_id",
|
};
|
||||||
Post {
|
let store = InMemoryStore::new();
|
||||||
blocks: vec![Block::Text {
|
store
|
||||||
text: "Hello, world!".to_string(),
|
.add_post(
|
||||||
}],
|
"example.com",
|
||||||
},
|
"home_posts",
|
||||||
);
|
"test_id",
|
||||||
|
expected_post.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let post = store
|
let post = store
|
||||||
.get_post("example.com", "home_posts", "test_id")
|
.get_post("example.com", "home_posts", "test_id")
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let Post { blocks } = post;
|
assert_eq!(post, expected_post);
|
||||||
assert_eq!(blocks.len(), 1);
|
|
||||||
|
|
||||||
let Block::Text { text } = &blocks[0] else {
|
|
||||||
panic!("page content must be a single text block")
|
|
||||||
};
|
|
||||||
assert_eq!(text, "Hello, world!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -367,59 +368,31 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_posts_for_page_works() {
|
async fn get_posts_for_page_works() {
|
||||||
let mut store = InMemoryStore::new();
|
let store = InMemoryStore::with_test_data().await;
|
||||||
store.add_post(
|
|
||||||
"example.com",
|
|
||||||
"home_posts",
|
|
||||||
"test_id",
|
|
||||||
Post {
|
|
||||||
blocks: vec![Block::Text {
|
|
||||||
text: "Hello, world!".to_string(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let posts = store
|
let posts = store
|
||||||
.get_posts_for_collection("example.com", "home_posts", CursorOptions::none(10))
|
.get_posts_for_collection("example.com", "home_posts", CursorOptions::none(10))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(posts.data.len(), 1);
|
assert_eq!(posts.data.len(), 2);
|
||||||
assert_eq!(posts.next, None);
|
assert_eq!(posts.next, None);
|
||||||
|
assert_eq!(
|
||||||
let Post { blocks } = &posts.data[0];
|
posts.data[0],
|
||||||
let Block::Text { text } = &blocks[0] else {
|
Post {
|
||||||
panic!("page content must be a single text block")
|
blocks: vec![Block::Text {
|
||||||
};
|
text: "Hello, world!".to_string()
|
||||||
assert_eq!(text, "Hello, world!");
|
}]
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_posts_for_page_with_cursor_works() {
|
async fn get_posts_for_page_with_cursor_works() {
|
||||||
let mut store = InMemoryStore::new();
|
let store = InMemoryStore::with_test_data().await;
|
||||||
store.add_post(
|
|
||||||
"example.com",
|
|
||||||
"home_posts",
|
|
||||||
"test_id",
|
|
||||||
Post {
|
|
||||||
blocks: vec![Block::Text {
|
|
||||||
text: "Hello, world!".to_string(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
store.add_post(
|
|
||||||
"example.com",
|
|
||||||
"home_posts",
|
|
||||||
"test_id_2",
|
|
||||||
Post {
|
|
||||||
blocks: vec![Block::Text {
|
|
||||||
text: "Hello, world!".to_string(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let posts = store
|
let posts = store
|
||||||
.get_posts_for_collection(
|
.get_posts_for_collection(
|
||||||
"example.com",
|
"example.com",
|
||||||
"home_posts",
|
"home_posts",
|
||||||
CursorOptions::after("test_id".to_string(), 10),
|
CursorOptions::after("post_id".to_string(), 10),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -429,33 +402,13 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_posts_for_page_returns_cursor() {
|
async fn get_posts_for_page_returns_cursor() {
|
||||||
let mut store = InMemoryStore::new();
|
let store = InMemoryStore::with_test_data().await;
|
||||||
store.add_post(
|
|
||||||
"example.com",
|
|
||||||
"home_posts",
|
|
||||||
"test_id",
|
|
||||||
Post {
|
|
||||||
blocks: vec![Block::Text {
|
|
||||||
text: "Hello, world!".to_string(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
store.add_post(
|
|
||||||
"example.com",
|
|
||||||
"home_posts",
|
|
||||||
"test_id_2",
|
|
||||||
Post {
|
|
||||||
blocks: vec![Block::Text {
|
|
||||||
text: "Hello, world!".to_string(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let posts = store
|
let posts = store
|
||||||
.get_posts_for_collection("example.com", "home_posts", CursorOptions::none(1))
|
.get_posts_for_collection("example.com", "home_posts", CursorOptions::none(1))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(posts.data.len(), 1);
|
assert_eq!(posts.data.len(), 1);
|
||||||
assert_eq!(posts.next, Some("test_id".to_string()));
|
assert_eq!(posts.next, Some("post_id".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -470,4 +423,33 @@ mod tests {
|
||||||
repository::site::Error::NotFound
|
repository::site::Error::NotFound
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn set_page_works() {
|
||||||
|
let store = InMemoryStore::new();
|
||||||
|
let content = PageContent::Single {
|
||||||
|
content: Post {
|
||||||
|
blocks: vec![Block::Text {
|
||||||
|
text: "hello!".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let _ = store
|
||||||
|
.set_page(
|
||||||
|
"example.com",
|
||||||
|
Page {
|
||||||
|
info: PageInfo {
|
||||||
|
title: "New page".to_string(),
|
||||||
|
name: "new-page".to_string(),
|
||||||
|
order: 0,
|
||||||
|
},
|
||||||
|
content: content.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let page = store.get_page("example.com", "new-page").await.unwrap();
|
||||||
|
assert_eq!(page.info.title, "New page");
|
||||||
|
assert_eq!(page.info.name, "new-page");
|
||||||
|
assert_eq!(page.content, content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,10 @@ pub struct SiteMetadata {
|
||||||
pub trait SiteRepository: Send + Sync + 'static {
|
pub trait SiteRepository: Send + Sync + 'static {
|
||||||
async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata>;
|
async fn get_site_by_domain(&self, domain: &str) -> Result<SiteMetadata>;
|
||||||
async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>>;
|
async fn get_pages_for_site(&self, domain: &str) -> Result<Vec<PageInfo>>;
|
||||||
|
|
||||||
async fn get_page(&self, domain: &str, name: &str) -> Result<Page>;
|
async fn get_page(&self, domain: &str, name: &str) -> Result<Page>;
|
||||||
|
async fn set_page(&self, domain: &str, page: Page) -> Result<()>;
|
||||||
|
|
||||||
async fn get_post(&self, domain: &str, collection_id: &str, id: &str) -> Result<Post>;
|
async fn get_post(&self, domain: &str, collection_id: &str, id: &str) -> Result<Post>;
|
||||||
async fn get_posts_for_collection(
|
async fn get_posts_for_collection(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -74,6 +74,13 @@ impl SiteService {
|
||||||
|
|
||||||
Ok(posts)
|
Ok(posts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn set_page(&self, domain: &str, page: Page) -> Result<()> {
|
||||||
|
let info = self.site_repository.get_site_by_domain(domain).await?;
|
||||||
|
self.site_repository.set_page(&info.domain, page).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
@ -100,13 +107,13 @@ impl From<repository::site::Error> for Error {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Error, SiteService};
|
use super::{Error, SiteService};
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::entities::site::{Block, PageContent},
|
domain::entities::site::{Block, Page, PageContent, PageInfo, Post},
|
||||||
outbound::repository::adapters::memory::InMemoryStore,
|
outbound::repository::adapters::memory::InMemoryStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_site_info() {
|
async fn gets_site_info() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let info = service.get_site("example.com").await.unwrap();
|
let info = service.get_site("example.com").await.unwrap();
|
||||||
assert_eq!(info.domain, "example.com");
|
assert_eq!(info.domain, "example.com");
|
||||||
assert_eq!(info.title, "Test site");
|
assert_eq!(info.title, "Test site");
|
||||||
|
@ -115,7 +122,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_nonexistent_site() {
|
async fn gets_nonexistent_site() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let result = service.get_site("nonexistent.com").await;
|
let result = service.get_site("nonexistent.com").await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(matches!(result.err().unwrap(), Error::NotFound));
|
assert!(matches!(result.err().unwrap(), Error::NotFound));
|
||||||
|
@ -123,7 +130,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_page_data() {
|
async fn gets_page_data() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let page = service.get_page("example.com", "/").await.unwrap();
|
let page = service.get_page("example.com", "/").await.unwrap();
|
||||||
|
|
||||||
assert_eq!(page.info.title, "Home");
|
assert_eq!(page.info.title, "Home");
|
||||||
|
@ -146,7 +153,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_nonexistent_page() {
|
async fn gets_nonexistent_page() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let result = service.get_page("example.com", "nonexistent").await;
|
let result = service.get_page("example.com", "nonexistent").await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(matches!(result.err().unwrap(), Error::NotFound));
|
assert!(matches!(result.err().unwrap(), Error::NotFound));
|
||||||
|
@ -154,7 +161,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_a_single_post() {
|
async fn gets_a_single_post() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let post = service
|
let post = service
|
||||||
.get_post("example.com", "home_posts", "post_id")
|
.get_post("example.com", "home_posts", "post_id")
|
||||||
.await
|
.await
|
||||||
|
@ -169,7 +176,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_nonexistent_post() {
|
async fn gets_nonexistent_post() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let result = service
|
let result = service
|
||||||
.get_post("example.com", "home_posts", "nonexistent")
|
.get_post("example.com", "home_posts", "nonexistent")
|
||||||
.await;
|
.await;
|
||||||
|
@ -179,7 +186,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_posts() {
|
async fn gets_posts() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let posts = service
|
let posts = service
|
||||||
.get_posts("example.com", "home_posts", None)
|
.get_posts("example.com", "home_posts", None)
|
||||||
.await
|
.await
|
||||||
|
@ -190,7 +197,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_posts_with_cursor() {
|
async fn gets_posts_with_cursor() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let posts = service
|
let posts = service
|
||||||
.get_posts("example.com", "home_posts", Some("post_id".to_string()))
|
.get_posts("example.com", "home_posts", Some("post_id".to_string()))
|
||||||
.await
|
.await
|
||||||
|
@ -201,9 +208,41 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gets_nonexistent_posts() {
|
async fn gets_nonexistent_posts() {
|
||||||
let service = SiteService::new(InMemoryStore::with_test_data());
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
let result = service.get_posts("example.com", "nonexistent", None).await;
|
let result = service.get_posts("example.com", "nonexistent", None).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(matches!(result.err().unwrap(), Error::NotFound));
|
assert!(matches!(result.err().unwrap(), Error::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sets_a_page() {
|
||||||
|
let content = PageContent::Single {
|
||||||
|
content: Post {
|
||||||
|
blocks: vec![Block::Text {
|
||||||
|
text: "Hello my new page!".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
|
service
|
||||||
|
.set_page(
|
||||||
|
"example.com",
|
||||||
|
Page {
|
||||||
|
info: PageInfo {
|
||||||
|
name: "new_page".to_string(),
|
||||||
|
title: "New page".to_string(),
|
||||||
|
order: 10,
|
||||||
|
},
|
||||||
|
content: content.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let page = service.get_page("example.com", "new_page").await.unwrap();
|
||||||
|
assert_eq!(page.info.title, "New page");
|
||||||
|
assert_eq!(page.info.name, "new_page");
|
||||||
|
assert_eq!(page.content, content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue