more fun useless stuff

This commit is contained in:
2025-10-18 12:39:53 +03:00
parent 33f288a464
commit 1f5f6ed30f
7 changed files with 548 additions and 129 deletions

333
main.py
View File

@@ -9,6 +9,7 @@ from datetime import datetime
import os import os
import json import json
import secrets import secrets
import random
from typing import List, Optional from typing import List, Optional
import uvicorn import uvicorn
from hashlib import sha256 from hashlib import sha256
@@ -58,7 +59,6 @@ class AdminUser(Base):
password_hash = Column(String(255)) password_hash = Column(String(255))
created_at = Column(DateTime, default=datetime.utcnow) created_at = Column(DateTime, default=datetime.utcnow)
# НОВАЯ МОДЕЛЬ: Настройки дашборда
class DashboardSettings(Base): class DashboardSettings(Base):
__tablename__ = "dashboard_settings" __tablename__ = "dashboard_settings"
@@ -67,6 +67,16 @@ class DashboardSettings(Base):
image_filename = Column(String(255), default="dashboard_default.jpg") image_filename = Column(String(255), default="dashboard_default.jpg")
updated_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow)
class MainImage(Base):
__tablename__ = "main_images"
id = Column(Integer, primary_key=True, index=True)
filename = Column(String(255))
description = Column(Text)
is_active = Column(Boolean, default=False)
uploaded_at = Column(DateTime, default=datetime.utcnow)
uploaded_by = Column(String(100))
# Создаем таблицы # Создаем таблицы
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
@@ -76,7 +86,8 @@ app = FastAPI(title="OldMarket Server")
os.makedirs("static/icons", exist_ok=True) os.makedirs("static/icons", exist_ok=True)
os.makedirs("static/apks", exist_ok=True) os.makedirs("static/apks", exist_ok=True)
os.makedirs("static/admin", exist_ok=True) os.makedirs("static/admin", exist_ok=True)
os.makedirs("static/dashboard", exist_ok=True) # Новая папка для изображений дашборда os.makedirs("static/dashboard", exist_ok=True)
os.makedirs("static/main_images", exist_ok=True)
# Монтируем статические файлы # Монтируем статические файлы
app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/static", StaticFiles(directory="static"), name="static")
@@ -120,7 +131,18 @@ def create_default_dashboard_settings(db: Session):
) )
db.add(default_settings) db.add(default_settings)
db.commit() db.commit()
print("Созданы дефолтные настройки дашборда")
# Создаем дефолтное главное изображение
def create_default_main_image(db: Session):
if db.query(MainImage).count() == 0:
default_image = MainImage(
filename="default_main.jpg",
description="Дефолтное главное изображение",
is_active=True,
uploaded_by="system"
)
db.add(default_image)
db.commit()
# Сессии для аутентификации # Сессии для аутентификации
sessions = {} sessions = {}
@@ -151,7 +173,38 @@ def require_auth(request: Request, db: Session = Depends(get_db)):
) )
return user return user
# API endpoints для клиентов (старая совместимость) # Корневой endpoint - отдает изображение
@app.get("/")
def get_main_image(db: Session = Depends(get_db)):
active_image = db.query(MainImage).filter(MainImage.is_active == True).first()
if active_image:
image_path = f"static/main_images/{active_image.filename}"
if os.path.exists(image_path):
return FileResponse(
image_path,
media_type="image/jpeg",
filename=active_image.filename
)
all_images = db.query(MainImage).all()
if all_images:
random_image = random.choice(all_images)
image_path = f"static/main_images/{random_image.filename}"
if os.path.exists(image_path):
return FileResponse(
image_path,
media_type="image/jpeg",
filename=random_image.filename
)
default_path = "static/main_images/default_main.jpg"
if os.path.exists(default_path):
return FileResponse(default_path, media_type="image/jpeg")
else:
raise HTTPException(status_code=404, detail="No images available")
# API endpoints для клиентов
@app.get("/api/apps") @app.get("/api/apps")
def get_apps(db: Session = Depends(get_db)): def get_apps(db: Session = Depends(get_db)):
apps = db.query(App).all() apps = db.query(App).all()
@@ -184,14 +237,12 @@ def get_reviews(app_id: int, db: Session = Depends(get_db)):
for review in reviews for review in reviews
] ]
# Скачивание основной версии приложения
@app.get("/api/download/{app_id}") @app.get("/api/download/{app_id}")
def download_app(app_id: int, db: Session = Depends(get_db)): def download_app(app_id: int, db: Session = Depends(get_db)):
app = db.query(App).filter(App.id == app_id).first() app = db.query(App).filter(App.id == app_id).first()
if not app: if not app:
raise HTTPException(status_code=404, detail="App not found") raise HTTPException(status_code=404, detail="App not found")
# Увеличиваем счетчик загрузок
app.downloads = (app.downloads or 0) + 1 app.downloads = (app.downloads or 0) + 1
db.commit() db.commit()
@@ -205,19 +256,16 @@ def download_app(app_id: int, db: Session = Depends(get_db)):
media_type='application/vnd.android.package-archive' media_type='application/vnd.android.package-archive'
) )
# Скачивание конкретной версии приложения
@app.get("/api/download/{app_id}/{version}") @app.get("/api/download/{app_id}/{version}")
def download_app_version(app_id: int, version: str, db: Session = Depends(get_db)): def download_app_version(app_id: int, version: str, db: Session = Depends(get_db)):
app = db.query(App).filter(App.id == app_id).first() app = db.query(App).filter(App.id == app_id).first()
if not app: if not app:
raise HTTPException(status_code=404, detail="App not found") raise HTTPException(status_code=404, detail="App not found")
# Ищем нужную версию в списке версий
versions = json.loads(app.versions) if app.versions else [] versions = json.loads(app.versions) if app.versions else []
target_version = None target_version = None
for ver in versions: for ver in versions:
# Сравниваем версию, игнорируя регистр и пробелы
if ver.get("version", "").replace(" ", "").lower() == version.replace(" ", "").lower(): if ver.get("version", "").replace(" ", "").lower() == version.replace(" ", "").lower():
target_version = ver target_version = ver
break break
@@ -225,7 +273,6 @@ def download_app_version(app_id: int, version: str, db: Session = Depends(get_db
if not target_version: if not target_version:
raise HTTPException(status_code=404, detail=f"Version '{version}' not found for this app") raise HTTPException(status_code=404, detail=f"Version '{version}' not found for this app")
# Увеличиваем счетчик загрузок основной версии
app.downloads = (app.downloads or 0) + 1 app.downloads = (app.downloads or 0) + 1
db.commit() db.commit()
@@ -247,6 +294,8 @@ def download_app_version(app_id: int, version: str, db: Session = Depends(get_db
@app.get("/admin/login", response_class=HTMLResponse) @app.get("/admin/login", response_class=HTMLResponse)
def admin_login(request: Request, db: Session = Depends(get_db)): def admin_login(request: Request, db: Session = Depends(get_db)):
create_default_admin(db) create_default_admin(db)
create_default_dashboard_settings(db)
create_default_main_image(db)
return templates.TemplateResponse("login.html", {"request": request}) return templates.TemplateResponse("login.html", {"request": request})
@app.post("/admin/login") @app.post("/admin/login")
@@ -277,16 +326,12 @@ def admin_logout():
# Защищенные роуты админ-панели # Защищенные роуты админ-панели
@app.get("/admin", response_class=HTMLResponse) @app.get("/admin", response_class=HTMLResponse)
def admin_panel(request: Request, user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)): def admin_panel(request: Request, user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)):
create_default_dashboard_settings(db)
apps_count = db.query(App).count() apps_count = db.query(App).count()
reviews_count = db.query(Review).count() reviews_count = db.query(Review).count()
# ИСПРАВЛЕННАЯ СТРОКА: используем func.sum для получения суммы загрузок
total_downloads_result = db.query(func.sum(App.downloads)).scalar() total_downloads_result = db.query(func.sum(App.downloads)).scalar()
total_downloads = total_downloads_result if total_downloads_result is not None else 0 total_downloads = total_downloads_result if total_downloads_result is not None else 0
# Получаем настройки дашборда
dashboard_settings = db.query(DashboardSettings).first() dashboard_settings = db.query(DashboardSettings).first()
return templates.TemplateResponse("admin.html", { return templates.TemplateResponse("admin.html", {
@@ -322,7 +367,15 @@ def admin_reviews(request: Request, user: AdminUser = Depends(require_auth), db:
"username": user.username "username": user.username
}) })
# НОВЫЕ ENDPOINTS: Управление настройками дашборда @app.get("/admin/main-images", response_class=HTMLResponse)
def admin_main_images(request: Request, user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)):
images = db.query(MainImage).all()
return templates.TemplateResponse("main_images.html", {
"request": request,
"images": images,
"username": user.username
})
@app.get("/admin/dashboard-settings", response_class=HTMLResponse) @app.get("/admin/dashboard-settings", response_class=HTMLResponse)
def dashboard_settings(request: Request, user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)): def dashboard_settings(request: Request, user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)):
dashboard_settings = db.query(DashboardSettings).first() dashboard_settings = db.query(DashboardSettings).first()
@@ -333,61 +386,7 @@ def dashboard_settings(request: Request, user: AdminUser = Depends(require_auth)
"dashboard_image": dashboard_settings.image_filename if dashboard_settings else "dashboard_default.jpg" "dashboard_image": dashboard_settings.image_filename if dashboard_settings else "dashboard_default.jpg"
}) })
@app.post("/api/admin/dashboard/motd") # API для админ-панели
def update_motd(
user: AdminUser = Depends(require_auth),
motd: str = Form(...),
db: Session = Depends(get_db)
):
dashboard_settings = db.query(DashboardSettings).first()
if not dashboard_settings:
dashboard_settings = DashboardSettings()
db.add(dashboard_settings)
dashboard_settings.motd = motd
dashboard_settings.updated_at = datetime.utcnow()
db.commit()
return {"message": "MOTD обновлен"}
@app.post("/api/admin/dashboard/image")
def update_dashboard_image(
user: AdminUser = Depends(require_auth),
image: UploadFile = File(...),
db: Session = Depends(get_db)
):
# Проверяем, что файл является изображением
if not image.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="Файл должен быть изображением")
# Генерируем уникальное имя файла
file_extension = os.path.splitext(image.filename)[1]
image_filename = f"dashboard_{int(datetime.now().timestamp())}{file_extension}"
image_path = f"static/dashboard/{image_filename}"
# Сохраняем изображение
contents = image.file.read()
with open(image_path, "wb") as f:
f.write(contents)
# Обновляем настройки дашборда
dashboard_settings = db.query(DashboardSettings).first()
if not dashboard_settings:
dashboard_settings = DashboardSettings()
db.add(dashboard_settings)
# Удаляем старое изображение если оно не дефолтное
if (dashboard_settings.image_filename != "dashboard_default.jpg" and
os.path.exists(f"static/dashboard/{dashboard_settings.image_filename}")):
os.remove(f"static/dashboard/{dashboard_settings.image_filename}")
dashboard_settings.image_filename = image_filename
dashboard_settings.updated_at = datetime.utcnow()
db.commit()
return {"message": "Изображение дашборда обновлено"}
# API для админ-панели (защищенные)
@app.get("/api/admin/apps") @app.get("/api/admin/apps")
def get_admin_apps(user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)): def get_admin_apps(user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)):
apps = db.query(App).all() apps = db.query(App).all()
@@ -409,30 +408,27 @@ def create_app(
api: str = Form(...), api: str = Form(...),
description: str = Form(...), description: str = Form(...),
is_game: bool = Form(False), is_game: bool = Form(False),
rating: float = Form(0.0),
apk_file: UploadFile = File(...), apk_file: UploadFile = File(...),
icon: UploadFile = File(...), icon: UploadFile = File(...),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
# Сохраняем APK
apk_filename = f"{package}_{version.replace(' ', '_')}.apk" apk_filename = f"{package}_{version.replace(' ', '_')}.apk"
apk_path = f"static/apks/{apk_filename}" apk_path = f"static/apks/{apk_filename}"
with open(apk_path, "wb") as f: with open(apk_path, "wb") as f:
f.write(apk_file.file.read()) f.write(apk_file.file.read())
# Сохраняем иконку
icon_filename = f"{package}_icon.png" icon_filename = f"{package}_icon.png"
icon_path = f"static/icons/{icon_filename}" icon_path = f"static/icons/{icon_filename}"
with open(icon_path, "wb") as f: with open(icon_path, "wb") as f:
f.write(icon.file.read()) f.write(icon.file.read())
# Создаем базовую версию
base_version = { base_version = {
"api": api, "api": api,
"apk_file": apk_filename, "apk_file": apk_filename,
"version": version "version": version
} }
# Создаем запись в БД
db_app = App( db_app = App(
name=name, name=name,
package=package, package=package,
@@ -440,6 +436,7 @@ def create_app(
api=api, api=api,
description=description, description=description,
is_game=is_game, is_game=is_game,
rating=rating,
apk_file=apk_filename, apk_file=apk_filename,
icon=icon_filename, icon=icon_filename,
screenshots="[]", screenshots="[]",
@@ -451,7 +448,6 @@ def create_app(
return {"message": "Приложение создано", "app_id": db_app.id} return {"message": "Приложение создано", "app_id": db_app.id}
# НОВЫЙ ENDPOINT: Обновление приложения
@app.put("/api/admin/apps/{app_id}") @app.put("/api/admin/apps/{app_id}")
def update_app( def update_app(
app_id: int, app_id: int,
@@ -470,7 +466,6 @@ def update_app(
if not app: if not app:
raise HTTPException(status_code=404, detail="App not found") raise HTTPException(status_code=404, detail="App not found")
# Обновляем поля если они переданы
if name is not None: if name is not None:
app.name = name app.name = name
if package is not None: if package is not None:
@@ -486,7 +481,6 @@ def update_app(
if rating is not None: if rating is not None:
app.rating = rating app.rating = rating
# Обновляем иконку если передана
if icon and icon.filename: if icon and icon.filename:
icon_filename = f"{app.package}_icon.png" icon_filename = f"{app.package}_icon.png"
icon_path = f"static/icons/{icon_filename}" icon_path = f"static/icons/{icon_filename}"
@@ -510,13 +504,11 @@ def add_app_version(
if not app: if not app:
raise HTTPException(status_code=404, detail="App not found") raise HTTPException(status_code=404, detail="App not found")
# Сохраняем APK
apk_filename = f"{app.package}_{version.replace(' ', '_')}.apk" apk_filename = f"{app.package}_{version.replace(' ', '_')}.apk"
apk_path = f"static/apks/{apk_filename}" apk_path = f"static/apks/{apk_filename}"
with open(apk_path, "wb") as f: with open(apk_path, "wb") as f:
f.write(apk_file.file.read()) f.write(apk_file.file.read())
# Добавляем версию
versions = json.loads(app.versions) if app.versions else [] versions = json.loads(app.versions) if app.versions else []
new_version = { new_version = {
"api": api, "api": api,
@@ -529,29 +521,6 @@ def add_app_version(
db.commit() db.commit()
return {"message": "Версия добавлена"} return {"message": "Версия добавлена"}
@app.delete("/api/admin/apps/{app_id}")
def delete_app(app_id: int, user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)):
app = db.query(App).filter(App.id == app_id).first()
if app:
# Удаляем все APK файлы версий
versions = json.loads(app.versions) if app.versions else []
for version in versions:
apk_file = version.get("apk_file")
if apk_file and os.path.exists(f"static/apks/{apk_file}"):
os.remove(f"static/apks/{apk_file}")
# Удаляем иконку
if os.path.exists(f"static/icons/{app.icon}"):
os.remove(f"static/icons/{app.icon}")
# Удаляем связанные отзывы
db.query(Review).filter(Review.app_id == app_id).delete()
db.delete(app)
db.commit()
return {"message": "Приложение удалено"}
# НОВЫЙ ENDPOINT: Установка основной версии из списка версий
@app.post("/api/admin/apps/{app_id}/set-version") @app.post("/api/admin/apps/{app_id}/set-version")
def set_main_version( def set_main_version(
app_id: int, app_id: int,
@@ -563,7 +532,6 @@ def set_main_version(
if not app: if not app:
raise HTTPException(status_code=404, detail="App not found") raise HTTPException(status_code=404, detail="App not found")
# Ищем версию в списке версий
versions = json.loads(app.versions) if app.versions else [] versions = json.loads(app.versions) if app.versions else []
target_version = None target_version = None
@@ -575,7 +543,6 @@ def set_main_version(
if not target_version: if not target_version:
raise HTTPException(status_code=404, detail="Version not found in app versions") raise HTTPException(status_code=404, detail="Version not found in app versions")
# Обновляем основную версию и APK файл
app.version = version app.version = version
app.apk_file = target_version.get("apk_file") app.apk_file = target_version.get("apk_file")
app.api = target_version.get("api") app.api = target_version.get("api")
@@ -583,7 +550,25 @@ def set_main_version(
db.commit() db.commit()
return {"message": f"Основная версия установлена на {version}"} return {"message": f"Основная версия установлена на {version}"}
# ИСПРАВЛЕННЫЙ МЕТОД: Добавление отзывов с правильным подсчетом @app.delete("/api/admin/apps/{app_id}")
def delete_app(app_id: int, user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)):
app = db.query(App).filter(App.id == app_id).first()
if app:
versions = json.loads(app.versions) if app.versions else []
for version in versions:
apk_file = version.get("apk_file")
if apk_file and os.path.exists(f"static/apks/{apk_file}"):
os.remove(f"static/apks/{apk_file}")
if os.path.exists(f"static/icons/{app.icon}"):
os.remove(f"static/icons/{app.icon}")
db.query(Review).filter(Review.app_id == app_id).delete()
db.delete(app)
db.commit()
return {"message": "Приложение удалено"}
@app.post("/api/admin/reviews") @app.post("/api/admin/reviews")
def create_review( def create_review(
user: AdminUser = Depends(require_auth), user: AdminUser = Depends(require_auth),
@@ -597,23 +582,18 @@ def create_review(
try: try:
avatar_filename = "default_avatar.png" avatar_filename = "default_avatar.png"
# Обрабатываем аватар только если он действительно загружен
if avatar and avatar.filename and avatar.filename.strip() != "": if avatar and avatar.filename and avatar.filename.strip() != "":
# Проверяем, что это изображение
if not avatar.content_type or not avatar.content_type.startswith('image/'): if not avatar.content_type or not avatar.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="Файл должен быть изображением") raise HTTPException(status_code=400, detail="Файл должен быть изображением")
# Генерируем уникальное имя файла
file_extension = os.path.splitext(avatar.filename)[1] file_extension = os.path.splitext(avatar.filename)[1]
avatar_filename = f"avatar_{int(datetime.now().timestamp())}{file_extension}" avatar_filename = f"avatar_{int(datetime.now().timestamp())}{file_extension}"
avatar_path = f"static/icons/{avatar_filename}" avatar_path = f"static/icons/{avatar_filename}"
# Сохраняем аватар
contents = avatar.file.read() contents = avatar.file.read()
with open(avatar_path, "wb") as f: with open(avatar_path, "wb") as f:
f.write(contents) f.write(contents)
# Создаем отзыв
db_review = Review( db_review = Review(
app_id=app_id, app_id=app_id,
username=username, username=username,
@@ -626,10 +606,8 @@ def create_review(
db.add(db_review) db.add(db_review)
db.commit() db.commit()
# ИСПРАВЛЕНИЕ: Правильно подсчитываем количество отзывов для приложения
review_count = db.query(Review).filter(Review.app_id == app_id).count() review_count = db.query(Review).filter(Review.app_id == app_id).count()
# Обновляем счетчик отзывов у приложения
app = db.query(App).filter(App.id == app_id).first() app = db.query(App).filter(App.id == app_id).first()
if app: if app:
app.review_count = review_count app.review_count = review_count
@@ -651,10 +629,8 @@ def delete_review(review_id: int, user: AdminUser = Depends(require_auth), db: S
db.delete(review) db.delete(review)
db.commit() db.commit()
# ИСПРАВЛЕНИЕ: Правильно подсчитываем количество отзывов для приложения после удаления
review_count = db.query(Review).filter(Review.app_id == app_id).count() review_count = db.query(Review).filter(Review.app_id == app_id).count()
# Обновляем счетчик отзывов у приложения
app = db.query(App).filter(App.id == app_id).first() app = db.query(App).filter(App.id == app_id).first()
if app: if app:
app.review_count = review_count app.review_count = review_count
@@ -662,5 +638,132 @@ def delete_review(review_id: int, user: AdminUser = Depends(require_auth), db: S
return {"message": "Отзыв удален"} return {"message": "Отзыв удален"}
# API для главных изображений
@app.get("/api/admin/main-images")
def get_main_images(user: AdminUser = Depends(require_auth), db: Session = Depends(get_db)):
images = db.query(MainImage).all()
return images
@app.post("/api/admin/main-images")
def upload_main_image(
user: AdminUser = Depends(require_auth),
description: str = Form(""),
image: UploadFile = File(...),
db: Session = Depends(get_db)
):
if not image.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="Файл должен быть изображением")
file_extension = os.path.splitext(image.filename)[1]
filename = f"main_{int(datetime.now().timestamp())}{file_extension}"
image_path = f"static/main_images/{filename}"
contents = image.file.read()
with open(image_path, "wb") as f:
f.write(contents)
db_image = MainImage(
filename=filename,
description=description,
uploaded_by=user.username
)
db.add(db_image)
db.commit()
db.refresh(db_image)
return {"message": "Изображение загружено", "image_id": db_image.id}
@app.post("/api/admin/main-images/{image_id}/activate")
def activate_main_image(
image_id: int,
user: AdminUser = Depends(require_auth),
db: Session = Depends(get_db)
):
db.query(MainImage).update({MainImage.is_active: False})
image = db.query(MainImage).filter(MainImage.id == image_id).first()
if image:
image.is_active = True
db.commit()
return {"message": f"Изображение '{image.filename}' активировано"}
else:
raise HTTPException(status_code=404, detail="Изображение не найдено")
@app.delete("/api/admin/main-images/{image_id}")
def delete_main_image(
image_id: int,
user: AdminUser = Depends(require_auth),
db: Session = Depends(get_db)
):
image = db.query(MainImage).filter(MainImage.id == image_id).first()
if image:
if os.path.exists(f"static/main_images/{image.filename}"):
os.remove(f"static/main_images/{image.filename}")
if image.is_active:
other_image = db.query(MainImage).filter(MainImage.id != image_id).first()
if other_image:
other_image.is_active = True
db.delete(image)
db.commit()
return {"message": "Изображение удалено"}
else:
raise HTTPException(status_code=404, detail="Изображение не найдено")
# API для настроек дашборда
@app.post("/api/admin/dashboard/motd")
def update_motd(
user: AdminUser = Depends(require_auth),
motd: str = Form(...),
db: Session = Depends(get_db)
):
dashboard_settings = db.query(DashboardSettings).first()
if not dashboard_settings:
dashboard_settings = DashboardSettings()
db.add(dashboard_settings)
dashboard_settings.motd = motd
dashboard_settings.updated_at = datetime.utcnow()
db.commit()
return {"message": "MOTD обновлен"}
@app.post("/api/admin/dashboard/image")
def update_dashboard_image(
user: AdminUser = Depends(require_auth),
image: UploadFile = File(...),
db: Session = Depends(get_db)
):
if not image.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="Файл должен быть изображением")
file_extension = os.path.splitext(image.filename)[1]
image_filename = f"dashboard_{int(datetime.now().timestamp())}{file_extension}"
image_path = f"static/dashboard/{image_filename}"
contents = image.file.read()
with open(image_path, "wb") as f:
f.write(contents)
dashboard_settings = db.query(DashboardSettings).first()
if not dashboard_settings:
dashboard_settings = DashboardSettings()
db.add(dashboard_settings)
if (dashboard_settings.image_filename != "dashboard_default.jpg" and
os.path.exists(f"static/dashboard/{dashboard_settings.image_filename}")):
os.remove(f"static/dashboard/{dashboard_settings.image_filename}")
dashboard_settings.image_filename = image_filename
dashboard_settings.updated_at = datetime.utcnow()
db.commit()
return {"message": "Изображение дашборда обновлено"}
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=5000) if not os.path.exists("static/main_images/default_main.jpg"):
os.makedirs("static/main_images", exist_ok=True)
open("static/main_images/default_main.jpg", "wb").close()
uvicorn.run(app, host="0.0.0.0", port=5001)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

@@ -173,6 +173,7 @@
<li><a href="/admin">Дашборд</a></li> <li><a href="/admin">Дашборд</a></li>
<li><a href="/admin/apps">Управление приложениями</a></li> <li><a href="/admin/apps">Управление приложениями</a></li>
<li><a href="/admin/reviews">Управление отзывами</a></li> <li><a href="/admin/reviews">Управление отзывами</a></li>
<li><a href="/admin/main-images">Главные изображения</a></li> <!-- НОВАЯ ССЫЛКА -->
<li><a href="/admin/dashboard-settings">Настройки дашборда</a></li> <li><a href="/admin/dashboard-settings">Настройки дашборда</a></li>
<li><a href="/api/apps" target="_blank">API приложений</a></li> <li><a href="/api/apps" target="_blank">API приложений</a></li>
<li><a href="/admin/logout">Выйти</a></li> <li><a href="/admin/logout">Выйти</a></li>

315
templates/main_images.html Normal file
View File

@@ -0,0 +1,315 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Управление главными изображениями - OldMarket Admin</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background: #f5f5f5;
}
.sidebar {
width: 250px;
background: #2c3e50;
color: white;
height: 100vh;
position: fixed;
padding: 20px;
}
.sidebar h2 {
margin-bottom: 30px;
text-align: center;
}
.sidebar .user-info {
background: #34495e;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
text-align: center;
}
.sidebar ul {
list-style: none;
}
.sidebar li {
margin: 15px 0;
}
.sidebar a {
color: white;
text-decoration: none;
padding: 10px;
display: block;
border-radius: 5px;
transition: background 0.3s;
}
.sidebar a:hover {
background: #34495e;
}
.content {
margin-left: 250px;
padding: 40px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.btn {
background: #3498db;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.btn:hover {
background: #2980b9;
}
.btn-success {
background: #27ae60;
}
.btn-success:hover {
background: #219a52;
}
.btn-warning {
background: #f39c12;
}
.btn-warning:hover {
background: #e67e22;
}
.btn-danger {
background: #e74c3c;
}
.btn-danger:hover {
background: #c0392b;
}
.images-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.image-card {
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.image-card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.image-info {
padding: 15px;
}
.image-actions {
display: flex;
gap: 10px;
margin-top: 10px;
}
.image-actions .btn {
flex: 1;
padding: 8px;
font-size: 12px;
}
.active-badge {
background: #27ae60;
color: white;
padding: 5px 10px;
border-radius: 15px;
font-size: 12px;
font-weight: bold;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
}
.modal-content {
background: white;
margin: 50px auto;
padding: 30px;
width: 80%;
max-width: 500px;
border-radius: 10px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea, select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.preview-image {
max-width: 100%;
max-height: 300px;
border-radius: 8px;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="sidebar">
<h2>OldMarket Admin</h2>
<div class="user-info">
Вы вошли как: <strong>{{ username }}</strong>
</div>
<ul>
<li><a href="/admin">Дашборд</a></li>
<li><a href="/admin/apps">Управление приложениями</a></li>
<li><a href="/admin/reviews">Управление отзывами</a></li>
<li><a href="/admin/main-images">Главные изображения</a></li>
<li><a href="/admin/dashboard-settings">Настройки дашборда</a></li>
<li><a href="/api/apps" target="_blank">API приложений</a></li>
<li><a href="/admin/logout">Выйти</a></li>
</ul>
</div>
<div class="content">
<div class="header">
<h1>Управление главными изображениями</h1>
<button class="btn" onclick="showUploadForm()">Загрузить изображение</button>
</div>
<p>Эти изображения показываются на корневом пути <code>/</code> сервера</p>
<div class="images-grid">
{% for image in images %}
<div class="image-card">
<img src="/static/main_images/{{ image.filename }}" alt="{{ image.description }}" onerror="this.src='/static/main_images/default_main.jpg'">
<div class="image-info">
<h3>{{ image.description or 'Без описания' }}</h3>
<p><strong>Файл:</strong> {{ image.filename }}</p>
<p><strong>Загружено:</strong> {{ image.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</p>
<p><strong>Кем:</strong> {{ image.uploaded_by }}</p>
<div class="image-actions">
{% if image.is_active %}
<span class="active-badge">АКТИВНО</span>
{% else %}
<button class="btn btn-success" onclick="activateImage({{ image.id }})">Сделать активным</button>
{% endif %}
<button class="btn btn-danger" onclick="deleteImage({{ image.id }})">Удалить</button>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Модальное окно для загрузки изображения -->
<div id="uploadModal" class="modal">
<div class="modal-content">
<h2>Загрузить новое изображение</h2>
<form id="uploadForm" enctype="multipart/form-data">
<div class="form-group">
<label>Описание (необязательно):</label>
<input type="text" name="description" placeholder="Описание изображения">
</div>
<div class="form-group">
<label>Выберите изображение:</label>
<input type="file" name="image" accept="image/*" required onchange="previewImage(this)">
<img id="imagePreview" class="preview-image" style="display: none;">
</div>
<div class="form-group">
<button type="submit" class="btn">Загрузить</button>
<button type="button" class="btn" onclick="hideUploadForm()">Отмена</button>
</div>
</form>
</div>
</div>
<script>
function showUploadForm() {
document.getElementById('uploadModal').style.display = 'block';
}
function hideUploadForm() {
document.getElementById('uploadModal').style.display = 'none';
document.getElementById('imagePreview').style.display = 'none';
}
function previewImage(input) {
const preview = document.getElementById('imagePreview');
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.style.display = 'block';
}
reader.readAsDataURL(input.files[0]);
}
}
async function activateImage(imageId) {
if (confirm('Сделать это изображение активным? Оно будет показываться на корневом пути /')) {
try {
const response = await fetch(`/api/admin/main-images/${imageId}/activate`, {
method: 'POST'
});
const result = await response.json();
alert(result.message);
location.reload();
} catch (error) {
alert('Ошибка при активации: ' + error);
}
}
}
async function deleteImage(imageId) {
if (confirm('Вы уверены, что хотите удалить это изображение?')) {
try {
const response = await fetch(`/api/admin/main-images/${imageId}`, {
method: 'DELETE'
});
const result = await response.json();
alert(result.message);
location.reload();
} catch (error) {
alert('Ошибка при удалении: ' + error);
}
}
}
document.getElementById('uploadForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
try {
const response = await fetch('/api/admin/main-images', {
method: 'POST',
body: formData
});
const result = await response.json();
alert(result.message);
hideUploadForm();
location.reload();
} catch (error) {
alert('Ошибка при загрузке: ' + error);
}
});
</script>
</body>
</html>