Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
cbe1d53
feat(journal): Implement GET for single entry retrieval
vickykohnen Dec 11, 2025
eee9333
feat(journal): Implement Delete for Single Entry
vickykohnen Dec 11, 2025
cf424ca
feat(logging): Implement basic console logging
vickykohnen Dec 11, 2025
e1d7819
feat(data model improvement): add validators
vickykohnen Dec 11, 2025
0d23c94
feat(devcontainer): add cloud CLI tool : Azure
vickykohnen Dec 11, 2025
7e8db21
feat(data model improvement): Correct the error in validator
vickykohnen Dec 11, 2025
efd8112
feat(data model improvement): Correct field validator error
vickykohnen Dec 11, 2025
6b896ff
feat(data model improvement): Correct ClassVar import error
vickykohnen Dec 11, 2025
933d4f6
Merge branch 'feature/delete-entry' of https://github.com/vickykohnen…
vickykohnen Dec 12, 2025
499e5bb
Merge branch 'feature/logging-setup' of https://github.com/vickykohne…
vickykohnen Dec 12, 2025
ba26e76
Merge branch 'feature/data-model-improvements' of https://github.com/…
vickykohnen Dec 12, 2025
d31c072
Merge branch 'feature/cloud-cli-setup' of https://github.com/vickykoh…
vickykohnen Dec 12, 2025
f2a4ac3
fix(network): Enforce service_healthy dependency on postgre to fix so…
vickykohnen Dec 12, 2025
412449b
fix(docker): Corrected postgres healthcheck command to resolve 'optio…
vickykohnen Dec 12, 2025
3b88b94
Merge branch 'feature/delete-entry' of https://github.com/vickykohnen…
vickykohnen Dec 12, 2025
5987754
Merge branch 'feature/logging-setup' of https://github.com/vickykohne…
vickykohnen Dec 12, 2025
0783cd6
Merge branch 'feature/data-model-improvements' of https://github.com/…
vickykohnen Dec 12, 2025
af60b31
Merge branch 'feature/cloud-cli-setup' of https://github.com/vickykoh…
vickykohnen Dec 12, 2025
59499f1
Merge branch 'fix/db-network-issue' of https://github.com/vickykohnen…
vickykohnen Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {}
},
Expand Down
5 changes: 3 additions & 2 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ services:
volumes:
- ..:/workspaces:cached
command: sleep infinity
env_file: ["../.env"]
env_file: ["../.env"]
postgres:
image: postgres:15
ports:
Expand All @@ -14,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
Expand Down
5 changes: 5 additions & 0 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
50 changes: 45 additions & 5 deletions api/models/entry.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pydantic import BaseModel, Field
from typing import Optional
from pydantic import BaseModel, Field, field_validator, model_validator
from typing import Optional, ClassVar
from datetime import datetime
from uuid import uuid4

Expand All @@ -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)."
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?"
)
Expand All @@ -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()
Expand Down
40 changes: 27 additions & 13 deletions api/routers/journal_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand All @@ -80,14 +86,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)):
Expand Down