import re import uuid from werkzeug.utils import secure_filename from storage.s3_client import ( get_s3_client, get_bucket_name, get_public_url, ) from storage.validators import validate_image_file def slugify(text): """ Chuyển text thành dạng an toàn để dùng trong tên file. Ví dụ: - "Router" -> "router" - "Core Switch 01" -> "core-switch-01" - "Thiết bị định tuyến" -> "thi-t-b-nh-tuy-n" Lưu ý: Hàm này đơn giản, chưa xử lý tiếng Việt hoàn hảo. Nếu muốn slug tiếng Việt đẹp hơn, sau này có thể dùng thư viện python-slugify. """ if text is None or text.strip() == "": return "file" text = text.lower().strip() # Thay toàn bộ ký tự không phải chữ/số bằng dấu gạch ngang. text = re.sub(r"[^a-z0-9]+", "-", text) # Xóa dấu gạch ngang thừa ở đầu/cuối. text = text.strip("-") return text or "file" def build_object_key(folder, business_name, extension): """ Tạo object key để lưu file trong bucket. MinIO/S3 không có folder thật như filesystem. Folder ở đây chỉ là prefix trong object key. Ví dụ: folder = "device-types" business_name = "Router" extension = "svg" Kết quả: device-types/router-550e8400.svg Dùng UUID để tránh trùng tên file. """ safe_name = slugify(business_name) unique_id = uuid.uuid4() return f"{folder}/{safe_name}-{unique_id}.{extension}" def upload_image(file, folder, business_name): # Hàm Upload dùng chung cho device_types và devices """ Upload một file ảnh lên MinIO/S3 và trả về public URL. Tham số: - file: file lấy từ request.files - folder: nhóm lưu trữ, ví dụ "device-types" hoặc "devices" - business_name: tên nghiệp vụ để đặt tên file đẹp hơn Ví dụ: + DeviceType name = Router + Device name = Switch tầng 2 Flow: 1. Validate file 2. Tạo object key 3. Upload file lên bucket 4. Trả về URL public để lưu vào database """ extension = validate_image_file(file) object_key = build_object_key( folder=folder, business_name=business_name, extension=extension, ) s3_client = get_s3_client() bucket_name = get_bucket_name() public_url = get_public_url() s3_client.upload_fileobj( file, bucket_name, object_key, ExtraArgs={ "ContentType": file.content_type }, ) return f"{public_url}/{bucket_name}/{object_key}" def upload_device_type_icon(file, device_type_name): """ Upload icon cho DeviceType. File sẽ được lưu trong bucket với prefix: device-types/ Ví dụ URL: http://localhost:9000/ndms/device-types/router-uuid.svg """ return upload_image( # Ở đoạn này có thể trả về một hàm luôn á ? Mà không cần phải gọi hàm đó vào bên trong và truyền tham số à ? file=file, folder="device-types", business_name=device_type_name, ) def upload_device_avatar(file, device_name): """ Upload avatar cho Device. File sẽ được lưu trong bucket với prefix: devices/ Ví dụ URL: http://localhost:9000/ndms/devices/switch-tang-2-uuid.png """ return upload_image( file=file, folder="devices", business_name=device_name, )