use crate::database::ConnectionPool; use async_trait::async_trait; use como_core::items::ItemService; use como_domain::{ item::{ queries::{GetItemQuery, GetItemsQuery}, requests::{CreateItemDto, UpdateItemDto}, responses::CreatedItemDto, ItemDto, }, user::ContextUserExt, Context, }; use std::{ collections::HashMap, sync::{Arc, Mutex}, }; use uuid::Uuid; pub struct DefaultItemService { pool: ConnectionPool, } impl DefaultItemService { pub fn new(connection_pool: ConnectionPool) -> Self { Self { pool: connection_pool, } } } #[async_trait] impl ItemService for DefaultItemService { async fn add_item( &self, context: &Context, item: CreateItemDto, ) -> anyhow::Result { let state = serde_json::to_string(&como_domain::item::ItemState::Created {})?; let user_id = context.get_user_id().ok_or(anyhow::anyhow!("no user id"))?; let rec = sqlx::query!( r#" INSERT INTO items (id, title, description, state, project_id, user_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, now(), now()) RETURNING id, title, description, state, project_id "#, Uuid::new_v4(), item.title, item.description, state, item.project_id, user_id, ) .fetch_one(&self.pool) .await?; Ok(CreatedItemDto { id: rec.id, title: rec.title, description: rec.description, state: como_domain::item::ItemState::Created {}, project_id: rec.project_id, }) } async fn get_item(&self, context: &Context, query: GetItemQuery) -> anyhow::Result { let user_id = context.get_user_id().ok_or(anyhow::anyhow!("no user id"))?; let rec = sqlx::query!( r#" SELECT id, title, description, state, project_id FROM items WHERE id = $1 AND user_id = $2 "#, query.item_id, user_id, ) .fetch_one(&self.pool) .await?; Ok(ItemDto { id: rec.id, title: rec.title, description: rec.description, state: serde_json::from_str(&rec.state)?, project_id: rec.project_id, }) } async fn get_items( &self, context: &Context, query: GetItemsQuery, ) -> anyhow::Result> { let user_id = context.get_user_id().ok_or(anyhow::anyhow!("no user id"))?; let recs = sqlx::query!( r#" SELECT id, title, description, state, project_id FROM items WHERE user_id = $1 and project_id = $2 LIMIT 500 "#, user_id, query.project_id, ) .fetch_all(&self.pool) .await?; Ok(recs .into_iter() .map(|rec| ItemDto { id: rec.id, title: rec.title, description: rec.description, state: serde_json::from_str(&rec.state).unwrap(), project_id: rec.project_id, }) .collect()) } async fn update_item(&self, context: &Context, item: UpdateItemDto) -> anyhow::Result { let state = item.state.map(|s| serde_json::to_string(&s)).transpose()?; let user_id = context.get_user_id().ok_or(anyhow::anyhow!("no user id"))?; let rec = sqlx::query!( r#" UPDATE items SET title = COALESCE($1, title), description = COALESCE($2, description), state = COALESCE($3, state), project_id = COALESCE($4, project_id), updated_at = now() WHERE id = $5 AND user_id = $6 RETURNING id, title, description, state, project_id "#, item.title, item.description, state, item.project_id, item.id, user_id, ) .fetch_one(&self.pool) .await?; Ok(ItemDto { id: rec.id, title: rec.title, description: rec.description, state: serde_json::from_str(&rec.state)?, project_id: rec.project_id, }) } } pub struct MemoryItemService { item_store: Arc>>, } impl MemoryItemService { pub fn new() -> Self { Self { item_store: Arc::new(Mutex::new(HashMap::new())), } } } #[async_trait] impl ItemService for MemoryItemService { async fn add_item( &self, _context: &Context, create_item: CreateItemDto, ) -> anyhow::Result { if let Ok(mut item_store) = self.item_store.lock() { let item = ItemDto { id: Uuid::new_v4(), title: create_item.title, description: create_item.description, state: como_domain::item::ItemState::Created, project_id: create_item.project_id, }; item_store.insert(item.id.to_string(), item.clone()); return Ok(item); } else { Err(anyhow::anyhow!("could not unlock item_store")) } } async fn get_item(&self, _context: &Context, query: GetItemQuery) -> anyhow::Result { if let Ok(item_store) = self.item_store.lock() { let item = item_store .get(&query.item_id.to_string()) .ok_or(anyhow::anyhow!("could not find item"))?; return Ok(item.clone()); } else { Err(anyhow::anyhow!("could not unlock item_store")) } } async fn get_items( &self, _context: &Context, _query: GetItemsQuery, ) -> anyhow::Result> { todo!() } async fn update_item(&self, _context: &Context, _item: UpdateItemDto) -> anyhow::Result { todo!() } }