HR-ATS-Portal/backend/LLM_CONTEXT_PROMPT.md

6.4 KiB
Raw Blame History

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/loginnot /auth/token, not REST-resource-only.
  • Standard wrapper on every handler:
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:
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)

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.