Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ Everything lives in `main.py` — a single-file FastAPI app:

- **Auth:** HTTP Basic Auth via `fastapi.security.HTTPBasic` + `secrets.compare_digest`. All endpoints share the `verify` dependency.
- **Reads:** Thin wrappers over `things.*` functions (e.g. `things.today()`, `things.get(uuid)`).
- **Search:** `GET /search?q=keyword` uses `things.search()` which runs SQL LIKE on task titles and notes across all collections. Supports optional `limit` parameter (default 50, max 200).
- **Health:** `GET /health` calls `things.today()` to verify the Things database is accessible. Returns 200 if OK, 503 if not.
- **Writes:** Fire-and-forget via the Things URL scheme. `things.url()` builds the URL; `_open()` calls `subprocess.run(['open', url])`. Returns `202 Accepted`. Things has no delete command — use complete or cancel instead.
- **Auto-docs:** FastAPI serves OpenAPI docs at `/docs` (Basic Auth prompted in browser).

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ All endpoints require Basic Auth (`Authorization: Basic ...`).
| GET | `/areas` | All areas |
| GET | `/tags` | All tags |
| GET | `/tasks/{uuid}` | Single task by UUID |
| GET | `/search?q=keyword` | Search tasks by title and notes |
| GET | `/health` | Health check (verifies Things DB is accessible) |

### Write

Expand Down Expand Up @@ -98,6 +100,15 @@ curl -u things:secret -X POST http://localhost:8000/tasks \

# Complete a task
curl -u things:secret -X POST http://localhost:8000/tasks/SOME-UUID/complete

# Search for tasks containing "groceries"
curl -u things:secret "http://localhost:8000/search?q=groceries"

# Limit search results
curl -u things:secret "http://localhost:8000/search?q=groceries&limit=10"

# Health check
curl -u things:secret http://localhost:8000/health
```

## Running as a system service
Expand Down
36 changes: 36 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@ def get_task(uuid: str, auth=Depends(verify)):
return item


# --- Search ---

@app.get("/search")
def search(
q: str = "",
limit: int = 50,
auth=Depends(verify),
):
"""Search tasks across all collections by title and notes.

Query parameters:
q: Search keyword (required). Searches task titles and notes.
limit: Maximum number of results to return (default: 50, max: 200).
"""
if not q.strip():
raise HTTPException(status_code=400, detail="Query parameter 'q' is required")

results = things.search(q.strip())
return results[:limit]


# --- Write models ---

class TaskCreate(BaseModel):
Expand Down Expand Up @@ -203,5 +224,20 @@ def update_project(uuid: str, body: ProjectUpdate, auth=Depends(verify)):
return {"status": "accepted"}


# --- Health check ---

@app.get("/health")
def health(auth=Depends(verify)):
"""Health check endpoint. Verifies the Things 3 database is accessible."""
try:
things.today()
return {"status": "ok", "things_db": "accessible"}
except Exception as e:
raise HTTPException(
status_code=503,
detail=f"Things 3 database unavailable: {str(e)}",
)


if __name__ == "__main__":
uvicorn.run("main:app", host=HOST, port=PORT, reload=False)