11from datetime import datetime
22from typing import TYPE_CHECKING
33
4- from sqlalchemy import Column , Integer , text
4+ from pydantic import field_validator
5+ from sqlalchemy import JSON , Column , Integer , text
56from sqlalchemy .orm import relationship
67from sqlmodel import Field , Relationship , SQLModel
78
89from app .features .models import FeaturePublic
910from app .links .models import MalwareFeatureLink
11+ from app .malware .utils import normalize_family_values
1012
1113if TYPE_CHECKING : # pragma: no cover - type checking only
1214 from app .features .models import Feature
@@ -19,9 +21,23 @@ class MalwareBase(SQLModel):
1921 md5 : str
2022 size : int
2123 sha256 : str
22- family : str | None = None
24+ family : list [str ] = Field (
25+ default_factory = list ,
26+ sa_column = Column (JSON , nullable = False , server_default = text ("'[]'::json" )),
27+ description = "惡意家族標籤列表" ,
28+ )
2329 source : str | None = None
2430 platform : str
31+ mitre_ids : list [str ] = Field (
32+ default_factory = list ,
33+ sa_column = Column (JSON , nullable = False , server_default = text ("'[]'::json" )),
34+ description = "對應的 MITRE ATT&CK 技術 ID" ,
35+ )
36+
37+ @field_validator ("family" , mode = "before" )
38+ @classmethod
39+ def _ensure_family_list (cls , value ): # type: ignore[override]
40+ return normalize_family_values (value )
2541
2642
2743class Malware (MalwareBase , table = True ):
@@ -57,9 +73,17 @@ class MalwareUpdate(SQLModel, table=False):
5773
5874 filename : str | None = None
5975 source : str | None = None
60- family : str | None = None
76+ family : list [ str ] | None = None
6177 platform : str | None = None
6278 last_seen : datetime | None = None
79+ mitre_ids : list [str ] | None = None
80+
81+ @field_validator ("family" , mode = "before" )
82+ @classmethod
83+ def _ensure_update_family (cls , value ): # type: ignore[override]
84+ if value is None :
85+ return None
86+ return normalize_family_values (value )
6387
6488
6589class MalwareDelete (SQLModel ):
0 commit comments