From b8b4fd53d074c142ea745c2ea40b32fd78a34b91 Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Wed, 25 Dec 2024 04:12:09 -0500 Subject: [PATCH 01/12] docs(journal): sync nightly updates --- apps/kbve.com/src/content/journal/12-24.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/kbve.com/src/content/journal/12-24.mdx b/apps/kbve.com/src/content/journal/12-24.mdx index 58db3345a..e7cb61ac3 100644 --- a/apps/kbve.com/src/content/journal/12-24.mdx +++ b/apps/kbve.com/src/content/journal/12-24.mdx @@ -26,4 +26,5 @@ import { Adsense, Tasks } from '@kbve/astropad'; ./kbve.sh -nx kilobase:seal --namespace=kanban --keyName=aws-config --secrets=AWS_ACCESS_KEY_I ``` - \ No newline at end of file + The deployment went well and we got the configurations through without any issues! + Now we can switch back to the react code and figure out how to talk to the main api. \ No newline at end of file From a3d504c3cbde9d5d6e6034ef3e183bdacbfdbc12 Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Wed, 25 Dec 2024 05:28:13 -0500 Subject: [PATCH 02/12] feat!(kanban): updating the kanban to work with the API. --- apps/kbve.com/src/content/journal/12-25.mdx | 24 +++ .../kbve.com/src/engine/kanban/KanbanBase.tsx | 107 +++++++++++- .../src/engine/kanban/ReactKanban.tsx | 164 +++++++++++++++++- apps/rust_kanban/README.md | 2 +- apps/rust_kanban/project.json | 8 +- apps/rust_kanban/src/main.rs | 86 +++++---- 6 files changed, 347 insertions(+), 44 deletions(-) create mode 100644 apps/kbve.com/src/content/journal/12-25.mdx diff --git a/apps/kbve.com/src/content/journal/12-25.mdx b/apps/kbve.com/src/content/journal/12-25.mdx new file mode 100644 index 000000000..dae9c8d67 --- /dev/null +++ b/apps/kbve.com/src/content/journal/12-25.mdx @@ -0,0 +1,24 @@ +--- +title: 'Decemeber: 25th' +category: Daily +date: 2024-12-25 12:00:00 +client: Self +unsplash: 1511512578047-dfb367046420 +img: https://images.unsplash.com/photo-1511512578047-dfb367046420?crop=entropy&cs=srgb&fm=jpg&ixid=MnwzNjM5Nzd8MHwxfHJhbmRvbXx8fHx8fHx8fDE2ODE3NDg2ODY&ixlib=rb-4.0.3&q=85 +description: Decemeber 25th. +tags: + - daily +--- + +import { Adsense, Tasks } from '@kbve/astropad'; + +## 2024 + +- 04:14AM + + **Kanban** + + Okay! We are almost done with the generic or well base kanban board. + Getting it all operational is the goal for today, then I will shift over to adding additional features. + Okay we need to push forward with some of the minor updates and get the new docker image built out! + Let me move the rust and react code up for now and loop back around, hopefully resolving the CORS issue. \ No newline at end of file diff --git a/apps/kbve.com/src/engine/kanban/KanbanBase.tsx b/apps/kbve.com/src/engine/kanban/KanbanBase.tsx index eb0f538ea..4614e5649 100644 --- a/apps/kbve.com/src/engine/kanban/KanbanBase.tsx +++ b/apps/kbve.com/src/engine/kanban/KanbanBase.tsx @@ -13,6 +13,8 @@ export class KanbanBase extends Kilobase { Record >; + private boardIdStore: WritableAtom; + constructor() { super(); @@ -27,6 +29,16 @@ export class KanbanBase extends Kilobase { decode: JSON.parse, }, ); + + // Persistent store for the current board_id + this.boardIdStore = persistentAtom( + 'kanBanBoardId', + null, + { + encode: JSON.stringify, + decode: JSON.parse, + }, + ); } /** @@ -71,8 +83,101 @@ export class KanbanBase extends Kilobase { console.log('Item positions reset to:', resetPositions); } + /** + * Load board data by board_id from the API. + * @param boardId - The board_id to load data for. + * @returns Promise | null> - The board data if found, or null if not found. + */ + async loadBoardData( + boardId: string, + ): Promise | null> { + try { + const response = await fetch( + `https://kanban.kbve.com/api/get_board`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ board_id: boardId }), + }, + ); + + if (!response.ok) { + // If the response is not ok, log an error and return null + console.error( + `Failed to fetch board data for board ID: ${boardId}`, + ); + return null; + } + + const result = await response.json(); + + // Validate the structure of the response + if ( + result && + typeof result === 'object' && + 'todo' in result && + 'in_progress' in result && + 'done' in result + ) { + this.itemPositionsStore.set(result); // Cache the data locally + console.log(`Loaded board data for board ID: ${boardId}`); + return result; + } + + console.error( + `Invalid board data structure for board ID: ${boardId}`, + ); + return null; + } catch (error) { + console.error('Error loading board data:', error); + return null; // Return null on error + } + } + + /** + * Save board data by board_id. + * @param boardId - The board_id to save data for. + * @param data - The Kanban data to save. + * @returns Promise + */ + async saveBoardData( + boardId: string, + data: Record, + ): Promise { + // Save to local storage + this.itemPositionsStore.set(data); + console.log(`Saved board data to local storage for ${boardId}`); + + // Save to the API + try { + const response = await fetch( + `https://kanban.kbve.com/api/get_board`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ board_id: boardId, ...data }), + }, + ); - + if (!response.ok) { + throw new Error('Failed to save board data to API'); + } + + console.log(`Saved board data to API for ${boardId}`); + } catch (error) { + console.error('Error saving board data:', error); + } + } + + /** + * Validate the board_id by attempting to load its data. + * @param boardId - The board_id to validate. + * @returns Promise - True if the board_id is valid, false otherwise. + */ + async validateBoardId(boardId: string): Promise { + const boardData = await this.loadBoardData(boardId); + return boardData !== null; // Valid if board data is found + } } // Export a singleton instance of the extended class for use throughout the application diff --git a/apps/kbve.com/src/engine/kanban/ReactKanban.tsx b/apps/kbve.com/src/engine/kanban/ReactKanban.tsx index 8f8c39b5e..b898670be 100644 --- a/apps/kbve.com/src/engine/kanban/ReactKanban.tsx +++ b/apps/kbve.com/src/engine/kanban/ReactKanban.tsx @@ -30,6 +30,14 @@ import { interface DroppableStoryProps { containers: string[]; + items: Record; + sidebarItems: string[]; + setItems: React.Dispatch< + React.SetStateAction< + Record + > + >; + setSidebarItems: React.Dispatch>; } // Sidebar wrapper and container styles @@ -432,9 +440,159 @@ const DroppableStory: React.FC = ({ containers }) => { ); }; +// TODO: API Changes - Having the ability to load and save board data to the API Server + +const BoardForm: React.FC<{ onSubmit: (boardId: string) => void }> = ({ + onSubmit, +}) => { + const [boardId, setBoardId] = useState(''); + + return ( +
+

Enter Board ID

