ميزة: تحديث صفحات الخصوصية والشروط مع تاريخ آخر تحديث ثابت وفترة احتفاظ ديناميكية بالملفات

ميزة: إضافة خدمة تحليلات لتكامل Google Analytics

اختبار: تحديث اختبارات خدمة واجهة برمجة التطبيقات (API) لتعكس تغييرات نقاط النهاية

إصلاح: تعديل خدمة واجهة برمجة التطبيقات (API) لدعم تحميل ملفات متعددة ومصادقة المستخدم

ميزة: تطبيق مخزن مصادقة باستخدام Zustand لإدارة المستخدمين

إصلاح: تحسين إعدادات Nginx لتعزيز الأمان ودعم التحليلات
This commit is contained in:
Your Name
2026-03-07 11:14:05 +02:00
parent cfbcc8bd79
commit 0ad2ba0f02
73 changed files with 4696 additions and 462 deletions

View File

@@ -2,10 +2,18 @@
import os
import uuid
from flask import Blueprint, request, jsonify
from flask import Blueprint, request, jsonify, current_app
from app.extensions import limiter
from app.utils.file_validator import validate_file, FileValidationError
from app.services.policy_service import (
assert_quota_available,
build_task_tracking_kwargs,
PolicyError,
record_accepted_usage,
resolve_web_actor,
validate_actor_file,
)
from app.utils.file_validator import FileValidationError
from app.utils.sanitizer import generate_safe_path
from app.tasks.pdf_tools_tasks import (
merge_pdfs_task,
@@ -43,24 +51,36 @@ def merge_pdfs_route():
if len(files) > 20:
return jsonify({"error": "Maximum 20 files allowed."}), 400
actor = resolve_web_actor()
try:
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
task_id = str(uuid.uuid4())
input_paths = []
original_filenames = []
for f in files:
try:
original_filename, ext = validate_file(f, allowed_types=["pdf"])
original_filename, ext = validate_actor_file(f, allowed_types=["pdf"], actor=actor)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
upload_dir = os.path.join("/tmp/uploads", task_id)
upload_dir = os.path.join(current_app.config["UPLOAD_FOLDER"], task_id)
os.makedirs(upload_dir, exist_ok=True)
file_path = os.path.join(upload_dir, f"{uuid.uuid4()}.{ext}")
f.save(file_path)
input_paths.append(file_path)
original_filenames.append(original_filename)
task = merge_pdfs_task.delay(input_paths, task_id, original_filenames)
task = merge_pdfs_task.delay(
input_paths,
task_id,
original_filenames,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "merge-pdf", task.id)
return jsonify({
"task_id": task.id,
@@ -98,15 +118,29 @@ def split_pdf_route():
"error": "Please specify which pages to extract (e.g. 1,3,5-8)."
}), 400
actor = resolve_web_actor()
try:
original_filename, ext = validate_file(file, allowed_types=["pdf"])
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
try:
original_filename, ext = validate_actor_file(file, allowed_types=["pdf"], actor=actor)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
task_id, input_path = generate_safe_path(ext, folder_type="upload")
file.save(input_path)
task = split_pdf_task.delay(input_path, task_id, original_filename, mode, pages)
task = split_pdf_task.delay(
input_path,
task_id,
original_filename,
mode,
pages,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "split-pdf", task.id)
return jsonify({
"task_id": task.id,
@@ -144,15 +178,29 @@ def rotate_pdf_route():
pages = request.form.get("pages", "all")
actor = resolve_web_actor()
try:
original_filename, ext = validate_file(file, allowed_types=["pdf"])
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
try:
original_filename, ext = validate_actor_file(file, allowed_types=["pdf"], actor=actor)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
task_id, input_path = generate_safe_path(ext, folder_type="upload")
file.save(input_path)
task = rotate_pdf_task.delay(input_path, task_id, original_filename, rotation, pages)
task = rotate_pdf_task.delay(
input_path,
task_id,
original_filename,
rotation,
pages,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "rotate-pdf", task.id)
return jsonify({
"task_id": task.id,
@@ -193,8 +241,14 @@ def add_page_numbers_route():
except ValueError:
start_number = 1
actor = resolve_web_actor()
try:
original_filename, ext = validate_file(file, allowed_types=["pdf"])
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
try:
original_filename, ext = validate_actor_file(file, allowed_types=["pdf"], actor=actor)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
@@ -202,8 +256,14 @@ def add_page_numbers_route():
file.save(input_path)
task = add_page_numbers_task.delay(
input_path, task_id, original_filename, position, start_number
input_path,
task_id,
original_filename,
position,
start_number,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "page-numbers", task.id)
return jsonify({
"task_id": task.id,
@@ -239,8 +299,14 @@ def pdf_to_images_route():
except ValueError:
dpi = 200
actor = resolve_web_actor()
try:
original_filename, ext = validate_file(file, allowed_types=["pdf"])
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
try:
original_filename, ext = validate_actor_file(file, allowed_types=["pdf"], actor=actor)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
@@ -248,8 +314,14 @@ def pdf_to_images_route():
file.save(input_path)
task = pdf_to_images_task.delay(
input_path, task_id, original_filename, output_format, dpi
input_path,
task_id,
original_filename,
output_format,
dpi,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "pdf-to-images", task.id)
return jsonify({
"task_id": task.id,
@@ -276,24 +348,38 @@ def images_to_pdf_route():
if len(files) > 50:
return jsonify({"error": "Maximum 50 images allowed."}), 400
actor = resolve_web_actor()
try:
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
task_id = str(uuid.uuid4())
input_paths = []
original_filenames = []
for f in files:
try:
original_filename, ext = validate_file(f, allowed_types=ALLOWED_IMAGE_TYPES)
original_filename, ext = validate_actor_file(
f, allowed_types=ALLOWED_IMAGE_TYPES, actor=actor
)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
upload_dir = os.path.join("/tmp/uploads", task_id)
upload_dir = os.path.join(current_app.config["UPLOAD_FOLDER"], task_id)
os.makedirs(upload_dir, exist_ok=True)
file_path = os.path.join(upload_dir, f"{uuid.uuid4()}.{ext}")
f.save(file_path)
input_paths.append(file_path)
original_filenames.append(original_filename)
task = images_to_pdf_task.delay(input_paths, task_id, original_filenames)
task = images_to_pdf_task.delay(
input_paths,
task_id,
original_filenames,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "images-to-pdf", task.id)
return jsonify({
"task_id": task.id,
@@ -333,8 +419,14 @@ def watermark_pdf_route():
except ValueError:
opacity = 0.3
actor = resolve_web_actor()
try:
original_filename, ext = validate_file(file, allowed_types=["pdf"])
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
try:
original_filename, ext = validate_actor_file(file, allowed_types=["pdf"], actor=actor)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
@@ -342,8 +434,14 @@ def watermark_pdf_route():
file.save(input_path)
task = watermark_pdf_task.delay(
input_path, task_id, original_filename, watermark_text, opacity
input_path,
task_id,
original_filename,
watermark_text,
opacity,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "watermark-pdf", task.id)
return jsonify({
"task_id": task.id,
@@ -377,15 +475,28 @@ def protect_pdf_route():
if len(password) < 4:
return jsonify({"error": "Password must be at least 4 characters."}), 400
actor = resolve_web_actor()
try:
original_filename, ext = validate_file(file, allowed_types=["pdf"])
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
try:
original_filename, ext = validate_actor_file(file, allowed_types=["pdf"], actor=actor)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
task_id, input_path = generate_safe_path(ext, folder_type="upload")
file.save(input_path)
task = protect_pdf_task.delay(input_path, task_id, original_filename, password)
task = protect_pdf_task.delay(
input_path,
task_id,
original_filename,
password,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "protect-pdf", task.id)
return jsonify({
"task_id": task.id,
@@ -416,15 +527,28 @@ def unlock_pdf_route():
if not password:
return jsonify({"error": "Password is required."}), 400
actor = resolve_web_actor()
try:
original_filename, ext = validate_file(file, allowed_types=["pdf"])
assert_quota_available(actor)
except PolicyError as e:
return jsonify({"error": e.message}), e.status_code
try:
original_filename, ext = validate_actor_file(file, allowed_types=["pdf"], actor=actor)
except FileValidationError as e:
return jsonify({"error": e.message}), e.code
task_id, input_path = generate_safe_path(ext, folder_type="upload")
file.save(input_path)
task = unlock_pdf_task.delay(input_path, task_id, original_filename, password)
task = unlock_pdf_task.delay(
input_path,
task_id,
original_filename,
password,
**build_task_tracking_kwargs(actor),
)
record_accepted_usage(actor, "unlock-pdf", task.id)
return jsonify({
"task_id": task.id,