141 lines
3.5 KiB
Python
141 lines
3.5 KiB
Python
|
|
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,
|
||
|
|
)
|