Lets each mule-image user (matched via OIDC preferred_username, overridable in Settings) browse their Nextcloud files/ tree from the mule-image UI and register subfolders as per-user SourceRoots. Reads stay direct on the bind-mounted /nextcloud-users path; mutations (upload, delete, rename, move within NC) dispatch through Nextcloud WebDAV so oc_filecache, trashbin, comments, and desktop-sync clients stay coherent. Backend: - users.nextcloud_username + nextcloud_app_password_enc (Fernet at rest, key derived from SECRET_KEY) — alembic 0016 - services/nextcloud_dav.py: minimal WebDAV client (PUT, MKCOL, DELETE, MOVE) with HTTP Basic auth via the per-user app password - routers/nextcloud.py: GET /browse, /whoami, GET/POST/DELETE /source-roots (path-scoped to current_user.nextcloud_username with realpath traversal guard) - PATCH /api/v1/auth/me to update nextcloud_username and app password - OIDC callback defaults nextcloud_username from preferred_username on first login; backfill on existing users; never overwrites a manual override - routers/upload.py: stream upload to NamedTemporaryFile, then PUT to WebDAV (with MKCOL chain) when destination is NC-rooted; existing Photo row creation runs unchanged - routers/discard.py empty-trash: WebDAV DELETE for NC files - routers/photos.py rename + move: WebDAV MOVE for NC paths; cross-system move/copy returns a clean error - routers/folders.py rename + create + permanent-delete: dispatch via WebDAV when targeting NC-rooted paths Frontend: - AuthUser carries nextcloud_username + has_nextcloud_app_password - services/api.ts: nextcloud + account namespaces - components/dialogs/NextcloudFolderPicker.tsx: lazy tree browser, name + submit -> POST /source-roots - SettingsDialog: new "Nextcloud library" card with username override + validate, app-password input, list/remove of NC libraries, and the picker entry point docker-compose.yml: NEXTCLOUD_USERS_HOST_PATH bind to /nextcloud-users on backend + 3 workers; NEXTCLOUD_USERS_ROOT + NEXTCLOUD_BASE_URL env. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
50 lines
2.2 KiB
Python
50 lines
2.2 KiB
Python
"""
|
|
User model definition
|
|
"""
|
|
from sqlalchemy import Column, String, Boolean, DateTime
|
|
from sqlalchemy.sql import func
|
|
import uuid
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = 'users'
|
|
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
username = Column(String(50), unique=True, nullable=False, index=True)
|
|
email = Column(String, unique=True, nullable=True)
|
|
# Nullable: OIDC-only users have no local password. Local accounts
|
|
# still always have one.
|
|
hashed_password = Column(String, nullable=True)
|
|
role = Column(String, nullable=False, default='user') # 'admin' | 'user'
|
|
is_active = Column(Boolean, default=True)
|
|
created_at = Column(DateTime, server_default=func.now())
|
|
|
|
# Absolute path to this user's photo directory (e.g., "/photos/daniel")
|
|
media_path = Column(String, nullable=False)
|
|
|
|
# OIDC identity — populated when a user signs in via Authentik (or any
|
|
# other OIDC provider later). `oidc_sub` is stable per provider, so
|
|
# lookups key on (oidc_issuer, oidc_sub). NULL for password-only users.
|
|
oidc_issuer = Column(String, nullable=True)
|
|
oidc_sub = Column(String, nullable=True)
|
|
|
|
# Profile bits that can come from OIDC claims or be filled in later.
|
|
# avatar_url wins over Gravatar when set; the /auth/me response
|
|
# computes the final avatar URL for the frontend.
|
|
avatar_url = Column(String, nullable=True)
|
|
display_name = Column(String, nullable=True)
|
|
|
|
# Nextcloud integration. `nextcloud_username` defaults to the
|
|
# `preferred_username` OIDC claim on first login but can be overridden
|
|
# in Settings (the local mule-image username doesn't always match the
|
|
# Nextcloud user — e.g. authentik `dtoro` ↔ Nextcloud `admin`).
|
|
# `nextcloud_app_password_enc` is the user's Nextcloud app password
|
|
# (created from Nextcloud → Settings → Security), Fernet-encrypted at
|
|
# rest with a key derived from settings.secret_key. Used as HTTP Basic
|
|
# auth on outgoing WebDAV calls when the user mutates a file under
|
|
# their Nextcloud-rooted SourceRoot.
|
|
nextcloud_username = Column(String, nullable=True, index=True)
|
|
nextcloud_app_password_enc = Column(String, nullable=True)
|