284 lines
9.5 KiB
Python
284 lines
9.5 KiB
Python
# backend/routes/cases.py
|
|
import os
|
|
from flask import Blueprint, request, jsonify, current_app
|
|
from werkzeug.utils import secure_filename
|
|
from datetime import datetime
|
|
|
|
from ..db import get_db_connection, release_db_connection
|
|
from ..middleware import token_required
|
|
|
|
cases_blueprint = Blueprint('cases', __name__)
|
|
|
|
# Helper to serialize case rows
|
|
def serialize_case(row):
|
|
return {
|
|
"id": row[0],
|
|
"platform": row[1],
|
|
"violator_url": row[2],
|
|
"violator_article": row[3],
|
|
"my_article": row[4],
|
|
"comment": row[5],
|
|
"status": row[6],
|
|
"created_at": row[7].isoformat() if row[7] else None,
|
|
"updated_at": row[8].isoformat() if row[8] else None,
|
|
}
|
|
|
|
# ---------- CASE CRUD ----------
|
|
@cases_blueprint.route('/cases', methods=['GET'])
|
|
@token_required
|
|
def list_cases(user_info):
|
|
company_id = user_info['company_id']
|
|
# ... rest of logic using company_id instead of current_user_id ...
|
|
"""
|
|
Получить список всех кейсов пользователя
|
|
---
|
|
tags:
|
|
- Cases
|
|
security:
|
|
- APIKeyHeader: []
|
|
parameters:
|
|
- name: article
|
|
in: query
|
|
type: string
|
|
description: Фильтр по артикулу
|
|
- name: status
|
|
in: query
|
|
type: string
|
|
description: Фильтр по статусу
|
|
- name: platform
|
|
in: query
|
|
type: string
|
|
description: Фильтр по площадке
|
|
responses:
|
|
200:
|
|
description: Список кейсов
|
|
"""
|
|
# optional filters: ?article=...&status=...&platform=...
|
|
article = request.args.get('article')
|
|
status = request.args.get('status')
|
|
platform = request.args.get('platform')
|
|
conn = get_db_connection()
|
|
cur = conn.cursor()
|
|
query = "SELECT id, platform, violator_url, violator_article, my_article, comment, status, created_at, updated_at FROM cases"
|
|
params = [current_user_id]
|
|
conditions = ["user_id = %s"]
|
|
if article:
|
|
conditions.append("violator_article = %s")
|
|
params.append(article)
|
|
if status:
|
|
conditions.append("status = %s")
|
|
params.append(status)
|
|
if platform:
|
|
conditions.append("platform = %s")
|
|
params.append(platform)
|
|
if conditions:
|
|
query += " WHERE " + " AND ".join(conditions)
|
|
query += " ORDER BY id DESC"
|
|
cur.execute(query, params)
|
|
rows = cur.fetchall()
|
|
release_db_connection(conn)
|
|
return jsonify([serialize_case(r) for r in rows])
|
|
|
|
@cases_blueprint.route('/cases', methods=['POST'])
|
|
@token_required
|
|
def create_case(current_user_id):
|
|
"""
|
|
Создать новый кейс
|
|
---
|
|
tags:
|
|
- Cases
|
|
security:
|
|
- APIKeyHeader: []
|
|
parameters:
|
|
- name: body
|
|
in: body
|
|
required: true
|
|
schema:
|
|
type: object
|
|
required: [platform, violator_url, violator_article]
|
|
properties:
|
|
platform:
|
|
type: string
|
|
violator_url:
|
|
type: string
|
|
violator_article:
|
|
type: string
|
|
my_article:
|
|
type: string
|
|
comment:
|
|
type: string
|
|
responses:
|
|
201:
|
|
description: Кейс создан
|
|
"""
|
|
data = request.json
|
|
required = ["platform", "violator_url", "violator_article"]
|
|
for f in required:
|
|
if f not in data:
|
|
return jsonify({"error": f"Missing field {f}"}), 400
|
|
# --- PLAN LIMIT CHECK ---
|
|
conn = get_db_connection()
|
|
cur = conn.cursor()
|
|
|
|
cur.execute("SELECT plan FROM users WHERE id = %s", (current_user_id,))
|
|
user_plan = cur.fetchone()[0]
|
|
|
|
if user_plan == 'free':
|
|
cur.execute("SELECT COUNT(*) FROM cases WHERE user_id = %s", (current_user_id,))
|
|
count = cur.fetchone()[0]
|
|
if count >= 5:
|
|
release_db_connection(conn)
|
|
return jsonify({"error": "Лимит кейсов для Free тарифа (5 шт) исчерпан. Перейдите на PRO."}), 403
|
|
# ------------------------
|
|
|
|
cur.execute(
|
|
"""INSERT INTO cases (user_id, platform, violator_url, violator_article, my_article, comment, status)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id""",
|
|
(
|
|
current_user_id,
|
|
data.get('platform'),
|
|
data.get('violator_url'),
|
|
data.get('violator_article'),
|
|
data.get('my_article'),
|
|
data.get('comment'),
|
|
data.get('status', 'new')
|
|
)
|
|
)
|
|
case_id = cur.fetchone()[0]
|
|
conn.commit()
|
|
release_db_connection(conn)
|
|
|
|
# --- TELEGRAM NOTIFICATION ---
|
|
from ..utils.telegram_utils import send_telegram_notification
|
|
msg = (
|
|
f"🚀 <b>Новый кейс #{case_id}</b>\n"
|
|
f"Площадка: {data.get('platform').upper()}\n"
|
|
f"Артикул: {data.get('violator_article')}\n"
|
|
f"Статус: NEW"
|
|
)
|
|
send_telegram_notification(msg)
|
|
# -----------------------------
|
|
|
|
# --- AUDIT LOG ---
|
|
from ..utils.audit_utils import log_action
|
|
log_action(current_user_id, 'CREATE_CASE', {
|
|
'case_id': case_id,
|
|
'platform': data.get('platform'),
|
|
'article': data.get('violator_article')
|
|
})
|
|
# -----------------
|
|
|
|
# --- WEBSOCKET NOTIFICATION ---
|
|
try:
|
|
from ..app import socketio
|
|
socketio.emit('case_created', {
|
|
'id': case_id,
|
|
'platform': data.get('platform'),
|
|
'article': data.get('violator_article'),
|
|
'message': f"Новый кейс на {data.get('platform').upper()} создан!"
|
|
})
|
|
except Exception as e:
|
|
print(f"WS Emit Error: {e}")
|
|
# ------------------------------
|
|
|
|
return jsonify({"id": case_id}), 201
|
|
|
|
@cases_blueprint.route('/cases/<int:case_id>', methods=['GET'])
|
|
@token_required
|
|
def get_case(current_user_id, case_id):
|
|
conn = get_db_connection()
|
|
cur = conn.cursor()
|
|
cur.execute("SELECT id, platform, violator_url, violator_article, my_article, comment, status, created_at, updated_at FROM cases WHERE id = %s AND user_id = %s", (case_id, current_user_id))
|
|
row = cur.fetchone()
|
|
release_db_connection(conn)
|
|
if not row:
|
|
return jsonify({"error": "Case not found"}), 404
|
|
return jsonify(serialize_case(row))
|
|
|
|
@cases_blueprint.route('/cases/<int:case_id>', methods=['PUT'])
|
|
@token_required
|
|
def update_case(current_user_id, case_id):
|
|
data = request.json
|
|
fields = []
|
|
params = []
|
|
allowed = ["platform", "violator_url", "violator_article", "my_article", "comment", "status"]
|
|
for f in allowed:
|
|
if f in data:
|
|
fields.append(f"{f} = %s")
|
|
params.append(data[f])
|
|
if not fields:
|
|
return jsonify({"error": "No updatable fields provided"}), 400
|
|
params.extend([case_id, current_user_id])
|
|
conn = get_db_connection()
|
|
cur = conn.cursor()
|
|
cur.execute(f"UPDATE cases SET {', '.join(fields)}, updated_at = %s WHERE id = %s AND user_id = %s",
|
|
(*params[:-2], datetime.utcnow(), case_id, current_user_id))
|
|
conn.commit()
|
|
release_db_connection(conn)
|
|
return jsonify({"message": "Case updated"})
|
|
|
|
@cases_blueprint.route('/cases/<int:case_id>', methods=['DELETE'])
|
|
@token_required
|
|
def delete_case(current_user_id, case_id):
|
|
conn = get_db_connection()
|
|
cur = conn.cursor()
|
|
cur.execute("DELETE FROM cases WHERE id = %s AND user_id = %s", (case_id, current_user_id))
|
|
conn.commit()
|
|
release_db_connection(conn)
|
|
|
|
# --- AUDIT LOG ---
|
|
from ..utils.audit_utils import log_action
|
|
log_action(current_user_id, 'DELETE_CASE', {'case_id': case_id})
|
|
# -----------------
|
|
|
|
return jsonify({"message": "Case deleted"})
|
|
|
|
# ---------- FILE UPLOAD ----------
|
|
@cases_blueprint.route('/cases/<int:case_id>/files', methods=['POST'])
|
|
@token_required
|
|
def upload_files(current_user_id, case_id):
|
|
if 'files' not in request.files:
|
|
return jsonify({"error": "No files part in request"}), 400
|
|
files = request.files.getlist('files')
|
|
saved_paths = []
|
|
upload_folder = current_app.config['UPLOAD_FOLDER']
|
|
conn = get_db_connection()
|
|
cur = conn.cursor()
|
|
|
|
# Check ownership
|
|
cur.execute("SELECT id FROM cases WHERE id = %s AND user_id = %s", (case_id, current_user_id))
|
|
if not cur.fetchone():
|
|
release_db_connection(conn)
|
|
return jsonify({"error": "Case not found"}), 404
|
|
|
|
for f in files:
|
|
if f.filename == '':
|
|
continue
|
|
filename = secure_filename(f.filename)
|
|
timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')
|
|
unique_name = f"{timestamp}_{filename}"
|
|
file_path = os.path.join(upload_folder, unique_name)
|
|
f.save(file_path)
|
|
# store relative path in DB
|
|
rel_path = os.path.relpath(file_path, start=os.path.dirname(os.path.abspath(__file__)))
|
|
cur.execute(
|
|
"INSERT INTO files (case_id, file_path) VALUES (%s, %s) RETURNING id",
|
|
(case_id, rel_path)
|
|
)
|
|
saved_paths.append(rel_path)
|
|
conn.commit()
|
|
release_db_connection(conn)
|
|
return jsonify({"files": saved_paths}), 201
|
|
|
|
# Get files for a case
|
|
@cases_blueprint.route('/cases/<int:case_id>/files', methods=['GET'])
|
|
@token_required
|
|
def list_files(current_user_id, case_id):
|
|
conn = get_db_connection()
|
|
cur = conn.cursor()
|
|
cur.execute("SELECT f.id, f.file_path, f.uploaded_at FROM files f JOIN cases c ON f.case_id = c.id WHERE f.case_id = %s AND c.user_id = %s", (case_id, current_user_id))
|
|
rows = cur.fetchall()
|
|
release_db_connection(conn)
|
|
files = [{"id": r[0], "path": r[1], "uploaded_at": r[2].isoformat()} for r in rows]
|
|
return jsonify(files)
|