From 03e5dd8bdabafd38945c1cc648648339d4aba765 Mon Sep 17 00:00:00 2001 From: Koncept Kit <63216427+konceptkit@users.noreply.github.com> Date: Tue, 27 Jan 2026 21:31:17 +0700 Subject: [PATCH] - 7 new API endpoints\ - Public theme config endpoint for frontend initialization (with 5-min cache)/- Admin CRUD for theme settings (get, update, reset)/- Logo and favicon upload/delete via Cloudflare R2 storage --- __pycache__/auth.cpython-312.pyc | Bin 10475 -> 10492 bytes __pycache__/r2_storage.cpython-312.pyc | Bin 8607 -> 8859 bytes __pycache__/server.cpython-312.pyc | Bin 308458 -> 330986 bytes r2_storage.py | 15 + server.py | 505 +++++++++++++++++++++++++ 5 files changed, 520 insertions(+) diff --git a/__pycache__/auth.cpython-312.pyc b/__pycache__/auth.cpython-312.pyc index 6cbc9138c13a0055d64f8d9a7f423f06be72dad3..87c5b3911e77a8afffb17286252373aa07be6e09 100644 GIT binary patch delta 191 zcmaDI_$QF}G%qg~0}%YIt;n?5$m=Q2XgxVxTu~~&NTIkSF)dA@JhLQ2p)4^cGX*42 zQd&H@OWa?ElUa6$+m!qE)dZ(Sxa4(an@!J^~H<;MHfGw delta 174 zcmewp_&SjHG%qg~0}wa}Ic92Z7qEL`nnVXtdGI^4? zzcBMHw%pX*q|~CKT%e|+JRmXouXrJ2)#QAMAQsDu?3SC4N_=EtteL!Bwv(}Wv!mQp z7RKhu*OjyxTP8Cr-(zb5Y3rE$Tv>&&b2E?1Bu2*W#q(7e87n3)Q0o?`0?9Umh)xjE QJ=t1amT~6hNcF{x0Io?kNB{r; diff --git a/__pycache__/r2_storage.cpython-312.pyc b/__pycache__/r2_storage.cpython-312.pyc index 9ffbb1705ded15d22d94ca0902563e6007b1f3cb..863f47649898b297b5241d814ae4b78d06471dfe 100644 GIT binary patch delta 1151 zcmZ8gOH30{6rI-&r5~m(rQgAhRUoBQ@e}`|rV0W|suA#0ja7M%u?j7|Rz%eJ2{A4) zFD4jGbg6FC%*s8kjEU}=u8awBThxUiG4bB1qH!kg%(>_O&a{~)`yTbU-zkbyg6q!g zRPK`Y+I^Rhs(3)UZ(68Tw@_Ipv<{9Nj_0wQu~9EDJeWbX5!8C>n~^ehhvbmxO6q40 z8emQuWG>baf!QrCsFOx!>M|axLVcKdM`hYPBWD_^y3rJzAglnv z_q#xWI0|voKMfkh{;N%im^fw0hnJkW@v}NxcVU9*OO6_!$m>hSXN#4bnU*_?lX^nt z4X~Um2yTESrB(>JO z7OeMVvf0Sn^?F}B2o>kVso|Pgmnn(MckVtTg9MvkJbu8 zg|MO*p0WQf-XUIj{?{8b;wLK+y^Sn_*j5AWj|{G*BV7ZI}*;?oeAhTI4MVF@Wcmzx_oE z2-u0qgW`TDN(}KbG(^^jpT39@4SyrH6PVj2f@;!+PQi4qy1L0U>pIWQ(q*gSx*BdX z9m;<(9 z$ADJkbA`efKZ)u=1csmkJeT+yU4vRd^@5ZcRa$)|m_lHb`VWFueQ;-mJ$WiB!2ZxDA^erzq_s^!fU zx6VpgzA;-Y6*zn;{0gLhh-=p{86vZeQC4dJ|j0YluF{y_N-Dxc;ZJphQ*o(#x zW8h*kF){Jrg$sHz8xqgP6UGAvFME=B@`h;Q#e?sy1c+|4zrO#Q|IB-DvM;Z^n(}}2 z`Md%=h0crRz4%-I6GGne=Vbb!e@)0aJ%UHjVwyr-T7bH>cIu(t2)MN%M6w-q&{pb( zpIj7C7rk0&ttIEvI>FyT{Y6RZT9a~ZhuRG~3|cwVGUy2CzzKg8bi4GREfeSKj%v8+ zEw3z==F1MyG#N9Cfs}*HnH#|mq;(Xn7XS-vjNb|MT|W!) zY{tp#N5!V12k=e!PFmV&0ivU$sSwG(irOCQ^5VDAW1eb#Vi=1gj z`7>8!Xc)Gtm`)(#fGx-IS-JxfcnU7g@I<(qZ1Rcl0vX^N!HD@L{DU}e;o2p>8O=Je zYZ%~jwC9H1xO|T;YX7xuxf#{#wW>}}3?LmBWq0uydkUNqrob^~Vp+0n>amz(5GyWR z%}Dm`uspi$?(I9tfjnxPzw1tuUH+w;kszNxm0MzIbh{9EZ>$AiNm{vJSYo+qSS?1Q zUZHH>PA}DJ6?PkKGl)e*0k9>o0gUZikugF9;MIQ#d@?cXI8gYf#B14h*@n~nvAUb8 zqW5U4c#XbV1)_nltS^}_R0fIqvNKWKo^VE_OC diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc index d4011c16249b49269cfc85c4863a6b39ed8e0c89..9eb9b40c20ddf5f4759b3baa8979cf1d493c22f5 100644 GIT binary patch delta 73007 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 T_xT3@w)SFce%9J`TK@k4qdItf delta 54455 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

Oq+o#L$N*^c&iw#Pq{d6m&N)ZEacFs{V}U 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 dict: + """Get theme config with caching.""" + import json + from datetime import datetime, timezone + + now = datetime.now(timezone.utc) + + # Check cache + if _theme_cache["config"] and _theme_cache["expires_at"] and _theme_cache["expires_at"] > now: + return _theme_cache["config"] + + # Build config from settings + config = dict(DEFAULT_THEME_CONFIG) + + # Fetch all theme.* settings + theme_settings = db.query(SystemSettings).filter( + SystemSettings.setting_key.like('theme.%') + ).all() + + for setting in theme_settings: + key = setting.setting_key.replace('theme.', '') + value = setting.setting_value + + if key == 'colors' and value: + try: + config['colors'] = json.loads(value) + except json.JSONDecodeError: + pass + elif key in config: + config[key] = value + + # Update cache + _theme_cache["config"] = config + _theme_cache["expires_at"] = now + timedelta(seconds=THEME_CACHE_TTL_SECONDS) + + return config + + +def invalidate_theme_cache(): + """Invalidate the theme config cache.""" + _theme_cache["config"] = None + _theme_cache["expires_at"] = None + + +@api_router.get("/config/theme") +async def get_theme_config(db: Session = Depends(get_db)): + """ + Get public theme configuration. + + This endpoint is public (no authentication required) and returns + the theme configuration for frontend initialization. + + Returns cached config with 5-minute TTL for performance. + """ + return get_theme_config_cached(db) + + +@api_router.get("/admin/settings/theme") +async def get_theme_settings_admin( + current_user: User = Depends(require_permission("settings.view")), + db: Session = Depends(get_db) +): + """ + Get theme settings with metadata (admin view). + + Returns the full theme configuration along with: + - Whether using default values + - Last update timestamp + - Who made the last update + """ + import json + + config = dict(DEFAULT_THEME_CONFIG) + is_default = True + updated_at = None + updated_by = None + + # Fetch all theme.* settings + theme_settings = db.query(SystemSettings).filter( + SystemSettings.setting_key.like('theme.%') + ).all() + + if theme_settings: + is_default = False + + # Find the most recent update + latest_setting = max(theme_settings, key=lambda s: s.updated_at or s.created_at) + updated_at = latest_setting.updated_at or latest_setting.created_at + if latest_setting.updater: + updated_by = f"{latest_setting.updater.first_name} {latest_setting.updater.last_name}" + + for setting in theme_settings: + key = setting.setting_key.replace('theme.', '') + value = setting.setting_value + + if key == 'colors' and value: + try: + config['colors'] = json.loads(value) + except json.JSONDecodeError: + pass + elif key in config: + config[key] = value + + return { + "config": config, + "is_default": is_default, + "updated_at": updated_at.isoformat() if updated_at else None, + "updated_by": updated_by + } + + +class ThemeSettingsUpdate(BaseModel): + """Request model for updating theme settings""" + site_name: Optional[str] = Field(None, max_length=200, description="Full site name") + site_short_name: Optional[str] = Field(None, max_length=50, description="Short name for PWA") + site_description: Optional[str] = Field(None, max_length=500, description="Site description for SEO meta tag") + colors: Optional[dict] = Field(None, description="Color scheme as HSL values") + meta_theme_color: Optional[str] = Field(None, pattern=r'^#[0-9A-Fa-f]{6}$', description="PWA theme color (hex)") + + +@api_router.put("/admin/settings/theme") +async def update_theme_settings( + request: ThemeSettingsUpdate, + current_user: User = Depends(require_permission("settings.edit")), + db: Session = Depends(get_db) +): + """ + Update theme settings (admin only). + + Updates one or more theme settings. Only provided fields are updated. + Changes are applied immediately and the cache is invalidated. + """ + import json + + updates = {} + + if request.site_name is not None: + set_setting( + db=db, + key='theme.site_name', + value=request.site_name, + user_id=str(current_user.id), + setting_type='plaintext', + description='Site name displayed in title and navigation', + is_sensitive=False + ) + updates['site_name'] = request.site_name + + if request.site_short_name is not None: + set_setting( + db=db, + key='theme.site_short_name', + value=request.site_short_name, + user_id=str(current_user.id), + setting_type='plaintext', + description='Short site name for PWA manifest', + is_sensitive=False + ) + updates['site_short_name'] = request.site_short_name + + if request.site_description is not None: + set_setting( + db=db, + key='theme.site_description', + value=request.site_description, + user_id=str(current_user.id), + setting_type='plaintext', + description='Site description for SEO meta tag', + is_sensitive=False + ) + updates['site_description'] = request.site_description + + if request.colors is not None: + set_setting( + db=db, + key='theme.colors', + value=json.dumps(request.colors), + user_id=str(current_user.id), + setting_type='json', + description='Theme color scheme as HSL values', + is_sensitive=False + ) + updates['colors'] = request.colors + + if request.meta_theme_color is not None: + set_setting( + db=db, + key='theme.meta_theme_color', + value=request.meta_theme_color, + user_id=str(current_user.id), + setting_type='plaintext', + description='PWA theme-color meta tag value', + is_sensitive=False + ) + updates['meta_theme_color'] = request.meta_theme_color + + # Invalidate cache + invalidate_theme_cache() + + return { + "success": True, + "message": "Theme settings updated successfully", + "updated_fields": list(updates.keys()), + "updated_at": datetime.now(timezone.utc).isoformat(), + "updated_by": f"{current_user.first_name} {current_user.last_name}" + } + + +@api_router.post("/admin/settings/theme/logo") +async def upload_theme_logo( + file: UploadFile = File(...), + current_user: User = Depends(require_permission("settings.edit")), + db: Session = Depends(get_db) +): + """ + Upload organization logo (admin only). + + Accepts PNG, JPEG, WebP, or SVG images. + Replaces any existing logo. + """ + r2 = get_r2_storage() + + # Get current logo key for deletion + old_logo_key = get_setting(db, 'theme.logo_key') + + # Delete old logo if exists + if old_logo_key: + try: + await r2.delete_file(old_logo_key) + except Exception as e: + print(f"Warning: Failed to delete old logo: {e}") + + # Upload new logo + try: + public_url, object_key, file_size = await r2.upload_file( + file=file, + folder="branding", + allowed_types=r2.ALLOWED_BRANDING_TYPES, + max_size_bytes=5 * 1024 * 1024 # 5MB limit for logos + ) + + # Store URL and key in settings + set_setting( + db=db, + key='theme.logo_url', + value=public_url, + user_id=str(current_user.id), + setting_type='plaintext', + description='Organization logo URL', + is_sensitive=False + ) + + set_setting( + db=db, + key='theme.logo_key', + value=object_key, + user_id=str(current_user.id), + setting_type='plaintext', + description='R2 object key for logo (for deletion)', + is_sensitive=False + ) + + # Invalidate cache + invalidate_theme_cache() + + return { + "success": True, + "message": "Logo uploaded successfully", + "logo_url": public_url, + "file_size": file_size + } + + except HTTPException: + raise + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Failed to upload logo: {str(e)}" + ) + + +@api_router.post("/admin/settings/theme/favicon") +async def upload_theme_favicon( + file: UploadFile = File(...), + current_user: User = Depends(require_permission("settings.edit")), + db: Session = Depends(get_db) +): + """ + Upload site favicon (admin only). + + Accepts ICO, PNG, or SVG images. + Replaces any existing favicon. + """ + r2 = get_r2_storage() + + # Get current favicon key for deletion + old_favicon_key = get_setting(db, 'theme.favicon_key') + + # Delete old favicon if exists + if old_favicon_key: + try: + await r2.delete_file(old_favicon_key) + except Exception as e: + print(f"Warning: Failed to delete old favicon: {e}") + + # Upload new favicon + try: + public_url, object_key, file_size = await r2.upload_file( + file=file, + folder="branding", + allowed_types=r2.ALLOWED_FAVICON_TYPES, + max_size_bytes=1 * 1024 * 1024 # 1MB limit for favicons + ) + + # Store URL and key in settings + set_setting( + db=db, + key='theme.favicon_url', + value=public_url, + user_id=str(current_user.id), + setting_type='plaintext', + description='Site favicon URL', + is_sensitive=False + ) + + set_setting( + db=db, + key='theme.favicon_key', + value=object_key, + user_id=str(current_user.id), + setting_type='plaintext', + description='R2 object key for favicon (for deletion)', + is_sensitive=False + ) + + # Invalidate cache + invalidate_theme_cache() + + return { + "success": True, + "message": "Favicon uploaded successfully", + "favicon_url": public_url, + "file_size": file_size + } + + except HTTPException: + raise + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Failed to upload favicon: {str(e)}" + ) + + +@api_router.delete("/admin/settings/theme/logo") +async def delete_theme_logo( + current_user: User = Depends(require_permission("settings.edit")), + db: Session = Depends(get_db) +): + """ + Delete organization logo (admin only). + + Removes the logo from R2 storage and clears the settings, + reverting to the default logo. + """ + r2 = get_r2_storage() + + # Get current logo key for deletion + logo_key = get_setting(db, 'theme.logo_key') + + if logo_key: + try: + await r2.delete_file(logo_key) + except Exception as e: + print(f"Warning: Failed to delete logo from R2: {e}") + + # Delete the settings + db.query(SystemSettings).filter( + SystemSettings.setting_key.in_(['theme.logo_url', 'theme.logo_key']) + ).delete(synchronize_session=False) + db.commit() + + # Invalidate cache + invalidate_theme_cache() + + return { + "success": True, + "message": "Logo deleted successfully" + } + + +@api_router.delete("/admin/settings/theme/favicon") +async def delete_theme_favicon( + current_user: User = Depends(require_permission("settings.edit")), + db: Session = Depends(get_db) +): + """ + Delete site favicon (admin only). + + Removes the favicon from R2 storage and clears the settings, + reverting to the default favicon. + """ + r2 = get_r2_storage() + + # Get current favicon key for deletion + favicon_key = get_setting(db, 'theme.favicon_key') + + if favicon_key: + try: + await r2.delete_file(favicon_key) + except Exception as e: + print(f"Warning: Failed to delete favicon from R2: {e}") + + # Delete the settings + db.query(SystemSettings).filter( + SystemSettings.setting_key.in_(['theme.favicon_url', 'theme.favicon_key']) + ).delete(synchronize_session=False) + db.commit() + + # Invalidate cache + invalidate_theme_cache() + + return { + "success": True, + "message": "Favicon deleted successfully" + } + + +@api_router.post("/admin/settings/theme/reset") +async def reset_theme_settings( + current_user: User = Depends(get_current_superadmin), + db: Session = Depends(get_db) +): + """ + Reset all theme settings to defaults (superadmin only). + + Deletes all theme.* settings from the database and removes + any uploaded logo/favicon from R2 storage. + """ + r2 = get_r2_storage() + + # Get keys for uploaded files + logo_key = get_setting(db, 'theme.logo_key') + favicon_key = get_setting(db, 'theme.favicon_key') + + # Delete files from R2 + if logo_key: + try: + await r2.delete_file(logo_key) + except Exception as e: + print(f"Warning: Failed to delete logo from R2: {e}") + + if favicon_key: + try: + await r2.delete_file(favicon_key) + except Exception as e: + print(f"Warning: Failed to delete favicon from R2: {e}") + + # Delete all theme settings + deleted_count = db.query(SystemSettings).filter( + SystemSettings.setting_key.like('theme.%') + ).delete(synchronize_session=False) + db.commit() + + # Invalidate cache + invalidate_theme_cache() + + return { + "success": True, + "message": "Theme settings reset to defaults", + "deleted_settings_count": deleted_count, + "config": DEFAULT_THEME_CONFIG + } + + # Include the router in the main app app.include_router(api_router)