c1aac7ecac
Co-authored-by: Cursor <cursoragent@cursor.com>
191 lines
5.9 KiB
Python
191 lines
5.9 KiB
Python
import os
|
|
|
|
from flask import (
|
|
Blueprint,
|
|
abort,
|
|
current_app,
|
|
flash,
|
|
jsonify,
|
|
redirect,
|
|
render_template,
|
|
request,
|
|
send_file,
|
|
url_for,
|
|
)
|
|
from flask_login import current_user, login_required
|
|
|
|
from app import db
|
|
from app.auth_utils import photo_owner_or_admin
|
|
from app.folder_utils import can_edit_folder
|
|
from app.models import Folder, Photo
|
|
from app.settings_service import get_settings
|
|
from app.storage_service import delete_photo_file, get_photo_stream
|
|
from app.upload_service import process_uploads
|
|
|
|
bp = Blueprint("main", __name__)
|
|
|
|
|
|
@bp.route("/")
|
|
def index():
|
|
photos = Photo.query.order_by(Photo.created_at.desc()).limit(24).all()
|
|
total_photos = Photo.query.count()
|
|
total_size = db.session.query(db.func.coalesce(db.func.sum(Photo.file_size), 0)).scalar() or 0
|
|
settings = get_settings()
|
|
return render_template(
|
|
"index.html",
|
|
photos=photos,
|
|
total_photos=total_photos,
|
|
total_size=int(total_size),
|
|
max_upload_mb=current_app.config["MAX_CONTENT_LENGTH"] // (1024 * 1024),
|
|
max_bulk_upload=settings.max_bulk_upload,
|
|
)
|
|
|
|
|
|
@bp.route("/upload", methods=["POST"])
|
|
@login_required
|
|
def upload():
|
|
folder_id = request.form.get("folder_id", type=int)
|
|
folder = None
|
|
if folder_id:
|
|
folder = Folder.query.get_or_404(folder_id)
|
|
if not can_edit_folder(folder):
|
|
abort(403)
|
|
|
|
result = process_uploads(
|
|
request.files,
|
|
current_user,
|
|
folder,
|
|
current_app.config["ALLOWED_EXTENSIONS"],
|
|
)
|
|
|
|
if result["uploaded"] == 0 and result["errors"]:
|
|
flash(result["errors"][0], "error")
|
|
elif result["uploaded"] == 1:
|
|
flash("Фото успешно загружено", "success")
|
|
elif result["uploaded"] > 1:
|
|
flash(f"Загружено {result['uploaded']} фото", "success")
|
|
|
|
for err in result["errors"]:
|
|
if result["uploaded"] > 0:
|
|
flash(err, "error")
|
|
|
|
if result["uploaded"] > 0:
|
|
from app.email_service import send_upload_notification
|
|
|
|
send_upload_notification(
|
|
current_user,
|
|
result["uploaded"],
|
|
folder.name if folder else None,
|
|
)
|
|
|
|
if folder:
|
|
return redirect(url_for("folders.view_folder", folder_id=folder.id))
|
|
return redirect(request.referrer or url_for("cabinet.index"))
|
|
|
|
|
|
@bp.route("/api/photos")
|
|
def api_photos():
|
|
photos = Photo.query.order_by(Photo.created_at.desc()).all()
|
|
return jsonify(
|
|
[
|
|
{
|
|
"id": p.id,
|
|
"url": p.url,
|
|
"original_name": p.original_name,
|
|
"file_size": p.file_size,
|
|
"size_human": p.size_human,
|
|
"user_id": p.user_id,
|
|
"created_at": p.created_at.isoformat(),
|
|
}
|
|
for p in photos
|
|
]
|
|
)
|
|
|
|
|
|
@bp.route("/uploads/<path:filename>")
|
|
def uploaded_file(filename):
|
|
photo = Photo.query.filter_by(filename=filename).first()
|
|
storage_backend = photo.storage_backend if photo else "local"
|
|
|
|
stream = get_photo_stream(filename, storage_backend)
|
|
if stream is None:
|
|
abort(404)
|
|
|
|
mimetype = photo.mime_type if photo else "application/octet-stream"
|
|
return send_file(stream, mimetype=mimetype)
|
|
|
|
|
|
@bp.route("/delete/<int:photo_id>", methods=["POST"])
|
|
@login_required
|
|
def delete_photo(photo_id):
|
|
photo = Photo.query.get_or_404(photo_id)
|
|
photo_owner_or_admin(photo)
|
|
|
|
delete_photo_file(photo.filename, photo.storage_backend)
|
|
db.session.delete(photo)
|
|
db.session.commit()
|
|
flash("Фото удалено", "success")
|
|
return redirect(request.referrer or url_for("main.index"))
|
|
|
|
|
|
cabinet_bp = Blueprint("cabinet", __name__, url_prefix="/cabinet")
|
|
|
|
|
|
@cabinet_bp.route("/")
|
|
@login_required
|
|
def index():
|
|
from app.folder_utils import process_pending_invites
|
|
from app.quota_utils import quota_status
|
|
|
|
process_pending_invites(current_user)
|
|
photos = (
|
|
Photo.query.filter_by(user_id=current_user.id, folder_id=None)
|
|
.order_by(Photo.created_at.desc())
|
|
.all()
|
|
)
|
|
folders = Folder.query.filter_by(owner_id=current_user.id).order_by(Folder.created_at.desc()).limit(6).all()
|
|
total_size = sum(p.file_size for p in photos)
|
|
quota = quota_status(current_user)
|
|
settings = get_settings()
|
|
return render_template(
|
|
"cabinet/index.html",
|
|
photos=photos,
|
|
folders=folders,
|
|
total_photos=len(photos),
|
|
total_size=total_size,
|
|
quota=quota,
|
|
max_upload_mb=current_app.config["MAX_CONTENT_LENGTH"] // (1024 * 1024),
|
|
max_bulk_upload=settings.max_bulk_upload,
|
|
)
|
|
|
|
|
|
@cabinet_bp.route("/profile", methods=["GET", "POST"])
|
|
@login_required
|
|
def profile():
|
|
from app.models import User
|
|
|
|
if request.method == "POST":
|
|
email = request.form.get("email", "").strip().lower()
|
|
current_password = request.form.get("current_password", "")
|
|
new_password = request.form.get("new_password", "")
|
|
new_password2 = request.form.get("new_password2", "")
|
|
|
|
other = User.query.filter(User.email == email, User.id != current_user.id).first()
|
|
if other:
|
|
flash("Этот email уже используется", "error")
|
|
elif not current_user.check_password(current_password):
|
|
flash("Неверный текущий пароль", "error")
|
|
elif new_password and len(new_password) < 6:
|
|
flash("Новый пароль — минимум 6 символов", "error")
|
|
elif new_password and new_password != new_password2:
|
|
flash("Новые пароли не совпадают", "error")
|
|
else:
|
|
current_user.email = email
|
|
if new_password:
|
|
current_user.set_password(new_password)
|
|
db.session.commit()
|
|
flash("Профиль обновлён", "success")
|
|
return redirect(url_for("cabinet.profile"))
|
|
|
|
return render_template("cabinet/profile.html")
|