# AGENTS.md ## Stack And Entry Points - Single-package Flask app. Runtime entrypoint is `app.py`, which exposes `app = create_app()` for Gunicorn and local runs. - App wiring lives in `office365_self_service/__init__.py`: settings load first, logging is configured, SQLite directories are created if needed, `db.create_all()` runs during app startup, and blueprints from `office365_self_service/routes.py` are registered there. - Core behavior is split by file: `routes.py` handles both HTML pages and JSON APIs, `services.py` contains Office 365 business logic, `graph.py` wraps Microsoft Graph calls, and `models.py` defines the SQLite-backed SQLAlchemy models. ## Commands - Create a local env and install deps with `python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt`. - Run locally with `python3 app.py`. - Run the test suite with `python3 -m unittest`. - Run one test class with `python3 -m unittest tests.test_app.AppRouteTests`. - Run one test method with `python3 -m unittest tests.test_app.AppRouteTests.test_redeem_marks_code_used_and_prevents_second_use`. - Container flow is `docker compose up -d --build`, with healthcheck hitting `http://localhost:8000/api/health`. ## Verified Repo Conventions - There is no configured linter, formatter, typechecker, pytest, or task runner in this repo. Do not invent `pytest`, `ruff`, `mypy`, `npm`, or `make` workflows. - Tests use the standard library `unittest` module in `tests/test_app.py`. - The Docker image runs Gunicorn via `gunicorn -w 2 -b 0.0.0.0:8000 app:app`; local development uses Flask's built-in server from `app.py`. ## Config And Data Gotchas - Copy `.env.example` to `.env` before running locally or via Docker Compose. - Graph readiness is configuration-driven in `office365_self_service/settings.py`. Missing `CLIENT_ID`, `TENANT_ID`, `CLIENT_SECRET`, or `DEFAULT_PASSWORD` does not stop app startup, but service calls fail later with configuration errors. - `WEB_AUTH_ENABLED=true` only enables real admin login protection when both `ADMIN_USERNAME` and `ADMIN_PASSWORD` are set. Otherwise settings downgrade to effectively unauthenticated admin APIs and emit a warning. - Relative SQLite URLs such as `sqlite:///redemption.db` resolve through Flask into `instance/redemption.db`. - If `DATABASE_URL` is set to a container path like `sqlite:////app/data/redemption.db` outside Docker, settings automatically remap it to the matching local repo path and record a warning. - App startup creates tables automatically with `db.create_all()`. There are no migrations in this repo. - Logs are written to `logs/office365_self_service.log` via a rotating file handler during app startup. ## API And Behavior Notes - Public health/config endpoints are `/api/health` and `/api/config`. Admin also has `/admin/api/health`, `/admin/api/session`, and authenticated config/data endpoints under `/admin/api/*`. - Redemption is stateful in the database: codes move `available -> processing -> used`, and failures release codes back to `available`. - Username handling is easy to guess wrong: plain usernames are lowercased and expanded with `DEFAULT_DOMAIN`; full UPNs are accepted as-is. If `DEFAULT_DOMAIN` is empty, callers must submit a full email address. - License assignment is optional. When `DEFAULT_LICENSE_SKU` is set and `LICENSE_ASSIGNMENT_REQUIRED=true`, failed license assignment triggers deletion of the newly created user and surfaces an error instead of a warning. ## Editing Guidance - Preserve the app-factory shape and the `service_factory` injection seam in `create_app()`. Tests rely on injecting fake services there. - Keep focused verification lightweight: for most backend changes, run the relevant `python3 -m unittest` target rather than assuming extra tooling exists.