+ setBoardId(e.target.value)} + className="border p-2 rounded-md mb-4 w-64" + /> + +
+ ); +}; + // Use the DroppableStory component with predefined containers -const ReactKanban = () => ( - -); +// const ReactKanban = () => ( +// +// ); + +const ReactKanban: React.FC = () => { + const [boardId, setBoardId] = useState(null); + const [items, setItems] = useState< + Record + >({}); + const [sidebarItems, setSidebarItems] = useState([ + 'Item 1', + 'Item 2', + 'Item 3', + 'Function', + 'IGBC', + ]); + const [isLoading, setIsLoading] = useState(false); + + // Fetch and validate board data + const fetchBoardData = async (id: string) => { + setIsLoading(true); + try { + const boardData = await kanbanBase.loadBoardData(id); + + if (boardData) { + setItems(boardData); + + // Remove placed items from the sidebar + const placedItems = new Set( + Object.values(boardData).flatMap((itemList) => + itemList.map((item) => item.id), + ), + ); + setSidebarItems((prevSidebarItems) => + prevSidebarItems.filter((item) => !placedItems.has(item)), + ); + } else { + alert('Invalid board ID. Please try again.'); + setBoardId(null); + } + } catch (error) { + console.error('Error fetching board data:', error); + alert('Failed to load board data.'); + } finally { + setIsLoading(false); + } + }; + + // Save board data + const saveBoardData = async () => { + if (!boardId) return; + + try { + await kanbanBase.saveBoardData(boardId, items); + alert('Board saved successfully!'); + } catch (error) { + console.error('Error saving board data:', error); + alert('Failed to save board.'); + } + }; + + // Reset board + const resetBoard = async () => { + const resetTemplate = { + TODO: [], + 'IN-PROGRESS': [], + DONE: [], + }; + + setItems(resetTemplate); + if (boardId) { + await kanbanBase.saveBoardData(boardId, resetTemplate); + } + }; + + // Handle form submission + const handleBoardIdSubmit = (id: string) => { + setBoardId(id); + fetchBoardData(id); + }; + + // Fetch data when `boardId` changes + useEffect(() => { + if (boardId) { + fetchBoardData(boardId); + } + }, [boardId]); + + // Render loading state + if (isLoading) { + return ( +
+ Loading... +
+ ); + } + + // Render the board form if no board ID is set + if (!boardId) { + return ; + } + + // Render the Kanban board + return ( +
+ +
+ + +
+
+ ); +}; export default ReactKanban; diff --git a/apps/rust_kanban/README.md b/apps/rust_kanban/README.md index a84a3e5b0..e2de0d256 100644 --- a/apps/rust_kanban/README.md +++ b/apps/rust_kanban/README.md @@ -2,4 +2,4 @@ The api that handles the json from the kanban. More notes will be added once we get a chance to get it all up and running. -Preparing the kanban build, okay this update to the readme should now trigger it? \ No newline at end of file +Added better CORS support and preparing the helm chart update afterwards. \ No newline at end of file diff --git a/apps/rust_kanban/project.json b/apps/rust_kanban/project.json index dda56aed7..75a6e1473 100644 --- a/apps/rust_kanban/project.json +++ b/apps/rust_kanban/project.json @@ -65,15 +65,15 @@ "metadata": { "images": ["kbve/kanban"], "load": true, - "tags": ["1.01", "1.01.1"] + "tags": ["1.02", "1.02.1"] }, "configurations": { "local": { - "tags": ["1.01", "1.01.1"], + "tags": ["1.02", "1.02.1"], "push": false }, "production": { - "tags": ["1.01", "1.01.1"], + "tags": ["1.02", "1.02.1"], "push": true, "customBuildOptions": "--push", "cache-from": [ @@ -95,7 +95,7 @@ "forwardAllArgs": false }, { - "command": "docker run --env-file /app/rust_kanban/.env -p 3000:3000 -p 3001:3001 -p 8086:8086 kbve/kanban:1.01", + "command": "docker run --env-file /app/rust_kanban/.env -p 3000:3000 -p 3001:3001 -p 8086:8086 kbve/kanban:1.02", "forwardAllArgs": false } ], diff --git a/apps/rust_kanban/src/main.rs b/apps/rust_kanban/src/main.rs index 8d38232a8..7770f582a 100644 --- a/apps/rust_kanban/src/main.rs +++ b/apps/rust_kanban/src/main.rs @@ -7,8 +7,13 @@ use axum::{ use axum::http::StatusCode; use axum::{ extract::State, Json }; +use axum::http::header; +use axum::http::HeaderValue; + + use futures::{ sink::SinkExt, stream::StreamExt }; use serde::{ Deserialize, Serialize }; +use serde_json::json; use std::{ sync::Arc, collections::HashMap, net::SocketAddr }; use tokio::sync::broadcast; use tower_http::{ services::ServeDir, cors::CorsLayer, trace::TraceLayer }; @@ -69,8 +74,19 @@ async fn main() { // CORS layer let cors = CorsLayer::new() - .allow_origin(tower_http::cors::Any) - .allow_methods(tower_http::cors::Any); + .allow_origin([ + "https://kbve.com".parse::().unwrap(), // Main domain + "https://kanban.kbve.com".parse::().unwrap(), // Subdomain + "http://localhost".parse::().unwrap(), // Localhost for development + "http://127.0.0.1".parse::().unwrap(), // Localhost alternative + ]) + .allow_methods(tower_http::cors::Any) + .allow_headers( + vec![ + header::CONTENT_TYPE + // Add other headers as needed + ] + ); // Router setup let app = Router::new() @@ -93,20 +109,23 @@ async fn get_board( State(state): State, Json(payload): Json> ) -> impl IntoResponse { - let board_id = payload.get("board_id").unwrap(); + let board_id = match payload.get("board_id") { + Some(id) => id, + None => { + let error_response = json!({"error": "Missing 'board_id' in request payload"}); + return (StatusCode::BAD_REQUEST, Json(error_response)).into_response(); + } + }; + match fetch_board(&state.dynamo_client, board_id).await { - Ok(board) => Json(board), + Ok(board) => Json(board).into_response(), Err(e) => { tracing::error!("Failed to fetch board: {}", e); - Json(KanbanBoard { - todo: vec![], - in_progress: vec![], - done: vec![], - }) + let error_response = json!({"error": "Failed to fetch board data"}); + (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)).into_response() } } } - // RESTful API: Save board to DynamoDB async fn save_board_handler( State(state): State, @@ -150,36 +169,33 @@ async fn save_board_handler( } } - pub async fn save_board(client: &Client, board_id: &str, board: KanbanBoard) -> Result<(), String> { - // Annotate the type for `item` - let item: HashMap = to_item(board).map_err(|e| { - tracing::error!("Failed to serialize board: {}", e); - e.to_string() + // Annotate the type for `item` + let item: HashMap = to_item(board).map_err(|e| { + tracing::error!("Failed to serialize board: {}", e); + e.to_string() + })?; + + // Add the partition key to the item + let mut item_with_key = item; + item_with_key.insert("board_id".to_string(), AttributeValue::S(board_id.to_string())); + + tracing::debug!("Final item for PutItem: {:?}", item_with_key); + + // Execute the PutItem request + client + .put_item() + .table_name("KanbanBoards") + .set_item(Some(item_with_key)) + .send().await + .map_err(|e| { + tracing::error!("PutItem request failed: {:?}", e); + e.to_string() })?; - // Add the partition key to the item - let mut item_with_key = item; - item_with_key.insert("board_id".to_string(), AttributeValue::S(board_id.to_string())); - - tracing::debug!("Final item for PutItem: {:?}", item_with_key); - - // Execute the PutItem request - client - .put_item() - .table_name("KanbanBoards") - .set_item(Some(item_with_key)) - .send() - .await - .map_err(|e| { - tracing::error!("PutItem request failed: {:?}", e); - e.to_string() - })?; - - Ok(()) + Ok(()) } - async fn fetch_board(client: &Client, board_id: &str) -> Result { let result = client .get_item() From 9e0a6ce4f42dc1fc26ec6c9b5bd4ea3fdc834a12 Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Wed, 25 Dec 2024 05:39:57 -0500 Subject: [PATCH 03/12] ci(kanban): updating the helm chart and its docker version. --- apps/kbve.com/src/content/journal/12-25.mdx | 3 ++- migrations/kube/charts/kanban/services/values.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/kbve.com/src/content/journal/12-25.mdx b/apps/kbve.com/src/content/journal/12-25.mdx index dae9c8d67..b29ae70d5 100644 --- a/apps/kbve.com/src/content/journal/12-25.mdx +++ b/apps/kbve.com/src/content/journal/12-25.mdx @@ -21,4 +21,5 @@ import { Adsense, Tasks } from '@kbve/astropad'; Okay! We are almost done with the generic or well base kanban board. Getting it all operational is the goal for today, then I will shift over to adding additional features. Okay we need to push forward with some of the minor updates and get the new docker image built out! - Let me move the rust and react code up for now and loop back around, hopefully resolving the CORS issue. \ No newline at end of file + Let me move the rust and react code up for now and loop back around, hopefully resolving the CORS issue. + The docker image of 1.02 was published, so now I am going to update the helm chart and have it deploy the new one. \ No newline at end of file diff --git a/migrations/kube/charts/kanban/services/values.yaml b/migrations/kube/charts/kanban/services/values.yaml index 1e1dee408..71d5781e4 100644 --- a/migrations/kube/charts/kanban/services/values.yaml +++ b/migrations/kube/charts/kanban/services/values.yaml @@ -4,7 +4,7 @@ kanban: replicaCount: 1 image: repository: kbve/kanban - tag: 1.01.1 + tag: 1.02.1 digest: 'sha256:c002234843480e38de0d9fa6fde5f18a449feea238be757c5afe7cd5bffaecf1' service: name: kanban From a86070de5cd440171a905f50f413f0e5c1ace475 Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Wed, 25 Dec 2024 06:42:39 -0500 Subject: [PATCH 04/12] ci(kanban): updating the kanban helm chart --- .../charts/kanban/services/templates/kanban-service.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/migrations/kube/charts/kanban/services/templates/kanban-service.yaml b/migrations/kube/charts/kanban/services/templates/kanban-service.yaml index 1ef413b9c..31c6a7685 100644 --- a/migrations/kube/charts/kanban/services/templates/kanban-service.yaml +++ b/migrations/kube/charts/kanban/services/templates/kanban-service.yaml @@ -69,6 +69,13 @@ metadata: nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" nginx.ingress.kubernetes.io/proxy-http-version: "1.1" nginx.ingress.kubernetes.io/proxy-connect-timeout: "60" + nginx.ingress.kubernetes.io/add-base-url: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:4321,https://kanban.kbve.com" + nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, OPTIONS" + nginx.ingress.kubernetes.io/cors-allow-headers: "Content-Type, Authorization" + nginx.ingress.kubernetes.io/cors-allow-credentials: "true" +spec: spec: rules: - host: {{ .Values.kanban.ingress.host }} From d0eeae0e2498b0c3b88414915ebdcbc1db99344e Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:23:11 -0500 Subject: [PATCH 05/12] feat(kanban): preparing for the new structs and overall refactor. --- apps/kbve.com/src/content/journal/12-25.mdx | 17 +- .../kbve.com/src/engine/kanban/KanbanBase.tsx | 43 ++- .../src/engine/kanban/ReactKanban.tsx | 249 +++++++++--------- apps/rust_kanban/src/main.rs | 40 +++ 4 files changed, 212 insertions(+), 137 deletions(-) diff --git a/apps/kbve.com/src/content/journal/12-25.mdx b/apps/kbve.com/src/content/journal/12-25.mdx index b29ae70d5..383cdfe21 100644 --- a/apps/kbve.com/src/content/journal/12-25.mdx +++ b/apps/kbve.com/src/content/journal/12-25.mdx @@ -22,4 +22,19 @@ import { Adsense, Tasks } from '@kbve/astropad'; Getting it all operational is the goal for today, then I will shift over to adding additional features. Okay we need to push forward with some of the minor updates and get the new docker image built out! Let me move the rust and react code up for now and loop back around, hopefully resolving the CORS issue. - The docker image of 1.02 was published, so now I am going to update the helm chart and have it deploy the new one. \ No newline at end of file + The docker image of 1.02 was published, so now I am going to update the helm chart and have it deploy the new one. + +- 07:33AM + + **React** + + Okay we got the communication between the kanban and the react component going, now we just need to figure out how to maintain that easy flow. + Right now its not pulling the right data from the api state, so I am going to focus on resolving that next. + +- 02:21PM + + **MerryXMas** + + Happy holidays! + Forgot to include that in my notes, xD + I am thinking of redoing the whole kanban system to help keep track of the notes a bit better, maybe even expanding the item collection that would be involved. \ No newline at end of file diff --git a/apps/kbve.com/src/engine/kanban/KanbanBase.tsx b/apps/kbve.com/src/engine/kanban/KanbanBase.tsx index 4614e5649..38244c75c 100644 --- a/apps/kbve.com/src/engine/kanban/KanbanBase.tsx +++ b/apps/kbve.com/src/engine/kanban/KanbanBase.tsx @@ -82,9 +82,9 @@ export class KanbanBase extends Kilobase { this.itemPositionsStore.set(resetPositions); // Save the reset structure console.log('Item positions reset to:', resetPositions); } - /** * Load board data by board_id from the API. + * Saves the fetched data to local storage. * @param boardId - The board_id to load data for. * @returns Promise | null> - The board data if found, or null if not found. */ @@ -102,7 +102,6 @@ export class KanbanBase extends Kilobase { ); if (!response.ok) { - // If the response is not ok, log an error and return null console.error( `Failed to fetch board data for board ID: ${boardId}`, ); @@ -111,7 +110,6 @@ export class KanbanBase extends Kilobase { const result = await response.json(); - // Validate the structure of the response if ( result && typeof result === 'object' && @@ -119,9 +117,17 @@ export class KanbanBase extends Kilobase { 'in_progress' in result && 'done' in result ) { - this.itemPositionsStore.set(result); // Cache the data locally - console.log(`Loaded board data for board ID: ${boardId}`); - return result; + // Save to local storage + const formattedResult = { + TODO: result.todo || [], + 'IN-PROGRESS': result.in_progress || [], + DONE: result.done || [], + }; + this.itemPositionsStore.set(formattedResult); + console.log( + `Loaded and saved board data for board ID: ${boardId}`, + ); + return formattedResult; } console.error( @@ -130,7 +136,7 @@ export class KanbanBase extends Kilobase { return null; } catch (error) { console.error('Error loading board data:', error); - return null; // Return null on error + return null; } } @@ -144,18 +150,26 @@ export class KanbanBase extends Kilobase { boardId: string, data: Record, ): Promise { - // Save to local storage - this.itemPositionsStore.set(data); - console.log(`Saved board data to local storage for ${boardId}`); - - // Save to the API try { + const currentPositions = this.itemPositionsStore.get(); + + // Ensure data structure matches what the API expects + + const formattedData = { + board_id: boardId, + todo: currentPositions.TODO || [], + in_progress: currentPositions['IN-PROGRESS'] || [], + done: currentPositions.DONE || [], + }; + console.log('Saving to API with data:', formattedData); + + // Save to the API const response = await fetch( - `https://kanban.kbve.com/api/get_board`, + `https://kanban.kbve.com/api/save_board`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ board_id: boardId, ...data }), + body: JSON.stringify(formattedData), }, ); @@ -166,6 +180,7 @@ export class KanbanBase extends Kilobase { console.log(`Saved board data to API for ${boardId}`); } catch (error) { console.error('Error saving board data:', error); + throw error; } } diff --git a/apps/kbve.com/src/engine/kanban/ReactKanban.tsx b/apps/kbve.com/src/engine/kanban/ReactKanban.tsx index b898670be..dc218b6b1 100644 --- a/apps/kbve.com/src/engine/kanban/ReactKanban.tsx +++ b/apps/kbve.com/src/engine/kanban/ReactKanban.tsx @@ -470,129 +470,134 @@ const BoardForm: React.FC<{ onSubmit: (boardId: string) => void }> = ({ // const ReactKanban = () => ( // // ); - const ReactKanban: React.FC = () => { - const [boardId, setBoardId] = useState(null); - const [items, setItems] = useState< - Record - >({}); - const [sidebarItems, setSidebarItems] = useState([ - 'Item 1', - 'Item 2', - 'Item 3', - 'Function', - 'IGBC', - ]); - const [isLoading, setIsLoading] = useState(false); - - // Fetch and validate board data - const fetchBoardData = async (id: string) => { - setIsLoading(true); - try { - const boardData = await kanbanBase.loadBoardData(id); - - if (boardData) { - setItems(boardData); - - // Remove placed items from the sidebar - const placedItems = new Set( - Object.values(boardData).flatMap((itemList) => - itemList.map((item) => item.id), - ), - ); - setSidebarItems((prevSidebarItems) => - prevSidebarItems.filter((item) => !placedItems.has(item)), - ); - } else { - alert('Invalid board ID. Please try again.'); - setBoardId(null); - } - } catch (error) { - console.error('Error fetching board data:', error); - alert('Failed to load board data.'); - } finally { - setIsLoading(false); - } - }; - - // Save board data - const saveBoardData = async () => { - if (!boardId) return; - - try { - await kanbanBase.saveBoardData(boardId, items); - alert('Board saved successfully!'); - } catch (error) { - console.error('Error saving board data:', error); - alert('Failed to save board.'); - } - }; - - // Reset board - const resetBoard = async () => { - const resetTemplate = { - TODO: [], - 'IN-PROGRESS': [], - DONE: [], - }; - - setItems(resetTemplate); - if (boardId) { - await kanbanBase.saveBoardData(boardId, resetTemplate); - } - }; - - // Handle form submission - const handleBoardIdSubmit = (id: string) => { - setBoardId(id); - fetchBoardData(id); - }; - - // Fetch data when `boardId` changes - useEffect(() => { - if (boardId) { - fetchBoardData(boardId); - } - }, [boardId]); - - // Render loading state - if (isLoading) { - return ( -
- Loading... -
- ); - } - - // Render the board form if no board ID is set - if (!boardId) { - return ; - } - - // Render the Kanban board - return ( -
- -
- - -
-
- ); + const [boardId, setBoardId] = useState(null); + const [items, setItems] = useState>({}); + const [sidebarItems, setSidebarItems] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + /** + * Load board data by boardId and update state. + * @param id - The boardId to load data for. + */ + const loadBoardData = async (id: string) => { + setIsLoading(true); + try { + // Fetch board data from the API or local storage + const boardData = await kanbanBase.loadBoardData(id); + + if (boardData) { + setItems(boardData); + + // Extract item IDs already placed in containers + const placedItems = new Set( + Object.values(boardData).flatMap((container) => + container.map((item) => item.id) + ) + ); + + // Update sidebar with items not in containers + setSidebarItems((prev) => + prev.filter((item) => !placedItems.has(item)) + ); + } else { + alert('No board data found for the provided Board ID.'); + setBoardId(null); + } + } catch (error) { + console.error('Error loading board data:', error); + alert('Failed to load board data.'); + } finally { + setIsLoading(false); + } + }; + + /** + * Save the current board data to the API. + */ + const saveBoardData = async () => { + if (!boardId) return; + + try { + await kanbanBase.saveBoardData(boardId, items); + alert('Board saved successfully!'); + } catch (error) { + console.error('Error saving board data:', error); + alert('Failed to save board.'); + } + }; + + /** + * Reset the Kanban board to the initial state. + */ + const resetBoard = () => { + const resetTemplate = { TODO: [], 'IN-PROGRESS': [], DONE: [] }; + setItems(resetTemplate); + + // Move all items back to the sidebar + const allItems = Object.values(items) + .flatMap((container) => container.map((item) => item.id)) + .filter((id) => !sidebarItems.includes(id)); + + setSidebarItems((prev) => [...prev, ...allItems]); + }; + + /** + * Handle the submission of a new board ID. + * @param id - The boardId to set and load. + */ + const handleBoardIdSubmit = (id: string) => { + setBoardId(id); + loadBoardData(id); + }; + + // Fetch data whenever `boardId` is set + useEffect(() => { + if (boardId) { + loadBoardData(boardId); + } + }, [boardId]); + + // Render loading state + if (isLoading) { + return ( +
+ Loading... +
+ ); + } + + // Render the board form if no board ID is set + if (!boardId) { + return ; + } + + // Render the Kanban board + return ( +
+ +
+ + +
+
+ ); }; export default ReactKanban; diff --git a/apps/rust_kanban/src/main.rs b/apps/rust_kanban/src/main.rs index 7770f582a..edd106a59 100644 --- a/apps/rust_kanban/src/main.rs +++ b/apps/rust_kanban/src/main.rs @@ -92,6 +92,7 @@ async fn main() { let app = Router::new() .route("/api/get_board", post(get_board)) .route("/api/save_board", post(save_board_handler)) + .route("/api/delete_board", post(delete_board_handler)) .route("/ws", get(websocket_handler)) .fallback_service(ServeDir::new("build").append_index_html_on_directories(true)) .with_state(state) @@ -235,3 +236,42 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { } } } + + +// TODO: Delete + +async fn delete_board_handler( + State(state): State, + Json(payload): Json> +) -> impl IntoResponse { + let board_id = match payload.get("board_id") { + Some(id) => id, + None => { + let error_response = json!({"error": "Missing 'board_id' in request payload"}); + return (StatusCode::BAD_REQUEST, Json(error_response)).into_response(); + } + }; + + match delete_board(&state.dynamo_client, board_id).await { + Ok(_) => Json(json!({"message": "Board deleted successfully"})).into_response(), + Err(e) => { + tracing::error!("Failed to delete board: {}", e); + let error_response = json!({"error": "Failed to delete board"}); + (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)).into_response() + } + } +} + +pub async fn delete_board(client: &Client, board_id: &str) -> Result<(), String> { + client + .delete_item() + .table_name("KanbanBoards") + .key("board_id", AttributeValue::S(board_id.to_string())) + .send() + .await + .map_err(|e| { + tracing::error!("DeleteItem request failed: {:?}", e); + e.to_string() + })?; + Ok(()) +} \ No newline at end of file From e62846a7f5e19c4ed7ff779fc39fbb869cb17a79 Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:15:04 -0500 Subject: [PATCH 06/12] docs(journal): new daily entry --- apps/kbve.com/src/content/journal/12-26.mdx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 apps/kbve.com/src/content/journal/12-26.mdx diff --git a/apps/kbve.com/src/content/journal/12-26.mdx b/apps/kbve.com/src/content/journal/12-26.mdx new file mode 100644 index 000000000..c56e2b9c1 --- /dev/null +++ b/apps/kbve.com/src/content/journal/12-26.mdx @@ -0,0 +1,21 @@ +--- +title: 'Decemeber: 26th' +category: Daily +date: 2024-12-26 12:00:00 +client: Self +unsplash: 1511512578047-dfb367046420 +img: https://images.unsplash.com/photo-1511512578047-dfb367046420?crop=entropy&cs=srgb&fm=jpg&ixid=MnwzNjM5Nzd8MHwxfHJhbmRvbXx8fHx8fHx8fDE2ODE3NDg2ODY&ixlib=rb-4.0.3&q=85 +description: Decemeber 26th. +tags: + - daily +--- + +import { Adsense, Tasks } from '@kbve/astropad'; + +## 2024 + +- 09:30AM + + **SPY** + + $600 for SPY!? This is great! \ No newline at end of file From d7990b62011c48281173209cc4aa311981ce2c94 Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:15:25 -0500 Subject: [PATCH 07/12] feat(kanban): better data structure for the kanban --- apps/rust_kanban/src/main.rs | 184 +++++++++++++++++++++++++---------- 1 file changed, 135 insertions(+), 49 deletions(-) diff --git a/apps/rust_kanban/src/main.rs b/apps/rust_kanban/src/main.rs index edd106a59..7c5ff8784 100644 --- a/apps/rust_kanban/src/main.rs +++ b/apps/rust_kanban/src/main.rs @@ -30,15 +30,45 @@ struct AppState { #[derive(Serialize, Deserialize, Debug)] pub struct KanbanBoard { - pub todo: Vec, - pub in_progress: Vec, - pub done: Vec, + pub todo: Vec, + pub in_progress: Vec, + pub done: Vec, + pub unassigned: Vec, // New field for unassigned items + pub metadata: Option, // Optional metadata for the board + pub actions: Vec, // List of actions performed on the board +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct KanbanMetadata { + pub board_id: String, // Unique ID for the board + pub owner: Option, // Owner's ID or name + pub created_at: Option, // ISO 8601 timestamp + pub updated_at: Option, // ISO 8601 timestamp } #[derive(Serialize, Deserialize, Debug)] pub struct KanbanItem { - pub id: String, - pub container: String, + pub id: String, // Unique identifier for the item + pub container: String, // The board/container this item belongs to + pub notes: Vec, // New field for notes + pub priority: Option, // Optional priority (1 = high, 5 = low) +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct KanbanNote { + pub note_id: String, // Unique identifier for the note + pub content: String, // The note's content + pub created_at: Option, // Timestamp for when the note was created +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct KanbanAction { + pub action_id: String, // Unique identifier for the action + pub item_id: Option, // The item ID this action is related to (optional for board-wide actions) + pub action_type: String, // The type of action, e.g., "move", "delete", "create" + pub performed_by: Option, // The user who performed the action + pub timestamp: String, // ISO 8601 timestamp for when the action occurred + pub details: Option, // Optional details about the action } #[cfg(feature = "jemalloc")] @@ -108,65 +138,104 @@ async fn main() { // RESTful API: Fetch board from DynamoDB async fn get_board( State(state): State, - Json(payload): Json> + Json(payload): Json>, ) -> impl IntoResponse { + // Extract board_id from the request payload let board_id = match payload.get("board_id") { - Some(id) => id, - None => { - let error_response = json!({"error": "Missing 'board_id' in request payload"}); - return (StatusCode::BAD_REQUEST, Json(error_response)).into_response(); - } + Some(id) => id, + None => { + let error_response = json!({ "error": "Missing 'board_id' in request payload" }); + return (StatusCode::BAD_REQUEST, Json(error_response)).into_response(); + } }; + // Fetch the board from DynamoDB match fetch_board(&state.dynamo_client, board_id).await { - Ok(board) => Json(board).into_response(), - Err(e) => { - tracing::error!("Failed to fetch board: {}", e); - let error_response = json!({"error": "Failed to fetch board data"}); - (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)).into_response() - } + Ok(board) => { + tracing::debug!("Successfully fetched board: {:?}", board); + Json(board).into_response() + } + Err(e) => { + tracing::error!("Failed to fetch board: {}", e); + let error_response = json!({ "error": "Failed to fetch board data" }); + (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)).into_response() + } } } + // RESTful API: Save board to DynamoDB async fn save_board_handler( State(state): State, - Json(payload): Json> + Json(payload): Json>, ) -> Result, impl IntoResponse> { let board_id = payload - .get("board_id") - .and_then(|v| v.as_str()) - .ok_or_else(|| { - tracing::error!("Missing or invalid 'board_id' in the payload"); - (StatusCode::BAD_REQUEST, "Missing or invalid 'board_id'") - })?; + .get("board_id") + .and_then(|v| v.as_str()) + .ok_or_else(|| { + tracing::error!("Missing or invalid 'board_id' in the payload"); + (StatusCode::BAD_REQUEST, "Missing or invalid 'board_id'") + })?; - let board_data = payload - .get("todo") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); + // Validate and deserialize fields + let todo = payload + .get("todo") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'todo' field missing or invalid, defaulting to empty"); + vec![] + }); let in_progress = payload - .get("in_progress") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); + .get("in_progress") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'in_progress' field missing or invalid, defaulting to empty"); + vec![] + }); let done = payload - .get("done") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); + .get("done") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'done' field missing or invalid, defaulting to empty"); + vec![] + }); + + let unassigned = payload + .get("unassigned") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'unassigned' field missing or invalid, defaulting to empty"); + vec![] + }); + + let metadata = payload + .get("metadata") + .and_then(|v| serde_json::from_value(v.clone()).ok()); + + let actions = payload + .get("actions") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'actions' field missing or invalid, defaulting to empty"); + vec![] + }); let board = KanbanBoard { - todo: board_data, - in_progress, - done, + todo, + in_progress, + done, + unassigned, + metadata, + actions, }; match save_board(&state.dynamo_client, board_id, board).await { - Ok(_) => Ok(Json("Board saved successfully")), - Err(e) => { - tracing::error!("Failed to save board: {}", e); - Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to save board")) - } + Ok(_) => Ok(Json("Board saved successfully")), + Err(e) => { + tracing::error!("Failed to save board: {}", e); + Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to save board")) + } } } @@ -199,15 +268,32 @@ pub async fn save_board(client: &Client, board_id: &str, board: KanbanBoard) -> async fn fetch_board(client: &Client, board_id: &str) -> Result { let result = client - .get_item() - .table_name("KanbanBoards") - .key("board_id", AttributeValue::S(board_id.to_string())) - .send().await - .map_err(|e| e.to_string())?; + .get_item() + .table_name("KanbanBoards") + .key("board_id", AttributeValue::S(board_id.to_string())) + .send() + .await + .map_err(|e| { + tracing::error!("Failed to fetch item from DynamoDB: {:?}", e); + e.to_string() + })?; let item = result.item.ok_or("Board not found")?; - let board: KanbanBoard = from_item(item).map_err(|e| e.to_string())?; - Ok(board) + + let board: KanbanBoard = from_item(item).map_err(|e| { + tracing::error!("Failed to deserialize board: {}", e); + e.to_string() + })?; + + // Ensure all fields are present + Ok(KanbanBoard { + todo: board.todo, + in_progress: board.in_progress, + done: board.done, + unassigned: board.unassigned, // Already a Vec, no need for unwrap_or_else + metadata: board.metadata, + actions: board.actions, // Already a Vec, no need for unwrap_or_else +}) } // Websockets //TODO: Websockets From 69018d390d860d2b772d6a0cfbd9055871388d97 Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:03:53 -0500 Subject: [PATCH 08/12] feat(kanban): adding and deleting items next on the data structure update. --- apps/rust_kanban/src/main.rs | 399 ++++++++++++++++++++++++----------- 1 file changed, 276 insertions(+), 123 deletions(-) diff --git a/apps/rust_kanban/src/main.rs b/apps/rust_kanban/src/main.rs index 7c5ff8784..3aeab3ae3 100644 --- a/apps/rust_kanban/src/main.rs +++ b/apps/rust_kanban/src/main.rs @@ -10,7 +10,6 @@ use axum::{ extract::State, Json }; use axum::http::header; use axum::http::HeaderValue; - use futures::{ sink::SinkExt, stream::StreamExt }; use serde::{ Deserialize, Serialize }; use serde_json::json; @@ -30,45 +29,69 @@ struct AppState { #[derive(Serialize, Deserialize, Debug)] pub struct KanbanBoard { - pub todo: Vec, - pub in_progress: Vec, - pub done: Vec, - pub unassigned: Vec, // New field for unassigned items - pub metadata: Option, // Optional metadata for the board - pub actions: Vec, // List of actions performed on the board + pub todo: Vec, + pub in_progress: Vec, + pub done: Vec, + pub unassigned: Vec, // New field for unassigned items + pub metadata: Option, // Optional metadata for the board + pub actions: Vec, // List of actions performed on the board } #[derive(Serialize, Deserialize, Debug)] pub struct KanbanMetadata { - pub board_id: String, // Unique ID for the board - pub owner: Option, // Owner's ID or name - pub created_at: Option, // ISO 8601 timestamp - pub updated_at: Option, // ISO 8601 timestamp + pub board_id: String, // Unique ID for the board + pub owner: Option, // Owner's ID or name + pub created_at: Option, // ISO 8601 timestamp + pub updated_at: Option, // ISO 8601 timestamp } #[derive(Serialize, Deserialize, Debug)] pub struct KanbanItem { - pub id: String, // Unique identifier for the item - pub container: String, // The board/container this item belongs to - pub notes: Vec, // New field for notes - pub priority: Option, // Optional priority (1 = high, 5 = low) + pub id: String, // Unique identifier for the item + pub container: String, // The board/container this item belongs to + pub notes: Vec, // New field for notes + pub priority: Option, // Optional priority (1 = high, 5 = low) } #[derive(Serialize, Deserialize, Debug)] pub struct KanbanNote { - pub note_id: String, // Unique identifier for the note - pub content: String, // The note's content - pub created_at: Option, // Timestamp for when the note was created + pub note_id: String, // Unique identifier for the note + pub content: String, // The note's content + pub created_at: Option, // Timestamp for when the note was created } #[derive(Serialize, Deserialize, Debug)] pub struct KanbanAction { - pub action_id: String, // Unique identifier for the action - pub item_id: Option, // The item ID this action is related to (optional for board-wide actions) - pub action_type: String, // The type of action, e.g., "move", "delete", "create" - pub performed_by: Option, // The user who performed the action - pub timestamp: String, // ISO 8601 timestamp for when the action occurred - pub details: Option, // Optional details about the action + pub action_id: String, // Unique identifier for the action + pub item_id: Option, // The item ID this action is related to (optional for board-wide actions) + pub action_type: String, // The type of action, e.g., "move", "delete", "create" + pub performed_by: Option, // The user who performed the action + pub timestamp: String, // ISO 8601 timestamp for when the action occurred + pub details: Option, // Optional details about the action +} + +impl KanbanItem { + pub fn validate(&self) -> Result<(), String> { + if self.id.trim().is_empty() { + return Err("Item ID cannot be empty".to_string()); + } + if self.container.trim().is_empty() { + return Err("Container cannot be empty".to_string()); + } + Ok(()) + } +} + +impl KanbanAction { + pub fn validate(&self) -> Result<(), String> { + if self.action_id.trim().is_empty() { + return Err("Action ID cannot be empty".into()); + } + if self.timestamp.trim().is_empty() { + return Err("Timestamp cannot be empty".into()); + } + Ok(()) + } } #[cfg(feature = "jemalloc")] @@ -123,6 +146,8 @@ async fn main() { .route("/api/get_board", post(get_board)) .route("/api/save_board", post(save_board_handler)) .route("/api/delete_board", post(delete_board_handler)) + .route("/api/add_item", post(add_item_handler)) + .route("/api/delete_item", post(delete_item_handler)) .route("/ws", get(websocket_handler)) .fallback_service(ServeDir::new("build").append_index_html_on_directories(true)) .with_state(state) @@ -138,104 +163,102 @@ async fn main() { // RESTful API: Fetch board from DynamoDB async fn get_board( State(state): State, - Json(payload): Json>, + Json(payload): Json> ) -> impl IntoResponse { // Extract board_id from the request payload let board_id = match payload.get("board_id") { - Some(id) => id, - None => { - let error_response = json!({ "error": "Missing 'board_id' in request payload" }); - return (StatusCode::BAD_REQUEST, Json(error_response)).into_response(); - } + Some(id) => id, + None => { + let error_response = json!({ "error": "Missing 'board_id' in request payload" }); + return (StatusCode::BAD_REQUEST, Json(error_response)).into_response(); + } }; // Fetch the board from DynamoDB match fetch_board(&state.dynamo_client, board_id).await { - Ok(board) => { - tracing::debug!("Successfully fetched board: {:?}", board); - Json(board).into_response() - } - Err(e) => { - tracing::error!("Failed to fetch board: {}", e); - let error_response = json!({ "error": "Failed to fetch board data" }); - (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)).into_response() - } + Ok(board) => { + tracing::debug!("Successfully fetched board: {:?}", board); + Json(board).into_response() + } + Err(e) => { + tracing::error!("Failed to fetch board: {}", e); + let error_response = json!({ "error": "Failed to fetch board data" }); + (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)).into_response() + } } } // RESTful API: Save board to DynamoDB async fn save_board_handler( State(state): State, - Json(payload): Json>, + Json(payload): Json> ) -> Result, impl IntoResponse> { let board_id = payload - .get("board_id") - .and_then(|v| v.as_str()) - .ok_or_else(|| { - tracing::error!("Missing or invalid 'board_id' in the payload"); - (StatusCode::BAD_REQUEST, "Missing or invalid 'board_id'") - })?; + .get("board_id") + .and_then(|v| v.as_str()) + .ok_or_else(|| { + tracing::error!("Missing or invalid 'board_id' in the payload"); + (StatusCode::BAD_REQUEST, "Missing or invalid 'board_id'") + })?; // Validate and deserialize fields let todo = payload - .get("todo") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_else(|| { - tracing::warn!("'todo' field missing or invalid, defaulting to empty"); - vec![] - }); + .get("todo") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'todo' field missing or invalid, defaulting to empty"); + vec![] + }); let in_progress = payload - .get("in_progress") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_else(|| { - tracing::warn!("'in_progress' field missing or invalid, defaulting to empty"); - vec![] - }); + .get("in_progress") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'in_progress' field missing or invalid, defaulting to empty"); + vec![] + }); let done = payload - .get("done") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_else(|| { - tracing::warn!("'done' field missing or invalid, defaulting to empty"); - vec![] - }); + .get("done") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'done' field missing or invalid, defaulting to empty"); + vec![] + }); let unassigned = payload - .get("unassigned") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_else(|| { - tracing::warn!("'unassigned' field missing or invalid, defaulting to empty"); - vec![] - }); + .get("unassigned") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'unassigned' field missing or invalid, defaulting to empty"); + vec![] + }); - let metadata = payload - .get("metadata") - .and_then(|v| serde_json::from_value(v.clone()).ok()); + let metadata = payload.get("metadata").and_then(|v| serde_json::from_value(v.clone()).ok()); let actions = payload - .get("actions") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_else(|| { - tracing::warn!("'actions' field missing or invalid, defaulting to empty"); - vec![] - }); + .get("actions") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_else(|| { + tracing::warn!("'actions' field missing or invalid, defaulting to empty"); + vec![] + }); let board = KanbanBoard { - todo, - in_progress, - done, - unassigned, - metadata, - actions, + todo, + in_progress, + done, + unassigned, + metadata, + actions, }; match save_board(&state.dynamo_client, board_id, board).await { - Ok(_) => Ok(Json("Board saved successfully")), - Err(e) => { - tracing::error!("Failed to save board: {}", e); - Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to save board")) - } + Ok(_) => Ok(Json("Board saved successfully")), + Err(e) => { + tracing::error!("Failed to save board: {}", e); + Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to save board")) + } } } @@ -268,21 +291,20 @@ pub async fn save_board(client: &Client, board_id: &str, board: KanbanBoard) -> async fn fetch_board(client: &Client, board_id: &str) -> Result { let result = client - .get_item() - .table_name("KanbanBoards") - .key("board_id", AttributeValue::S(board_id.to_string())) - .send() - .await - .map_err(|e| { - tracing::error!("Failed to fetch item from DynamoDB: {:?}", e); - e.to_string() - })?; + .get_item() + .table_name("KanbanBoards") + .key("board_id", AttributeValue::S(board_id.to_string())) + .send().await + .map_err(|e| { + tracing::error!("Failed to fetch item from DynamoDB: {:?}", e); + e.to_string() + })?; let item = result.item.ok_or("Board not found")?; let board: KanbanBoard = from_item(item).map_err(|e| { - tracing::error!("Failed to deserialize board: {}", e); - e.to_string() + tracing::error!("Failed to deserialize board: {}", e); + e.to_string() })?; // Ensure all fields are present @@ -293,7 +315,7 @@ async fn fetch_board(client: &Client, board_id: &str) -> Result, no need for unwrap_or_else metadata: board.metadata, actions: board.actions, // Already a Vec, no need for unwrap_or_else -}) + }) } // Websockets //TODO: Websockets @@ -323,7 +345,6 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { } } - // TODO: Delete async fn delete_board_handler( @@ -331,33 +352,165 @@ async fn delete_board_handler( Json(payload): Json> ) -> impl IntoResponse { let board_id = match payload.get("board_id") { - Some(id) => id, - None => { - let error_response = json!({"error": "Missing 'board_id' in request payload"}); - return (StatusCode::BAD_REQUEST, Json(error_response)).into_response(); - } + Some(id) => id, + None => { + let error_response = json!({"error": "Missing 'board_id' in request payload"}); + return (StatusCode::BAD_REQUEST, Json(error_response)).into_response(); + } }; match delete_board(&state.dynamo_client, board_id).await { - Ok(_) => Json(json!({"message": "Board deleted successfully"})).into_response(), - Err(e) => { - tracing::error!("Failed to delete board: {}", e); - let error_response = json!({"error": "Failed to delete board"}); - (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)).into_response() - } + Ok(_) => Json(json!({"message": "Board deleted successfully"})).into_response(), + Err(e) => { + tracing::error!("Failed to delete board: {}", e); + let error_response = json!({"error": "Failed to delete board"}); + (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)).into_response() + } } } pub async fn delete_board(client: &Client, board_id: &str) -> Result<(), String> { client - .delete_item() - .table_name("KanbanBoards") - .key("board_id", AttributeValue::S(board_id.to_string())) - .send() + .delete_item() + .table_name("KanbanBoards") + .key("board_id", AttributeValue::S(board_id.to_string())) + .send().await + .map_err(|e| { + tracing::error!("DeleteItem request failed: {:?}", e); + e.to_string() + })?; + Ok(()) +} + +// TODO: Add Item handler + +async fn add_item(dynamo_client: &Client, board_id: &str, item: KanbanItem) -> Result<(), String> { + // Fetch the board from DynamoDB + let mut board = fetch_board(dynamo_client, board_id).await.map_err(|e| + format!("Failed to fetch board: {}", e) + )?; + + // Add the item to the "todo" column; adjust as needed + board.todo.push(item); + + // Save the updated board back to DynamoDB + save_board(dynamo_client, board_id, board).await.map_err(|e| + format!("Failed to save board: {}", e) + )?; + + Ok(()) +} + +async fn add_item_handler( + State(state): State, + Json(payload): Json> +) -> impl IntoResponse { + // Extract the board_id and item details + let board_id = match payload.get("board_id").and_then(|v| v.as_str()) { + Some(id) => id, + None => { + return ( + StatusCode::BAD_REQUEST, + Json(json!({"error": "Missing or invalid 'board_id'"})), + ).into_response(); + } + }; + + let item: KanbanItem = match + payload.get("item").and_then(|v| serde_json::from_value(v.clone()).ok()) + { + Some(item) => item, + None => { + return ( + StatusCode::BAD_REQUEST, + Json(json!({"error": "Invalid or missing 'item'"})), + ).into_response(); + } + }; + + // Validate the item + if let Err(err) = item.validate() { + return (StatusCode::BAD_REQUEST, Json(json!({"error": err}))).into_response(); + } + + // Call the add_item function + match add_item(&state.dynamo_client, board_id, item).await { + Ok(_) => Json(json!({"message": "Item added successfully"})).into_response(), + Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": err}))).into_response(), + } +} + + +// TODO: Delete Item Handler + +async fn delete_item( + dynamo_client: &Client, + board_id: &str, + item_id: &str, +) -> Result<(), String> { + // Fetch the board from DynamoDB + let mut board = fetch_board(dynamo_client, board_id) + .await + .map_err(|e| format!("Failed to fetch board: {}", e))?; + + // Remove the item from all columns + let mut found = false; + for column in [&mut board.todo, &mut board.in_progress, &mut board.done, &mut board.unassigned] + { + if let Some(pos) = column.iter().position(|item| item.id == item_id) { + column.remove(pos); + found = true; + break; + } + } + + // If the item was not found, return an error + if !found { + return Err(format!("Item with id '{}' not found on board '{}'", item_id, board_id)); + } + + // Save the updated board back to DynamoDB + save_board(dynamo_client, board_id, board) .await - .map_err(|e| { - tracing::error!("DeleteItem request failed: {:?}", e); - e.to_string() - })?; + .map_err(|e| format!("Failed to save board: {}", e))?; + Ok(()) -} \ No newline at end of file +} + +async fn delete_item_handler( + State(state): State, + Json(payload): Json>, +) -> impl IntoResponse { + // Extract the board_id and item_id + let board_id = match payload.get("board_id") { + Some(id) => id, + None => { + return ( + StatusCode::BAD_REQUEST, + Json(json!({"error": "Missing or invalid 'board_id'"})), + ) + .into_response(); + } + }; + + let item_id = match payload.get("item_id") { + Some(id) => id, + None => { + return ( + StatusCode::BAD_REQUEST, + Json(json!({"error": "Missing or invalid 'item_id'"})), + ) + .into_response(); + } + }; + + // Call the delete_item function + match delete_item(&state.dynamo_client, board_id, item_id).await { + Ok(_) => Json(json!({"message": "Item deleted successfully"})).into_response(), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": err})), + ) + .into_response(), + } +} From 665801db24a6bdb809521923db2af9a4c7e970ca Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:06:36 -0500 Subject: [PATCH 09/12] docs(journal): updating the daily entry. --- apps/kbve.com/src/content/journal/12-26.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/kbve.com/src/content/journal/12-26.mdx b/apps/kbve.com/src/content/journal/12-26.mdx index c56e2b9c1..46a2560b9 100644 --- a/apps/kbve.com/src/content/journal/12-26.mdx +++ b/apps/kbve.com/src/content/journal/12-26.mdx @@ -18,4 +18,12 @@ import { Adsense, Tasks } from '@kbve/astropad'; **SPY** - $600 for SPY!? This is great! \ No newline at end of file + $600 for SPY!? This is great! + +- 01:05PM + + **Items** + + The plan is to rework the kanban data structure, so that we can expand upon it. + Once the basic saving, loading and notes are done, we can move forward with adding in the AI tools around it. + If everything can get up and running, we can move back around to the AStar pathfinding in unity. \ No newline at end of file From e59404879febb9c9990e45491d63a14055c11c69 Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:22:57 -0500 Subject: [PATCH 10/12] feat(kanban): updating the react code to handle the new props and data structure. --- apps/kbve.com/src/engine/kanban/KanbanBase.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/kbve.com/src/engine/kanban/KanbanBase.tsx b/apps/kbve.com/src/engine/kanban/KanbanBase.tsx index 38244c75c..b966da557 100644 --- a/apps/kbve.com/src/engine/kanban/KanbanBase.tsx +++ b/apps/kbve.com/src/engine/kanban/KanbanBase.tsx @@ -115,18 +115,28 @@ export class KanbanBase extends Kilobase { typeof result === 'object' && 'todo' in result && 'in_progress' in result && - 'done' in result + 'done' in result && + 'unassigned' in result && + 'metadata' in result && + 'actions' in result ) { // Save to local storage const formattedResult = { TODO: result.todo || [], 'IN-PROGRESS': result.in_progress || [], DONE: result.done || [], + UNASSIGNED: result.unassigned || [], }; + this.itemPositionsStore.set(formattedResult); console.log( `Loaded and saved board data for board ID: ${boardId}`, ); + + // Optionally, you could handle metadata and actions separately if needed + console.log('Metadata:', result.metadata); + console.log('Actions:', result.actions); + return formattedResult; } From ed749edd17010a25c5fef798871671978c03435b Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:28:45 -0500 Subject: [PATCH 11/12] ci(kanban): preparing to build 1.03 docker image and then updating the helm chart. --- apps/kbve.com/src/engine/kanban/KanbanBase.tsx | 10 +++++++++- apps/rust_kanban/README.md | 3 ++- apps/rust_kanban/project.json | 8 ++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/kbve.com/src/engine/kanban/KanbanBase.tsx b/apps/kbve.com/src/engine/kanban/KanbanBase.tsx index b966da557..b664c8c55 100644 --- a/apps/kbve.com/src/engine/kanban/KanbanBase.tsx +++ b/apps/kbve.com/src/engine/kanban/KanbanBase.tsx @@ -82,6 +82,7 @@ export class KanbanBase extends Kilobase { this.itemPositionsStore.set(resetPositions); // Save the reset structure console.log('Item positions reset to:', resetPositions); } + /** * Load board data by board_id from the API. * Saves the fetched data to local storage. @@ -201,7 +202,14 @@ export class KanbanBase extends Kilobase { */ async validateBoardId(boardId: string): Promise { const boardData = await this.loadBoardData(boardId); - return boardData !== null; // Valid if board data is found + + if (boardData) { + console.log('Validation passed for board ID:', boardId); + return true; + } + + console.error('Validation failed for board ID:', boardId); + return false; } } diff --git a/apps/rust_kanban/README.md b/apps/rust_kanban/README.md index e2de0d256..290b2e404 100644 --- a/apps/rust_kanban/README.md +++ b/apps/rust_kanban/README.md @@ -2,4 +2,5 @@ The api that handles the json from the kanban. More notes will be added once we get a chance to get it all up and running. -Added better CORS support and preparing the helm chart update afterwards. \ No newline at end of file +Added better CORS support and preparing the helm chart update afterwards. +New data structure update, preparing to build the docker image. \ No newline at end of file diff --git a/apps/rust_kanban/project.json b/apps/rust_kanban/project.json index 75a6e1473..acb271277 100644 --- a/apps/rust_kanban/project.json +++ b/apps/rust_kanban/project.json @@ -65,15 +65,15 @@ "metadata": { "images": ["kbve/kanban"], "load": true, - "tags": ["1.02", "1.02.1"] + "tags": ["1.03", "1.03.1"] }, "configurations": { "local": { - "tags": ["1.02", "1.02.1"], + "tags": ["1.03", "1.03.1"], "push": false }, "production": { - "tags": ["1.02", "1.02.1"], + "tags": ["1.03", "1.03.1"], "push": true, "customBuildOptions": "--push", "cache-from": [ @@ -95,7 +95,7 @@ "forwardAllArgs": false }, { - "command": "docker run --env-file /app/rust_kanban/.env -p 3000:3000 -p 3001:3001 -p 8086:8086 kbve/kanban:1.02", + "command": "docker run --env-file /app/rust_kanban/.env -p 3000:3000 -p 3001:3001 -p 8086:8086 kbve/kanban:1.03", "forwardAllArgs": false } ], From 0f78e02104a254d39af9fedfd155e95affe5157b Mon Sep 17 00:00:00 2001 From: h0lybyte <5599058+h0lybyte@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:38:27 -0500 Subject: [PATCH 12/12] ci(kanban): updating the helm chart to deploy the latest docker image. --- apps/kbve.com/src/content/journal/12-26.mdx | 12 +++++++++++- migrations/kube/charts/kanban/services/values.yaml | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/kbve.com/src/content/journal/12-26.mdx b/apps/kbve.com/src/content/journal/12-26.mdx index 46a2560b9..aadd6899c 100644 --- a/apps/kbve.com/src/content/journal/12-26.mdx +++ b/apps/kbve.com/src/content/journal/12-26.mdx @@ -26,4 +26,14 @@ import { Adsense, Tasks } from '@kbve/astropad'; The plan is to rework the kanban data structure, so that we can expand upon it. Once the basic saving, loading and notes are done, we can move forward with adding in the AI tools around it. - If everything can get up and running, we can move back around to the AStar pathfinding in unity. \ No newline at end of file + If everything can get up and running, we can move back around to the AStar pathfinding in unity. + Okay the new docker image should start the build and now I am going to update the helm chart.\ + + +- 01:34PM + + **GitOps** + + We need the unity game also running, so lets go back around and make sure that rareicon game server is up and running. + To get the gameserver up and running, we need to include the repo URL, which is `https://github.com/KBVE/kbve.git` and the branch is `dev`. + Then enable self-healing and add the path, `/migrations/kube/charts/rareicon` and we should have the gameserver up and running for rareicon. \ No newline at end of file diff --git a/migrations/kube/charts/kanban/services/values.yaml b/migrations/kube/charts/kanban/services/values.yaml index 71d5781e4..584f4b383 100644 --- a/migrations/kube/charts/kanban/services/values.yaml +++ b/migrations/kube/charts/kanban/services/values.yaml @@ -4,7 +4,7 @@ kanban: replicaCount: 1 image: repository: kbve/kanban - tag: 1.02.1 + tag: 1.03.1 digest: 'sha256:c002234843480e38de0d9fa6fde5f18a449feea238be757c5afe7cd5bffaecf1' service: name: kanban