Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -23,7 +23,8 @@ app.secret_key = os.getenv("FLASK_SECRET_KEY", 'your_unique_secret_key_korea_glo
|
|
| 23 |
app.config.update(
|
| 24 |
SESSION_COOKIE_SAMESITE="None",
|
| 25 |
SESSION_COOKIE_SECURE=True,
|
| 26 |
-
SESSION_COOKIE_HTTPONLY=True
|
|
|
|
| 27 |
)
|
| 28 |
|
| 29 |
DATA_FILE = 'data_kglobal.json'
|
|
@@ -42,7 +43,6 @@ CURRENCY_NAME = 'Доллар США ($)'
|
|
| 42 |
DOWNLOAD_RETRIES = 3
|
| 43 |
DOWNLOAD_DELAY = 5
|
| 44 |
UPLOAD_DELAY = 2
|
| 45 |
-
BACKUP_INTERVAL = 1800
|
| 46 |
|
| 47 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 48 |
|
|
@@ -137,9 +137,6 @@ def _load_from_file(file_path, default_value, lock):
|
|
| 137 |
if 'orders' not in content: content['orders'] = {}
|
| 138 |
elif file_path == USERS_FILE:
|
| 139 |
if not isinstance(content, dict): raise ValueError("Users file is not a dictionary")
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
pass
|
| 143 |
return content
|
| 144 |
except (FileNotFoundError, json.JSONDecodeError, ValueError) as e:
|
| 145 |
logging.error(f"Error loading local file {file_path}: {e}. Returning default.")
|
|
@@ -183,7 +180,9 @@ def save_data(new_data):
|
|
| 183 |
with open(DATA_FILE, 'w', encoding='utf-8') as file:
|
| 184 |
json.dump(app_data, file, ensure_ascii=False, indent=4)
|
| 185 |
logging.info(f"Data successfully saved to {DATA_FILE} and memory cache updated.")
|
| 186 |
-
|
|
|
|
|
|
|
| 187 |
except Exception as e:
|
| 188 |
logging.error(f"Error saving data to {DATA_FILE}: {e}", exc_info=True)
|
| 189 |
return False
|
|
@@ -203,7 +202,9 @@ def save_users(new_users):
|
|
| 203 |
with open(USERS_FILE, 'w', encoding='utf-8') as file:
|
| 204 |
json.dump(app_users, file, ensure_ascii=False, indent=4)
|
| 205 |
logging.info(f"User data successfully saved to {USERS_FILE} and memory cache updated.")
|
| 206 |
-
|
|
|
|
|
|
|
| 207 |
except Exception as e:
|
| 208 |
logging.error(f"Error saving user data to {USERS_FILE}: {e}", exc_info=True)
|
| 209 |
return False
|
|
@@ -221,16 +222,14 @@ def upload_db_to_hf(specific_file=None):
|
|
| 221 |
for file_name in files_to_upload:
|
| 222 |
if os.path.exists(file_name):
|
| 223 |
try:
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
| 233 |
-
)
|
| 234 |
logging.info(f"File {file_name} successfully uploaded to Hugging Face.")
|
| 235 |
time.sleep(UPLOAD_DELAY)
|
| 236 |
except Exception as e:
|
|
@@ -246,21 +245,6 @@ def upload_db_to_hf(specific_file=None):
|
|
| 246 |
logging.error(f"General error during Hugging Face upload initialization or process: {e}", exc_info=True)
|
| 247 |
return False
|
| 248 |
|
| 249 |
-
def periodic_backup():
|
| 250 |
-
logging.info(f"Setting up periodic backup every {BACKUP_INTERVAL} seconds.")
|
| 251 |
-
while True:
|
| 252 |
-
time.sleep(BACKUP_INTERVAL)
|
| 253 |
-
logging.info("Starting periodic backup...")
|
| 254 |
-
try:
|
| 255 |
-
upload_success = upload_db_to_hf()
|
| 256 |
-
if upload_success:
|
| 257 |
-
logging.info("Periodic backup finished successfully.")
|
| 258 |
-
else:
|
| 259 |
-
logging.warning("Periodic backup finished with errors (some files might not have been uploaded).")
|
| 260 |
-
except Exception as e:
|
| 261 |
-
logging.error(f"Error during periodic backup execution: {e}", exc_info=True)
|
| 262 |
-
|
| 263 |
-
|
| 264 |
CATALOG_TEMPLATE = '''
|
| 265 |
<!DOCTYPE html>
|
| 266 |
<html lang="ru">
|
|
@@ -344,7 +328,7 @@ CATALOG_TEMPLATE = '''
|
|
| 344 |
.cart-item-price { font-size: 0.9rem; color: #444c5a; }
|
| 345 |
body.dark-mode .cart-item-price { color: #8aa9b5; }
|
| 346 |
.cart-item-total { font-weight: bold; text-align: right; grid-column: 3; font-size: 1rem; }
|
| 347 |
-
.cart-item-remove {
|
| 348 |
.cart-item-remove:hover { color: #c53030; }
|
| 349 |
.quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #d4d9e3; border-radius: 8px; font-size: 1rem; margin: 10px 0; box-sizing: border-box; }
|
| 350 |
body.dark-mode .quantity-input, body.dark-mode .color-select { background-color: #1a114f; border-color: #102a50; color: #c8d0e0; }
|
|
@@ -1117,8 +1101,8 @@ ADMIN_TEMPLATE = '''
|
|
| 1117 |
<button type="submit" class="button download-hf-button" title="Скачать файлы (перезапишет локальные)"><i class="fas fa-download"></i> Скачать БД</button>
|
| 1118 |
</form>
|
| 1119 |
</div>
|
| 1120 |
-
<p style="font-size: 0.85rem; color: #5e6e75;">Резервное копирование происходит автоматически кажд
|
| 1121 |
-
<p style="font-size: 0.85rem; color: #5e6e75;">Сохранение данных (товары, пользователи, категории) происходит
|
| 1122 |
</div>
|
| 1123 |
|
| 1124 |
|
|
@@ -1703,9 +1687,13 @@ def login():
|
|
| 1703 |
localStorage.setItem('kglobalUserToken', '{token_attempt}');
|
| 1704 |
console.log('Stored token in localStorage.');
|
| 1705 |
}} catch (e) {{ console.error("Error saving token to localStorage:", e); }}
|
| 1706 |
-
window.location.href =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1707 |
</script>
|
| 1708 |
-
<p>Вход выполнен успешно. Перенаправление в <a href="{url_for('catalog')}">каталог</a>...</p>
|
| 1709 |
</body></html>
|
| 1710 |
'''
|
| 1711 |
return login_response_html
|
|
@@ -1758,9 +1746,13 @@ def logout():
|
|
| 1758 |
localStorage.removeItem('kglobalUserToken');
|
| 1759 |
console.log('Removed stored token from localStorage.');
|
| 1760 |
}} catch (e) {{ console.error("Error removing from localStorage:", e); }}
|
| 1761 |
-
window.location.href =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1762 |
</script>
|
| 1763 |
-
<p>Выход выполнен. Перенаправление на <a href="{url_for('catalog')}">главную страницу</a>...</p>
|
| 1764 |
</body></html>
|
| 1765 |
'''
|
| 1766 |
return logout_response_html
|
|
@@ -2275,8 +2267,7 @@ def admin():
|
|
| 2275 |
categories=display_categories,
|
| 2276 |
users=display_users_sorted_dict,
|
| 2277 |
repo_id=REPO_ID,
|
| 2278 |
-
currency_code=CURRENCY_CODE
|
| 2279 |
-
backup_interval=BACKUP_INTERVAL
|
| 2280 |
)
|
| 2281 |
|
| 2282 |
@app.route('/force_upload', methods=['POST'])
|
|
@@ -2289,8 +2280,8 @@ def force_upload():
|
|
| 2289 |
else:
|
| 2290 |
flash("Во время загрузки на Hugging Face произошли ошибки (не все файлы могли быть загружены). Проверьте логи.", 'warning')
|
| 2291 |
except Exception as e:
|
| 2292 |
-
logging.error(f"
|
| 2293 |
-
flash(f"
|
| 2294 |
return redirect(url_for('admin'))
|
| 2295 |
|
| 2296 |
@app.route('/force_download', methods=['POST'])
|
|
@@ -2303,8 +2294,8 @@ def force_download():
|
|
| 2303 |
else:
|
| 2304 |
flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Используются текущие локальные данные. Проверьте логи.", 'error')
|
| 2305 |
except Exception as e:
|
| 2306 |
-
logging.error(f"
|
| 2307 |
-
flash(f"
|
| 2308 |
return redirect(url_for('admin'))
|
| 2309 |
|
| 2310 |
|
|
@@ -2315,20 +2306,13 @@ if __name__ == '__main__':
|
|
| 2315 |
load_initial_data()
|
| 2316 |
logging.info("Initial data load complete.")
|
| 2317 |
|
| 2318 |
-
if HF_TOKEN_WRITE:
|
| 2319 |
-
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
| 2320 |
-
backup_thread.start()
|
| 2321 |
-
logging.info("Periodic backup thread started.")
|
| 2322 |
-
else:
|
| 2323 |
-
logging.warning("Periodic backup thread *not* started (HF_TOKEN_WRITE not set).")
|
| 2324 |
-
|
| 2325 |
port = int(os.environ.get('PORT', 7860))
|
| 2326 |
-
logging.info(f"Starting Flask app server on host 0.0.0.0 and port {port}")
|
| 2327 |
|
| 2328 |
try:
|
| 2329 |
from waitress import serve
|
| 2330 |
serve(app, host='0.0.0.0', port=port, threads=8)
|
| 2331 |
except ImportError:
|
| 2332 |
-
logging.warning("Waitress not found. Falling back to Flask development server.")
|
| 2333 |
logging.warning("Install waitress for a production-ready server: pip install waitress")
|
| 2334 |
app.run(debug=False, host='0.0.0.0', port=port)
|
|
|
|
| 23 |
app.config.update(
|
| 24 |
SESSION_COOKIE_SAMESITE="None",
|
| 25 |
SESSION_COOKIE_SECURE=True,
|
| 26 |
+
SESSION_COOKIE_HTTPONLY=True,
|
| 27 |
+
PREFERRED_URL_SCHEME="https"
|
| 28 |
)
|
| 29 |
|
| 30 |
DATA_FILE = 'data_kglobal.json'
|
|
|
|
| 43 |
DOWNLOAD_RETRIES = 3
|
| 44 |
DOWNLOAD_DELAY = 5
|
| 45 |
UPLOAD_DELAY = 2
|
|
|
|
| 46 |
|
| 47 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 48 |
|
|
|
|
| 137 |
if 'orders' not in content: content['orders'] = {}
|
| 138 |
elif file_path == USERS_FILE:
|
| 139 |
if not isinstance(content, dict): raise ValueError("Users file is not a dictionary")
|
|
|
|
|
|
|
|
|
|
| 140 |
return content
|
| 141 |
except (FileNotFoundError, json.JSONDecodeError, ValueError) as e:
|
| 142 |
logging.error(f"Error loading local file {file_path}: {e}. Returning default.")
|
|
|
|
| 180 |
with open(DATA_FILE, 'w', encoding='utf-8') as file:
|
| 181 |
json.dump(app_data, file, ensure_ascii=False, indent=4)
|
| 182 |
logging.info(f"Data successfully saved to {DATA_FILE} and memory cache updated.")
|
| 183 |
+
|
| 184 |
+
upload_db_to_hf(DATA_FILE)
|
| 185 |
+
return True
|
| 186 |
except Exception as e:
|
| 187 |
logging.error(f"Error saving data to {DATA_FILE}: {e}", exc_info=True)
|
| 188 |
return False
|
|
|
|
| 202 |
with open(USERS_FILE, 'w', encoding='utf-8') as file:
|
| 203 |
json.dump(app_users, file, ensure_ascii=False, indent=4)
|
| 204 |
logging.info(f"User data successfully saved to {USERS_FILE} and memory cache updated.")
|
| 205 |
+
|
| 206 |
+
upload_db_to_hf(USERS_FILE)
|
| 207 |
+
return True
|
| 208 |
except Exception as e:
|
| 209 |
logging.error(f"Error saving user data to {USERS_FILE}: {e}", exc_info=True)
|
| 210 |
return False
|
|
|
|
| 222 |
for file_name in files_to_upload:
|
| 223 |
if os.path.exists(file_name):
|
| 224 |
try:
|
| 225 |
+
api.upload_file(
|
| 226 |
+
path_or_fileobj=file_name,
|
| 227 |
+
path_in_repo=file_name,
|
| 228 |
+
repo_id=REPO_ID,
|
| 229 |
+
repo_type="dataset",
|
| 230 |
+
token=HF_TOKEN_WRITE,
|
| 231 |
+
commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
| 232 |
+
)
|
|
|
|
|
|
|
| 233 |
logging.info(f"File {file_name} successfully uploaded to Hugging Face.")
|
| 234 |
time.sleep(UPLOAD_DELAY)
|
| 235 |
except Exception as e:
|
|
|
|
| 245 |
logging.error(f"General error during Hugging Face upload initialization or process: {e}", exc_info=True)
|
| 246 |
return False
|
| 247 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
CATALOG_TEMPLATE = '''
|
| 249 |
<!DOCTYPE html>
|
| 250 |
<html lang="ru">
|
|
|
|
| 328 |
.cart-item-price { font-size: 0.9rem; color: #444c5a; }
|
| 329 |
body.dark-mode .cart-item-price { color: #8aa9b5; }
|
| 330 |
.cart-item-total { font-weight: bold; text-align: right; grid-column: 3; font-size: 1rem; }
|
| 331 |
+
.cart-item-remove { background:none; border:none; color:#f56565; cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
|
| 332 |
.cart-item-remove:hover { color: #c53030; }
|
| 333 |
.quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #d4d9e3; border-radius: 8px; font-size: 1rem; margin: 10px 0; box-sizing: border-box; }
|
| 334 |
body.dark-mode .quantity-input, body.dark-mode .color-select { background-color: #1a114f; border-color: #102a50; color: #c8d0e0; }
|
|
|
|
| 1101 |
<button type="submit" class="button download-hf-button" title="Скачать файлы (перезапишет локальные)"><i class="fas fa-download"></i> Скачать БД</button>
|
| 1102 |
</form>
|
| 1103 |
</div>
|
| 1104 |
+
<p style="font-size: 0.85rem; color: #5e6e75;">Резервное копирование происходит автоматически после каждого сохранения изменений. Используйте эти кнопки для немедленной синхронизации.</p>
|
| 1105 |
+
<p style="font-size: 0.85rem; color: #5e6e75;">Сохранение данных (товары, пользователи, категории) происходит локально, синхронизация с датацентром - автоматически после сохранения или принудительно.</p>
|
| 1106 |
</div>
|
| 1107 |
|
| 1108 |
|
|
|
|
| 1687 |
localStorage.setItem('kglobalUserToken', '{token_attempt}');
|
| 1688 |
console.log('Stored token in localStorage.');
|
| 1689 |
}} catch (e) {{ console.error("Error saving token to localStorage:", e); }}
|
| 1690 |
+
if (window.parent && window.parent.location.href !== window.location.href) {{
|
| 1691 |
+
window.parent.location.reload(true); // Attempt to reload parent frame for session re-check
|
| 1692 |
+
}} else {{
|
| 1693 |
+
window.location.href = "{url_for('catalog', _external=True, _scheme=app.config['PREFERRED_URL_SCHEME'])}";
|
| 1694 |
+
}}
|
| 1695 |
</script>
|
| 1696 |
+
<p>Вход выполнен успешно. Перенаправление в <a href="{url_for('catalog', _external=True, _scheme=app.config['PREFERRED_URL_SCHEME'])}">каталог</a>...</p>
|
| 1697 |
</body></html>
|
| 1698 |
'''
|
| 1699 |
return login_response_html
|
|
|
|
| 1746 |
localStorage.removeItem('kglobalUserToken');
|
| 1747 |
console.log('Removed stored token from localStorage.');
|
| 1748 |
}} catch (e) {{ console.error("Error removing from localStorage:", e); }}
|
| 1749 |
+
if (window.parent && window.parent.location.href !== window.location.href) {{
|
| 1750 |
+
window.parent.location.reload(true);
|
| 1751 |
+
}} else {{
|
| 1752 |
+
window.location.href = "{url_for('catalog', _external=True, _scheme=app.config['PREFERRED_URL_SCHEME'])}";
|
| 1753 |
+
}}
|
| 1754 |
</script>
|
| 1755 |
+
<p>Выход выполнен. Перенаправление на <a href="{url_for('catalog', _external=True, _scheme=app.config['PREFERRED_URL_SCHEME'])}">главную страницу</a>...</p>
|
| 1756 |
</body></html>
|
| 1757 |
'''
|
| 1758 |
return logout_response_html
|
|
|
|
| 2267 |
categories=display_categories,
|
| 2268 |
users=display_users_sorted_dict,
|
| 2269 |
repo_id=REPO_ID,
|
| 2270 |
+
currency_code=CURRENCY_CODE
|
|
|
|
| 2271 |
)
|
| 2272 |
|
| 2273 |
@app.route('/force_upload', methods=['POST'])
|
|
|
|
| 2280 |
else:
|
| 2281 |
flash("Во время загрузки на Hugging Face произошли ошибки (не все файлы могли быть загружены). Проверьте логи.", 'warning')
|
| 2282 |
except Exception as e:
|
| 2283 |
+
logging.error(f"Critical error during forced upload: {e}", exc_info=True)
|
| 2284 |
+
flash(f"Critical error during forced upload to Hugging Face: {e}", 'error')
|
| 2285 |
return redirect(url_for('admin'))
|
| 2286 |
|
| 2287 |
@app.route('/force_download', methods=['POST'])
|
|
|
|
| 2294 |
else:
|
| 2295 |
flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Используются текущие локальные данные. Проверьте логи.", 'error')
|
| 2296 |
except Exception as e:
|
| 2297 |
+
logging.error(f"Critical error during forced download: {e}", exc_info=True)
|
| 2298 |
+
flash(f"Critical error during forced download from Hugging Face: {e}", 'error')
|
| 2299 |
return redirect(url_for('admin'))
|
| 2300 |
|
| 2301 |
|
|
|
|
| 2306 |
load_initial_data()
|
| 2307 |
logging.info("Initial data load complete.")
|
| 2308 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2309 |
port = int(os.environ.get('PORT', 7860))
|
| 2310 |
+
logging.info(f"Starting Flask app server on host 0.0.0.0 and port {port}. Ensure HTTPS is configured for session cookies with SameSite=None to work correctly in iframes.")
|
| 2311 |
|
| 2312 |
try:
|
| 2313 |
from waitress import serve
|
| 2314 |
serve(app, host='0.0.0.0', port=port, threads=8)
|
| 2315 |
except ImportError:
|
| 2316 |
+
logging.warning("Waitress not found. Falling back to Flask development server. NOT FOR PRODUCTION USE.")
|
| 2317 |
logging.warning("Install waitress for a production-ready server: pip install waitress")
|
| 2318 |
app.run(debug=False, host='0.0.0.0', port=port)
|