devicetypeAPIGET1
This commit is contained in:
0
backend/ scheduler/alert_worker.py
Normal file
0
backend/ scheduler/alert_worker.py
Normal file
0
backend/ scheduler/ping_worker.py
Normal file
0
backend/ scheduler/ping_worker.py
Normal file
0
backend/ scheduler/scheduler.py
Normal file
0
backend/ scheduler/scheduler.py
Normal file
0
backend/ scheduler/snmp_worker.py
Normal file
0
backend/ scheduler/snmp_worker.py
Normal file
9
backend/.env.example
Normal file
9
backend/.env.example
Normal file
@@ -0,0 +1,9 @@
|
||||
#Account Database configuration for PostgreSQL
|
||||
POSTGRES_USER=your_user_here
|
||||
POSTGRES_PASSWORD= your_strong_password_here
|
||||
POSTGRES_DB=your_database_here
|
||||
|
||||
#Account Minio S3 configuration
|
||||
MINIO_ROOT_USER=your_root_user_here
|
||||
MINIO_ROOT_PASSWORD=your_strong_password_here
|
||||
MINIO_BUCKET_NAME=your_bucket_name_here
|
||||
@@ -1,26 +1,30 @@
|
||||
from flask import Flask, jsonify
|
||||
# from flask_cors import CORS
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask
|
||||
|
||||
load_dotenv() # Đọc file .env
|
||||
from common.exceptions.handler import (
|
||||
register_error_handlers
|
||||
)
|
||||
|
||||
app = Flask(__name__) # ← Đây là dòng quan trọng nhất
|
||||
|
||||
# Cho phép React (localhost:5173) gọi API từ backend
|
||||
CORS(app, origins=["http://localhost:5173", "http://127.0.0.1:5173"])
|
||||
from modules.device_type.routes import (
|
||||
device_type_bp
|
||||
)
|
||||
|
||||
@app.route('/')
|
||||
def home():
|
||||
return jsonify({
|
||||
"message": "✅ Backend NDMS đang chạy thành công!",
|
||||
"status": "ok"
|
||||
})
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/health')
|
||||
def health():
|
||||
return jsonify({"status": "healthy"})
|
||||
# Register Global Exception Handlers
|
||||
register_error_handlers(app)
|
||||
|
||||
# Phần này bạn hay dùng
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8000, debug=True)
|
||||
# Register Blueprints
|
||||
app.register_blueprint(
|
||||
device_type_bp,
|
||||
url_prefix="/api"
|
||||
)
|
||||
|
||||
# @app.route("/")
|
||||
# def home():
|
||||
# return {
|
||||
# "message": "NDMS Backend Running"
|
||||
# }
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
||||
9
backend/common/constants/status_code.py
Normal file
9
backend/common/constants/status_code.py
Normal file
@@ -0,0 +1,9 @@
|
||||
HTTP_OK = 200
|
||||
HTTP_CREATED = 201
|
||||
HTTP_BAD_REQUEST = 400
|
||||
HTTP_UNAUTHORIZED = 401
|
||||
HTTP_FORBIDDEN = 403
|
||||
HTTP_NOT_FOUND = 404
|
||||
HTTP_CONFLICT = 409
|
||||
HTTP_UNPROCESSABLE_ENTITY = 422
|
||||
HTTP_INTERNAL_SERVER_ERROR = 500
|
||||
62
backend/common/exceptions/app_exception.py
Normal file
62
backend/common/exceptions/app_exception.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# Đây là exception chung cho toàn bộ dự án
|
||||
class AppException(Exception): # Tạo ra class AppException kế thừa lại Exception của thư viện
|
||||
def __init__(
|
||||
self,
|
||||
message,
|
||||
status_code=400,
|
||||
error_code="BAD_REQUEST",
|
||||
payload=None
|
||||
):
|
||||
super().__init__(message) #kế thừa thêm thuộc tính
|
||||
self.message = message
|
||||
self.status_code = status_code
|
||||
self.error_code = error_code
|
||||
self.payload = payload or {}
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"success": False,
|
||||
"error": self.error_code,
|
||||
"message": self.message,
|
||||
"details": self.payload
|
||||
}
|
||||
|
||||
# Rồi từ thằng kế thừa lại kế thừa tiếp từ thằng AppException
|
||||
class BadRequestException(AppException):
|
||||
def __init__(self, message="Bad request", payload=None):
|
||||
super().__init__(
|
||||
message=message,
|
||||
status_code=400,
|
||||
error_code="BAD_REQUEST",
|
||||
payload=payload
|
||||
)
|
||||
|
||||
|
||||
class NotFoundException(AppException):
|
||||
def __init__(self, message="Resource not found", payload=None):
|
||||
super().__init__(
|
||||
message=message,
|
||||
status_code=404,
|
||||
error_code="NOT_FOUND",
|
||||
payload=payload
|
||||
)
|
||||
|
||||
|
||||
class ConflictException(AppException):
|
||||
def __init__(self, message="Resource already exists", payload=None):
|
||||
super().__init__(
|
||||
message=message,
|
||||
status_code=409,
|
||||
error_code="CONFLICT",
|
||||
payload=payload
|
||||
)
|
||||
|
||||
|
||||
class UnprocessableEntityException(AppException):
|
||||
def __init__(self, message="Unprocessable entity", payload=None):
|
||||
super().__init__(
|
||||
message=message,
|
||||
status_code=422,
|
||||
error_code="UNPROCESSABLE_ENTITY",
|
||||
payload=payload
|
||||
)
|
||||
50
backend/common/exceptions/handler.py
Normal file
50
backend/common/exceptions/handler.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from marshmallow import ValidationError
|
||||
|
||||
from common.exceptions.app_exception import AppException
|
||||
from common.response.api_response import error_response
|
||||
from common.constants.status_code import (
|
||||
HTTP_BAD_REQUEST,
|
||||
HTTP_NOT_FOUND,
|
||||
HTTP_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
# Phải tạo global error handler(ở project cũ là để phần này ra app.py nhưng bây giờ chỉ cần tạo file hander rồi đăng kí ở app.py để nó thành global bắt lỗi toàn dự án)
|
||||
# Ngoài ra để có được các lỗi của toàn dự án, thì phải custom exception theo dự án bằng cách kế thừa lại Exception để custom thêm thuộc tính mới
|
||||
|
||||
def register_error_handlers(app): # Đăng ký hàm này ở app.py để nó bắt lỗi toàn dự án
|
||||
|
||||
@app.errorhandler(AppException)
|
||||
def handle_app_exception(error):
|
||||
return error_response(
|
||||
message=error.message,
|
||||
error=error.error_code,
|
||||
status_code=error.status_code,
|
||||
details=getattr(error, "payload", None)
|
||||
)
|
||||
|
||||
|
||||
@app.errorhandler(ValidationError)
|
||||
def handle_validation_error(error):
|
||||
return error_response(
|
||||
message="Invalid request data",
|
||||
error="VALIDATION_ERROR",
|
||||
status_code=HTTP_BAD_REQUEST,
|
||||
details=error.messages
|
||||
)
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def handle_not_found(error):
|
||||
return error_response(
|
||||
message="Route not found",
|
||||
error="NOT_FOUND",
|
||||
status_code=HTTP_NOT_FOUND
|
||||
)
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
def handle_internal_error(error):
|
||||
return error_response(
|
||||
message="Internal server error",
|
||||
error="INTERNAL_SERVER_ERROR",
|
||||
status_code=HTTP_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
28
backend/common/response/api_response.py
Normal file
28
backend/common/response/api_response.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from flask import jsonify
|
||||
from common.constants.status_code import HTTP_OK, HTTP_BAD_REQUEST
|
||||
|
||||
|
||||
def success_response(
|
||||
data=None,
|
||||
message="Success",
|
||||
status_code=HTTP_OK
|
||||
):
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": message,
|
||||
"data": data
|
||||
}), status_code
|
||||
|
||||
|
||||
def error_response(
|
||||
message="Error",
|
||||
error="ERROR",
|
||||
status_code=HTTP_BAD_REQUEST,
|
||||
details=None
|
||||
):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": error,
|
||||
"message": message,
|
||||
"details": details
|
||||
}), status_code
|
||||
25
backend/config/database.py
Normal file
25
backend/config/database.py
Normal file
@@ -0,0 +1,25 @@
|
||||
#Connection to Database
|
||||
from psycopg2.pool import SimpleConnectionPool
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
db_pool = SimpleConnectionPool(
|
||||
minconn=1,
|
||||
maxconn=10,
|
||||
host=os.getenv("DB_HOST"),
|
||||
port=os.getenv("DB_PORT"),
|
||||
database=os.getenv("POSTGRES_DB"),
|
||||
user=os.getenv("POSTGRES_USER"),
|
||||
password=os.getenv("POSTGRES_PASSWORD")
|
||||
)
|
||||
# Tạo ra 10 conection khác nhau, mỗi lần dùng thì vào đây lấy 1 connection ra => Nó tạo sẵn một nhóm connection. Khi cần thì lấy ra, dùng xong trả lại.
|
||||
# khác với kiểu cũ là mỗi lần request khác nhau lại phải tạo mới connection nó sẽ bị nặng và lâu hơn
|
||||
|
||||
|
||||
def get_connection():
|
||||
return db_pool.getconn() # Lấy ra connection trong này
|
||||
|
||||
def release_connection(conn):
|
||||
db_pool.putconn(conn) # trả connection lại pool này
|
||||
0
backend/database/init.sql
Normal file
0
backend/database/init.sql
Normal file
1
backend/database/schema.sql
Normal file
1
backend/database/schema.sql
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
0
backend/database/seed.sql
Normal file
0
backend/database/seed.sql
Normal file
83
backend/modules/device_type/controller.py
Normal file
83
backend/modules/device_type/controller.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# ============================================
|
||||
# CONTROLLER: Nhận request → Validate → Gọi Service → Trả response
|
||||
# ============================================
|
||||
# Controller KHÔNG chứa business logic (check trùng tên, tính toán...)
|
||||
# Controller CHỈ làm 3 việc:
|
||||
# 1. Lấy dữ liệu từ request (body, params, query string)
|
||||
# 2. Validate dữ liệu (bằng Marshmallow schema)
|
||||
# 3. Gọi service → trả response
|
||||
# ============================================
|
||||
|
||||
from flask import request
|
||||
from common.response.api_response import success_response
|
||||
from common.constants.status_code import HTTP_CREATED
|
||||
from modules.device_type.service import (
|
||||
get_device_types_service,
|
||||
create_device_type_service
|
||||
)
|
||||
from modules.device_type.schemas import CreateDeviceTypeSchema
|
||||
|
||||
|
||||
def get_device_types():
|
||||
"""GET /api/device-types — Lấy danh sách tất cả"""
|
||||
device_types = get_device_types_service()
|
||||
return success_response(
|
||||
data=device_types,
|
||||
message="Device types retrieved successfully"
|
||||
)
|
||||
|
||||
|
||||
def get_device_type_by_id(device_type_id):
|
||||
"""GET /api/device-types/<device_type_id> — Lấy chi tiết theo ID"""
|
||||
# TODO: Triển khai sau
|
||||
pass
|
||||
|
||||
|
||||
def create_device_type():
|
||||
"""
|
||||
POST /api/device-types — Tạo mới device type
|
||||
|
||||
Luồng xử lý:
|
||||
1. Lấy JSON body từ request
|
||||
2. Validate bằng Marshmallow schema
|
||||
- Nếu sai → Marshmallow tự throw ValidationError
|
||||
- Global handler bắt → trả 400 kèm chi tiết lỗi
|
||||
3. Gọi service (service sẽ check trùng tên + insert DB)
|
||||
4. Trả 201 Created + data vừa tạo
|
||||
"""
|
||||
# Bước 1: Lấy JSON body từ request
|
||||
body = request.get_json()
|
||||
# request.get_json() parse body thành dict Python
|
||||
# VD: {"name": "Router", "color": "#3B82F6"} → {'name': 'Router', 'color': '#3B82F6'}
|
||||
|
||||
# Bước 2: Validate bằng schema
|
||||
schema = CreateDeviceTypeSchema()
|
||||
data = schema.load(body)
|
||||
# schema.load() làm 2 việc:
|
||||
# a) Validate: check required, type, regex... → nếu sai throw ValidationError
|
||||
# b) Deserialize: loại bỏ field thừa, áp dụng load_default
|
||||
# VD: body = {"name": "Router", "color": "#3B82F6", "hack": "xss"}
|
||||
# → data = {"name": "Router", "color": "#3B82F6", "sort_order": 0, "is_active": True}
|
||||
# → "hack" bị loại bỏ, sort_order/is_active được thêm default
|
||||
|
||||
# Bước 3: Gọi service
|
||||
new_device_type = create_device_type_service(data)
|
||||
|
||||
# Bước 4: Trả response 201 Created
|
||||
return success_response(
|
||||
data=new_device_type,
|
||||
message="Device type created successfully",
|
||||
status_code=HTTP_CREATED # 201 thay vì 200 — đúng chuẩn REST cho POST thành công
|
||||
)
|
||||
|
||||
|
||||
def update_device_type(device_type_id):
|
||||
"""PUT /api/device-types/<device_type_id> — Cập nhật"""
|
||||
# TODO: Triển khai sau
|
||||
pass
|
||||
|
||||
|
||||
def delete_device_type(device_type_id):
|
||||
"""DELETE /api/device-types/<device_type_id> — Xóa"""
|
||||
# TODO: Triển khai sau
|
||||
pass
|
||||
30
backend/modules/device_type/exceptions.py
Normal file
30
backend/modules/device_type/exceptions.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Đây là exception cho riêng cho từng module
|
||||
from common.exceptions.app_exception import (
|
||||
NotFoundException,
|
||||
ConflictException,
|
||||
BadRequestException
|
||||
)
|
||||
|
||||
|
||||
class DeviceTypeNotFoundException(NotFoundException):
|
||||
def __init__(self, device_type_id):
|
||||
super().__init__(
|
||||
message=f"Device type not found with id={device_type_id}",
|
||||
payload={"device_type_id": device_type_id}
|
||||
)
|
||||
|
||||
|
||||
class DeviceTypeAlreadyExistsException(ConflictException):
|
||||
def __init__(self, name):
|
||||
super().__init__(
|
||||
message=f"Device type already exists with name={name}",
|
||||
payload={"name": name}
|
||||
)
|
||||
|
||||
|
||||
class DeviceTypeInUseException(BadRequestException):
|
||||
def __init__(self, device_type_id):
|
||||
super().__init__(
|
||||
message="Cannot delete device type because it is being used by devices",
|
||||
payload={"device_type_id": device_type_id}
|
||||
)
|
||||
120
backend/modules/device_type/repository.py
Normal file
120
backend/modules/device_type/repository.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from config.database import get_connection, release_connection
|
||||
|
||||
# ============================================
|
||||
# Hàm chung: Chuyển 1 row (tuple) thành dict
|
||||
# Tại sao tách riêng? Vì nhiều hàm đều cần chuyển row → dict,
|
||||
# viết 1 lần dùng lại, tránh copy-paste
|
||||
# ============================================
|
||||
def _row_to_dict(row):
|
||||
return {
|
||||
"id": str(row[0]),
|
||||
"name": row[1],
|
||||
"description": row[2],
|
||||
"icon_url": row[3],
|
||||
"color": row[4],
|
||||
"sort_order": row[5],
|
||||
"is_active": row[6],
|
||||
"created": row[7].isoformat() if row[7] else None,
|
||||
"modified": row[8].isoformat() if row[8] else None
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# GET ALL: Lấy danh sách tất cả device types
|
||||
# ============================================
|
||||
def find_all_device_types():
|
||||
conn = get_connection()
|
||||
cur = None
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT id, name, description, icon_url, color, sort_order, is_active, created, modified
|
||||
FROM device_types
|
||||
ORDER BY sort_order ASC, name ASC
|
||||
""")
|
||||
|
||||
rows = cur.fetchall()
|
||||
|
||||
device_types = []
|
||||
for row in rows:
|
||||
device_types.append(_row_to_dict(row))
|
||||
|
||||
# ✅ FIX: return PHẢI nằm NGOÀI vòng for
|
||||
# Trước đó return nằm trong for → chỉ trả về 1 row rồi dừng
|
||||
return device_types
|
||||
|
||||
finally:
|
||||
if cur:
|
||||
cur.close()
|
||||
# ✅ FIX: PHẢI trả connection về pool, không thì sau 10 request app sẽ đứng
|
||||
release_connection(conn)
|
||||
|
||||
|
||||
# ============================================
|
||||
# FIND BY NAME: Tìm device type theo tên (để check trùng tên khi tạo mới)
|
||||
# Trả về dict nếu tìm thấy, None nếu không
|
||||
# ============================================
|
||||
def find_device_type_by_name(name):
|
||||
conn = get_connection()
|
||||
cur = None
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
# Dùng LOWER() để so sánh không phân biệt hoa/thường
|
||||
# VD: "Router" và "router" coi như trùng
|
||||
cur.execute("""
|
||||
SELECT id, name, description, icon_url, color, sort_order, is_active, created, modified
|
||||
FROM device_types
|
||||
WHERE LOWER(name) = LOWER(%s)
|
||||
""", (name,))
|
||||
# ⚠️ LƯU Ý: (name,) có dấu phẩy → tạo tuple 1 phần tử
|
||||
# Nếu viết (name) thì Python hiểu là string, không phải tuple → lỗi
|
||||
|
||||
row = cur.fetchone() # fetchone() vì chỉ cần 1 kết quả
|
||||
if row:
|
||||
return _row_to_dict(row)
|
||||
return None
|
||||
|
||||
finally:
|
||||
if cur:
|
||||
cur.close()
|
||||
release_connection(conn)
|
||||
|
||||
|
||||
# ============================================
|
||||
# INSERT: Thêm mới 1 device type vào database
|
||||
# Trả về dict của device type vừa tạo (có id, created, modified từ DB)
|
||||
# ============================================
|
||||
def insert_device_type(data):
|
||||
conn = get_connection()
|
||||
cur = None
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
INSERT INTO device_types (name, description, icon_url, color, sort_order, is_active)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
RETURNING id, name, description, icon_url, color, sort_order, is_active, created, modified
|
||||
""", (
|
||||
data["name"],
|
||||
data.get("description"), # .get() trả về None nếu không có key
|
||||
data.get("icon_url"),
|
||||
data["color"],
|
||||
data.get("sort_order", 0), # default = 0 nếu không truyền
|
||||
data.get("is_active", True) # default = True nếu không truyền
|
||||
))
|
||||
# RETURNING: PostgreSQL trả về row vừa INSERT (bao gồm id, created, modified do DB tự tạo)
|
||||
# → Không cần SELECT lại sau khi INSERT
|
||||
|
||||
row = cur.fetchone()
|
||||
conn.commit() # ⚠️ QUAN TRỌNG: INSERT/UPDATE/DELETE phải commit() để lưu vào DB
|
||||
# Nếu không commit → dữ liệu chỉ nằm trong transaction, khi connection đóng → mất hết
|
||||
|
||||
return _row_to_dict(row)
|
||||
|
||||
except Exception:
|
||||
conn.rollback() # Nếu có lỗi → rollback để hủy transaction, tránh dữ liệu bẩn
|
||||
raise # raise lại exception để tầng trên (service/controller) xử lý
|
||||
|
||||
finally:
|
||||
if cur:
|
||||
cur.close()
|
||||
release_connection(conn)
|
||||
21
backend/modules/device_type/routes.py
Normal file
21
backend/modules/device_type/routes.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from flask import Blueprint
|
||||
|
||||
from modules.device_type.controller import (
|
||||
get_device_types,
|
||||
get_device_type_by_id,
|
||||
create_device_type,
|
||||
update_device_type,
|
||||
delete_device_type
|
||||
)
|
||||
|
||||
device_type_bp = Blueprint(
|
||||
"device_type",
|
||||
__name__,
|
||||
url_prefix="/device-types"
|
||||
)
|
||||
|
||||
device_type_bp.route("", methods=["GET"])(get_device_types)
|
||||
device_type_bp.route("/<device_type_id>", methods=["GET"])(get_device_type_by_id)
|
||||
device_type_bp.route("", methods=["POST"])(create_device_type)
|
||||
device_type_bp.route("/<device_type_id>", methods=["PUT"])(update_device_type)
|
||||
device_type_bp.route("/<device_type_id>", methods=["DELETE"])(delete_device_type)
|
||||
67
backend/modules/device_type/schemas.py
Normal file
67
backend/modules/device_type/schemas.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
HEX_COLOR_REGEX = r"^#[0-9A-Fa-f]{6}$"
|
||||
|
||||
|
||||
class CreateDeviceTypeSchema(Schema):
|
||||
name = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(min=1, max=100)
|
||||
)
|
||||
|
||||
description = fields.String(
|
||||
required=False,
|
||||
allow_none=True
|
||||
)
|
||||
|
||||
icon_url = fields.String(
|
||||
required=False,
|
||||
allow_none=True,
|
||||
validate=validate.Length(max=512)
|
||||
)
|
||||
|
||||
color = fields.String(
|
||||
required=True,
|
||||
validate=validate.Regexp(HEX_COLOR_REGEX)
|
||||
)
|
||||
|
||||
sort_order = fields.Integer(
|
||||
required=False,
|
||||
load_default=0
|
||||
)
|
||||
|
||||
is_active = fields.Boolean(
|
||||
required=False,
|
||||
load_default=True
|
||||
)
|
||||
|
||||
|
||||
class UpdateDeviceTypeSchema(Schema):
|
||||
name = fields.String(
|
||||
required=False,
|
||||
validate=validate.Length(min=1, max=100)
|
||||
)
|
||||
|
||||
description = fields.String(
|
||||
required=False,
|
||||
allow_none=True
|
||||
)
|
||||
|
||||
icon_url = fields.String(
|
||||
required=False,
|
||||
allow_none=True,
|
||||
validate=validate.Length(max=512)
|
||||
)
|
||||
|
||||
color = fields.String(
|
||||
required=False,
|
||||
validate=validate.Regexp(HEX_COLOR_REGEX)
|
||||
)
|
||||
|
||||
sort_order = fields.Integer(
|
||||
required=False
|
||||
)
|
||||
|
||||
is_active = fields.Boolean(
|
||||
required=False
|
||||
)
|
||||
38
backend/modules/device_type/service.py
Normal file
38
backend/modules/device_type/service.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# ============================================
|
||||
# SERVICE LAYER: Chứa Business Logic
|
||||
# ============================================
|
||||
# Controller chỉ nhận request + trả response
|
||||
# Repository chỉ truy vấn DB
|
||||
# Service ở giữa: chứa LOGIC NGHIỆP VỤ (validate, check điều kiện, tính toán)
|
||||
# ============================================
|
||||
|
||||
from modules.device_type.repository import (
|
||||
find_all_device_types,
|
||||
find_device_type_by_name,
|
||||
insert_device_type
|
||||
)
|
||||
from modules.device_type.exceptions import DeviceTypeAlreadyExistsException
|
||||
|
||||
|
||||
def get_device_types_service():
|
||||
"""Lấy danh sách tất cả device types — hiện tại không có logic gì thêm"""
|
||||
return find_all_device_types()
|
||||
|
||||
|
||||
def create_device_type_service(data):
|
||||
"""
|
||||
Tạo mới device type.
|
||||
Business logic:
|
||||
1. Check xem tên đã tồn tại chưa (UNIQUE constraint)
|
||||
2. Nếu trùng → throw exception
|
||||
3. Nếu không trùng → insert vào DB
|
||||
"""
|
||||
# Bước 1: Check trùng tên
|
||||
existing = find_device_type_by_name(data["name"])
|
||||
if existing:
|
||||
# Throw exception → global handler bắt → trả 409 Conflict
|
||||
raise DeviceTypeAlreadyExistsException(data["name"])
|
||||
|
||||
# Bước 2: Insert vào DB
|
||||
new_device_type = insert_device_type(data)
|
||||
return new_device_type
|
||||
0
backend/modules/device_type/validator.py
Normal file
0
backend/modules/device_type/validator.py
Normal file
@@ -10,3 +10,5 @@ python-dotenv==1.2.2
|
||||
SQLAlchemy==2.0.49
|
||||
typing_extensions==4.15.0
|
||||
Werkzeug==3.1.8
|
||||
marshmallow==3.21.2
|
||||
APScheduler==3.10.4
|
||||
Reference in New Issue
Block a user