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
115 changes: 60 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

A compact FastAPI backend for managing college food stalls, staff, menus and orders.

Quick summary
- Auth: Firebase ID tokens (verify with firebase_admin).
- Data: Firestore collections (colleges, stalls, staffs, users, menu_items, orders).
- AI menu extraction: optional (Google Gemini) — returns JSON list of {name, price, description}.
- Payments: Razorpay integration for creating orders and a webhook to record payments.
- Docker ready (Dockerfile + compose.yaml).

Quick start (local)
### Quick summary
- **Auth:** Firebase ID tokens (verify with firebase_admin).
- **Data:** Firestore collections (colleges, stalls, staffs, users, menu_items, orders).
- **Analytics:** Staff performance tracking (monthly/daily logs) and manager dashboard.
- **AI menu extraction:** optional (Google Gemini) — returns JSON list of items.
- **Payments:** Razorpay integration for orders + webhook validation.
- **Docker ready:** `Dockerfile` + `compose.yaml` included.

### Quick start (local)
1. Create & activate a virtualenv:

```bash
Expand All @@ -32,7 +33,7 @@ python main.py
uvicorn app.app:app --reload --host 0.0.0.0 --port 8000
```

Docker
### Quick start (Docker)
- Build and run container (simple):

```bash
Expand All @@ -49,7 +50,7 @@ docker compose -f compose.yaml --env-file .env up --build -d
docker compose -f compose.yaml down
```

Authentication
### Authentication
- Use Firebase ID token (NOT the UID). Send in Authorization header as a Bearer token:

