more CRUDdy bits
This commit is contained in:
parent
0f79975ad6
commit
7a4f3b15b7
4 changed files with 174 additions and 28 deletions
|
@ -31,11 +31,13 @@ async fn setup_inmem_store() -> outbound::repository::adapters::memory::InMemory
|
||||||
|
|
||||||
let store = outbound::repository::adapters::memory::InMemoryStore::new();
|
let store = outbound::repository::adapters::memory::InMemoryStore::new();
|
||||||
store
|
store
|
||||||
.add_site(outbound::repository::site::SiteMetadata {
|
.create_site(outbound::repository::site::SiteMetadata {
|
||||||
domain: "localhost".to_string(),
|
domain: "localhost".to_string(),
|
||||||
title: "Test site".to_string(),
|
title: "Test site".to_string(),
|
||||||
})
|
})
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
store
|
store
|
||||||
.set_page(
|
.set_page(
|
||||||
"localhost",
|
"localhost",
|
||||||
|
@ -56,6 +58,7 @@ async fn setup_inmem_store() -> outbound::repository::adapters::memory::InMemory
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
store
|
store
|
||||||
.set_page(
|
.set_page(
|
||||||
"localhost",
|
"localhost",
|
||||||
|
@ -73,6 +76,7 @@ async fn setup_inmem_store() -> outbound::repository::adapters::memory::InMemory
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
store
|
store
|
||||||
.add_post(
|
.add_post(
|
||||||
"localhost",
|
"localhost",
|
||||||
|
@ -85,6 +89,7 @@ async fn setup_inmem_store() -> outbound::repository::adapters::memory::InMemory
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
store
|
store
|
||||||
.add_post(
|
.add_post(
|
||||||
"localhost",
|
"localhost",
|
||||||
|
|
|
@ -36,14 +36,6 @@ impl InMemoryStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_site(&self, site: SiteMetadata) {
|
|
||||||
self.data
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.sites
|
|
||||||
.insert(site.domain.clone(), site);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_post(&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.data
|
self.data
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -61,11 +53,12 @@ impl InMemoryStore {
|
||||||
let store = InMemoryStore::new();
|
let store = InMemoryStore::new();
|
||||||
|
|
||||||
store
|
store
|
||||||
.add_site(SiteMetadata {
|
.create_site(SiteMetadata {
|
||||||
domain: "example.com".to_string(),
|
domain: "example.com".to_string(),
|
||||||
title: "Test site".to_string(),
|
title: "Test site".to_string(),
|
||||||
})
|
})
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
store
|
store
|
||||||
.set_page(
|
.set_page(
|
||||||
|
@ -167,6 +160,21 @@ impl SiteRepository for InMemoryStore {
|
||||||
Ok(pages)
|
Ok(pages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_site(&self, site: SiteMetadata) -> Result<()> {
|
||||||
|
// Check for existing site
|
||||||
|
if self.data.lock().await.sites.contains_key(&site.domain) {
|
||||||
|
return Err(repository::site::Error::Conflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.sites
|
||||||
|
.insert(site.domain.clone(), site);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_page(&self, domain: &str, page: &str) -> Result<Page> {
|
async fn get_page(&self, domain: &str, page: &str) -> Result<Page> {
|
||||||
self.data
|
self.data
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -187,6 +195,21 @@ impl SiteRepository for InMemoryStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn delete_page(&self, domain: &str, page: &str) -> Result<()> {
|
||||||
|
let deleted = self
|
||||||
|
.data
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.pages
|
||||||
|
.remove(&(domain.to_string(), page.to_string()));
|
||||||
|
|
||||||
|
if deleted.is_none() {
|
||||||
|
return Err(repository::site::Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.data
|
self.data
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -259,7 +282,10 @@ mod tests {
|
||||||
cursor::CursorOptions,
|
cursor::CursorOptions,
|
||||||
site::{Block, Page, PageContent, PageInfo, Post},
|
site::{Block, Page, PageContent, PageInfo, Post},
|
||||||
},
|
},
|
||||||
outbound::repository::{self, site::SiteRepository},
|
outbound::repository::{
|
||||||
|
self,
|
||||||
|
site::{SiteMetadata, SiteRepository},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::InMemoryStore;
|
use super::InMemoryStore;
|
||||||
|
@ -445,4 +471,57 @@ mod tests {
|
||||||
assert_eq!(page.info.name, "new-page");
|
assert_eq!(page.info.name, "new-page");
|
||||||
assert_eq!(page.content, content);
|
assert_eq!(page.content, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn delete_page_works() {
|
||||||
|
let store = InMemoryStore::with_test_data().await;
|
||||||
|
let _ = store.delete_page("example.com", "/").await.unwrap();
|
||||||
|
let result = store.get_page("example.com", "/").await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(
|
||||||
|
result.err().unwrap(),
|
||||||
|
repository::site::Error::NotFound
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn delete_page_for_nonexistent_fails() {
|
||||||
|
let store = InMemoryStore::new();
|
||||||
|
let result = store.delete_page("example.com", "/").await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(
|
||||||
|
result.err().unwrap(),
|
||||||
|
repository::site::Error::NotFound
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn creates_a_site() {
|
||||||
|
let store = InMemoryStore::new();
|
||||||
|
let _ = store
|
||||||
|
.create_site(SiteMetadata {
|
||||||
|
domain: "example.com".to_string(),
|
||||||
|
title: "Test site".to_string(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
let info = store.get_site_by_domain("example.com").await.unwrap();
|
||||||
|
assert_eq!(info.domain, "example.com");
|
||||||
|
assert_eq!(info.title, "Test site");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn doesnt_overwrite_existing_site() {
|
||||||
|
let store = InMemoryStore::with_test_data().await;
|
||||||
|
let result = store
|
||||||
|
.create_site(SiteMetadata {
|
||||||
|
domain: "example.com".to_string(),
|
||||||
|
title: "Test site".to_string(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(
|
||||||
|
result.err().unwrap(),
|
||||||
|
repository::site::Error::Conflict
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,11 @@ 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 create_site(&self, site: SiteMetadata) -> Result<()>;
|
||||||
|
|
||||||
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 set_page(&self, domain: &str, page: Page) -> Result<()>;
|
||||||
|
async fn delete_page(&self, domain: &str, page: &str) -> 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(
|
||||||
|
@ -36,6 +39,9 @@ pub enum Error {
|
||||||
#[error("resource not found")]
|
#[error("resource not found")]
|
||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
|
#[error("a resource with the same identifier already exists")]
|
||||||
|
Conflict,
|
||||||
|
|
||||||
#[error("the server encountered an error: {0}")]
|
#[error("the server encountered an error: {0}")]
|
||||||
ServerError(String),
|
ServerError(String),
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,10 @@ use crate::{
|
||||||
cursor::{CursorOptions, Paginated},
|
cursor::{CursorOptions, Paginated},
|
||||||
site::{Page, Post, SiteInfo},
|
site::{Page, Post, SiteInfo},
|
||||||
},
|
},
|
||||||
outbound::repository::{self, site::SiteRepository},
|
outbound::repository::{
|
||||||
|
self,
|
||||||
|
site::{SiteMetadata, SiteRepository},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -36,18 +39,34 @@ impl SiteService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn create_site(&self, site: SiteMetadata) -> Result<()> {
|
||||||
|
self.site_repository.create_site(site).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_page(&self, domain: &str, name: &str) -> Result<Page> {
|
pub async fn get_page(&self, domain: &str, name: &str) -> Result<Page> {
|
||||||
let info = self.site_repository.get_site_by_domain(domain).await?;
|
let page = self.site_repository.get_page(domain, name).await?;
|
||||||
let page = self.site_repository.get_page(&info.domain, name).await?;
|
|
||||||
|
|
||||||
Ok(page)
|
Ok(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn set_page(&self, domain: &str, page: Page) -> Result<()> {
|
||||||
|
self.site_repository.set_page(domain, page).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_page(&self, domain: &str, page: &str) -> Result<()> {
|
||||||
|
self.site_repository.delete_page(domain, page).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_post(&self, domain: &str, collection_id: &str, id: &str) -> Result<Post> {
|
pub async fn get_post(&self, domain: &str, collection_id: &str, id: &str) -> Result<Post> {
|
||||||
let info = self.site_repository.get_site_by_domain(domain).await?;
|
|
||||||
let post = self
|
let post = self
|
||||||
.site_repository
|
.site_repository
|
||||||
.get_post(&info.domain, collection_id, id)
|
.get_post(domain, collection_id, id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(post)
|
Ok(post)
|
||||||
|
@ -59,11 +78,10 @@ impl SiteService {
|
||||||
collection_id: &str,
|
collection_id: &str,
|
||||||
cursor: Option<String>,
|
cursor: Option<String>,
|
||||||
) -> Result<Paginated<Post, String>> {
|
) -> Result<Paginated<Post, String>> {
|
||||||
let info = self.site_repository.get_site_by_domain(domain).await?;
|
|
||||||
let posts = self
|
let posts = self
|
||||||
.site_repository
|
.site_repository
|
||||||
.get_posts_for_collection(
|
.get_posts_for_collection(
|
||||||
&info.domain,
|
domain,
|
||||||
collection_id,
|
collection_id,
|
||||||
CursorOptions {
|
CursorOptions {
|
||||||
after: cursor,
|
after: cursor,
|
||||||
|
@ -74,13 +92,6 @@ 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>;
|
||||||
|
@ -108,7 +119,7 @@ mod tests {
|
||||||
use super::{Error, SiteService};
|
use super::{Error, SiteService};
|
||||||
use crate::{
|
use crate::{
|
||||||
domain::entities::site::{Block, Page, PageContent, PageInfo, Post},
|
domain::entities::site::{Block, Page, PageContent, PageInfo, Post},
|
||||||
outbound::repository::adapters::memory::InMemoryStore,
|
outbound::repository::{adapters::memory::InMemoryStore, site::SiteMetadata},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -244,4 +255,49 @@ mod tests {
|
||||||
assert_eq!(page.info.name, "new_page");
|
assert_eq!(page.info.name, "new_page");
|
||||||
assert_eq!(page.content, content);
|
assert_eq!(page.content, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deletes_a_page() {
|
||||||
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
|
service.delete_page("example.com", "about").await.unwrap();
|
||||||
|
let result = service.get_page("example.com", "about").await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.err().unwrap(), Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deletes_a_nonexistent_page() {
|
||||||
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
|
let result = service.delete_page("example.com", "nonexistent").await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.err().unwrap(), Error::NotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn creates_a_site() {
|
||||||
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
|
service
|
||||||
|
.create_site(SiteMetadata {
|
||||||
|
domain: "new.com".to_string(),
|
||||||
|
title: "New site".to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn doesnt_create_a_site_that_already_exists() {
|
||||||
|
let service = SiteService::new(InMemoryStore::with_test_data().await);
|
||||||
|
let result = service
|
||||||
|
.create_site(SiteMetadata {
|
||||||
|
domain: "example.com".to_string(),
|
||||||
|
title: "Example site".to_string(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(
|
||||||
|
result.err().unwrap(),
|
||||||
|
Error::RepositoryError(crate::outbound::repository::site::Error::Conflict)
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue