droplyvictor89 commited on
Commit
619733e
·
verified ·
1 Parent(s): 0da3abf

Upload app.py

Browse files
Files changed (1) hide show
  1. static/app.py +204 -261
static/app.py CHANGED
@@ -1,283 +1,226 @@
1
- # ================================================
2
- # VSERVERS v2.0 HuggingFace Spaces
3
- # Flask server: serveste HTML + proxy B2
4
- # Auto-seed: adauga elevii automat la primul start
5
- # ================================================
6
-
7
- from flask import Flask, send_from_directory, request, jsonify
8
- import requests, base64, time, threading
9
- import firebase_admin
10
- from firebase_admin import credentials, firestore
11
 
12
  app = Flask(__name__, static_folder='static')
13
 
14
- # ── FIREBASE INIT ───────────────────────────────
15
- _fb_config = {
 
 
 
 
 
16
  "type": "service_account",
17
- "project_id": "vservers1",
18
- "private_key_id": "web-api-key",
19
- "client_email": "firebase-adminsdk@vservers1.iam.gserviceaccount.com",
 
20
  "token_uri": "https://oauth2.googleapis.com/token",
21
- "apiKey": "AIzaSyB9--Onx3-_YjD-YzblhZjaWSVVqTQJ1lU",
22
- "authDomain": "vservers1.firebaseapp.com",
23
  }
24
 
