Files
gh-christianlouis-docuelevate/app/views/onboarding.py
T
copilot-swe-agent[bot] e0de0fd6fb feat: add multi-step user onboarding wizard
- 5-step wizard: Welcome → Profile → Plan → Storage → All Set!
- New migration 017: onboarding_completed, contact_email, preferred_destination fields
- REST API at /api/onboarding/{status,profile,plan,storage,complete}
- GET /onboarding view with configured-destinations helper
- OAuth callback redirects first-time users to onboarding
- 16 unit tests for all endpoints; 65 total tests pass

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
2026-03-07 11:12:21 +00:00

92 lines
3.4 KiB
Python

"""View route for the user onboarding wizard."""
import logging
from fastapi import Depends, Request
from sqlalchemy.orm import Session
from app.config import Settings
from app.config import settings as _settings
from app.models import UserProfile
from app.utils.subscription import get_all_tiers
from app.views.base import APIRouter, get_db, require_login, templates
logger = logging.getLogger(__name__)
router = APIRouter()
# ---------------------------------------------------------------------------
# Destination helper
# ---------------------------------------------------------------------------
_DESTINATION_META: list[dict] = [
{"id": "dropbox", "name": "Dropbox", "icon": "fab fa-dropbox"},
{"id": "gdrive", "name": "Google Drive", "icon": "fab fa-google-drive"},
{"id": "onedrive", "name": "OneDrive", "icon": "fab fa-microsoft"},
{"id": "s3", "name": "Amazon S3", "icon": "fab fa-aws"},
{"id": "nextcloud", "name": "Nextcloud", "icon": "fas fa-cloud"},
{"id": "webdav", "name": "WebDAV", "icon": "fas fa-server"},
{"id": "sftp", "name": "SFTP", "icon": "fas fa-terminal"},
{"id": "ftp", "name": "FTP", "icon": "fas fa-server"},
]
def _get_configured_destinations(cfg: Settings) -> list[dict]:
"""Return which storage providers are fully configured in the current settings.
Each entry is a dict with ``id``, ``name``, and ``icon`` keys.
Args:
cfg: The application settings object (``app.config.settings``).
Returns:
A list of destination dicts for providers that have the required
credentials set.
"""
checks: dict[str, bool] = {
"dropbox": bool(cfg.dropbox_refresh_token and cfg.dropbox_app_key),
"gdrive": bool(cfg.google_drive_credentials_json or cfg.google_drive_refresh_token),
"onedrive": bool(cfg.onedrive_refresh_token and cfg.onedrive_client_id),
"s3": bool(cfg.aws_access_key_id and cfg.s3_bucket_name),
"nextcloud": bool(cfg.nextcloud_upload_url and cfg.nextcloud_username),
"webdav": bool(cfg.webdav_url and cfg.webdav_username),
"sftp": bool(cfg.sftp_host and cfg.sftp_username),
"ftp": bool(cfg.ftp_host and cfg.ftp_username),
}
return [meta for meta in _DESTINATION_META if checks.get(meta["id"], False)]
# ---------------------------------------------------------------------------
# Route
# ---------------------------------------------------------------------------
@router.get("/onboarding", include_in_schema=False)
@require_login
async def onboarding_page(request: Request, db: Session = Depends(get_db)):
"""Render the multi-step onboarding wizard.
Redirects to ``/upload`` when the user has already completed onboarding.
"""
user = request.session.get("user") or {}
user_id = user.get("sub") or user.get("preferred_username") or user.get("email") or user.get("id")
if user_id:
profile = db.query(UserProfile).filter(UserProfile.user_id == user_id).first()
if profile and profile.onboarding_completed:
from starlette.responses import RedirectResponse
return RedirectResponse(url="/upload", status_code=302)
configured_destinations = _get_configured_destinations(_settings)
tiers = get_all_tiers(db)
return templates.TemplateResponse(
"onboarding.html",
{
"request": request,
"user": user,
"configured_destinations": configured_destinations,
"tiers": tiers,
},
)