Skip to content
Merged
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
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# TaskBox local configuration (safe defaults for the SQLite lab)
APP_ENV=development
TASKBOX_DATABASE_URL=sqlite:///./taskbox.db
TASKBOX_JWT_SECRET=change-me-in-development
TASKBOX_JWT_EXPIRES=3600
TASKBOX_WEBHOOK_SECRET=change-me-in-development
LOG_LEVEL=INFO

# PostgreSQL transition lab (course/labs/07-operations/docker-compose.yml)
POSTGRES_DB=taskbox
POSTGRES_USER=taskbox
POSTGRES_PASSWORD=taskbox
POSTGRES_PORT=5432
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

on:
push:
pull_request:

jobs:
python:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.13', '3.14']
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
- run: uv sync --all-groups --frozen
- run: uv run ruff check .
- run: uv run pytest
- run: uv run python scripts/validate_course_map.py
- name: OpenAPI contract
run: |
uv run python scripts/check_openapi_contract.py
docs:
name: Course and docs validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: site/.nvmrc
cache: npm
cache-dependency-path: site/package-lock.json
- run: npm ci
working-directory: site
- run: npm run build
working-directory: site
43 changes: 43 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Deploy course site

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: site/.nvmrc
cache: npm
cache-dependency-path: site/package-lock.json
- run: npm ci
working-directory: site
- run: npm run build
working-directory: site
- uses: actions/upload-pages-artifact@v3
with:
path: site/dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.venv/
__pycache__/
*.py[cod]
.pytest_cache/
.ruff_cache/
.mypy_cache/
.DS_Store
.env
*.db
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
37 changes: 37 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# API Course Refresh — contributor rules

## Scope

This branch refreshes the public `ialimustufa/API` repository. The upstream baseline is
`086ce40f65fbdbce845ef463f5011def79f1c48d`. Work may be committed and pushed only from
`codex/api-course-refresh` to `origin/codex/api-course-refresh`. Do not push directly to
`main`, open a PR, change GitHub settings, manually trigger deployment workflows, or
deploy Pages.

## Frozen decisions

- Preserve the original `README.md` and `API_Basics.ipynb` byte-for-byte under
`legacy/original/`; do not execute or rewrite the notebook.
- The corrected legacy implementation is Flask with clean `/api/v1/jokes` routes,
in-memory state, configurable hashed Basic Auth, and RFC 9457 errors. Do not register
the old `/joke` or `/adjoke` paths; document their mapping instead.
- The new course application is FastAPI TaskBox with SQLite-first persistence, a required
PostgreSQL/Docker lab, JWT authentication, project roles, cursor pagination, and signed
webhook import.
- Python learner baseline is 3.13 (`>=3.13,<3.15`) using `uv`; docs use Astro 7.3.1,
Starlight 0.42.0, Node 24 LTS, and npm 11 or newer.

## Agent boundaries

Agents receive an exclusive path allowlist. Do not edit outside it, switch branches,
rebase/reset, or change frozen contracts. Only the integrator may modify Git metadata,
commit, or push, and only for the current `codex/api-course-refresh` branch as allowed
above. Keep starter lab code intentionally incomplete; CI validates solution code only.
If a decision is missing, report `NEEDS-DECISION` rather than redesigning a neighboring
subsystem.

## Handoff

Every agent reports `STATUS: DONE | BLOCKED | NEEDS-DECISION`, changed files, commands run,
results, contract deviations, and remaining risks. The integrator owns shared manifests,
lockfiles, composition-root wiring, cross-path fixes, commits, pushes, and final verification.
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.13-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy
WORKDIR /app
RUN pip install --no-cache-dir uv
COPY pyproject.toml uv.lock README.md LICENSE ./
COPY src ./src
COPY migrations ./migrations
COPY alembic.ini ./
RUN uv sync --frozen --no-dev
EXPOSE 8000
CMD ["/app/.venv/bin/uvicorn", "taskbox.main:app", "--host", "0.0.0.0", "--port", "8000"]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Ali Mustufa Shaikh

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
52 changes: 49 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
# API Basics
# API Engineering Course

Making an Simple API Server using Flask Restful and Python.
This repository is a beginner-to-production course for designing, building, testing, and operating APIs.

Fork this [Postman Public Workspace](https://www.postman.com/iali-dev/workspace/getting-hands-dirty-with-apis/overview) to get started!
The new course uses Python, FastAPI, SQLite, and a TaskBox capstone. It includes runnable labs, generated OpenAPI contracts, authentication, webhooks, observability, and deployment exercises. Historical material is preserved under [`legacy/`](legacy/).

## Start here

1. Install Python 3.13+, Node 24 LTS, and `uv`.
2. Run `uv sync --all-groups --frozen`.
3. Start TaskBox with `uv run uvicorn taskbox.main:app --reload`.
4. Open the API docs at `http://127.0.0.1:8000/docs`.
5. Follow the 40-hour sequence in [`course/course-map.yml`](course/course-map.yml).
6. Start the course site with `cd site && npm ci && npm run dev`.

Local site routes start at `http://localhost:4321/`. The GitHub Pages build uses
`/API/`, so the deployed setup page is `https://ialimustufa.github.io/API/setup/`.

## Verify everything

```bash
uv run pytest
uv run ruff check .
uv run python scripts/validate_course_map.py
uv run python scripts/check_openapi_contract.py
cd site
npm ci
npm run check
npm run build
```

## Run with Docker

The root Compose stack deploys the SQLite-first TaskBox API with a persistent
Docker volume:

```bash
docker compose up --build
curl http://127.0.0.1:8000/healthz
curl http://127.0.0.1:8000/readyz
```

Set `TASKBOX_JWT_SECRET` and `TASKBOX_WEBHOOK_SECRET` to long random values in
`.env` before exposing the API. The PostgreSQL transition is a separate required
exercise in [`course/labs/07-operations`](course/labs/07-operations/).

See [`AGENTS.md`](AGENTS.md) for repository conventions and implementation boundaries.

## License

Authored course and application code is MIT licensed. Historical third-party material retains its original provenance; see [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).
16 changes: 16 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Third-party notices

The archived notebook at `legacy/original/API_Basics.ipynb` contains a small
seed dataset of programming jokes attributed in the notebook to the
[`shrutikapoor08/devjoke` README](https://github.com/shrutikapoor08/devjoke/blob/master/README.md).
That notebook is retained unchanged for provenance and historical learning.

The original repository's third-party attribution and licensing terms remain
the responsibility of their respective authors. Newly authored code and course
material in this repository may be distributed under the repository's MIT
license, but that license does not relicense, replace, or claim ownership of
the historical joke text or any other material preserved in the archive.

If the archived notebook is redistributed separately, retain this notice and
the original attribution links. Maintained examples should use original or
appropriately licensed fixture data.
29 changes: 29 additions & 0 deletions alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[alembic]
script_location = migrations
prepend_sys_path = .
sqlalchemy.url = sqlite:///./taskbox.db

[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
13 changes: 13 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
services:
app:
build: .
environment:
TASKBOX_DATABASE_URL: sqlite:////data/taskbox.db
TASKBOX_JWT_SECRET: ${TASKBOX_JWT_SECRET:-change-me-in-development}
TASKBOX_WEBHOOK_SECRET: ${TASKBOX_WEBHOOK_SECRET:-change-me-in-development}
ports:
- "8000:8000"
volumes:
- taskbox_data:/data
volumes:
taskbox_data:
Loading
Loading