idea / app.py
vsmdvic's picture
Upload 20 files
469a4d4 verified
from flask import Flask, send_from_directory, request, jsonify, Response
import requests, time, threading, json, os
import cloudinary
import cloudinary.uploader
import cloudinary.api
app = Flask(__name__, static_folder='static')
# ── Cloudinary config ─────────────────────────────────────────
cloudinary.config(
cloud_name = "db6wrclc7",
api_key = "371945239656253",
api_secret = "ZgN-B2lyAz6zk2MI2N5b_1dc1r8",
secure = True
)
# ── Firebase ──────────────────────────────────────────────────
FIREBASE_API_KEY = "AIzaSyB9--Onx3-_YjD-YzblhZjaWSVVqTQJ1lU"
FIRESTORE_BASE = "https://firestore.googleapis.com/v1/projects/vservers1/databases/(default)/documents"
# ════════════════════════════════════════════════════════════
# CLOUDINARY ROUTES
# ════════════════════════════════════════════════════════════
@app.route("/drive/upload", methods=["POST"])
def drive_upload():
try:
elev_vpass = request.form.get("elevId", "unknown")
materie = request.form.get("materieId", "general")
f = request.files.get("file")
if not f:
return jsonify({"error": "no file"}), 400
file_bytes = f.read()
file_name = f.filename or "fisier"
print(f"[UPLOAD] {elev_vpass}/{materie}/{file_name} ({len(file_bytes)} bytes)")
# Folder: SGE/RV-0022/Matematica
folder = f"SGE/{elev_vpass}/{materie}"
# Upload ca raw β€” public_id = numele complet cu extensie
import io
result = cloudinary.uploader.upload(
io.BytesIO(file_bytes),
folder = folder,
public_id = file_name, # pastram extensia
resource_type = "raw",
use_filename = True,
unique_filename = False,
overwrite = False,
context = f"uploaded_at={int(time.time())}|original_name={file_name}",
)
print(f"[UPLOAD] OK β†’ {result['public_id']}")
_fs_log("upload", {"elevVpass": elev_vpass, "materie": materie,
"fisier": file_name, "fileId": result["public_id"]})
return jsonify({
"fileId": result["public_id"],
"name": file_name,
"size": result.get("bytes", 0),
"url": result.get("secure_url", ""),
})
except Exception as e:
import traceback; traceback.print_exc()
return jsonify({"error": str(e)}), 500
@app.route("/drive/list", methods=["GET"])
def drive_list():
try:
elev_vpass = request.args.get("elevId", "")
materie = request.args.get("materieId", "")
folder = f"SGE/{elev_vpass}/{materie}"
result = cloudinary.api.resources(
type = "upload",
resource_type = "raw",
prefix = folder + "/",
max_results = 100,
)
files = []
for r in result.get("resources", []):
pid = r["public_id"]
# Incearca sa extraga numele original din context
ctx = r.get("context", {}).get("custom", {})
name = ctx.get("original_name", "")
if not name:
# fallback: ultimul segment din public_id
name = pid.split("/")[-1]
files.append({
"id": pid,
"name": name,
"size": r.get("bytes", 0),
"mimeType": r.get("resource_type", "raw"),
"createdTime": r.get("created_at", ""),
"url": r.get("secure_url", ""),
})
return jsonify({"files": files})
except cloudinary.exceptions.NotFound:
return jsonify({"files": []})
except Exception as e:
print(f"[LIST ERROR] {e}")
return jsonify({"files": [], "error": str(e)})
@app.route("/drive/download/<path:file_id>", methods=["GET"])
def drive_download(file_id):
try:
# Cloudinary: genereaza URL de download direct
url = cloudinary.utils.cloudinary_url(
file_id,
resource_type = "raw",
type = "upload",
)[0]
# Redirect catre URL-ul Cloudinary
from flask import redirect
return redirect(url)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/drive/delete/<path:file_id>", methods=["DELETE"])
def drive_delete(file_id):
try:
cloudinary.uploader.destroy(file_id, resource_type="raw")
return jsonify({"ok": True})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/drive/test", methods=["GET"])
def drive_test():
try:
result = cloudinary.api.ping()
return jsonify({"ok": True, "cloudinary": result})
except Exception as e:
return jsonify({"ok": False, "error": str(e)}), 500
# ════════════════════════════════════════════════════════════
# FIRESTORE HELPERS
# ════════════════════════════════════════════════════════════
def _to_fields(data):
fields = {}
for k, v in data.items():
if v is None: fields[k] = {"nullValue": None}
elif isinstance(v, bool): fields[k] = {"booleanValue": v}
elif isinstance(v, int): fields[k] = {"integerValue": str(v)}
else: fields[k] = {"stringValue": str(v)}
return fields
def _fs_add(col, data):
r = requests.post(f"{FIRESTORE_BASE}/{col}?key={FIREBASE_API_KEY}",
json={"fields": _to_fields(data)}, timeout=15)
return r.status_code == 200
def _fs_log(tip, extra={}):
try:
payload = {"tip": tip, "ts": int(time.time()), **extra}
_fs_add("loguri", payload)
except: pass
# ── Log endpoint ──────────────────────────────────────────────
@app.route("/api/log", methods=["POST"])
def api_log():
try:
data = request.get_json(silent=True) or {}
_fs_log(data.get("tip","event"), {k:v for k,v in data.items() if k!="tip"})
return jsonify({"ok": True})
except Exception as e:
return jsonify({"error": str(e)}), 500
# ── Logs endpoint ─────────────────────────────────────────────
@app.route("/api/logs", methods=["GET"])
def api_logs():
try:
r = requests.get(f"{FIRESTORE_BASE}/loguri?key={FIREBASE_API_KEY}&pageSize=200", timeout=15)
docs = r.json().get("documents", [])
logs = []
for d in docs:
f = d.get("fields", {})
entry = {}
for k, v in f.items():
entry[k] = v.get("stringValue") or v.get("integerValue") or v.get("booleanValue") or ""
entry["_id"] = d["name"].split("/")[-1]
logs.append(entry)
logs.sort(key=lambda x: int(x.get("ts",0)), reverse=True)
return jsonify({"logs": logs[:150]})
except Exception as e:
return jsonify({"error": str(e), "logs": []}), 500
# ── Ping / Online ─────────────────────────────────────────────
_online = {}
@app.route("/api/ping", methods=["POST"])
def api_ping():
data = request.get_json(silent=True) or {}
key = f"{data.get('vpass','?')}_{data.get('role','?')}"
_online[key] = {"ts": time.time(), "vpass": data.get("vpass","?"),
"role": data.get("role","?"), "name": data.get("name","")}
now = time.time()
expired = [k for k,v in _online.items() if now - v["ts"] > 120]
for k in expired: del _online[k]
return jsonify({"ok": True})
@app.route("/api/online", methods=["GET"])
def api_online():
now = time.time()
active = {k:v for k,v in _online.items() if now - v["ts"] <= 120}
return jsonify({
"total": len(active),
"elevi": [v for v in active.values() if v["role"]=="elev"],
"profesori": [v for v in active.values() if v["role"]=="profesor"],
})
# ════════════════════════════════════════════════════════════
# CLEANUP β€” sterge fisierele mai vechi de 7 zile din Cloudinary
# ════════════════════════════════════════════════════════════
def cleanup_old_files():
while True:
try:
time.sleep(3600)
print("[CLEANUP] Verific fisiere vechi...")
limit_ts = int(time.time()) - 7 * 24 * 3600
result = cloudinary.api.resources(
type="upload", resource_type="raw",
prefix="SGE/", max_results=500,
)
deleted = 0
for r in result.get("resources", []):
created = r.get("created_at","")
if created:
try:
from datetime import datetime, timezone
dt = datetime.fromisoformat(created.replace("Z","+00:00"))
if int(dt.timestamp()) < limit_ts:
cloudinary.uploader.destroy(r["public_id"], resource_type="raw")
deleted += 1
print(f"[CLEANUP] Sters: {r['public_id']}")
_fs_log("cleanup_delete", {"fisier": r["public_id"]})
except: pass
print(f"[CLEANUP] Gata: {deleted} sterse.")
except Exception as ex:
print(f"[CLEANUP] Eroare: {ex}")
time.sleep(300)
# ════════════════════════════════════════════════════════════
# AUTO SEED
# ════════════════════════════════════════════════════════════
ELEVI = [
{"pozitie":1,"nume":"Andronic Aniela","vpassId":"AA-0001"},
{"pozitie":2,"nume":"Badan Alexandra","vpassId":"BA-0002"},
{"pozitie":3,"nume":"Bunescu Lavinia","vpassId":"BL-0003"},
{"pozitie":4,"nume":"Bunescu Lorena","vpassId":"BL-0004"},
{"pozitie":5,"nume":"Burdujan Damian","vpassId":"BD-0005"},
{"pozitie":6,"nume":"Ceban Madalina","vpassId":"CM-0006"},
{"pozitie":7,"nume":"Ceban Valeria","vpassId":"CV-0007"},
{"pozitie":8,"nume":"Cebanu Nichita","vpassId":"CN-0008"},
{"pozitie":9,"nume":"Chele Ilinca","vpassId":"CI-0009"},
{"pozitie":10,"nume":"Chihai Mirela","vpassId":"CM-0010"},
{"pozitie":11,"nume":"Cojocari Ciprian","vpassId":"CC-0011"},
{"pozitie":12,"nume":"Creciun Leonard","vpassId":"CL-0012"},
{"pozitie":13,"nume":"Lozovan Sanda","vpassId":"LS-0013"},
{"pozitie":14,"nume":"Lungu Sergiu","vpassId":"LS-0014"},
{"pozitie":15,"nume":"Mahnea Adelina","vpassId":"MA-0015"},
{"pozitie":16,"nume":"Morcan Ina","vpassId":"MI-0016"},
{"pozitie":17,"nume":"Morcan Ion","vpassId":"MI-0017"},
{"pozitie":18,"nume":"Popa Cristina","vpassId":"PC-0018"},
{"pozitie":19,"nume":"Pricob Adelina","vpassId":"PA-0019"},
{"pozitie":20,"nume":"Prinos Catalina","vpassId":"PC-0020"},
{"pozitie":21,"nume":"Radu Daniela","vpassId":"RD-0021"},
{"pozitie":22,"nume":"Rosca Victor","vpassId":"RV-0022"},
{"pozitie":23,"nume":"Rotari Cristian","vpassId":"RC-0023"},
{"pozitie":24,"nume":"Rotaru Camelia","vpassId":"RC-0024"},
{"pozitie":25,"nume":"Sambris Stanislav","vpassId":"SS-0025"},
{"pozitie":26,"nume":"Sirbu Delia","vpassId":"SD-0026"},
{"pozitie":27,"nume":"Soltoian Elena","vpassId":"SE-0027"},
{"pozitie":28,"nume":"Tabuncic Chiril","vpassId":"TC-0028"},
{"pozitie":29,"nume":"Turcan Gabriel","vpassId":"TG-0029"},
{"pozitie":30,"nume":"Turcan Mihai","vpassId":"TM-0030"},
{"pozitie":31,"nume":"Turcanu Ilie","vpassId":"TI-0031"},
{"pozitie":32,"nume":"Virlan Daniela","vpassId":"VD-0032"},
]
def auto_seed():
time.sleep(4)
try:
r = requests.get(f"{FIRESTORE_BASE}/elevi?key={FIREBASE_API_KEY}&pageSize=1", timeout=15)
if r.json().get("documents"):
print("[SGE] DB populat β€” seed omis."); return
print("[SGE] Seed automat...")
ok = 0
for e in ELEVI:
if _fs_add("elevi", {"nume":e["nume"],"vpassId":e["vpassId"],
"pozitie":e["pozitie"],"pin":None,"confirmed":False}):
ok += 1
time.sleep(0.12)
print(f"[SGE] Seed gata: {ok}/{len(ELEVI)}")
except Exception as ex:
print(f"[SGE] Seed eroare: {ex}")
threading.Thread(target=auto_seed, daemon=True).start()
threading.Thread(target=cleanup_old_files, daemon=True).start()
# ── Serve static ──────────────────────────────────────────────
@app.route("/")
def index(): return send_from_directory("static", "index.html")
@app.errorhandler(404)
def not_found(e): return send_from_directory("static", "503.html"), 404
@app.route("/<path:filename>")
def serve_files(filename):
try: return send_from_directory("static", filename)
except: return send_from_directory("static", "503.html"), 404
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860, debug=False)