437 lines
17 KiB
Python
437 lines
17 KiB
Python
import os
|
|
import shutil
|
|
from flask import Blueprint, request, jsonify, send_from_directory
|
|
from flask_jwt_extended import jwt_required, get_jwt_identity
|
|
from models.documents.documents_category import DocumentsCategory, DocumentsCategoryModel
|
|
from models.documents.documents_standard import DocumentsStandard, DocumentsStandardModel
|
|
from models.documents.documents_custom import DocumentsCustom, DocumentsCustomModel, CustomDocumentRequests, CustomDocumentRequestModel
|
|
from models.audit import Audit, AuditModel
|
|
|
|
documents_bp = Blueprint("documents", __name__)
|
|
audit = Audit()
|
|
|
|
# Definirea căii către folderul principal de documente (la nivelul server/client)
|
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
DOCUMENTS_ROOT = os.path.join(BASE_DIR, "documents")
|
|
os.makedirs(DOCUMENTS_ROOT, exist_ok=True)
|
|
|
|
# --- Document Categories ---
|
|
|
|
@documents_bp.route("/categories/add", methods=["POST"])
|
|
@jwt_required()
|
|
def add_category():
|
|
user_id = get_jwt_identity()
|
|
data = request.get_json()
|
|
name = data.get("name")
|
|
access = data.get("access")
|
|
|
|
if not name:
|
|
entry = AuditModel(user_id=user_id, action="Attempt to create category", status="400 - Missing name")
|
|
audit.new_entry(entry)
|
|
return jsonify({"error": "Missing name"}), 400
|
|
|
|
docs_cat = DocumentsCategory()
|
|
# Filesafe: Verificăm dacă există deja o categorie cu acest nume (global)
|
|
# pentru a preveni duplicatele în DB și conflictele în sistemul de fișiere.
|
|
existing_categories = docs_cat.get_all_entries()
|
|
for cat in existing_categories:
|
|
if cat.name and cat.name.lower() == name.lower():
|
|
return jsonify({"message": "Category already exists", "id": cat.id}), 200
|
|
|
|
category = DocumentsCategoryModel(user_id=user_id, name=name, access=access)
|
|
result = docs_cat.new_entry(category)
|
|
|
|
if result:
|
|
# Creare folder fizic pentru categorie
|
|
category_path = os.path.join(DOCUMENTS_ROOT, name)
|
|
os.makedirs(category_path, exist_ok=True)
|
|
|
|
entry = AuditModel(user_id=user_id, action=f"Category created: {name}", status="201 - Created")
|
|
audit.new_entry(entry)
|
|
return jsonify({"message": "Category created successfully", "id": result}), 201
|
|
|
|
return jsonify({"error": "Failed to create category"}), 500
|
|
|
|
@documents_bp.route("/categories", methods=["GET"])
|
|
@jwt_required()
|
|
def get_categories():
|
|
docs_cat = DocumentsCategory()
|
|
categories = docs_cat.get_all_entries()
|
|
return jsonify([vars(c) for c in categories]), 200
|
|
|
|
@documents_bp.route("/categories/update/<int:id>", methods=["PUT"])
|
|
@jwt_required()
|
|
def update_category(id):
|
|
user_id = get_jwt_identity()
|
|
data = request.get_json()
|
|
name = data.get("name")
|
|
access = data.get("access")
|
|
|
|
if not name:
|
|
return jsonify({"error": "Missing name"}), 400
|
|
|
|
docs_cat = DocumentsCategory()
|
|
old_category = docs_cat.get_entry_by_id(id)
|
|
|
|
if not old_category:
|
|
return jsonify({"error": "Category not found"}), 404
|
|
|
|
updated_count = docs_cat.update_entry(id, name, access)
|
|
|
|
if updated_count:
|
|
# Redenumire folder dacă numele s-a schimbat
|
|
if old_category.name != name:
|
|
old_path = os.path.join(DOCUMENTS_ROOT, old_category.name)
|
|
new_path = os.path.join(DOCUMENTS_ROOT, name)
|
|
if os.path.exists(old_path):
|
|
os.rename(old_path, new_path)
|
|
else:
|
|
os.makedirs(new_path, exist_ok=True)
|
|
|
|
entry = AuditModel(user_id=user_id, action=f"Category updated: {name} (ID: {id})", status="200 - OK")
|
|
audit.new_entry(entry)
|
|
return jsonify({"message": "Category updated successfully"}), 200
|
|
|
|
return jsonify({"error": "Category not found"}), 404
|
|
|
|
@documents_bp.route("/categories/delete/<int:id>", methods=["DELETE"])
|
|
@jwt_required()
|
|
def delete_category(id):
|
|
user_id = get_jwt_identity()
|
|
docs_cat = DocumentsCategory()
|
|
|
|
# Preluăm categoria înainte de ștergere pentru a cunoaște numele folderului
|
|
category = docs_cat.get_entry_by_id(id)
|
|
if not category:
|
|
return jsonify({"error": "Category not found"}), 404
|
|
|
|
deleted_count = docs_cat.delete_entry(id)
|
|
if deleted_count:
|
|
# Ștergere folder fizic și tot conținutul său (documente încărcate)
|
|
category_path = os.path.join(DOCUMENTS_ROOT, category.name)
|
|
if os.path.exists(category_path):
|
|
shutil.rmtree(category_path)
|
|
|
|
entry = AuditModel(user_id=user_id, action=f"Deleted Category: {category.name} (ID: {id})", status="200 - OK")
|
|
audit.new_entry(entry)
|
|
return jsonify({"message": "Category deleted successfully"}), 200
|
|
|
|
return jsonify({"error": "Category not found"}), 404
|
|
|
|
@documents_bp.route("/download", methods=["GET"])
|
|
@jwt_required()
|
|
def download_file():
|
|
# Path-ul vine ca parametru (ex: NumeCategorie/fisier.pdf)
|
|
file_path = request.args.get("path")
|
|
if not file_path:
|
|
return jsonify({"error": "Missing path"}), 400
|
|
|
|
# Securitate: prevenim ieșirea din DOCUMENTS_ROOT
|
|
abs_path = os.path.abspath(os.path.join(DOCUMENTS_ROOT, file_path))
|
|
if not abs_path.startswith(os.path.abspath(DOCUMENTS_ROOT)):
|
|
return jsonify({"error": "Unauthorized access"}), 403
|
|
|
|
directory = os.path.dirname(abs_path)
|
|
filename = os.path.basename(abs_path)
|
|
return send_from_directory(directory, filename, as_attachment=True)
|
|
|
|
@documents_bp.route("/categories/refresh/<int:category_id>", methods=["POST"])
|
|
@jwt_required()
|
|
def refresh_category_files(category_id):
|
|
user_id = get_jwt_identity()
|
|
docs_cat = DocumentsCategory()
|
|
docs_std = DocumentsStandard()
|
|
|
|
category = docs_cat.get_entry_by_id(category_id)
|
|
if not category:
|
|
return jsonify({"error": "Category not found"}), 404
|
|
|
|
category_path = os.path.join(DOCUMENTS_ROOT, category.name)
|
|
if not os.path.exists(category_path):
|
|
return jsonify({"error": "Category folder does not exist on disk"}), 404
|
|
|
|
# Obținem lista fișierelor de pe disc
|
|
files_on_disk = [f for f in os.listdir(category_path) if os.path.isfile(os.path.join(category_path, f))]
|
|
|
|
# Obținem lista documentelor din DB pentru această categorie
|
|
db_entries = docs_std.get_entries_by_category(category_id)
|
|
db_file_paths = {entry.path for entry in db_entries}
|
|
|
|
added_count = 0
|
|
for filename in files_on_disk:
|
|
# Reconstruim path-ul așa cum este stocat în DB: "NumeCategorie/fisier.ext"
|
|
relative_path = f"{category.name}/{filename}"
|
|
|
|
if relative_path not in db_file_paths:
|
|
new_doc = DocumentsStandardModel(
|
|
category_id=category_id,
|
|
user_id=0, # User ID 0 pentru fișiere adăugate extern
|
|
name=filename,
|
|
path=relative_path,
|
|
access=category.access
|
|
)
|
|
if docs_std.new_entry(new_doc):
|
|
added_count += 1
|
|
|
|
if added_count > 0:
|
|
audit.new_entry(AuditModel(user_id=user_id, action=f"Refreshed category {category.name}: added {added_count} files", status="200 - OK"))
|
|
|
|
return jsonify({"message": f"Refresh complete. Added {added_count} new documents.", "added": added_count}), 200
|
|
|
|
# --- Document Standards Category ---
|
|
@documents_bp.route("/standards/category/<int:category_id>", methods=["GET"])
|
|
@jwt_required()
|
|
def get_standards_by_category(category_id):
|
|
docs_std = DocumentsStandard()
|
|
items = docs_std.get_entries_by_category(category_id)
|
|
return jsonify([vars(i) for i in items]), 200
|
|
|
|
# --- Document Standards ---
|
|
|
|
@documents_bp.route("/standards/add", methods=["POST"])
|
|
@jwt_required()
|
|
def add_standard():
|
|
user_id = get_jwt_identity()
|
|
data = request.get_json()
|
|
name = data.get("name")
|
|
path = data.get("path")
|
|
access = data.get("access")
|
|
category_id = data.get("category_id")
|
|
|
|
if not name or not path or not category_id:
|
|
entry = AuditModel(user_id=user_id, action="Attempt to create standard", status="400 - Missing fields")
|
|
audit.new_entry(entry)
|
|
return jsonify({"error": "Missing name, path or category_id"}), 400
|
|
|
|
# Mutare fizică a fișierului din folderul de upload în folderul categoriei
|
|
# Sursa este folderul unde Flet salvează implicit upload-urile
|
|
source_path = os.path.join(BASE_DIR, "client", "assets", "uploads", os.path.basename(path))
|
|
dest_path = os.path.join(DOCUMENTS_ROOT, path)
|
|
print(source_path)
|
|
print(dest_path)
|
|
|
|
if os.path.exists(source_path):
|
|
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
|
shutil.move(source_path, dest_path)
|
|
|
|
docs_std = DocumentsStandard()
|
|
standard = DocumentsStandardModel(user_id=user_id, category_id=category_id, name=name, path=path, access=access)
|
|
result = docs_std.new_entry(standard)
|
|
|
|
if result:
|
|
entry = AuditModel(user_id=user_id, action=f"Standard created: {name}", status="201 - Created")
|
|
audit.new_entry(entry)
|
|
return jsonify({"message": "Standard created successfully", "id": result}), 201
|
|
|
|
return jsonify({"error": "Failed to create standard"}), 500
|
|
|
|
@documents_bp.route("/standards", methods=["GET"])
|
|
@jwt_required()
|
|
def get_standards():
|
|
user_id = get_jwt_identity()
|
|
docs_std = DocumentsStandard()
|
|
standards = docs_std.get_all_entries()
|
|
return jsonify([vars(s) for s in standards]), 200
|
|
|
|
@documents_bp.route("/standards/delete/<int:id>", methods=["DELETE"])
|
|
@jwt_required()
|
|
def delete_standard(id):
|
|
user_id = get_jwt_identity()
|
|
docs_std = DocumentsStandard()
|
|
|
|
# Preluăm documentul înainte de ștergere pentru a cunoaște calea fișierului
|
|
standard = docs_std.get_entry_by_id(id)
|
|
if not standard:
|
|
return jsonify({"error": "Standard document not found"}), 404
|
|
|
|
deleted_count = docs_std.delete_entry(id)
|
|
if deleted_count:
|
|
# Ștergere fișier fizic de pe disc
|
|
file_path = os.path.join(DOCUMENTS_ROOT, standard.path)
|
|
if os.path.exists(file_path):
|
|
try:
|
|
os.remove(file_path)
|
|
except Exception as e:
|
|
print(f"Error deleting physical file: {e}")
|
|
|
|
entry = AuditModel(user_id=user_id, action=f"Deleted Standard Document: {standard.name} (ID: {id})", status="200 - OK")
|
|
audit.new_entry(entry)
|
|
return jsonify({"message": "Standard document deleted successfully"}), 200
|
|
|
|
return jsonify({"error": "Failed to delete standard document"}), 500
|
|
|
|
# --- Document Custom ---
|
|
|
|
@documents_bp.route("/customs/add", methods=["POST"])
|
|
@jwt_required()
|
|
def add_custom():
|
|
user_id = get_jwt_identity()
|
|
data = request.get_json()
|
|
name = data.get("name", "").strip()
|
|
path = data.get("path", "").strip()
|
|
access = data.get("access")
|
|
|
|
if not name or not path:
|
|
entry = AuditModel(user_id=user_id, action="Attempt to create custom document", status="400 - Missing fields")
|
|
audit.new_entry(entry)
|
|
return jsonify({"error": "Missing name or path"}), 400
|
|
|
|
# Physical file move from upload folder to documents/Custom folder
|
|
filename = os.path.basename(path)
|
|
# The 'path' variable here is the filename from the client, not the full path.
|
|
# We construct the full path for the database entry later.
|
|
|
|
source_path = os.path.join(BASE_DIR, "client", "assets", "uploads", os.path.basename(path))
|
|
dest_dir = os.path.normpath(os.path.join(DOCUMENTS_ROOT, "Custom"))
|
|
dest_path = os.path.join(dest_dir, filename) # This is the actual destination path for shutil.move
|
|
print(source_path)
|
|
print(dest_path)
|
|
if os.path.exists(source_path):
|
|
os.makedirs(dest_dir, exist_ok=True) # Ensure the destination directory exists
|
|
shutil.move(source_path, dest_path)
|
|
# Update the 'path' variable to be stored in the database to reflect its new location
|
|
db_path = f"Custom/{filename}"
|
|
else:
|
|
# Log an error and return a response if the source file doesn't exist
|
|
error_message = f"Source file not found for custom document: {source_path}"
|
|
print(f"ERROR: {error_message}") # For immediate debug visibility
|
|
entry = AuditModel(user_id=user_id, action=f"Failed to create custom document (file not found): {name}", status="404 - File not found")
|
|
audit.new_entry(entry)
|
|
return jsonify({"error": "Uploaded file not found on server."}), 404
|
|
|
|
docs_custom = DocumentsCustom()
|
|
custom = DocumentsCustomModel(user_id=user_id, name=name, path=db_path, access=access) # Use db_path here
|
|
result = docs_custom.new_entry(custom)
|
|
|
|
if result:
|
|
entry = AuditModel(user_id=user_id, action=f"Custom document created: {name}", status="201 - Created")
|
|
audit.new_entry(entry)
|
|
return jsonify({"message": "Custom document created successfully", "id": result}), 201
|
|
|
|
# If we reach here, it means the DB entry failed after the file was moved.
|
|
# This is a potential issue, as the file is moved but not recorded.
|
|
entry = AuditModel(user_id=user_id, action=f"Failed to create custom document (DB entry failed): {name}", status="500 - DB error")
|
|
audit.new_entry(entry)
|
|
return jsonify({"error": "Failed to create custom document"}), 500
|
|
|
|
@documents_bp.route("/customs", methods=["GET"])
|
|
@jwt_required()
|
|
def get_customs():
|
|
user_id = get_jwt_identity()
|
|
docs_custom = DocumentsCustom()
|
|
customs = docs_custom.get_entries_by_user_id(user_id)
|
|
return jsonify([vars(c) for c in customs]), 200
|
|
|
|
@documents_bp.route("/customs/<int:id>", methods=["GET"])
|
|
@jwt_required()
|
|
def get_custom_by_id(id):
|
|
docs_custom = DocumentsCustom()
|
|
document = docs_custom.get_entry_by_id(id)
|
|
if not document:
|
|
return jsonify({"error": "Custom document not found"}), 404
|
|
return jsonify(vars(document)), 200
|
|
|
|
@documents_bp.route("/customs/delete/<int:id>", methods=["DELETE"])
|
|
@jwt_required()
|
|
def delete_custom(id):
|
|
user_id = get_jwt_identity()
|
|
docs_custom = DocumentsCustom()
|
|
deleted_count = docs_custom.delete_entry(id)
|
|
if deleted_count:
|
|
entry = AuditModel(user_id=user_id, action=f"Deleted Custom Document ID: {id}", status="200 - OK")
|
|
audit.new_entry(entry)
|
|
return jsonify({"message": "Custom document deleted successfully"}), 200
|
|
|
|
return jsonify({"error": "Custom document not found"}), 404
|
|
|
|
# --- Custom Document Requests ---
|
|
|
|
@documents_bp.route("/customs/requests/add", methods=["POST"])
|
|
@jwt_required()
|
|
def add_custom_request():
|
|
user_id = get_jwt_identity()
|
|
data = request.get_json()
|
|
request_text = data.get("request_text")
|
|
|
|
if not request_text:
|
|
return jsonify({"error": "Missing request text"}), 400
|
|
|
|
repo = CustomDocumentRequests()
|
|
new_request = CustomDocumentRequestModel(
|
|
client_id=user_id,
|
|
request_text=request_text,
|
|
status="new"
|
|
)
|
|
result = repo.new_entry(new_request)
|
|
|
|
if result:
|
|
audit.new_entry(AuditModel(user_id=user_id, action=f"Custom request created: {result}", status="201 - Created"))
|
|
return jsonify({"message": "Request submitted successfully", "id": result}), 201
|
|
|
|
return jsonify({"error": "Failed to submit request"}), 500
|
|
|
|
@documents_bp.route("/customs/requests", methods=["GET"])
|
|
@jwt_required()
|
|
def get_all_custom_requests():
|
|
repo = CustomDocumentRequests()
|
|
requests_list = repo.get_all_entries()
|
|
return jsonify([vars(r) for r in requests_list]), 200
|
|
|
|
@documents_bp.route("/customs/requests/<int:id>", methods=["GET"])
|
|
@jwt_required()
|
|
def get_custom_request_by_id(id):
|
|
repo = CustomDocumentRequests()
|
|
req = repo.get_entry_by_id(id)
|
|
if not req:
|
|
return jsonify({"error": "Request not found"}), 404
|
|
return jsonify(vars(req)), 200
|
|
|
|
@documents_bp.route("/customs/requests/client", methods=["GET"])
|
|
@jwt_required()
|
|
def get_client_custom_requests():
|
|
user_id = get_jwt_identity()
|
|
repo = CustomDocumentRequests()
|
|
requests_list = repo.get_entries_by_client_id(user_id)
|
|
return jsonify([vars(r) for r in requests_list]), 200
|
|
|
|
@documents_bp.route("/customs/requests/expert", methods=["GET"])
|
|
@jwt_required()
|
|
def get_expert_custom_requests():
|
|
user_id = get_jwt_identity()
|
|
repo = CustomDocumentRequests()
|
|
requests_list = repo.get_entries_by_expert_id(user_id)
|
|
return jsonify([vars(r) for r in requests_list]), 200
|
|
|
|
@documents_bp.route("/customs/requests/update/<int:id>", methods=["PUT"])
|
|
@jwt_required()
|
|
def update_custom_request(id):
|
|
user_id = get_jwt_identity()
|
|
data = request.get_json()
|
|
|
|
repo = CustomDocumentRequests()
|
|
success = repo.update_entry(
|
|
id=id,
|
|
request_text=data.get("request_text"),
|
|
status=data.get("status"),
|
|
price=data.get("price"),
|
|
expert_id=data.get("expert_id"),
|
|
document_id=data.get("document_id")
|
|
)
|
|
|
|
if success:
|
|
audit.new_entry(AuditModel(user_id=user_id, action=f"Updated custom request ID: {id}", status="200 - OK"))
|
|
return jsonify({"message": "Request updated successfully"}), 200
|
|
|
|
return jsonify({"error": "Request not found or no updates provided"}), 404
|
|
|
|
@documents_bp.route("/customs/requests/delete/<int:id>", methods=["DELETE"])
|
|
@jwt_required()
|
|
def delete_custom_request(id):
|
|
user_id = get_jwt_identity()
|
|
repo = CustomDocumentRequests()
|
|
|
|
if repo.delete_entry(id):
|
|
audit.new_entry(AuditModel(user_id=user_id, action=f"Deleted custom request ID: {id}", status="200 - OK"))
|
|
return jsonify({"message": "Request deleted successfully"}), 200
|
|
|
|
return jsonify({"error": "Request not found"}), 404 |