import json from uuid import uuid4 from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from pydantic import BaseModel from backend.app.api.dependencies import get_current_user from backend.app.database import AVATARS_DIR, get_connection router = APIRouter() class UserConfigUpdate(BaseModel): email: str | None = None bio: str | None = None class UserPluginConfigUpdate(BaseModel): instance: str | None = None access_token: str | None = None post_prefix: str | None = None hashtag: str | None = None @router.get('/me') def get_current_user_profile(user: dict = Depends(get_current_user)): with get_connection() as conn: row = conn.execute( 'SELECT * FROM users WHERE id = ?', (user['id'],), ).fetchone() if row is None: raise HTTPException(status_code=404, detail='User not found') return dict(row) @router.put('/me') def update_current_user_profile( payload: UserConfigUpdate, user: dict = Depends(get_current_user), ): with get_connection() as conn: current = conn.execute('SELECT * FROM users WHERE id = ?', (user['id'],)).fetchone() if current is None: raise HTTPException(status_code=404, detail='User not found') email = payload.email or current['email'] bio = payload.bio if payload.bio is not None else current['bio'] conn.execute( ''' UPDATE users SET email = ?, bio = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? ''', (email, bio, user['id']), ) conn.commit() return {'status': 'updated'} @router.post('/avatar') async def upload_avatar( avatar: UploadFile = File(...), user: dict = Depends(get_current_user), ): allowed_types = { 'image/gif': '.gif', 'image/jpeg': '.jpg', 'image/png': '.png', 'image/webp': '.webp', } suffix = allowed_types.get(avatar.content_type or '') if suffix is None: raise HTTPException(status_code=415, detail='Avatar must be a PNG, JPEG, GIF, or WebP image') contents = await avatar.read(2 * 1024 * 1024 + 1) if len(contents) > 2 * 1024 * 1024: raise HTTPException(status_code=413, detail='Avatar must be 2 MB or smaller') avatar_path = AVATARS_DIR / f'{user["id"]}{suffix}' for existing_path in AVATARS_DIR.glob(f'{user["id"]}.*'): if existing_path != avatar_path: existing_path.unlink(missing_ok=True) avatar_path.write_bytes(contents) avatar_url = f'/media/{avatar_path.name}' with get_connection() as conn: conn.execute( 'UPDATE users SET avatar_url = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', (avatar_url, user['id']), ) conn.commit() return {'avatar_url': avatar_url} @router.get('/plugins/{plugin_name}') def get_user_plugin_config(plugin_name: str, user: dict = Depends(get_current_user)): with get_connection() as conn: row = conn.execute( 'SELECT * FROM user_plugin_config WHERE user_id = ? AND plugin_name = ?', (user['id'], plugin_name), ).fetchone() if row is None: return {} config = json.loads(row['config']) if row['config'] else {} return config @router.put('/plugins/{plugin_name}') def update_user_plugin_config( plugin_name: str, payload: UserPluginConfigUpdate, user: dict = Depends(get_current_user), ): with get_connection() as conn: current = conn.execute( 'SELECT * FROM user_plugin_config WHERE user_id = ? AND plugin_name = ?', (user['id'], plugin_name), ).fetchone() current_config = json.loads(current['config']) if current and current['config'] else {} updates = payload.model_dump(exclude_none=True) merged = {**current_config, **updates} if current is None: conn.execute( ''' INSERT INTO user_plugin_config (id, user_id, plugin_name, config, created_at, updated_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ''', (str(uuid4()), user['id'], plugin_name, json.dumps(merged, ensure_ascii=False)), ) else: conn.execute( ''' UPDATE user_plugin_config SET config = ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND plugin_name = ? ''', (json.dumps(merged, ensure_ascii=False), user['id'], plugin_name), ) conn.commit() return merged