Files
mule-image/backend/app/models/heaps.py
dtoro 348e9c3585 feat: multi-user auth with per-user media isolation
Introduce username/password authentication with admin and user roles.
Each user gets their own media directory under /photos/{username}/ with
isolated photos, folders, heaps, and tags. Admins manage users and
observe the full library from a dedicated Settings page.

Backend:
- User model with bcrypt passwords and JWT access/refresh tokens
- Auth router (login, refresh, setup, change-password, status)
- Admin router (user CRUD with last-admin protection)
- user_id FK added to photos, folders, source_roots, heaps, tags
- All data routers scoped by authenticated user
- Scanner inherits user_id from source root owner
- Thumbnails stored under user-prefixed paths for isolation
- Library endpoints accept ?scope=global for admin cross-user view
- Alembic migration 0009 with data migration for existing installs
- Defensive bootstrap.py handles fresh vs existing DB startup

Frontend:
- AuthContext with token lifecycle, auto-refresh, login/logout
- Login page, first-run setup page, auth gate in App.tsx
- Bearer token interceptor on all API requests
- User identity + logout in left sidebar
- Admin-only Settings page with Library Management and Users tabs
- UserManagement panel (add, edit role, reset password, deactivate)
- Settings shows global stats across all users for admin
- Filter bar, right sidebar, keyboard hints hidden on settings page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:47:43 +02:00

40 lines
1.4 KiB
Python

"""
Heap model definitions
"""
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Table, Index
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
import uuid
from app.database import Base
# Association table for many-to-many relationship with additional fields
heap_photos = Table(
'heap_photos',
Base.metadata,
Column('heap_id', String, ForeignKey('heaps.id', ondelete='CASCADE'), primary_key=True),
Column('photo_id', String, ForeignKey('photos.id', ondelete='CASCADE'), primary_key=True),
Column('added_at', DateTime, server_default=func.now()),
Column('sort_order', Integer, default=0),
Index('ix_heap_photos_heap_id', 'heap_id'),
Index('ix_heap_photos_photo_id', 'photo_id'),
)
class Heap(Base):
__tablename__ = 'heaps'
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
name = Column(String, nullable=False)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, onupdate=func.now())
is_active = Column(Boolean, default=False) # For active heap feature
# Owner
user_id = Column(String, ForeignKey('users.id'), nullable=True, index=True)
# Relationships
photos = relationship("Photo", secondary=heap_photos, backref="heaps")
class HeapPhoto:
"""Helper class for heap-photo associations (not a table model)"""
pass