HR-ATS-Portal/backend/LLM_CONTEXT_PROMPT.md

131 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# HR-ATS Backend LLM Context Prompt
Copy everything below the line into any LLM session before asking it to write or edit backend code.
---
You are coding inside **HR-ATS-Portal** (`backend/`). You must follow this house style **exactly**. Mirror neighboring files. Do not invent alternate patterns, layers, or response shapes. Prefer matching existing code over “cleaner” industry defaults.
## Goal
Every change must look like it was written by the same author as `backend/users/` and `backend/inbox/`.
## Package layout (every domain)
```
backend/<domain>/
app.py # routes only — HTTP in/out
views.py # service class — business logic
models.py # SQLModel table + classmethod DB accessors
serializers.py # hand-rolled dict builders (no Pydantic response models)
plugins.py # pure helpers (hash, JWT, clean payload) — NO FastAPI imports
permissions.py # OAuth2 scheme + Depends aliases (auth domains only)
```
- Bare `router = APIRouter()`; mount in `main.py` with `app.include_router(...)`.
- No package `__init__.py`. Run from `backend/` so imports are top-level (`users.app`, `db_setup`).
- Non-DB config: module-level `load_dotenv()` + `os.getenv(...)`. Do **not** extend `db_setup.Settings` for app secrets.
## Layer duties (non-negotiable)
| Layer | Owns | Must NOT do |
|---|---|---|
| `app.py` | Routes, inline request Pydantic models, `JSONResponse`, HTTP token envelope via serializers, inject `session` / `CurrentUser` | Business rules, SQL, JWT crypto beyond calling plugin functions |
| `views.py` | Business checks, call models, raise `HTTPException`, return ORM user (auth) or serialized dict (CRUD) | Call `serialize_token` or build login HTTP payloads |
| `models.py` | Fields, queries, inserts/updates/soft-delete, `selectinload` when needed | HTTPException, FastAPI, serializers |
| `serializers.py` | `serialize_*` → plain `dict` | DB, Depends |
| `plugins.py` | Pure helpers; raise library errors (`jwt.*`) | Import FastAPI / raise HTTPException |
| `permissions.py` | `OAuth2PasswordBearer`, `get_current_user`, `CurrentUser` alias, `require_permission` | Route handlers |
## Exact route pattern (`app.py`)
- Paths are verb-in-path: `/users/create`, `/users/fetch`, `/users/login`**not** `/auth/token`, not REST-resource-only.
- Standard wrapper on every handler:
```python
try:
service=User(session=session)
data=await service.some_method(...)
return JSONResponse(content={"data":data,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
```
- List: `{"data":items,"total":total,"status_code":200}`. By id: include `"total":1`.
- Login / refresh — **service returns ORM user**; route mints tokens and serializes:
```python
user=await service.authenticate_user(form_data.username,form_data.password)
tokens=serialize_token(create_access_token(user),create_refresh_token(user),user)
return JSONResponse(content={**tokens,"status_code":200})
```
- Request body models stay **inline in `app.py`** (`UserCreate`, `UserUpdate`, `TokenRefresh`). Never move them into `serializers.py`.
- Use `session: AsyncSession = Depends(get_session)` by default. Use `Annotated` only when required (`OAuth2PasswordRequestForm`, `CurrentUser` before other defaulted params).
- Preserve tight local spacing: `service=User(session=session)`, `detail=str(e)`. Do not pretty-reformat unrelated code.
## Exact service pattern (`views.py`)
```python
class User:
def __init__(self,session:AsyncSession):
self.session=session
async def create_user(self,payload):
...
return serialize_user(user)
```
- Leave service method parameters untyped (match existing).
- Raise `HTTPException(status_code=...,detail="...")` for domain errors.
- `authenticate_user` / `refresh_access_token` return the **Users ORM instance only**.
- If you loaded via an accessor that does not `selectinload(role)`, re-fetch with `get_user_by_id` before serialization that touches `user.role`. (`get_user_by_email` and `get_user_by_id` both eager-load `role` today.)
## Exact model pattern (`models.py`)
- SQLModel `table=True`; accessors as `@classmethod async def`.
- Soft delete sets `is_deleted=True` and `is_active=False`.
- `selectinload` relations that serializers read.
- Commits happen inside write accessors (existing convention).
## Serializers
- Hand-built dicts only. `str(uuid)`, `.isoformat()` for datetimes. Never include `password`.
- Token response: OAuth2 fields at **root** (`access_token`, `refresh_token`, `token_type`, `expires_in`); user record under `data`.
## Auth (when touching users auth)
- PyJWT access + refresh with a `type` claim; `decode_token(..., expected_type=...)` rejects mismatches.
- Protect `/users/*` with `current_user: CurrentUser` except `/users/login` and `/users/refresh`. Prefer `Depends(require_permission(...))` on mutating/list routes that need a specific tag; keep `/users/me` on plain `CurrentUser` so users can discover a missing-role state.
- `get_current_user`: decode access → DB by `sub` → reject missing/deleted/inactive → return `serialize_user(user, with_permissions=True)`.
- Login: `OAuth2PasswordRequestForm` (username = email). `tokenUrl="users/login"` (no leading slash).
- JWT `iat`/`exp` use `datetime.now(timezone.utc)` only — never naive `datetime.now()`.
## Dependencies / env
- Add pins to `backend/requirements.txt` under banner comments with a trailing `# why` comment.
- Put secrets in `backend/.env`; keep key names in `backend/.env.example`.
## Hard bans
1. No repository / use-case / DTO layers beyond inline request models.
2. No Pydantic response models; no alternate envelopes; no `/api/v1` prefix.
3. No `serialize_token` inside `views.py`.
4. No FastAPI imports in `plugins.py`.
5. No drive-by refactors, renames, or whole-file reformats.
6. RBAC exists in `users/permissions.py`; do not invent a second scheme.
7. Do not edit unrelated domains (`inbox/` vs `users/`) unless asked.
8. Do not add `__init__.py` to make packages.
## Workflow when adding an endpoint
1. Model accessor (if DB).
2. Service method in `views.py`.
3. `serialize_*` if new shape.
4. Route in `app.py` with the standard try/except + `JSONResponse`.
5. Add `current_user: CurrentUser` or `Depends(require_permission(...))` if the route is protected.
Before finishing, re-read the touched files and confirm they still match a sibling files structure, naming, spacing, and response shape.