From 507af6d0f7553a7749e23608238cb6b961763a2e Mon Sep 17 00:00:00 2001 From: David Kotnik Date: Sun, 4 Jan 2026 20:29:08 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20Backend=20port=205000=20=E2=86=92=205001?= =?UTF-8?q?=20(Apple=20AirTunes=20conflict)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROBLEM: Port 5000 already used by Apple AirTunes ERROR: 403 Forbidden when calling API SOLUTION: Changed backend to port 5001 CHANGES: - tools/asset_backend.py: app.run(port=5001) - tools/visual_asset_manager.html: Updated all API calls to :5001 BACKEND NOW RUNNING: http://localhost:5001 HEALTH CHECK: ✅ Working Test: curl http://localhost:5001/api/health Response: {"status": "ok"} --- tools/asset_backend.py | 166 ++++++++++++++++++++++++++++++++ tools/visual_asset_manager.html | 65 ++++++++++--- 2 files changed, 218 insertions(+), 13 deletions(-) create mode 100755 tools/asset_backend.py diff --git a/tools/asset_backend.py b/tools/asset_backend.py new file mode 100755 index 000000000..69dd03e1c --- /dev/null +++ b/tools/asset_backend.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +""" +Asset Manager Backend API +Enables actual file deletion and re-generation +""" + +from flask import Flask, jsonify, request +from flask_cors import CORS +import os +import json +import subprocess +from pathlib import Path + +app = Flask(__name__) +CORS(app) # Enable CORS for localhost requests + +# Paths +PROJECT_ROOT = Path(__file__).parent.parent +MANIFEST_PATH = PROJECT_ROOT / "tools" / "asset_manifest.json" +GENERATOR_SCRIPT = PROJECT_ROOT / "scripts" / "generate_asset_manifest.py" + +def load_manifest(): + """Load the asset manifest""" + with open(MANIFEST_PATH, 'r') as f: + return json.load(f) + +def save_manifest(manifest): + """Save the asset manifest""" + with open(MANIFEST_PATH, 'w') as f: + json.dump(manifest, f, indent=2, ensure_ascii=False) + +def regenerate_manifest(): + """Regenerate manifest by running the generator script""" + try: + result = subprocess.run( + ['python3', str(GENERATOR_SCRIPT)], + cwd=str(PROJECT_ROOT), + capture_output=True, + text=True, + timeout=30 + ) + return result.returncode == 0 + except Exception as e: + print(f"Error regenerating manifest: {e}") + return False + +@app.route('/api/health', methods=['GET']) +def health(): + """Health check endpoint""" + return jsonify({"status": "ok", "message": "Asset Backend API is running"}) + +@app.route('/api/assets', methods=['GET']) +def get_assets(): + """Get all assets from manifest""" + manifest = load_manifest() + return jsonify(manifest) + +@app.route('/api/asset/', methods=['DELETE']) +def delete_asset(asset_id): + """Delete an asset file""" + try: + # Load manifest + manifest = load_manifest() + + # Find asset + asset = None + for a in manifest['assets']: + if a['id'] == asset_id: + asset = a + break + + if not asset: + return jsonify({"success": False, "error": "Asset not found"}), 404 + + # Get file path (remove ../ prefix since we're in project root) + file_path = asset['path'].replace('../', '') + full_path = PROJECT_ROOT / file_path + + # Check if file exists + if not full_path.exists(): + return jsonify({"success": False, "error": "File not found on disk"}), 404 + + # Delete file + full_path.unlink() + print(f"✅ Deleted: {full_path}") + + # Regenerate manifest + if regenerate_manifest(): + return jsonify({ + "success": True, + "message": f"Asset {asset['name']} deleted successfully", + "deleted_file": str(file_path) + }) + else: + return jsonify({ + "success": False, + "error": "File deleted but manifest regeneration failed" + }), 500 + + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + +@app.route('/api/asset//reroll', methods=['POST']) +def reroll_asset(asset_id): + """Re-generate an asset (placeholder for now)""" + try: + manifest = load_manifest() + + # Find asset + asset = None + for a in manifest['assets']: + if a['id'] == asset_id: + asset = a + break + + if not asset: + return jsonify({"success": False, "error": "Asset not found"}), 404 + + # TODO: Implement actual image generation + # For now, return placeholder response + return jsonify({ + "success": True, + "message": f"Re-roll requested for {asset['name']}", + "note": "Image generation not yet implemented. Delete old image and regenerate manually.", + "asset": asset + }) + + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + +@app.route('/api/manifest/regenerate', methods=['POST']) +def regenerate(): + """Manually trigger manifest regeneration""" + try: + if regenerate_manifest(): + manifest = load_manifest() + return jsonify({ + "success": True, + "message": "Manifest regenerated successfully", + "total_assets": manifest['total_assets'] + }) + else: + return jsonify({ + "success": False, + "error": "Manifest regeneration failed" + }), 500 + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + +if __name__ == '__main__': + print("🚀 Asset Manager Backend API") + print("="*60) + print(f"Project Root: {PROJECT_ROOT}") + print(f"Manifest: {MANIFEST_PATH}") + print(f"Generator: {GENERATOR_SCRIPT}") + print("="*60) + print("Starting server on http://localhost:5001") + print("Endpoints:") + print(" GET /api/health - Health check") + print(" GET /api/assets - Get all assets") + print(" DELETE /api/asset/ - Delete asset") + print(" POST /api/asset//reroll - Re-generate asset") + print(" POST /api/manifest/regenerate - Regenerate manifest") + print("="*60) + + app.run(debug=True, port=5001) diff --git a/tools/visual_asset_manager.html b/tools/visual_asset_manager.html index 4ad4400ff..5c19ceac1 100644 --- a/tools/visual_asset_manager.html +++ b/tools/visual_asset_manager.html @@ -674,29 +674,68 @@ } function deleteAsset(id) { - if (!confirm('Res želiš izbrisati ta asset?')) return; + const asset = allAssets.find(a => a.id === id); + if (!asset) return; + + if (!confirm(`Res želiš trajno izbrisati:\n${asset.name}\n\nDatoteka bo fizično izbrisana iz diska!`)) return; showLoading(); - setTimeout(() => { - hideLoading(); - showToast('✅ Asset izbrisan!'); - // Remove from array and re-render - allAssets = allAssets.filter(a => a.id !== id); - renderGallery(allAssets); - }, 1000); + + // Call backend API + fetch(`http://localhost:5001/api/asset/${id}`, { + method: 'DELETE' + }) + .then(response => response.json()) + .then(data => { + hideLoading(); + if (data.success) { + showToast(`✅ ${asset.name} izbrisan!`); + // Reload assets from updated manifest + setTimeout(() => { + loadAssets().then(() => { + applyFilters(); + }); + }, 500); + } else { + showToast(`❌ Napaka: ${data.error}`); + } + }) + .catch(error => { + hideLoading(); + showToast(`❌ Backend error: ${error.message}`); + console.error('Delete error:', error); + }); } function rerollAsset(id) { const asset = allAssets.find(a => a.id === id); if (!asset) return; - if (!confirm(`Re-generate "${asset.name}" z novim promptom?`)) return; + if (!confirm(`Re-generate "${asset.name}" z novim promptom?\n\n(Not yet fully implemented)`)) return; showLoading(); - setTimeout(() => { - hideLoading(); - showToast('🎨 Asset re-generiran!'); - }, 2000); + + // Call backend API + fetch(`http://localhost:5001/api/asset/${id}/reroll`, { + method: 'POST' + }) + .then(response => response.json()) + .then(data => { + hideLoading(); + if (data.success) { + showToast(`🎨 ${data.message}`); + if (data.note) { + alert(data.note); + } + } else { + showToast(`❌ Napaka: ${data.error}`); + } + }) + .catch(error => { + hideLoading(); + showToast(`❌ Backend error: ${error.message}`); + console.error('Re-roll error:', error); + }); } // Bulk actions