25
- # Folosim REST API Firestore direct (nu SDK — mai simplu pe HF)
26
- FIREBASE_API_KEY = "AIzaSyB9--Onx3-_YjD-YzblhZjaWSVVqTQJ1lU"
27
- FIRESTORE_BASE = "https://firestore.googleapis.com/v1/projects/vservers1/databases/(default)/documents"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- # ── B2 CONFIG ───────────────────────────────────
30
- B2_KEY_ID = "00341e8b3c9f4f50000000001"
31
- B2_APP_KEY = "K0034Mj8ROtD0+LRgWfTrhSgVlRPVFY"
32
- B2_BUCKET = "vservers-files"
33
- B2_BUCKET_ID = "c4d16e081bf3dc999fc40f15"
34
- _auth = {"token": None, "api_url": None, "download_url": None, "expires": 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- # ── LISTA ELEVI CLASA 7B ────────────────────────
37
  ELEVI = [
38
- {"pozitie": 1, "nume": "Andronic Aniela", "vpassId": "AA-0001"},
39
- {"pozitie": 2, "nume": "Badan Alexandra", "vpassId": "BA-0002"},
40
- {"pozitie": 3, "nume": "Bunescu Lavinia", "vpassId": "BL-0003"},
41
- {"pozitie": 4, "nume": "Bunescu Lorena", "vpassId": "BL-0004"},
42
- {"pozitie": 5, "nume": "Burdujan Damian", "vpassId": "BD-0005"},
43
- {"pozitie": 6, "nume": "Ceban Madalina", "vpassId": "CM-0006"},
44
- {"pozitie": 7, "nume": "Ceban Valeria", "vpassId": "CV-0007"},
45
- {"pozitie": 8, "nume": "Cebanu Nichita", "vpassId": "CN-0008"},
46
- {"pozitie": 9, "nume": "Chele Ilinca", "vpassId": "CI-0009"},
47
- {"pozitie": 10, "nume": "Chihai Mirela", "vpassId": "CM-0010"},
48
- {"pozitie": 11, "nume": "Cojocari Ciprian", "vpassId": "CC-0011"},
49
- {"pozitie": 12, "nume": "Creciun Leonard", "vpassId": "CL-0012"},
50
- {"pozitie": 13, "nume": "Lozovan Sanda", "vpassId": "LS-0013"},
51
- {"pozitie": 14, "nume": "Lungu Sergiu", "vpassId": "LS-0014"},
52
- {"pozitie": 15, "nume": "Mahnea Adelina", "vpassId": "MA-0015"},
53
- {"pozitie": 16, "nume": "Morcan Ina", "vpassId": "MI-0016"},
54
- {"pozitie": 17, "nume": "Morcan Ion", "vpassId": "MI-0017"},
55
- {"pozitie": 18, "nume": "Popa Cristina", "vpassId": "PC-0018"},
56
- {"pozitie": 19, "nume": "Pricob Adelina", "vpassId": "PA-0019"},
57
- {"pozitie": 20, "nume": "Prinos Catalina", "vpassId": "PC-0020"},
58
- {"pozitie": 21, "nume": "Radu Daniela", "vpassId": "RD-0021"},
59
- {"pozitie": 22, "nume": "Rosca Victor", "vpassId": "RV-0022"},
60
- {"pozitie": 23, "nume": "Rotari Cristian", "vpassId": "RC-0023"},
61
- {"pozitie": 24, "nume": "Rotaru Camelia", "vpassId": "RC-0024"},
62
- {"pozitie": 25, "nume": "Sambris Stanislav", "vpassId": "SS-0025"},
63
- {"pozitie": 26, "nume": "Sirbu Delia", "vpassId": "SD-0026"},
64
- {"pozitie": 27, "nume": "Soltoian Elena", "vpassId": "SE-0027"},
65
- {"pozitie": 28, "nume": "Tabuncic Chiril", "vpassId": "TC-0028"},
66
- {"pozitie": 29, "nume": "Turcan Gabriel", "vpassId": "TG-0029"},
67
- {"pozitie": 30, "nume": "Turcan Mihai", "vpassId": "TM-0030"},
68
- {"pozitie": 31, "nume": "Turcanu Ilie", "vpassId": "TI-0031"},
69
- {"pozitie": 32, "nume": "Virlan Daniela", "vpassId": "VD-0032"},
70
  ]
71
 
72
- # ── FIRESTORE REST HELPERS ──────────────────────
73
-
74
- def fs_list(collection):
75
- """Lista toate documentele dintr-o colectie."""
76
- r = requests.get(
77
- f"{FIRESTORE_BASE}/{collection}?key={FIREBASE_API_KEY}",
78
- timeout=15
79
- )
80
- if r.status_code == 200:
81
- return r.json().get("documents", [])
82
- return []
83
-
84
- def fs_add(collection, data):
85
- """Adauga un document nou (auto-ID)."""
86
- # Converteste dict Python la format Firestore
87
- fields = {}
88
- for k, v in data.items():
89
- if v is None:
90
- fields[k] = {"nullValue": None}
91
- elif isinstance(v, bool):
92
- fields[k] = {"booleanValue": v}
93
- elif isinstance(v, int):
94
- fields[k] = {"integerValue": str(v)}
95
- elif isinstance(v, str):
96
- fields[k] = {"stringValue": v}
97
- else:
98
- fields[k] = {"stringValue": str(v)}
99
-
100
- r = requests.post(
101
- f"{FIRESTORE_BASE}/{collection}?key={FIREBASE_API_KEY}",
102
- json={"fields": fields},
103
- timeout=15
104
- )
105
- return r.status_code == 200
106
-
107
- # ── AUTO-SEED ───────────────────────────────────
108
 
109
  def auto_seed():
110
- """Ruleaza la startup. Daca 'elevi' e gol, adauga toti elevii."""
111
- print("[VSERVERS] Verificare baza de date...")
112
- time.sleep(3) # asteapta serverul sa porneasca complet
113
-
114
  try:
115
- existing = fs_list("elevi")
116
- if existing:
117
- print(f"[VSERVERS] {len(existing)} elevi gasiti — seed omis.")
118
- return
119
-
120
- print("[VSERVERS] Baza de date goala — pornire seed automat...")
121
- ok = 0
122
  for e in ELEVI:
123
- success = fs_add("elevi", {
124
- "nume": e["nume"],
125
- "vpassId": e["vpassId"],
126
- "pozitie": e["pozitie"],
127
- "pin": None,
128
- "confirmed": False
129
- })
130
- if success:
131
- ok += 1
132
- print(f"[VSERVERS] ✓ {e['vpassId']} — {e['nume']}")
133
- else:
134
- print(f"[VSERVERS] ✗ {e['vpassId']} — eroare")
135
- time.sleep(0.1) # pauza mica intre scrieri
136
-
137
- print(f"[VSERVERS] Seed finalizat: {ok}/{len(ELEVI)} elevi adaugati.")
138
-
139
- except Exception as ex:
140
- print(f"[VSERVERS] Eroare seed: {ex}")
141
-
142
- # Porneste seed in background thread la startup
143
- threading.Thread(target=auto_seed, daemon=True).start()
144
 
145
- # ── B2 AUTH ─────────────────────────────────────
146
-
147
- def get_auth():
148
- if _auth["token"] and _auth["expires"] > time.time():
149
- return _auth
150
- cred = base64.b64encode(f"{B2_KEY_ID}:{B2_APP_KEY}".encode()).decode()
151
- r = requests.get(
152
- "https://api.backblazeb2.com/b2api/v2/b2_authorize_account",
153
- headers={"Authorization": f"Basic {cred}"},
154
- timeout=15
155
- )
156
- r.raise_for_status()
157
- data = r.json()
158
- _auth.update({
159
- "token": data["authorizationToken"],
160
- "api_url": data["apiUrl"],
161
- "download_url": data["downloadUrl"],
162
- "expires": time.time() + 23 * 3600
163
- })
164
- return _auth
165
-
166
- # ── ROUTES ──────────────────────────────────────
167
 
168
  @app.route("/")
169
- def index():
170
- return send_from_directory("static", "index.html")
171
-
172
- @app.route("/<path:filename>")
173
- def static_files(filename):
174
- return send_from_directory("static", filename)
175
-
176
- @app.route("/b2proxy", methods=["POST", "OPTIONS"])
177
- def b2proxy():
178
- if request.method == "OPTIONS":
179
- return _cors("", 204)
180
-
181
- action = request.headers.get("X-Action", "")
182
- path = request.headers.get("X-Path", "")
183
-
184
- try:
185
- auth = get_auth()
186
 
187
- if action == "list":
188
- r = requests.post(
189
- f"{auth['api_url']}/b2api/v2/b2_list_file_names",
190
- headers={"Authorization": auth["token"], "Content-Type": "application/json"},
191
- json={"bucketId": B2_BUCKET_ID, "prefix": path, "delimiter": "/", "maxFileCount": 1000},
192
- timeout=15
193
- )
194
- data = r.json()
195
- files = [
196
- {
197
- "name": f["fileName"].split("/")[-1],
198
- "key": f["fileName"],
199
- "url": f"{auth['download_url']}/file/{B2_BUCKET}/{f['fileName']}?Authorization={auth['token']}"
200
- }
201
- for f in data.get("files", [])
202
- if f["fileName"].split("/")[-1]
203
- ]
204
- return _cors(jsonify({"files": files}))
205
-
206
- if action == "upload-url":
207
- r = requests.post(
208
- f"{auth['api_url']}/b2api/v2/b2_get_upload_url",
209
- headers={"Authorization": auth["token"], "Content-Type": "application/json"},
210
- json={"bucketId": B2_BUCKET_ID},
211
- timeout=15
212
- )
213
- data = r.json()
214
- return _cors(jsonify({
215
- "uploadUrl": data["uploadUrl"],
216
- "uploadToken": data["authorizationToken"]
217
- }))
218
-
219
- if action == "upload":
220
- file_data = request.data
221
- content_type = request.headers.get("X-Content-Type", "application/octet-stream")
222
- sha1 = request.headers.get("X-Sha1", "do_not_verify")
223
- r = requests.post(
224
- f"{auth['api_url']}/b2api/v2/b2_get_upload_url",
225
- headers={"Authorization": auth["token"], "Content-Type": "application/json"},
226
- json={"bucketId": B2_BUCKET_ID}, timeout=15
227
- )
228
- up = r.json()
229
- r2 = requests.post(
230
- up["uploadUrl"],
231
- headers={
232
- "Authorization": up["authorizationToken"],
233
- "X-Bz-File-Name": requests.utils.quote(path, safe=""),
234
- "Content-Type": content_type,
235
- "X-Bz-Content-Sha1": sha1,
236
- "Content-Length": str(len(file_data))
237
- },
238
- data=file_data, timeout=120
239
- )
240
- if r2.status_code == 200:
241
- return _cors(jsonify({"ok": True}))
242
- return _cors(jsonify({"error": r2.text}), 500)
243
-
244
- if action == "delete":
245
- r = requests.post(
246
- f"{auth['api_url']}/b2api/v2/b2_list_file_names",
247
- headers={"Authorization": auth["token"], "Content-Type": "application/json"},
248
- json={"bucketId": B2_BUCKET_ID, "prefix": path, "maxFileCount": 1}, timeout=15
249
- )
250
- files = r.json().get("files", [])
251
- if files:
252
- f = files[0]
253
- requests.post(
254
- f"{auth['api_url']}/b2api/v2/b2_delete_file_version",
255
- headers={"Authorization": auth["token"], "Content-Type": "application/json"},
256
- json={"fileId": f["fileId"], "fileName": f["fileName"]}, timeout=15
257
- )
258
- return _cors(jsonify({"ok": True}))
259
-
260
- return _cors(jsonify({"error": "Unknown action"}), 400)
261
-
262
- except requests.exceptions.Timeout:
263
- return _cors(jsonify({"error": "err-015 — Timeout"}), 504)
264
- except requests.exceptions.ConnectionError:
265
- return _cors(jsonify({"error": "err-027 — Retea"}), 503)
266
- except Exception as e:
267
- return _cors(jsonify({"error": f"err-016 — {str(e)}"}), 500)
268
-
269
-
270
- def _cors(response, status=200):
271
- if isinstance(response, str):
272
- from flask import make_response
273
- response = make_response(response, status)
274
- else:
275
- response.status_code = status
276
- response.headers["Access-Control-Allow-Origin"] = "*"
277
- response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
278
- response.headers["Access-Control-Allow-Headers"] = "Content-Type,X-Action,X-Path,X-File-Name,X-Content-Type,X-Sha1"
279
- return response
280
 
 
 
 
 
281
 
282
- if __name__ == "__main__":
283
- app.run(host="0.0.0.0", port=7860, debug=False)
 
1
+ from flask import Flask, send_from_directory, request, jsonify, Response
2
+ import requests, time, threading, json, os
 
 
 
 
 
 
 
 
3
 
4
  app = Flask(__name__, static_folder='static')
5
 
6
+ # ── Firebase ──────────────────────────────────────────────────
7
+ FIREBASE_API_KEY = "AIzaSyB9--Onx3-_YjD-YzblhZjaWSVVqTQJ1lU"
8
+ FIRESTORE_BASE = "https://firestore.googleapis.com/v1/projects/vservers1/databases/(default)/documents"
9
+
10
+ # ── Google Drive ──────────────────────────────────────────────
11
+ DRIVE_FOLDER_ID = "10c-8bQnYWCz-XgZZT4DfF67cQRsHipNo"
12
+ DRIVE_SA = {
13
  "type": "service_account",
14
+ "project_id": "vservers-40",
15
+ "private_key_id": "f6cf589fcc62e4bab8e557032d3b6b483d12dc8d",
16
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyrlnxR+n8hSkE\nYym7ewU6mUezQnUtBRuxWb/AupzIygJqHmMG7r4BztRkZ/dmBQXWmb9VhUpPmEvR\nP1tvXyz2Hh2wY7T/BdzYLOB3uQCXgRuNHFD104kRyPoQQPPuL7ctAHryrFh69614\n6xZ/4i+i8hbOwqjfe9tkHjuaKNrk7w9bCth1fmEPGyavTIy+8OL9RYIppapscFbn\nxOg9tP2VEaKH3GxvQ/Iafi9xQG1gmXT0rGES1+AoJ/O1eL/5SJO9zaZWSBQvq/eg\n0m+V4S2liXdnuvlivHEP1ucIqTc8jUtJ93fuDVhfqNgy/1pahWBNqypkLrnRIvhn\n8CwBWEQbAgMBAAECggEAGNtSXdNwRJXNr/OPkao6fv1l0RU0sc+dG53tpAUR3Ijl\nrxeNFSDPQ/ce6tzfuMyIFGOND3uca9g26+QRdzvZSF/GJ4ynWDbbkyMjEuSkpW7r\ng0TmKlyEG/pGd05h4Me4hsUjVXEOWgTsl/60QZQYpmVhMOA5l+VmDtZ52idG1EQF\nyQR2pydQRG1QPK14s/Se5L4aTe44bH7ri0uLbyw7y01pCyneBsIhzn2cj3rn78Om\n2ZZkv4rx0iySDhlL2KqwuxnNK+UFk3veFYkCJFVO/7rkg98qnSUUIT0jj6skF1Bq\nCF9xDWBrVEukDRyEi3gTtClGusb7XBxOvOWSYEGZ6QKBgQDlyv2nZXDzb0wANzYg\ngL6pITP42fR7E/fkfhU1F40eaQteV+ZVaKxO3oiz1l2L23XTp2qqgkrqYHome9U3\nYFZkDH4YfGuo1MZTIRmHKdbOmc41ZKH6lVyDkEhTe9dI+GuDwpLbDPDxi0Bt7dbe\n2aRzif60VnrtghctzvodTz8TJwKBgQDHDxqYQ+qDgWID2PERB26oOsfMa49sbSCV\n18+wmgx+ydM1UTW2aj3hRbDTxZT6GUw3Ngc23HxpPTyF20g4i0jM1LQ2DiOzI9Ij\ntyYBZzK9ec5J7+Z3gcP3irJpS8S2SoUWKD8kl50nbzXj+SjF26uDUci9fG10jtoR\nJ8Os3zHP7QKBgQDMCugayM93yT7R/jR4vfkOUuZENLyKwRtf77jDEOuEsj+fASwM\ncMp3qc/26ATel/tS+hiT2OfOn+Y238RezJNJeXJKKciq/GwyCnUReMw9XYMmE/pk\neFXSmL4wKwnpyHQnZhFiomYcBMssEYG3FciZs1HQLe4vkVElouCiP+jBBwKBgQCD\nfXOo3zwbUC0Js5VSFWHAWMvAOdDM460hriQwWSIl4nXVA2cCr11e4GU1DpAhQPK6\nicLsN2srLVs8ZKRpTYByZZMBHgfw/pmCJCpDxQKcbMiayJCpoptrej/uFDHF5KXT\nBBTpvAkAkpK7m8uWH0xFe5GpsXawBuj/ag/0sp655QKBgG3xU85qag5+89t5Wn8h\nn84B7ES734nbx6ya9/gDAWRIEvs8BHzgHaelXtfaXu+tm3cjqNsQtbprZZnNE7O6\nJoHIwiYFqlkgA9qGQnMyIT/vYCcJTtnokIoVdTN9IpMU7Lyo2rsxy0I0Q/rtUekg\nBOjMnuzHdt91ngSyg3BCbhKn\n-----END PRIVATE KEY-----\n",
17
+ "client_email": "vservers-uploader@vservers-40.iam.gserviceaccount.com",
18
  "token_uri": "https://oauth2.googleapis.com/token",
 
 
19
  }
20
 
21
+ _drive_token = None
22
+ _drive_token_exp = 0
23
+
24
+ def get_drive_token():
25
+ global _drive_token, _drive_token_exp
26
+ now = time.time()
27
+ if _drive_token and now < _drive_token_exp - 60:
28
+ return _drive_token
29
+ import jwt as pyjwt
30
+ private_key = DRIVE_SA["private_key"]
31
+ claim = {
32
+ "iss": DRIVE_SA["client_email"],
33
+ "scope": "https://www.googleapis.com/auth/drive",
34
+ "aud": DRIVE_SA["token_uri"],
35
+ "iat": int(now),
36
+ "exp": int(now) + 3600,
37
+ }
38
+ signed = pyjwt.encode(claim, private_key, algorithm="RS256")
39
+ r = requests.post(DRIVE_SA["token_uri"], data={
40
+ "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
41
+ "assertion": signed,
42
+ }, timeout=15)
43
+ r.raise_for_status()
44
+ _drive_token = r.json()["access_token"]
45
+ _drive_token_exp = now + 3600
46
+ return _drive_token
47
+
48
+ def drive_headers():
49
+ return {"Authorization": f"Bearer {get_drive_token()}"}
50
+
51
+ def get_or_create_folder(name, parent_id):
52
+ """Găsește sau creează un subfolder în Drive."""
53
+ token = get_drive_token()
54
+ q = f"name='{name}' and '{parent_id}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false"
55
+ r = requests.get("https://www.googleapis.com/drive/v3/files",
56
+ params={"q": q, "fields": "files(id,name)"},
57
+ headers=drive_headers(), timeout=15)
58
+ files = r.json().get("files", [])
59
+ if files:
60
+ return files[0]["id"]
61
+ # Creaza folder
62
+ r2 = requests.post("https://www.googleapis.com/drive/v3/files",
63
+ headers={**drive_headers(), "Content-Type": "application/json"},
64
+ json={"name": name, "mimeType": "application/vnd.google-apps.folder", "parents": [parent_id]},
65
+ timeout=15)
66
+ return r2.json()["id"]
67
+
68
+ # ── Ruta upload ───────────────────────────────────────────────
69
+ @app.route("/drive/upload", methods=["POST"])
70
+ def drive_upload():
71
+ try:
72
+ elev_id = request.form.get("elevId", "unknown")
73
+ materie = request.form.get("materieId", "general")
74
+ f = request.files.get("file")
75
+ if not f:
76
+ return jsonify({"error": "no file"}), 400
77
+
78
+ # Structura: VSERVERS / elevId / materieId / fisier.ext
79
+ elev_folder = get_or_create_folder(elev_id, DRIVE_FOLDER_ID)
80
+ materie_folder = get_or_create_folder(materie, elev_folder)
81
+
82
+ # Upload multipart
83
+ metadata = json.dumps({"name": f.filename, "parents": [materie_folder]})
84
+ r = requests.post(
85
+ "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,size",
86
+ headers={"Authorization": f"Bearer {get_drive_token()}"},
87
+ files={
88
+ "metadata": ("metadata", metadata, "application/json"),
89
+ "file": (f.filename, f.stream, f.mimetype or "application/octet-stream"),
90
+ },
91
+ timeout=120
92
+ )
93
+ data = r.json()
94
+ return jsonify({"fileId": data.get("id"), "name": data.get("name"), "size": data.get("size")})
95
+ except Exception as e:
96
+ print(f"[DRIVE UPLOAD ERROR] {e}")
97
+ return jsonify({"error": str(e)}), 500
98
+
99
+ # ── Ruta list ─────────────────────────────────────────────────
100
+ @app.route("/drive/list", methods=["GET"])
101
+ def drive_list():
102
+ try:
103
+ elev_id = request.args.get("elevId", "")
104
+ materie = request.args.get("materieId", "")
105
+
106
+ # Gaseste folderele
107
+ q_elev = f"name='{elev_id}' and '{DRIVE_FOLDER_ID}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false"
108
+ r1 = requests.get("https://www.googleapis.com/drive/v3/files",
109
+ params={"q": q_elev, "fields": "files(id)"}, headers=drive_headers(), timeout=15)
110
+ ef = r1.json().get("files", [])
111
+ if not ef:
112
+ return jsonify({"files": []})
113
+
114
+ q_mat = f"name='{materie}' and '{ef[0]['id']}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false"
115
+ r2 = requests.get("https://www.googleapis.com/drive/v3/files",
116
+ params={"q": q_mat, "fields": "files(id)"}, headers=drive_headers(), timeout=15)
117
+ mf = r2.json().get("files", [])
118
+ if not mf:
119
+ return jsonify({"files": []})
120
+
121
+ # Lista fisiere din folder
122
+ q_files = f"'{mf[0]['id']}' in parents and trashed=false"
123
+ r3 = requests.get("https://www.googleapis.com/drive/v3/files",
124
+ params={"q": q_files, "fields": "files(id,name,size,mimeType,createdTime)"},
125
+ headers=drive_headers(), timeout=15)
126
+ return jsonify({"files": r3.json().get("files", [])})
127
+ except Exception as e:
128
+ print(f"[DRIVE LIST ERROR] {e}")
129
+ return jsonify({"error": str(e)}), 500
130
 
131
+ # ── Ruta download (proxy) ─────────────────────────────────────
132
+ @app.route("/drive/download/<file_id>", methods=["GET"])
133
+ def drive_download(file_id):
134
+ try:
135
+ # Metadata
136
+ r_meta = requests.get(f"https://www.googleapis.com/drive/v3/files/{file_id}",
137
+ params={"fields": "name,mimeType"}, headers=drive_headers(), timeout=15)
138
+ meta = r_meta.json()
139
+ # Continut
140
+ r_file = requests.get(f"https://www.googleapis.com/drive/v3/files/{file_id}?alt=media",
141
+ headers=drive_headers(), timeout=120, stream=True)
142
+ headers = {
143
+ "Content-Disposition": f"attachment; filename=\"{meta.get('name','file')}\"",
144
+ "Content-Type": meta.get("mimeType", "application/octet-stream"),
145
+ }
146
+ return Response(r_file.iter_content(chunk_size=8192), headers=headers)
147
+ except Exception as e:
148
+ print(f"[DRIVE DOWNLOAD ERROR] {e}")
149
+ return jsonify({"error": str(e)}), 500
150
 
151
+ # ── Seed Firestore ────────────────────────────────────────────
152
  ELEVI = [
153
+ {"pozitie":1,"nume":"Andronic Aniela","vpassId":"AA-0001"},
154
+ {"pozitie":2,"nume":"Badan Alexandra","vpassId":"BA-0002"},
155
+ {"pozitie":3,"nume":"Bunescu Lavinia","vpassId":"BL-0003"},
156
+ {"pozitie":4,"nume":"Bunescu Lorena","vpassId":"BL-0004"},
157
+ {"pozitie":5,"nume":"Burdujan Damian","vpassId":"BD-0005"},
158
+ {"pozitie":6,"nume":"Ceban Madalina","vpassId":"CM-0006"},
159
+ {"pozitie":7,"nume":"Ceban Valeria","vpassId":"CV-0007"},
160
+ {"pozitie":8,"nume":"Cebanu Nichita","vpassId":"CN-0008"},
161
+ {"pozitie":9,"nume":"Chele Ilinca","vpassId":"CI-0009"},
162
+ {"pozitie":10,"nume":"Chihai Mirela","vpassId":"CM-0010"},
163
+ {"pozitie":11,"nume":"Cojocari Ciprian","vpassId":"CC-0011"},
164
+ {"pozitie":12,"nume":"Creciun Leonard","vpassId":"CL-0012"},
165
+ {"pozitie":13,"nume":"Lozovan Sanda","vpassId":"LS-0013"},
166
+ {"pozitie":14,"nume":"Lungu Sergiu","vpassId":"LS-0014"},
167
+ {"pozitie":15,"nume":"Mahnea Adelina","vpassId":"MA-0015"},
168
+ {"pozitie":16,"nume":"Morcan Ina","vpassId":"MI-0016"},
169
+ {"pozitie":17,"nume":"Morcan Ion","vpassId":"MI-0017"},
170
+ {"pozitie":18,"nume":"Popa Cristina","vpassId":"PC-0018"},
171
+ {"pozitie":19,"nume":"Pricob Adelina","vpassId":"PA-0019"},
172
+ {"pozitie":20,"nume":"Prinos Catalina","vpassId":"PC-0020"},
173
+ {"pozitie":21,"nume":"Radu Daniela","vpassId":"RD-0021"},
174
+ {"pozitie":22,"nume":"Rosca Victor","vpassId":"RV-0022"},
175
+ {"pozitie":23,"nume":"Rotari Cristian","vpassId":"RC-0023"},
176
+ {"pozitie":24,"nume":"Rotaru Camelia","vpassId":"RC-0024"},
177
+ {"pozitie":25,"nume":"Sambris Stanislav","vpassId":"SS-0025"},
178
+ {"pozitie":26,"nume":"Sirbu Delia","vpassId":"SD-0026"},
179
+ {"pozitie":27,"nume":"Soltoian Elena","vpassId":"SE-0027"},
180
+ {"pozitie":28,"nume":"Tabuncic Chiril","vpassId":"TC-0028"},
181
+ {"pozitie":29,"nume":"Turcan Gabriel","vpassId":"TG-0029"},
182
+ {"pozitie":30,"nume":"Turcan Mihai","vpassId":"TM-0030"},
183
+ {"pozitie":31,"nume":"Turcanu Ilie","vpassId":"TI-0031"},
184
+ {"pozitie":32,"nume":"Virlan Daniela","vpassId":"VD-0032"},
185
  ]
186
 
187
+ def fs_add(col, data):
188
+ fields={}
189
+ for k,v in data.items():
190
+ if v is None: fields[k]={"nullValue":None}
191
+ elif isinstance(v,bool): fields[k]={"booleanValue":v}
192
+ elif isinstance(v,int): fields[k]={"integerValue":str(v)}
193
+ else: fields[k]={"stringValue":str(v)}
194
+ r=requests.post(f"{FIRESTORE_BASE}/{col}?key={FIREBASE_API_KEY}",json={"fields":fields},timeout=15)
195
+ return r.status_code==200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
  def auto_seed():
198
+ time.sleep(4)
199
+ print("[VSERVERS] Verificare DB...")
 
 
200
  try:
201
+ r=requests.get(f"{FIRESTORE_BASE}/elevi?key={FIREBASE_API_KEY}&pageSize=1",timeout=15)
202
+ if r.json().get("documents"):
203
+ print("[VSERVERS] DB populat — seed omis."); return
204
+ print("[VSERVERS] Seed automat pornit...")
205
+ ok=0
 
 
206
  for e in ELEVI:
207
+ if fs_add("elevi",{"nume":e["nume"],"vpassId":e["vpassId"],"pozitie":e["pozitie"],"pin":None,"confirmed":False}):
208
+ ok+=1; print(f"[VSERVERS] ✓ {e['vpassId']}")
209
+ time.sleep(0.12)
210
+ print(f"[VSERVERS] Seed gata: {ok}/{len(ELEVI)}")
211
+ except Exception as ex: print(f"[VSERVERS] Seed eroare: {ex}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
+ threading.Thread(target=auto_seed,daemon=True).start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
  @app.route("/")
216
+ def index(): return send_from_directory("static","index.html")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
+ @app.errorhandler(404)
219
+ def not_found(e): return send_from_directory("static","404.html"),404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
+ @app.route("/<path:filename>")
222
+ def serve_files(filename):
223
+ try: return send_from_directory("static",filename)
224
+ except: return send_from_directory("static","404.html"),404
225
 
226
+ if __name__=="__main__": app.run(host="0.0.0.0",port=7860,debug=False)