From cbe1d53575e1a691579141c6488a6792e07f92b8 Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Thu, 11 Dec 2025 17:15:44 +0100 Subject: [PATCH 01/10] feat(journal): Implement GET for single entry retrieval --- api/routers/journal_router.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/api/routers/journal_router.py b/api/routers/journal_router.py index 2becd60..08e1316 100644 --- a/api/routers/journal_router.py +++ b/api/routers/journal_router.py @@ -54,12 +54,18 @@ async def get_entry(request: Request, entry_id: str, entry_service: EntryService TODO: Implement this endpoint to return a single journal entry by ID Steps to implement: - 1. Use the entry_service to get the entry by ID - 2. Return 404 if entry not found - 3. Return the entry as JSON if found - - Hint: Check the update_entry endpoint for similar patterns - """ + """ + # 1. Use the entry_service to get the entry by ID + entry = await entry_service.get_entry(entry_id) + + # 2. Return 404 if entry not found + if entry is None: + raise HTTPException(status_code=404, detail=f"Entry with ID {entry_id} not found") + + # 3. Return the entry as JSON if found + return entry + + # Hint: Check the update_entry endpoint for similar patterns raise HTTPException(status_code=501, detail="Not implemented - complete this endpoint!") @router.patch("/entries/{entry_id}") From eee933327ad44a5261d85afb00bbfc26c473b641 Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Thu, 11 Dec 2025 22:21:27 +0100 Subject: [PATCH 02/10] feat(journal): Implement Delete for Single Entry --- api/routers/journal_router.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/api/routers/journal_router.py b/api/routers/journal_router.py index 2becd60..2dc88f9 100644 --- a/api/routers/journal_router.py +++ b/api/routers/journal_router.py @@ -80,14 +80,22 @@ async def delete_entry(entry_id: str, entry_service: EntryService = Depends(get_ TODO: Implement this endpoint to delete a specific journal entry Steps to implement: - 1. Check if the entry exists first - 2. Delete the entry using entry_service - 3. Return appropriate response - 4. Return 404 if entry not found - - Hint: Look at how the update_entry endpoint checks for existence """ - raise HTTPException(status_code=501, detail="Not implemented - complete this endpoint!") + # 1. Check if the entry exists first + entry_to_delete = await entry_service.get_entry(entry_id) + + # 4. Return 404 if entry not found + if not entry_to_delete: + raise HTTPException(status_code=404, detail="Entry not found") + + # 2. Delete the entry using entry_service + await entry_service.delete_entry(entry_id) + + # 3. Return appropriate response + return {"detail": f"Entry with ID {entry_id} deleted"} + + # Hint: Look at how the update_entry endpoint checks for existence + # raise HTTPException(status_code=501, detail="Not implemented - complete this endpoint!") @router.delete("/entries") async def delete_all_entries(entry_service: EntryService = Depends(get_entry_service)): From cf424ca6193280ef9f5b596bc3ed8911c5cfb423 Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Thu, 11 Dec 2025 22:30:19 +0100 Subject: [PATCH 03/10] feat(logging): Implement basic console logging --- api/main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/main.py b/api/main.py index 7f09c47..287b2c1 100644 --- a/api/main.py +++ b/api/main.py @@ -11,7 +11,12 @@ # 1. Configure logging with basicConfig() # 2. Set level to logging.INFO # 3. Add console handler +logging.basicConfig( + level=logging.INFO, + handlers=[logging.StreamHandler()]) + # 4. Test by adding a log message when the app starts +logging.info("Application starting up") app = FastAPI(title="LearningSteps API", description="A simple learning journal API for tracking daily work, struggles, and intentions") app.include_router(journal_router) \ No newline at end of file From e1d7819b77a00f4f855fdbb5ea7caf46ca60a011 Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Thu, 11 Dec 2025 22:49:48 +0100 Subject: [PATCH 04/10] feat(data model improvement): add validators --- api/models/entry.py | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/api/models/entry.py b/api/models/entry.py index d2703ed..d0b6544 100644 --- a/api/models/entry.py +++ b/api/models/entry.py @@ -23,26 +23,31 @@ class EntryCreate(BaseModel): class Entry(BaseModel): # TODO: Add field validation rules - # TODO: Add custom validators + # TODO: Add schema versioning - # TODO: Add data sanitization methods + schema_version: ClassVar[int] = 1 id: str = Field( default_factory=lambda: str(uuid4()), description="Unique identifier for the entry (UUID)." + min_length=36, # Enforce UUID format length + max_length=36 ) work: str = Field( ..., + min_length=5, # Ensure work is not too short max_length=256, description="What did you work on today?" ) struggle: str = Field( ..., + min_length=5, # Ensure struggle is not too short max_length=256, description="What’s one thing you struggled with today?" ) intention: str = Field( ..., + min_length=5, # Ensures intention is not too short max_length=256, description="What will you study/work on tomorrow?" ) @@ -54,7 +59,42 @@ class Entry(BaseModel): default_factory=datetime.utcnow, description="Timestamp when the entry was last updated." ) + + # TODO: Add custom validators + @field_validator('id', mode='before') + @classmethod + def validate_uuid_format(cls, v: str): + """Custom validator to ensure the ID is a valid UUID string.""" + if v is not None and len(v) != 36: + raise ValueError('ID must be a 36-character UUID string.') + return v + + @model_validator(mode='after') + def check_intention_for_tomorrow(self) -> 'Entry': + """Custom model validator to check a business rule across fields.""" + if "sleep" in self.intention.lower(): + # This is an example of checking a rule across fields + print("Warning: Intention includes 'sleep'. Ensure productive work is planned.") + return self + + # TODO: Add data sanitization methods + @field_validator('work', 'struggle', 'intention', mode='before') + @classmethod + def sanitize_input(cls, v: str): + """Sanitization: Strip whitespace from input fields.""" + if isinstance(v, str): + # Strip leading/trailing whitespace from user input + v = v.strip() + return v + @field_validator('work', 'struggle', 'intention', mode='after') + @classmethod + def normalize_case(cls, v: str): + """Sanitization: Normalize text to sentence case (Capitalize first letter).""" + if v: + return v[0].upper() + v[1:] + return v + model_config = { "json_encoders": { datetime: lambda v: v.isoformat() From 0d23c9428b0b43cd21919a3a9e57c542a43e9b7b Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Thu, 11 Dec 2025 22:56:12 +0100 Subject: [PATCH 05/10] feat(devcontainer): add cloud CLI tool : Azure --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 695c7a6..a84b216 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "dockerComposeFile": ["./docker-compose.yml"], "features": { // TODO: Add one of these cloud CLI tools based on your needs: - // "ghcr.io/devcontainers/features/azure-cli:1": {}, + "ghcr.io/devcontainers/features/azure-cli:1": {}, // "ghcr.io/devcontainers/features/aws-cli:1": {}, // "ghcr.io/devcontainers/features/gcloud:1": {} }, From 7e8db210acf77bc452746077ad75d9713782ad8e Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Fri, 12 Dec 2025 00:18:40 +0100 Subject: [PATCH 06/10] feat(data model improvement): Correct the error in validator --- api/models/entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/models/entry.py b/api/models/entry.py index d0b6544..0825711 100644 --- a/api/models/entry.py +++ b/api/models/entry.py @@ -29,7 +29,7 @@ class Entry(BaseModel): id: str = Field( default_factory=lambda: str(uuid4()), - description="Unique identifier for the entry (UUID)." + description="Unique identifier for the entry (UUID).", min_length=36, # Enforce UUID format length max_length=36 ) From efd8112c32bee0a9df87d294707e285dc62e6967 Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Fri, 12 Dec 2025 00:33:04 +0100 Subject: [PATCH 07/10] feat(data model improvement): Correct field validator error --- api/models/entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/models/entry.py b/api/models/entry.py index 0825711..25fe817 100644 --- a/api/models/entry.py +++ b/api/models/entry.py @@ -1,5 +1,5 @@ -from pydantic import BaseModel, Field -from typing import Optional +from pydantic import BaseModel, Field, field_validator, model_validator, ClassVar +from typing import Optional, ClassVar from datetime import datetime from uuid import uuid4 From 6b896ff9304408535bf56aa9c5bc9ee3702685bd Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Fri, 12 Dec 2025 00:39:59 +0100 Subject: [PATCH 08/10] feat(data model improvement): Correct ClassVar import error --- api/models/entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/models/entry.py b/api/models/entry.py index 25fe817..5fae1d3 100644 --- a/api/models/entry.py +++ b/api/models/entry.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, Field, field_validator, model_validator, ClassVar +from pydantic import BaseModel, Field, field_validator, model_validator from typing import Optional, ClassVar from datetime import datetime from uuid import uuid4 From f2a4ac3700cb5b85dcf0082ee97c1ea7dbe9c820 Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Fri, 12 Dec 2025 11:01:54 +0100 Subject: [PATCH 09/10] fix(network): Enforce service_healthy dependency on postgre to fix socket.gaierror --- .devcontainer/docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index e3e4e98..524f392 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -5,6 +5,9 @@ services: - ..:/workspaces:cached command: sleep infinity env_file: ["../.env"] + depends_on: # fix for socket.gaierror + postgres: + condition: service_healthy postgres: image: postgres:15 ports: From 412449bbc24d8599fb9c41ecc97481ef17aa6e7c Mon Sep 17 00:00:00 2001 From: vickykohnen Date: Fri, 12 Dec 2025 18:13:15 +0100 Subject: [PATCH 10/10] fix(docker): Corrected postgres healthcheck command to resolve 'option requires an argument -- U' --- .devcontainer/docker-compose.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 524f392..d24067c 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -4,10 +4,7 @@ services: volumes: - ..:/workspaces:cached command: sleep infinity - env_file: ["../.env"] - depends_on: # fix for socket.gaierror - postgres: - condition: service_healthy + env_file: ["../.env"] postgres: image: postgres:15 ports: @@ -17,7 +14,8 @@ services: - postgres_data:/var/lib/postgresql/data - ../database_setup.sql:/docker-entrypoint-initdb.d/database_setup.sql healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + # test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + test: ["CMD-SHELL", "sh -c 'pg_isready -U $$POSTGRES_USER'"] # fix health issue interval: 30s timeout: 10s retries: 3