Release 1.2: bulk upload, S3/SFTP/FTP, SMTP, password reset, user groups, git deploy

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-06 22:38:37 +03:00
parent db2cef41bb
commit c1aac7ecac
33 changed files with 1649 additions and 150 deletions
+96
View File
@@ -0,0 +1,96 @@
import os
import uuid
from datetime import datetime, timezone
from werkzeug.utils import secure_filename
from app import db
from app.models import Photo
from app.quota_utils import check_upload_quota
from app.settings_service import get_settings
from app.storage_service import save_photo_file
def allowed_file(filename, allowed_extensions):
return "." in filename and filename.rsplit(".", 1)[1].lower() in allowed_extensions
def collect_upload_files(request_files):
files = request_files.getlist("photos")
if not files or all(f.filename == "" for f in files):
single = request_files.get("photo")
if single and single.filename:
files = [single]
return [f for f in files if f and f.filename]
def process_uploads(request_files, user, folder, allowed_extensions):
settings = get_settings()
max_bulk = settings.max_bulk_upload or 100
files = collect_upload_files(request_files)
if not files:
return {"uploaded": 0, "errors": ["Файлы не выбраны"], "photos": []}
if len(files) > max_bulk:
return {
"uploaded": 0,
"errors": [f"Максимум {max_bulk} файлов за раз"],
"photos": [],
}
total_size = 0
valid_files = []
errors = []
for file in files:
if not allowed_file(file.filename, allowed_extensions):
errors.append(f"{file.filename}: недопустимый формат")
continue
file.seek(0, os.SEEK_END)
size = file.tell()
file.seek(0)
total_size += size
valid_files.append((file, size))
if not valid_files:
return {"uploaded": 0, "errors": errors, "photos": []}
ok, quota_msg = check_upload_quota(user, total_size)
if not ok:
return {"uploaded": 0, "errors": [quota_msg], "photos": []}
uploaded_photos = []
for file, _size in valid_files:
ext = file.filename.rsplit(".", 1)[1].lower()
stored_name = f"{uuid.uuid4().hex}.{ext}"
safe_original = secure_filename(file.filename) or f"photo.{ext}"
try:
_path, file_size, storage_backend, sync_errors = save_photo_file(file, stored_name)
for sync_err in sync_errors:
errors.append(f"{safe_original}: {sync_err}")
photo = Photo(
filename=stored_name,
original_name=safe_original,
file_size=file_size,
mime_type=file.content_type or f"image/{ext}",
user_id=user.id,
folder_id=folder.id if folder else None,
storage_backend=storage_backend,
created_at=datetime.now(timezone.utc),
)
db.session.add(photo)
uploaded_photos.append(photo)
except Exception as exc:
errors.append(f"{safe_original}: {exc}")
if uploaded_photos:
db.session.commit()
return {
"uploaded": len(uploaded_photos),
"errors": errors,
"photos": uploaded_photos,
}