Refactor to manager-centric model, add team calendar for all users

Key changes:
- Removed office-centric model (deleted offices.py, office-rules)
- Renamed to team-rules, managers are part of their own team
- Team calendar visible to all (read-only for employees)
- Admins can have a manager assigned
This commit is contained in:
Stefano Manfredi
2025-12-02 13:30:04 +00:00
parent 2ad8ba3424
commit 7168fa4b72
30 changed files with 1016 additions and 910 deletions

263
CLAUDE.md Normal file
View File

@@ -0,0 +1,263 @@
# CLAUDE.md - Project Intelligence
## Project Overview
**Org-Parking** is a manager-centric parking spot management system for organizations. It features fair parking assignment based on presence/parking ratio, supporting both standalone JWT authentication and Authelia/LLDAP SSO integration.
### Technology Stack
- **Backend:** FastAPI + SQLAlchemy + SQLite
- **Frontend:** Vanilla JavaScript (no frameworks)
- **Auth:** JWT tokens + Authelia SSO support
- **Containerization:** Docker + Docker Compose
### Architecture
```
app/routes/ → API endpoints (auth, users, managers, presence, parking)
services/ → Business logic (parking algorithm, auth, notifications)
database/ → SQLAlchemy models and connection
frontend/ → Static HTML pages + JS modules
utils/ → Auth middleware
```
## Build & Run Commands
```bash
# Development
python -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
# Docker
docker compose up -d
# Dependencies
pip install -r requirements.txt
# Initialize test database
python create_test_db.py
```
## Code Style & Conventions
### Python
- FastAPI async patterns with `Depends()` for dependency injection
- Pydantic models for request/response validation
- SQLAlchemy ORM (no raw SQL)
- UUIDs as string primary keys: `str(uuid.uuid4())`
- Dates stored as TEXT in "YYYY-MM-DD" format
### JavaScript
- ES6 modules with centralized API client (`/js/api.js`)
- Token stored in localStorage, auto-included in requests
- Utility functions in `/js/utils.js`
- Role-based navigation in `/js/nav.js`
### Authentication
- Dual mode: JWT tokens (standalone) or Authelia headers (SSO)
- LDAP users have `password_hash = None`
- Check pattern: `config.AUTHELIA_ENABLED and user.password_hash is None`
---
## Known Issues & Technical Debt
### Critical
1. **Inactive Notification System**
- Location: [services/notifications.py](services/notifications.py)
- Issue: All code implemented but no scheduler integrated
- TODO: Integrate APScheduler or similar
2. **Default SECRET_KEY**
- Location: [app/config.py:13](app/config.py#L13)
- Issue: Defaults to "change-me-in-production"
- Fix: Add startup validation to error if default key used
3. **No CSRF Protection**
- Forms use token auth only, vulnerable to CSRF attacks
- Fix: Implement CSRF tokens or validate referer header
4. **No Rate Limiting**
- Login endpoint has no brute force protection
- Fix: Add slowapi or similar middleware
### Performance
1. **N+1 Query Problems**
- Location: [app/routes/managers.py:244-259](app/routes/managers.py#L244-L259)
- Location: [app/routes/presence.py:336-419](app/routes/presence.py#L336-L419)
- Issue: Loops that query database for each item
- Fix: Use joins and relationship loading
2. **Inefficient Spot Prefix Lookups**
- Location: [services/parking.py:56-64](services/parking.py#L56-L64)
- Issue: Repeated DB queries for same data
- Fix: Cache in request context
### Code Quality
1. **Duplicated LDAP Check Logic** (4+ locations)
```python
# Appears in: users.py:91, 168, 257, 280
is_ldap_user = config.AUTHELIA_ENABLED and user.password_hash is None
```
- Fix: Create `utils.is_ldap_user(user)` helper
2. **Inline JavaScript in HTML**
- 500+ lines embedded across pages
- Affected: team-rules.html, team-calendar.html, settings.html
- Fix: Extract to separate JS files
3. **Inconsistent Response Formats**
- Some endpoints return dicts, others Pydantic models
- Fix: Standardize on Pydantic response schemas
4. **God Object: User Model**
- Location: [database/models.py:11-47](database/models.py#L11-L47)
- Issue: 27 columns mixing auth, profile, preferences, manager settings
- Fix: Normalize into UserProfile, UserPreferences, ManagerSettings tables
5. **Repetitive CRUD in managers.py**
- Location: [app/routes/managers.py](app/routes/managers.py)
- Issue: 4 resources × 3 operations with near-identical code
- Fix: Create generic CRUD factory or base class
6. **Silent Exception Handling**
- Location: [app/routes/presence.py:135-143](app/routes/presence.py#L135-L143)
- Issue: Catches all exceptions and only prints
- Fix: Log properly and propagate meaningful errors
---
## Areas for Simplification
### 1. Consolidate Response Building
The `user_to_response()` pattern is duplicated in [users.py:76-107](app/routes/users.py#L76-L107) and [users.py:254-274](app/routes/users.py#L254-L274). Create single reusable function.
### 2. Generic CRUD Router Factory
[managers.py](app/routes/managers.py) has 12 nearly identical endpoints. Create:
```python
def create_crud_router(model: Type, schema: Type, parent_key: str):
router = APIRouter()
# Generate GET, POST, DELETE endpoints
return router
```
### 3. Frontend State Management
Each page maintains its own globals (`currentUser`, `currentData`). Consider simple app-level state or Web Components.
### 4. Date Handling
All dates stored as TEXT strings. Could use proper DATE columns for better query performance and validation.
### 5. Notification Service Activation
Create [services/scheduler.py](services/scheduler.py) with APScheduler:
```python
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()
scheduler.add_job(process_notification_queue, 'interval', minutes=5)
```
---
## Security Improvements Needed
| Priority | Issue | Location | Recommendation |
|----------|-------|----------|----------------|
| HIGH | Rate limiting | main.py | Add slowapi middleware |
| HIGH | CSRF protection | All forms | Implement CSRF tokens |
| HIGH | Secret validation | config.py:13 | Error on default key |
| MEDIUM | Password validation | auth.py:63-67 | Enforce complexity rules |
| MEDIUM | Input sanitization | notification emails | Use template library |
| LOW | CORS configuration | compose.yml | Document production settings |
---
## Testing Strategy (Missing)
No tests currently exist. Recommended structure:
```
tests/
├── conftest.py # Fixtures (test DB, client, auth)
├── test_auth.py # JWT and Authelia modes
├── test_parking.py # Fair assignment algorithm
├── test_presence.py # Bulk operations, team calendar
├── test_managers.py # CRUD operations
└── integration/
└── test_workflows.py # End-to-end scenarios
```
Key test scenarios:
1. Parking algorithm with varying ratios
2. Manager closing days affect assignments
3. Guarantee and exclusion rules
4. Authelia header authentication flow
5. LDAP vs local user password handling
---
## API Quick Reference
### Authentication
- `POST /api/auth/register` - Create user (standalone mode)
- `POST /api/auth/login` - Get JWT token
- `GET /api/auth/me` - Current user (JWT or Authelia)
### Presence
- `POST /api/presence/mark` - Mark single day
- `POST /api/presence/mark-bulk` - Mark multiple days
- `GET /api/presence/team` - Team calendar with parking
### Parking
- `POST /api/parking/manual-assign` - Manager assigns spot
- `POST /api/parking/reassign-spot` - Reassign existing spot
- `GET /api/parking/eligible-users/{id}` - Users for reassignment
### Manager Settings
- `GET/POST/DELETE /api/managers/closing-days`
- `GET/POST/DELETE /api/managers/weekly-closing-days`
- `GET/POST/DELETE /api/managers/guarantees`
- `GET/POST/DELETE /api/managers/exclusions`
---
## Development Notes
### Adding a New Route
1. Create file in `app/routes/`
2. Use `APIRouter(prefix="/api/...", tags=["..."])`
3. Register in `main.py`: `app.include_router(...)`
4. Add auth dependency: `current_user: User = Depends(get_current_user)`
### Database Migrations
No migration system (Alembic) configured. Schema changes require:
1. Update [database/models.py](database/models.py)
2. Delete SQLite file or write manual migration
3. Run `create_test_db.py` for fresh database
### Frontend Page Pattern
```html
<script type="module">
import api from '/js/api.js';
import { initNav } from '/js/nav.js';
document.addEventListener('DOMContentLoaded', async () => {
await api.checkAuth(true); // Redirect to login if not auth'd
initNav();
// Page-specific logic
});
</script>
```
---
## File Quick Links
| Purpose | File |
|---------|------|
| Main entry | [main.py](main.py) |
| Configuration | [app/config.py](app/config.py) |
| Database models | [database/models.py](database/models.py) |
| Parking algorithm | [services/parking.py](services/parking.py) |
| Auth middleware | [utils/auth_middleware.py](utils/auth_middleware.py) |
| Frontend API client | [frontend/js/api.js](frontend/js/api.js) |
| CSS styles | [frontend/css/styles.css](frontend/css/styles.css) |
| Docker config | [compose.yml](compose.yml) |