116 lines
4.6 KiB
Python
116 lines
4.6 KiB
Python
# register.py — user registration and magic link sender
|
|
|
|
from fastapi import APIRouter, Form, Request, HTTPException, Depends
|
|
from sqlmodel import Session, select
|
|
from models import User, UserQuota
|
|
from database import get_db
|
|
import uuid
|
|
import smtplib
|
|
from email.message import EmailMessage
|
|
from pathlib import Path
|
|
import os
|
|
|
|
router = APIRouter()
|
|
|
|
MAGIC_FROM = "noreply@dicta2stream.net"
|
|
MAGIC_DOMAIN = "https://dicta2stream.net"
|
|
DATA_ROOT = Path("./data")
|
|
|
|
def initialize_user_directory(username: str):
|
|
"""Initialize user directory with a silent stream.opus file"""
|
|
try:
|
|
user_dir = DATA_ROOT / username
|
|
default_stream_path = DATA_ROOT / "stream.opus"
|
|
|
|
print(f"[DEBUG] Initializing user directory: {user_dir.absolute()}")
|
|
|
|
# Create the directory if it doesn't exist
|
|
user_dir.mkdir(parents=True, exist_ok=True)
|
|
print(f"[DEBUG] Directory created or already exists: {user_dir.exists()}")
|
|
|
|
# Create stream.opus by copying the default stream.opus file
|
|
user_stream_path = user_dir / "stream.opus"
|
|
print(f"[DEBUG] Creating stream.opus at: {user_stream_path.absolute()}")
|
|
|
|
if not user_stream_path.exists():
|
|
if default_stream_path.exists():
|
|
import shutil
|
|
shutil.copy2(default_stream_path, user_stream_path)
|
|
print(f"[DEBUG] Copied default stream.opus to {user_stream_path}")
|
|
else:
|
|
print(f"[ERROR] Default stream.opus not found at {default_stream_path}")
|
|
# Fallback: create an empty file to prevent errors
|
|
with open(user_stream_path, 'wb') as f:
|
|
f.write(b'')
|
|
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error initializing user directory for {username}: {str(e)}")
|
|
return False
|
|
|
|
@router.post("/register")
|
|
def register(request: Request, email: str = Form(...), user: str = Form(...), db: Session = Depends(get_db)):
|
|
from sqlalchemy.exc import IntegrityError
|
|
# Try to find user by email or username
|
|
existing_user = db.get(User, email)
|
|
if not existing_user:
|
|
# Try by username (since username is not primary key, need to query)
|
|
stmt = select(User).where(User.username == user)
|
|
existing_user = db.exec(stmt).first()
|
|
token = str(uuid.uuid4())
|
|
if existing_user:
|
|
# Update token, timestamp, and ip, set confirmed False
|
|
from datetime import datetime
|
|
existing_user.token = token
|
|
existing_user.token_created = datetime.utcnow()
|
|
existing_user.confirmed = False
|
|
existing_user.ip = request.client.host
|
|
db.add(existing_user)
|
|
try:
|
|
db.commit()
|
|
except Exception as e:
|
|
db.rollback()
|
|
raise HTTPException(status_code=500, detail=f"Database error: {e}")
|
|
else:
|
|
# Register new user
|
|
db.add(User(email=email, username=user, token=token, confirmed=False, ip=request.client.host))
|
|
db.add(UserQuota(uid=user))
|
|
|
|
try:
|
|
# First commit the user to the database
|
|
db.commit()
|
|
|
|
# Only after successful commit, initialize the user directory
|
|
initialize_user_directory(user)
|
|
except Exception as e:
|
|
db.rollback()
|
|
if isinstance(e, IntegrityError):
|
|
# Race condition: user created after our check
|
|
# Try again as login
|
|
stmt = select(User).where((User.email == email) | (User.username == user))
|
|
existing_user = db.exec(stmt).first()
|
|
if existing_user:
|
|
existing_user.token = token
|
|
existing_user.confirmed = False
|
|
existing_user.ip = request.client.host
|
|
db.add(existing_user)
|
|
db.commit()
|
|
else:
|
|
raise HTTPException(status_code=409, detail="Username or email already exists.")
|
|
else:
|
|
raise HTTPException(status_code=500, detail=f"Database error: {e}")
|
|
# Send magic link
|
|
msg = EmailMessage()
|
|
msg["From"] = MAGIC_FROM
|
|
msg["To"] = email
|
|
msg["Subject"] = "Your magic login link"
|
|
msg.set_content(
|
|
f"Hello {user},\n\nClick to confirm your account:\n{MAGIC_DOMAIN}/?token={token}\n\nThis link is valid for one-time login."
|
|
)
|
|
try:
|
|
with smtplib.SMTP("localhost") as smtp:
|
|
smtp.send_message(msg)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Email failed: {e}")
|
|
return { "message": "Confirmation sent" }
|