```
Expand All @@ -58,14 +59,14 @@ Authorization: Bearer <idToken>

- Obtain a test idToken via the helper `get_token.py` (dev-only).

Key environment variables
### Key environment variables
- FIREBASE_SERVICE_ACCOUNT: required — service account JSON content (or path) used by firebase_admin. For CI you can set `CI=true` to skip strict local validation.
- FIREBASE_API_KEY, FIREBASE_PROJECT_ID, etc. — used by helper scripts.
- GEMINI_API_KEY — optional, required for image-based menu scanning (Gemini model: gemini-2.5-flash).
- RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET — required for creating Razorpay orders.
- RAZORPAY_WEBHOOK_SECRET — required for validating Razorpay webhook signatures (header `X-Razorpay-Signature`).

Important files
### Important files
- `app/firebase_init.py` — initializes firebase_admin and exposes `db` (Firestore client).
- `app/auth.py` — verifies tokens and initializes manager records when a manager signs in using the stall email.
- `app/schema.py` — Pydantic models (MenuSchema, MenuItemSchema, MenuScanResponse, CreateOrderSchema, etc.).
Expand All @@ -75,72 +76,76 @@ Important files
- `app/webhook.py` — Razorpay webhook: validates HMAC signature and updates `orders` documents with `razorpay_payment_id`, `razorpay_payment_data`, `status: 'PAID'`, and a generated `pickup_code`.
- `get_token.py` — helper to exchange email/password for idToken (dev/test only).

Core rules / behavior (short)
### Core rules / behavior (short)
- Staff authorization: tokens are verified and mapped to a `staffs` document. Only staff with `status == 'active'` are allowed to use staff routes.
- Stall ownership: a staff can only manage the stall they belong to (stall_id is enforced on menu writes/updates).
- Manager init: when a user signs in and their email matches a stall's registered email, a manager `staffs` document is auto-created.
- `POST /staff/add-member` returns a `reset_link` (Firebase password reset) to onboard newly created staff users.

Menu upload & scan
### Menu upload & scan
- Menu upload expects JSON matching `MenuSchema` (see `app/schema.py`): `stall_id` must match authenticated staff's stall; `items` cannot be empty; `price` must be > 0.
- Image scan (`POST /staff/menu/scan-image`) accepts JPEG/PNG only and max file size 5MB; uses Gemini (`gemini-2.5-flash`) to extract items and returns a `MenuScanResponse` that must be reviewed before saving.

API (selected endpoints)
- GET /health — health check

Auth
- POST /auth/verify-staff — Verify staff token; initializes manager if needed.
- POST /auth/verify-student — Verify student token and auto-register student (by college domain).

User (student)
- GET /user/menu — List menus for the student's college (only active & verified stalls returned).
- POST /user/order/create — Create a Razorpay order (payload: CreateOrderSchema)
- POST /user/order/verify — Client-side payment verification endpoint (accepts razorpay_order_id, razorpay_payment_id, razorpay_signature and internal_order_id); verifies signature and marks the internal order PAID with a pickup code.
- PATCH /user/profile — Update student profile (name, roll_number, phone).
- GET /user/orders — List student's orders (shows pickup code for PAID/READY orders).

Staff / Manager
- POST /staff/add-member — Manager adds a staff (payload: {email}) and receives a `reset_link`.
- GET /staff/list — Manager: list staff for manager's stall
- DELETE /staff/{staff_uid} — Manager: remove a staff member (must be same stall)
- PUT /staff/{staff_uid}/email — Manager: change a staff's email (creates user if needed)

Staff menu management
- POST /staff/menu — Upload menu JSON for the authenticated staff's stall (MenuSchema)
- GET /staff/menu — Get menu for authenticated staff's stall
- POST /staff/menu/scan-image — Upload image (JPEG/PNG, <5MB) → returns MenuScanResponse (requires GEMINI_API_KEY)
- PATCH /staff/menu/{item_id} — Update a menu item
- DELETE /staff/menu/{item_id} — Delete a menu item

Staff order management
- GET /staff/orders?status=PAID — List stall orders by status (default PAID)
- PATCH /staff/orders/{order_id}/status — Update an order status (only for orders belonging to the staff's stall)
- POST /staff/orders/verify-pickup — Verify 4-digit pickup code and mark order CLAIMED

Webhook
- POST /webhook/razorpay — Razorpay will POST payment events here; the endpoint verifies `X-Razorpay-Signature` using `RAZORPAY_WEBHOOK_SECRET` and updates the related `orders/{internal_order_id}` with `razorpay_payment_id`, `razorpay_payment_data`, `status: 'PAID'`, and a generated `pickup_code`. Configure Razorpay webhook to include `notes.internal_order_id` when creating payments.

Testing & troubleshooting
### API (selected endpoints)
- `GET /health` — health check

### Auth
- `POST /auth/verify-staff` — Verify staff token; initializes manager if needed.
- `POST /auth/verify-student` — Verify student token and auto-register student (by college domain).

### User (student)
- `GET /user/menu` — List menus for the student's college (only active & verified stalls returned).
- `POST /user/order/create` — Create a Razorpay order (payload: CreateOrderSchema)
- `POST /user/order/verify` — Client-side payment verification endpoint (accepts razorpay_order_id, razorpay_payment_id, razorpay_signature and internal_order_id); verifies signature and marks the internal order PAID with a pickup code.
- `PATCH /user/profile` — Update student profile (name, roll_number, phone).
- `GET /user/orders` — List student's orders (shows pickup code for PAID/READY orders).

### Staff / Manager
- `PATCH /staff/profile` — Update authenticated staff's profile (name, phone).
- `POST /staff/add-member` — Manager adds a staff (payload: {email}) and receives a `reset_link`.
- `GET /staff/list` — Manager: list staff for manager's stall
- `DELETE /staff/{staff_uid}` — Manager: remove a staff member (must be same stall)
- `PUT /staff/{staff_uid}/email` — Manager: change a staff's email (creates user if needed)

### Staff menu management
- `POST /staff/menu` — Upload menu JSON for the authenticated staff's stall (MenuSchema)
- `GET /staff/menu` — Get menu for authenticated staff's stall
- `POST /staff/menu/scan-image` — Upload image (JPEG/PNG, <5MB) → returns MenuScanResponse (requires GEMINI_API_KEY)
- `PATCH /staff/menu/{item_id}` — Update a menu item
- `DELETE /staff/menu/{item_id}` — Delete a menu item

### Staff order management
- `GET /staff/orders?status=PAID` — List stall orders by status (default PAID)
- `PATCH /staff/orders/{order_id}/status` — Update an order status (only for orders belonging to the staff's stall)
- `POST /staff/orders/verify-pickup` — Verify 4-digit pickup code and mark order CLAIMED

### Analytics & Performance
- `GET /staff/performance/overview?month=X&year=Y` — Manager: Get monthly leaderboard/stats for all staff.

### Webhook
- `POST /webhook/razorpay` — Razorpay will POST payment events here; the endpoint verifies `X-Razorpay-Signature` using `RAZORPAY_WEBHOOK_SECRET` and updates the related `orders/{internal_order_id}` with `razorpay_payment_id`, `razorpay_payment_data`, `status: 'PAID'`, and a generated `pickup_code`. Configure Razorpay webhook to include `notes.internal_order_id` when creating payments.

### Testing & troubleshooting
- Swagger UI: http://localhost:8000/docs — use the Authorize button and paste the idToken (Bearer token).
- If you see {"message":"Authorization header required"} or 401: ensure header name is exactly `Authorization` and value starts with `Bearer ` followed by the idToken.
- If token expired or invalid: re-login to get a fresh idToken.

Security notes
### Security notes
- Do NOT commit secrets. The repo includes a `secrets/` folder in .gitignore — keep service account JSON and .env out of VCS.
- The server uses Firestore security via server-side checks: stall_id and college_id are validated in code before writes.

Where to look next (dev pointers)
### Where to look next (dev pointers)
- To change menu schema, edit `app/schema.py` (Pydantic models used for validation).
- To modify staff authorization behavior, check `app/auth.py` and `app/staff.py:get_staff_details`.
- GEMINI integration is in `app/staff.py` (_extract_menu_from_image) and requires `GEMINI_API_KEY`.

Short checklist for running locally
### Short checklist for running locally
1. Populate `.env` (or export env vars). Ensure FIREBASE_SERVICE_ACCOUNT is set.
2. pip install -r requirements.txt
3. uvicorn app.app:app --reload --host 0.0.0.0 --port 8000
4. Use Swagger or a REST client. Authorize with `Authorization: Bearer <idToken>`.

License
### License
- See LICENSE in repo root.

Last updated: 2026-01-17
Last updated: 2026-01-20
38 changes: 28 additions & 10 deletions app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
UpdateOrderStatusSchema,
UpdateUserProfileSchema,
VerifyPickupSchema,
VerifyPaymentSchema
VerifyPaymentSchema,

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation (add 2 spaces). Warning

Wrong hanging indentation (add 2 spaces).
UpdateStaffProfileSchema

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation (add 2 spaces). Warning

Wrong hanging indentation (add 2 spaces).
)
from .auth import (
authenticate_student,
Expand All @@ -29,15 +30,16 @@
add_staff_member,
get_stall_orders,
update_order_status_staff,
get_my_staff_profile,
get_staff_me,
verify_order_pickup,
activate_staff
activate_staff,

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation (add 2 spaces). Warning

Wrong hanging indentation (add 2 spaces).
update_staff_profile

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation (add 2 spaces). Warning

Wrong hanging indentation (add 2 spaces).
)
from .manager import (
get_my_staff,
remove_staff_member,
update_staff_email,
get_stall_performance_overview

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation (add 2 spaces). Warning

Wrong hanging indentation (add 2 spaces).
)
from .user import (
get_user_menu,
Expand Down Expand Up @@ -67,7 +69,6 @@

security = HTTPBearer()

app.include_router(webhook_router)

@app.get("/health", tags=["health"])
def health_check():
Expand All @@ -77,6 +78,8 @@
"environment": os.getenv("ENV", "development")
}

app.include_router(webhook_router)

Check warning

Code scanning / Prospector (reported by Codacy)

expected 2 blank lines after class or function definition, found 1 (E305) Warning

expected 2 blank lines after class or function definition, found 1 (E305)

@app.post('/auth/verify-staff', tags=["auth"])
async def verify_staff_endpoint(credentials: HTTPAuthorizationCredentials = Security(security)):
return await verify_staff_access(credentials.credentials)
Expand Down Expand Up @@ -118,19 +121,21 @@
):
return await verify_payment_and_update_order( payment_data, credentials.credentials )

@app.get("/staff/performance/overview", tags=["manager"])
async def get_stall_performance_overview_endpoint(

Check warning

Code scanning / Pylint (reported by Codacy)

Missing function docstring Warning

Missing function docstring

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing function or method docstring Warning

Missing function or method docstring
month: int,

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation before block (add 4 spaces). Warning

Wrong hanging indentation before block (add 4 spaces).
year: int,

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation before block (add 4 spaces). Warning

Wrong hanging indentation before block (add 4 spaces).

Check warning

Code scanning / Pylint (reported by Codacy)

Missing function docstring Warning

Missing function docstring

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing function or method docstring Warning

Missing function or method docstring
credentials: HTTPAuthorizationCredentials = Security(security)
):
return await get_stall_performance_overview(month, year, credentials.credentials)

@app.post('/staff/add-member', tags=["manager"])
async def add_staff_endpoint(
staff_data: AddStaffSchema,

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation before block (add 4 spaces). Warning

Wrong hanging indentation before block (add 4 spaces).
credentials: HTTPAuthorizationCredentials = Security(security)
):
return await add_staff_member(staff_data, credentials.credentials)

@app.post("/staff/activate",tags=["staff"])
async def activate_staff_endpoint(
credentials:HTTPAuthorizationCredentials = Security(security)
):
return await activate_staff(credentials.credentials)

@app.get('/staff/list', tags=["manager"])
async def get_staff_list_endpoint(
credentials: HTTPAuthorizationCredentials = Security(security)
Expand All @@ -152,12 +157,25 @@
):
return await update_staff_email(staff_uid, update_data.new_email, credentials.credentials)

@app.post("/staff/activate",tags=["staff", "manager"])

Check warning

Code scanning / Pylint (reported by Codacy)

Exactly one space required after comma Warning

Exactly one space required after comma
async def activate_staff_endpoint(
credentials:HTTPAuthorizationCredentials = Security(security)
):
return await activate_staff(credentials.credentials)

@app.get("/staff/me", tags=["staff", "manager"])
async def get_staff_me_endpoint(

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation before block (add 4 spaces). Warning

Wrong hanging indentation before block (add 4 spaces).
credentials: HTTPAuthorizationCredentials = Security(security)
):
return await get_staff_me(credentials.credentials)

@app.patch("/staff/profile", tags=["staff", "manager"])
async def update_staff_profile_endpoint(

Check warning

Code scanning / Pylint (reported by Codacy)

Missing function docstring Warning

Missing function docstring

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing function or method docstring Warning

Missing function or method docstring
profile_data: UpdateStaffProfileSchema,

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation before block (add 4 spaces). Warning

Wrong hanging indentation before block (add 4 spaces).
credentials: HTTPAuthorizationCredentials = Security(security)
):
return await update_staff_profile(profile_data, credentials.credentials)

@app.post("/staff/menu", tags=["staff", "manager"])
async def upload_menu_endpoint(
menu_data: MenuSchema,
Expand All @@ -175,7 +193,7 @@

@app.post("/staff/menu/scan-image", tags=["staff", "manager"], response_model=MenuScanResponse)
async def scan_menu_endpoint(
file: UploadFile = File(...),

Check warning

Code scanning / Pylint (reported by Codacy)

Wrong hanging indentation before block (add 4 spaces). Warning

Wrong hanging indentation before block (add 4 spaces).
credentials: HTTPAuthorizationCredentials = Security(security)
):
token = credentials.credentials
Expand Down
Loading
Loading