diff --git a/.gitIgnore b/.gitIgnore index e69de29..bb32ee7 100644 --- a/.gitIgnore +++ b/.gitIgnore @@ -0,0 +1,2 @@ +Model/.env +.env \ No newline at end of file diff --git a/.idea/Managed-Server.iml b/.idea/Managed-Server.iml index 5e764c4..d356d82 100644 --- a/.idea/Managed-Server.iml +++ b/.idea/Managed-Server.iml @@ -1,9 +1,17 @@ + + + + + - + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..69a1ce5 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0dbbef0 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..c5f4aea 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/Core/AgentManager.go b/Core/AgentManager.go deleted file mode 100644 index 6101c34..0000000 --- a/Core/AgentManager.go +++ /dev/null @@ -1,40 +0,0 @@ -package Core - -import "github.com/your/repo/Model" - -/* - Agent Model = Managing + model -*/ - -type AgentManager struct { -} - -func (self *AgentManager) createAgent(uuid string) error { - - return nil -} - -/* -agent 의 상태 Status 는 총 3가지로 나뉜다. - -Running : 동작중인 상태 -waiting : 대기중인 상태 -Stopping : 정지후 사라지기 전에 상태 -*/ -func (self *AgentManager) checkAgent(uuid string) bool { - - return false -} - -func (self *AgentManager) updateAgentStatus(uuid string, status bool) bool { - agtStat := &Model.NewAgentStatusDB() - record := &Model.AgentStatusRecord{ - UUID: uuid, - Status: status, - } - agtStat.UpdateRecord() -} - -func (self *AgentManager) deleteAgent(uuid string) bool { - -} diff --git a/Core/CommandDispatcher.go b/Core/CommandDispatcher.go new file mode 100644 index 0000000..0da2d35 --- /dev/null +++ b/Core/CommandDispatcher.go @@ -0,0 +1,273 @@ +package Core + +import ( + "encoding/json" + "fmt" + "github.com/HTTPs-omma/HTTPsBAS-HSProtocol/HSProtocol" + "github.com/your/repo/Model" +) + +// https://github.com/HTTPs-omma/HSProtocol +type CommandDispatcher struct { +} + +// Command 상수를 정의 + +func (cd *CommandDispatcher) Action(hs *HSProtocol.HS) (*HSProtocol.HS, error) { + // hsMgr := HSProtocol.NewHSProtocolManager() + + switch hs.Command { + case HSProtocol.UPDATE_AGENT_PROTOCOL: + return UPDATE_AGENT_PROTOCOL(hs) + case HSProtocol.UPDATE_AGENT_STATUS: + return UPDATE_AGENT_STATUS(hs) + case HSProtocol.SEND_AGENT_SYS_INFO: + return SEND_AGENT_SYS_INFO(hs) + case HSProtocol.ERROR_ACK: + break // 예약 + case HSProtocol.SEND_AGENT_APP_INFO: + return SEND_AGENT_APP_INFO(hs) + case HSProtocol.FETCH_INSTRUCTION: + return FETCH_INSTRUCTION(hs) + case HSProtocol.SEND_PROCEDURE_LOG: + return SEND_PROCEDURE_LOG(hs) + } + + return nil, fmt.Errorf("Invalid Command") +} + +// Command: 1 (0b0000000001) +func UPDATE_AGENT_PROTOCOL(hs *HSProtocol.HS) (*HSProtocol.HS, error) { + agsmd, err := Model.NewAgentStatusDB() + if err != nil { + return nil, err + } + rst, err := agsmd.ExistRecord() + if err != nil { + return nil, err + } + if rst { + return nil, fmt.Errorf("Agent Status DB : no Records") + } + + records, err := agsmd.SelectAllRecords() + if err != nil { + return nil, err + } + + hs_uuid := HSProtocol.ByteArrayToHexString(hs.UUID) + + flag := false + for _, record := range records { + if record.UUID == hs_uuid { + flag = true + } + } + + if flag == true { + agsmd.UpdateRecord(&Model.AgentStatusRecord{ + UUID: hs_uuid, + Status: Model.BinaryToAgentStatus(hs.HealthStatus), + }) + return &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + }, nil + } else if (flag == false) && (hs.HealthStatus == uint8(HSProtocol.WAIT)) { + agsmd.InsertRecord(&Model.AgentStatusRecord{ + UUID: hs_uuid, + Status: Model.BinaryToAgentStatus(hs.HealthStatus), + }) + + return &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + }, nil + } + + return nil, fmt.Errorf("incorrect AgentStatusRecords") +} + +// Command: 2 (0b0000000010) +func UPDATE_AGENT_STATUS(hs *HSProtocol.HS) (*HSProtocol.HS, error) { + + // protocolID := binary.BigEndian.Uint32(hs.Data) + agsDb, err := Model.NewAgentStatusDB() + if err != nil { + return nil, err + } + + agsDb.InsertRecord(&Model.AgentStatusRecord{ + ID: 0, + UUID: HSProtocol.ByteArrayToHexString(hs.UUID), + Protocol: Model.BinaryToProtocol(hs.ProtocolID), + Status: Model.BinaryToAgentStatus(hs.HealthStatus), + }) + err = agsDb.InsertRecord(&Model.AgentStatusRecord{}) + if err != nil { + return nil, err + } + + return &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + }, nil +} + +// Command: 3 (0b0000000011) +func SEND_AGENT_SYS_INFO(hs *HSProtocol.HS) (*HSProtocol.HS, error) { + + sysDB, err := Model.NewSystemInfoDB() + if err != nil { + return nil, err + } + sysinfo := Model.DsystemInfoDB{} + err = json.Unmarshal(hs.Data, &sysinfo) + if err != nil { + return nil, err + } + + err = sysDB.InsertRecord(&sysinfo) + if err != nil { + return nil, err + } + + return &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + }, nil +} + +// Command: 5 (0b0000000101) +func SEND_AGENT_APP_INFO(hs *HSProtocol.HS) (*HSProtocol.HS, error) { + + appDB, err := Model.NewApplicationDB() + if err != nil { + return nil, err + } + applist, err := appDB.FromJSON(hs.Data) + if err != nil { + return nil, err + } + + for _, Dapp := range applist { + err = appDB.InsertRecord(&Dapp) + if err != nil { + return nil, err + } + } + + return &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + }, nil +} + +// Command: 6 (0b0000000110) +func FETCH_INSTRUCTION(hs *HSProtocol.HS) (*HSProtocol.HS, error) { + agentUuid := HSProtocol.ByteArrayToHexString(hs.UUID) + fmt.Println("agent uuid : " + agentUuid) + jobdb, err := Model.NewJobDB() + if err != nil { + return nil, err + } + job, err, exist := jobdb.PopbyAgentUUID(agentUuid) + + if err != nil { + return nil, err + } + + //fmt.Println("debug === : " + job.ProcedureID) + if exist == true { // job 이 있다면 + cmdMgr, err := NewInstructionManager() + if err != nil { + return nil, err + } + + cmdData, issuccess := cmdMgr.GetByID(job.ProcedureID) // 프로시저를 불러와야함. + if issuccess != true { + return nil, fmt.Errorf("job procedure not found") + } + bData, err := cmdData.ToBytes() + if err != nil { + return nil, err + } + return &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: bData, + }, nil + } + + // false + return &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + }, nil + +} + +// Command: 7 (0b0000000111) +func SEND_PROCEDURE_LOG(hs *HSProtocol.HS) (*HSProtocol.HS, error) { + + //hs_uuid := HSProtocol.ByteArrayToHexString(hs.UUID) + + logdb, err := Model.NewOperationLogDB() + if err != nil { + return nil, err + } + log := &Model.OperationLogDocument{} + err = json.Unmarshal(hs.Data, &log) + if err != nil { + return nil, err + } + + _, err = logdb.InsertDocument(log) + if err != nil { + return nil, err + } + + return &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + }, nil +} diff --git a/Core/InstructionManager.go b/Core/InstructionManager.go new file mode 100644 index 0000000..5387195 --- /dev/null +++ b/Core/InstructionManager.go @@ -0,0 +1,143 @@ +package Core + +import ( + "fmt" + "gopkg.in/yaml.v2" + "io/ioutil" + "log" + "os" + "path/filepath" +) + +/** +ChatGpt 로 생성한 코드임 +*/ + +// InstructionData는 주어진 YAML 데이터를 저장할 구조체입니다. +type InstructionData struct { + ID string `yaml:"id"` + MITREID string `yaml:"MITRE_ID"` + Description string `yaml:"Description"` + Tool string `yaml:"tool"` + RequisiteCommand string `yaml:"requisite_command"` + Command string `yaml:"command"` + Cleanup string `yaml:"cleanup"` +} + +// ToBytes는 InstructionData 구조체를 YAML 바이트 슬라이스로 변환하는 함수입니다. +func (cd *InstructionData) ToBytes() ([]byte, error) { + // YAML로 직렬화 + data, err := yaml.Marshal(cd) + if err != nil { + return nil, err + } + return data, nil +} + +// InstructionManager는 모든 InstructionData를 관리하는 구조체입니다. +type InstructionManager struct { + commands map[string]InstructionData +} + +// NewInstructionManager는 InstructionManager를 초기화하고 YAML 파일들을 읽어들입니다. +func NewInstructionManager() (*InstructionManager, error) { + cm := &InstructionManager{commands: make(map[string]InstructionData)} + + err := cm.loadCommands() + if err != nil { + return nil, err + } + + return cm, nil +} + +// loadCommands는 주어진 경로에서 모든 YAML 파일을 읽어들여 InstructionData로 변환합니다. +func (cm *InstructionManager) loadCommands() error { + // 디렉토리 내의 모든 YAML 파일을 찾습니다. + files, err := filepath.Glob(filepath.Join("../HTTPsBAS-Procedures/", "*.yaml")) + if err != nil { + return fmt.Errorf("failed to read directory: %w", err) + } + + // 각 파일을 읽고 InstructionData로 변환 + for _, file := range files { + err := cm.loadCommandFile(file) + if err != nil { + log.Printf("failed to load file %s: %v\n", file, err) + } + } + + return nil +} + +// loadCommandFile은 하나의 YAML 파일을 읽어 InstructionData로 변환하고 저장합니다. +func (cm *InstructionManager) loadCommandFile(filepath string) error { + file, err := os.Open(filepath) + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + // 파일 내용 읽기 + data, err := ioutil.ReadAll(file) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + // YAML 데이터를 InstructionData 구조체로 변환 + var command InstructionData + err = yaml.Unmarshal(data, &command) + if err != nil { + return fmt.Errorf("failed to unmarshal yaml: %w", err) + } + + // ID를 키로 맵에 저장 + cm.commands[command.ID] = command + + return nil +} + +// GetByID는 주어진 ID에 해당하는 InstructionData를 반환합니다. +func (cm *InstructionManager) GetByID(id string) (*InstructionData, bool) { + command, exists := cm.commands[id] + if !exists { + return nil, false + } + return &command, true +} + +/** 이전에 쓰고 현재는 사용하지 않는 코드라서 주석처리했습니다. +해당 코드를 지우기전에 허남정 연구원에게 연락주시길 바랍니다. + + +//// Insert는 새로운 InstructionData를 삽입하는 함수입니다. +//// 이미 동일한 ID가 존재하면 false를 반환하고, 성공적으로 삽입되면 true를 반환합니다. +//func (cm *InstructionManager) Insert(command InstructionData) bool { +// // ID가 이미 존재하는지 확인 +// if _, exists := cm.commands[command.ID]; exists { +// return false // 이미 존재하면 삽입하지 않고 false 반환 +// } +// +// // 맵에 새 InstructionData 삽입 +// cm.commands[command.ID] = command +// return true +//} + +//func main() { +// // ../CommandDB/ 디렉토리에 있는 모든 YAML 파일을 읽어 InstructionManager를 초기화합니다. +// InstructionManager, err := NewInstructionManager("../CommandDB/") +// if err != nil { +// log.Fatalf("Failed to initialize command manager: %v", err) +// } +// +// // ID로 데이터를 가져오기 (예시) +// id := "P_Collection_Kimsuky_001" +// command, exists := InstructionManager.GetByID(id) +// if exists { +// fmt.Printf("Command found: %+v\n", command) +// } else { +// fmt.Printf("Command with ID %s not found\n", id) +// } +//} + +*/ diff --git a/Core/InstructionManager_test.go b/Core/InstructionManager_test.go new file mode 100644 index 0000000..2e8654f --- /dev/null +++ b/Core/InstructionManager_test.go @@ -0,0 +1,23 @@ +package Core + +import ( + "fmt" + "testing" +) + +// InstructionManager의 기본 동작을 테스트 +func TestInstructionManager_LoadAndGetByID(t *testing.T) { + + cm, err := NewInstructionManager() + if err != nil { + t.Fatalf("err: %s", err) + } + // ID로 InstructionData 가져오기 + id := "P_Collection_Kimsuky_001" + command, exists := cm.GetByID(id) + if !exists { + t.Fatalf("Expected command with ID %s not found", id) + } + + fmt.Println(command) +} diff --git a/Model/AgentStatus.go b/Model/AgentStatus.go new file mode 100644 index 0000000..a1efbe9 --- /dev/null +++ b/Model/AgentStatus.go @@ -0,0 +1,266 @@ +package Model + +import ( + "database/sql" + "fmt" + "github.com/HTTPs-omma/HTTPsBAS-HSProtocol/HSProtocol" + "time" +) + +// Binary 값을 AgentStatus로 변환하는 메서드를 구현합니다. +func BinaryToAgentStatus(i uint8) AgentStatus { + switch i { + case 0b00: + return HSProtocol.NEW + case 0b01: + return HSProtocol.WAIT + case 0b10: + return HSProtocol.RUN + case 0b11: + return HSProtocol.DELETED + default: + return HSProtocol.UNKNOWN + } +} + +// Binary 값을 AgentStatus로 변환하는 메서드를 구현합니다. +func BinaryToProtocol(i uint8) Protocol { + switch i { + case 0b0001: + return HSProtocol.TCP + case 0b0010: + return HSProtocol.UDP + case 0b0011: + return HSProtocol.HTTP + case 0b0100: + return HSProtocol.HTTPS + default: + return HSProtocol.UNKNOWN + } +} + +// Protocol 유형을 정의합니다. +type Protocol uint8 + +// // AgentStatus 유형을 정의합니다. +type AgentStatus int + +type AgentStatusDB struct { + dbName string +} + +type AgentStatusRecord struct { + ID int + UUID string + Status AgentStatus + Protocol Protocol + CreatedAt time.Time + UpdatedAt time.Time +} + +// NewAgentStatusDB creates a new instance of AgentStatusDB with the default table name. +func NewAgentStatusDB() (*AgentStatusDB, error) { + db := &AgentStatusDB{dbName: "AgentStatus"} + err := db.CreateTable() + if err != nil { + return nil, err + } + return db, nil +} + +// CreateTable creates the AgentStatus table if it does not exist. +func (s *AgentStatusDB) CreateTable() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + sqlStmt := ` + CREATE TABLE IF NOT EXISTS %s ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + uuid TEXT NOT NULL UNIQUE, + status int, + protocol int Default 0, + createAt DATETIME DEFAULT CURRENT_TIMESTAMP, + updateAt DATETIME DEFAULT CURRENT_TIMESTAMP + ); + ` + + sqlStmt = fmt.Sprintf(sqlStmt, s.dbName) + + _, err = db.Exec(sqlStmt) + if err != nil { + return err + } + + sqlTrigger := fmt.Sprintf(` + CREATE TRIGGER IF NOT EXISTS update_ModificationTime + AFTER UPDATE ON %s + FOR EACH ROW + BEGIN + UPDATE %s SET + updateAt = CURRENT_TIMESTAMP + WHERE id = NEW.id; + END; + `, s.dbName, s.dbName) + + _, err = db.Exec(sqlTrigger) + if err != nil { + return err + } + + return nil +} + +// InsertRecord inserts a new record into the AgentStatus table. +func (s *AgentStatusDB) InsertRecord(data *AgentStatusRecord) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + checkQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE uuid = ?`, s.dbName) + var count int + err = db.QueryRow(checkQuery, data.UUID).Scan(&count) + if err != nil { + return err + } + if count > 0 { + // 중복된 항목이 있으면 업데이트 + return s.UpdateRecord(data) + } + + query := fmt.Sprintf(`INSERT INTO %s (uuid, status, protocol) VALUES (?, ?, ?)`, s.dbName) + stmt, err := db.Prepare(query) + if err != nil { + return err + } + defer stmt.Close() + + _, err = stmt.Exec(data.UUID, data.Status, data.Protocol) + if err != nil { + return err + } + + return nil +} + +func (s *AgentStatusDB) SelectAllRecords() ([]AgentStatusRecord, error) { + db, err := getDBPtr() + if err != nil { + return nil, err + } + defer db.Close() + + query := fmt.Sprintf(`SELECT id, uuid, status, protocol, createAt, updateAt FROM %s`, s.dbName) + rows, err := db.Query(query) + defer rows.Close() + if err != nil { + return nil, err + } + defer rows.Close() + + records := []AgentStatusRecord{} + for rows.Next() { + var record AgentStatusRecord + err := rows.Scan(&record.ID, &record.UUID, &record.Status, &record.Protocol, &record.CreatedAt, &record.UpdatedAt) + if err != nil { + return nil, err + } + records = append(records, record) + } + + return records, nil +} + +func (s *AgentStatusDB) SelectRecordByUUID(uuid string) ([]AgentStatusRecord, error) { + db, err := getDBPtr() + if err != nil { + return nil, err + } + defer db.Close() + + query := fmt.Sprintf(`SELECT id, uuid, status, protocol, createAt, updateAt FROM %s WHERE uuid = ?`, s.dbName) + row := db.QueryRow(query, uuid) + + var records []AgentStatusRecord + var record AgentStatusRecord + err = row.Scan(&record.ID, &record.UUID, &record.Status, &record.Protocol, &record.CreatedAt, &record.UpdatedAt) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil // Return nil if no record is found + } + return nil, err + } + + return append(records, record), nil +} + +// UpdateRecord updates the status and protocol of a record identified by its UUID. +func (s *AgentStatusDB) UpdateRecord(data *AgentStatusRecord) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`UPDATE %s SET status = ? WHERE uuid = ?`, s.dbName) + _, err = db.Exec(query, data.Status, data.UUID) + if err != nil { + return err + } + + return nil +} + +// DeleteRecord deletes a record from the AgentStatus table based on its UUID. +func (s *AgentStatusDB) DeleteRecord(uuid string) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`DELETE FROM %s WHERE uuid = ?`, s.dbName) + _, err = db.Exec(query, uuid) + if err != nil { + return err + } + + return nil +} + +func (s *AgentStatusDB) DeleteAllRecord() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`DELETE FROM %s`) + _, err = db.Exec(query) + if err != nil { + return err + } + + return nil +} + +func (s *AgentStatusDB) ExistRecord() (bool, error) { + db, err := getDBPtr() + if err != nil { + return false, err + } + defer db.Close() + + query := fmt.Sprintf(`SELECT EXISTS(SELECT 1 FROM %s)`, s.dbName) + var exists bool + err = db.QueryRow(query).Scan(&exists) + if err != nil { + return false, err + } + + return exists, nil +} diff --git a/Model/AgentStatus_test.go b/Model/AgentStatus_test.go new file mode 100644 index 0000000..b3621bc --- /dev/null +++ b/Model/AgentStatus_test.go @@ -0,0 +1,84 @@ +package Model + +import ( + "testing" + "time" +) + +func TestAgentStatusDB(t *testing.T) { + // 새로운 DB 인스턴스 생성 + db := NewAgentStatusDB() + + // 테이블 생성 테스트 + if err := db.CreateTable(); err != nil { + t.Fatalf("Failed to create table: %v", err) + } + + // 테스트 데이터 생성 + record := &AgentStatusRecord{ + UUID: "test-uuid", + Status: Running, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + // InsertRecord 테스트 + if err := db.InsertRecord(record); err != nil { + t.Fatalf("Failed to insert record: %v", err) + } + + // SelectRecords 테스트 - 삽입 후 조회 + records, err := db.SelectRecords() + if err != nil { + t.Fatalf("Failed to select records: %v", err) + } + + if len(records) != 1 { + t.Fatalf("Expected 1 record, got %d", len(records)) + } + + if records[0].UUID != record.UUID || records[0].Status != record.Status { + t.Fatalf("Record mismatch: expected %v, got %v", record, records[0]) + } + + // UpdateRecord 테스트 - 상태 변경 + record.Status = Stopping + if err := db.UpdateRecord(record); err != nil { + t.Fatalf("Failed to update record: %v", err) + } + + // 업데이트 후 다시 조회 + updatedRecords, err := db.SelectRecords() + if err != nil { + t.Fatalf("Failed to select records after update: %v", err) + } + + if updatedRecords[0].Status != Stopping { + t.Fatalf("Expected status 'inactive', got '%s'", updatedRecords[0].Status) + } + + // DeleteRecord 테스트 + if err := db.DeleteRecord(record.UUID); err != nil { + t.Fatalf("Failed to delete record: %v", err) + } + + // 삭제 후 조회하여 레코드가 없는지 확인 + finalRecords, err := db.SelectRecords() + if err != nil { + t.Fatalf("Failed to select records after delete: %v", err) + } + + if len(finalRecords) != 0 { + t.Fatalf("Expected 0 records, got %d", len(finalRecords)) + } + + // ExistRecord 테스트 - 데이터가 없는 상태에서 확인 + exists, err := db.ExistRecord() + if err != nil { + t.Fatalf("Failed to check if record exists: %v", err) + } + + if exists { + t.Fatalf("Expected no records to exist, but some do") + } +} diff --git a/Model/ApplicationDB.go b/Model/ApplicationDB.go new file mode 100644 index 0000000..2ac8c6e --- /dev/null +++ b/Model/ApplicationDB.go @@ -0,0 +1,368 @@ +package Model + +import ( + "encoding/json" + "fmt" + "time" +) + +type ApplicationDB struct { + dbName string +} + +func NewApplicationDB() (*ApplicationDB, error) { + appDB := &ApplicationDB{"Application"} + err := appDB.createTable() + if err != nil { + return nil, err + } + return appDB, nil +} + +type DapplicationDB struct { + ID int `json:"id"` + AgentUUID string `json:"agent_uuid"` + Name string `json:"name"` + Version string `json:"version"` + Language string `json:"language"` + Vendor string `json:"vendor"` + InstallDate2 string `json:"install_date"` + InstallLocation string `json:"install_location"` + InstallSource string `json:"install_source"` + PackageName string `json:"package_name"` + PackageCode string `json:"package_code"` + RegCompany string `json:"reg_company"` + RegOwner string `json:"reg_owner"` + URLInfoAbout string `json:"url_info_about"` + Description string `json:"description"` + IsDeleted bool `json:"is_deleted"` + CreateAt time.Time `json:"create_at"` + UpdateAt time.Time `json:"update_at"` + DeletedAt time.Time `json:"deleted_at"` +} + +func (a *ApplicationDB) createTable() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + sqlStmt := ` + CREATE TABLE IF NOT EXISTS %s ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 내부 ID, 자동 증가 + AgentUUID VARCHAR(255), + Name VARCHAR(255), -- 제품 이름 + Version VARCHAR(50), -- 제품 버전 + Language VARCHAR(10), -- 제품의 언어 + Vendor VARCHAR(255), -- 제품 공급자 + InstallDate2 VARCHAR(20), -- 설치 날짜 + InstallLocation TEXT, -- 패키지 설치 위치 + InstallSource TEXT, -- 설치 소스 위치 + PackageName VARCHAR(255), -- 원래 패키지 이름 + PackageCode VARCHAR(255) UNIQUE NOT NULL, -- 패키지 식별자 UUID + RegCompany VARCHAR(255), -- 제품을 사용하는 것으로 등록된 회사 이름 + RegOwner VARCHAR(255), -- 제품을 사용하는 것으로 등록된 사용자 이름 + URLInfoAbout TEXT, -- 제품에 대한 정보가 제공되는 URL + Description TEXT, -- 제품 설명 + isDeleted bool DEFAULT FALSE, -- apllication 제거 여부를 파악함 + createAt DATETIME DEFAULT CURRENT_TIMESTAMP, -- 레코드 생성 시간 + updateAt DATETIME DEFAULT CURRENT_TIMESTAMP, -- 레코드 업데이트 시간 + deletedAt DATETIME DEFAULT CURRENT_TIMESTAMP -- 제거된 시간 + ); + ` + sqlStmt = fmt.Sprintf(sqlStmt, a.dbName) + + _, err = db.Exec(sqlStmt) + if err != nil { + return err + } + + sqlModifyTrigger := fmt.Sprintf(` + CREATE TRIGGER IF NOT EXISTS update_ModificationTime + AFTER UPDATE ON %s + FOR EACH ROW + BEGIN + UPDATE %s SET + updateAt = CURRENT_TIMESTAMP + WHERE id = NEW.id; + END; + `, a.dbName, a.dbName) + + _, err = db.Exec(sqlModifyTrigger) + if err != nil { + return err + } + + return nil +} + +/* +refer : https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa394378(v=vs.85) +class Win32_Product : CIM_Product + + { + uint16 AssignmentType; + string Caption; + string Description; + string IdentifyingNumber; + string InstallDate; + datetime InstallDate2; + string InstallLocation; + sint16 InstallState; + string HelpLink; + string HelpTelephone; + string InstallSource; + string Language; + string LocalPackage; + string Name; + string PackageCache; + string PackageCode; + string PackageName; + string ProductID; + string RegOwner; + string RegCompany; + string SKUNumber; + string Transforms; + string URLInfoAbout; + string URLUpdateInfo; + string Vendor; + uint32 WordCount; + string Version; + }; +*/ +type Win32_Product struct { + Name string // 제품 이름 + Version string // 제품 버전 + Language string // 제품의 언어 + Vendor string // 제품 공급자 + InstallDate2 string // 설치 날짜 + InstallLocation string // 패키지 설치 위치 + InstallSource string // 설치 소스 위치 + PackageName string // 원래 패키지 이름 + PackageCode string // 패키지 식별자 + RegCompany string // 제품을 사용하는 것으로 등록된 회사 이름 + RegOwner string // 제품을 사용하는 것으로 등록된 사용자 이름 + URLInfoAbout string // 제품에 대한 정보가 제공되는 URL + Description string // 제품 설명 +} + +func (a *ApplicationDB) InsertRecord(data *DapplicationDB) error { + // 데이터베이스 연결 + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + // PackageCode 중복 확인 쿼리 + checkQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE PackageCode = ? AND AgentUUID = ?`, a.dbName) + var count int + err = db.QueryRow(checkQuery, data.PackageCode, data.AgentUUID).Scan(&count) + if err != nil { + return err + } + + if count > 0 { + // 중복된 항목이 있으면 업데이트 + return a.UpdateByPackageCode(data) + } + //fmt.Println("dedbug") + + // 중복된 항목이 없으면 삽입 + query := fmt.Sprintf(`INSERT INTO %s (AgentUUID, Name, Version, Language, Vendor, + InstallDate2, InstallLocation, InstallSource, PackageName, PackageCode, RegCompany, + RegOwner, URLInfoAbout, Description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, a.dbName) + + stmt, err := db.Prepare(query) + if err != nil { + return err + } + defer stmt.Close() + + _, err = stmt.Exec(data.AgentUUID, data.Name, data.Version, data.Language, data.Vendor, + data.InstallDate2, data.InstallLocation, data.InstallSource, data.PackageName, + data.PackageCode, data.RegCompany, data.RegOwner, data.URLInfoAbout, data.Description) + + if err != nil { + return err + } + + return nil +} + +func (a *ApplicationDB) UpdateByPackageCode(data *DapplicationDB) error { + // 데이터베이스 연결 + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`UPDATE %s SET Name = ?, AgentUUID = ?, Version = ?, Language = ?, Vendor = ?, + InstallDate2 = ?, InstallLocation = ?, InstallSource = ?, PackageName = ?, RegCompany = ?, + RegOwner = ?, URLInfoAbout = ?, Description = ? WHERE PackageCode = ?`, a.dbName) + + _, err = db.Exec(query, data.Name, data.AgentUUID, data.Version, data.Language, data.Vendor, + data.InstallDate2, data.InstallLocation, data.InstallSource, data.PackageName, + data.RegCompany, data.RegOwner, data.URLInfoAbout, data.Description, data.PackageCode) + + if err != nil { + return err + } + return nil +} + +func (s *ApplicationDB) SelectByPackageCode(packageCode string) (*DapplicationDB, error) { + db, err := getDBPtr() + if err != nil { + return nil, err + } + defer db.Close() + + query := fmt.Sprintf(`SELECT * FROM %s WHERE PackageCode = '%s' LIMIT 1`, s.dbName, packageCode) + row, err := db.Query(query) + defer row.Close() + if err != nil { + return nil, err + } + + var data DapplicationDB + + if row.Next() == false { + return &DapplicationDB{PackageCode: "-1"}, nil + } + err = row.Scan(&data.ID, &data.AgentUUID, &data.Name, &data.Version, &data.Language, &data.Vendor, + &data.InstallDate2, &data.InstallLocation, &data.InstallSource, &data.PackageName, + &data.PackageCode, &data.RegCompany, &data.RegOwner, &data.URLInfoAbout, &data.Description, + &data.IsDeleted, &data.CreateAt, &data.UpdateAt, &data.DeletedAt) + + if err != nil { + return nil, err + } + + return &data, nil +} + +func (s *ApplicationDB) DeleteByPackageCode(packageCode string) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`DELETE FROM %s WHERE PackageCode = ?`, s.dbName) + _, err = db.Exec(query, packageCode) + if err != nil { + return err + } + + return nil +} + +func (s *ApplicationDB) DeleteByAgentUUID(agentUUID string) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`DELETE FROM %s WHERE AgentUUID = ?`, s.dbName) + _, err = db.Exec(query, agentUUID) + if err != nil { + return err + } + + return nil +} + +func (s *ApplicationDB) DeleteAllRecords() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`DELETE FROM %s WHERE`, s.dbName) + _, err = db.Exec(query) + if err != nil { + return err + } + + return nil +} + +func (s *ApplicationDB) SelectAllRecords() ([]DapplicationDB, error) { + db, err := getDBPtr() + if err != nil { + return nil, err + } + defer db.Close() + + query := fmt.Sprintf(`SELECT * FROM %s `, s.dbName) + row, err := db.Query(query) + defer row.Close() + if err != nil { + return nil, err + } + + rows := []DapplicationDB{} + + for row.Next() { + var data DapplicationDB + + err = row.Scan(&data.ID, &data.AgentUUID, &data.Name, &data.Version, &data.Language, &data.Vendor, + &data.InstallDate2, &data.InstallLocation, &data.InstallSource, &data.PackageName, + &data.PackageCode, &data.RegCompany, &data.RegOwner, &data.URLInfoAbout, &data.Description, + &data.IsDeleted, &data.CreateAt, &data.UpdateAt, &data.DeletedAt) + if err != nil { + return nil, err + } + rows = append(rows, data) + } + + return rows, nil +} + +// SelectRecordByUUID retrieves a record from the ApplicationDB table by UUID. +func (s *ApplicationDB) SelectRecordByUUID(uuid string) ([]DapplicationDB, error) { + db, err := getDBPtr() + if err != nil { + return nil, err + } + defer db.Close() + + query := fmt.Sprintf(`SELECT * FROM %s WHERE AgentUUID = ?`, s.dbName) + rows, err := db.Query(query, uuid) + if err != nil { + return nil, err + } + defer rows.Close() + + results := []DapplicationDB{} + for rows.Next() { + var data DapplicationDB + err = rows.Scan(&data.ID, &data.AgentUUID, &data.Name, &data.Version, &data.Language, &data.Vendor, + &data.InstallDate2, &data.InstallLocation, &data.InstallSource, &data.PackageName, + &data.PackageCode, &data.RegCompany, &data.RegOwner, &data.URLInfoAbout, &data.Description, + &data.IsDeleted, &data.CreateAt, &data.UpdateAt, &data.DeletedAt) + if err != nil { + return nil, err + } + results = append(results, data) + } + + return results, nil +} + +// ToJSON: 구조체를 JSON 바이트로 마샬링 +func (s *ApplicationDB) ToJSON(data []DapplicationDB) ([]byte, error) { + return json.Marshal(data) +} + +// FromJSON: JSON 바이트를 구조체로 언마샬링 +func (s *ApplicationDB) FromJSON(data []byte) ([]DapplicationDB, error) { + var result []DapplicationDB + err := json.Unmarshal(data, &result) + return result, err +} diff --git a/Model/ApplicationDB_test.go b/Model/ApplicationDB_test.go new file mode 100644 index 0000000..ad1cbde --- /dev/null +++ b/Model/ApplicationDB_test.go @@ -0,0 +1,104 @@ +package Model + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestApplicationDB_createTable(t1 *testing.T) { + type fields struct { + dbName string + } + tests := []struct { + name string + fields fields + }{ + {name: "Test case 1"}, + } + + for _, tt := range tests { + t1.Run(tt.name, func(t *testing.T) { + appdb := NewApplicationDB() + err := appdb.createTable() + if err != nil { + t1.Fatalf("Error creating table: %v", err) + } + + // ========== 검증 ============= + dbPtr, err := getDBPtr() + if err != nil { + t1.Fatalf(appdb.dbName + " : DB 포인터를 가져올 수 없습니다. getDBPtr() 함수 오류\n" + err.Error()) + } + + query := fmt.Sprintf("select * from sqlite_master where name = '%s'", appdb.dbName) + + dsys := &sqlite_master{} + + rst := dbPtr.QueryRow(query).Scan(&dsys.Type, &dsys.name, &dsys.tbl_name, &dsys.rootpage, &dsys.sql) + if rst != nil { + t1.Fatalf(appdb.dbName + " : 생성된 테이블이 존재하지 않습니다.") + } + assert.Equal(t1, dsys.name, appdb.dbName) + }) + } +} + +func TestApplicationDB_CRUD(t1 *testing.T) { + type fields struct { + dbName string + } + tests := []struct { + name string + fields fields + }{ + {name: "Test case 1"}, + } + + for _, tt := range tests { + t1.Run(tt.name, func(t *testing.T) { + }) + } +} + +//func TestApplicationDB_CreateAll(t1 *testing.T) { +// type fields struct { +// dbName string +// } +// tests := []struct { +// name string +// fields fields +// }{ +// {name: "Test case 1"}, +// } +// for _, tt := range tests { +// t1.Run(tt.name, func(t *testing.T) { +// // ============ Create 테스트 ================ +// appdb := NewApplicationDB() +// appdb.createTable() +// +// wind32 := getApplicationList() +// for _, wind := range wind32 { +// data := DapplicationDB{} +// data.Name = wind.Name +// data.Description = wind.Description +// data.Version = wind.Version +// data.Vendor = wind.Vendor +// data.InstallDate2 = wind.InstallDate2 +// data.InstallLocation = wind.InstallLocation +// data.InstallSource = wind.InstallSource +// data.Language = wind.Language +// data.PackageCode = wind.PackageCode +// data.PackageName = wind.PackageName +// data.RegCompany = wind.RegCompany +// data.RegOwner = wind.RegOwner +// data.URLInfoAbout = wind.URLInfoAbout +// +// err := appdb.insertRecord(data) +// if err != nil { +// t.Fatalf(appdb.dbName + " : insert 에러\n" + err.Error()) +// } +// } +// }) +// } +//} diff --git a/Model/FileMetaDB.go b/Model/FileMetaDB.go deleted file mode 100644 index 537f47c..0000000 --- a/Model/FileMetaDB.go +++ /dev/null @@ -1,91 +0,0 @@ -package Model - -// -//import ( -// "log" -//) -// -//import ( -// _ "github.com/mattn/go-sqlite3" -//) -// -//type FileMetaDB struct { -// dbPath string -//} -// -//func NewFileMetaDB() (metaTable *FileMetaDB) { -// -// metaTable = &FileMetaDB{ -// dbPath: "file:db.db?cache=shared", -// } -// -// metaTable.createTable() -// -// return metaTable -//} -// -///* -//refer : https://github.com/steffenfritz/FileTrove?tab=readme-ov-file -//action : 인덱싱을 위한 데이터 테이블을 생성합니다. -//- 인덱싱 테이블을 만들 때, -// -//단일 바이너리 애플리케이션이 디렉터리 트리를 탐색하고 Siegfried를 사용하여 모든 일반 파일을 형식별로 식별합니다. -//이때 다음 정보를 제공합니다 : -//1. MIME type -//- 파일의 인터넷 미디어 타입을 나타내며, 파일의 형식을 식별합니다. -//- ex. 이미지 파일은 image/jpeg, -//2. PRONOM identifier -//3. Format version -//4. Identification proof and note -//5. filename extension -// -//os.Stat() is giving you the -//6. File size -//7. File creation time -//8. File modification time -//9. File access time -// -//and the same for directories -//Furthermore it creates and calculates -// -//10. UUIDv4s as unique identifiers (not stable across sessions) -//11. hash sums (md5, sha1, sha256, sha512 and blake2b-512) -//12. the entropy of each file (up to 1GB) -//13. and it extracts some EXIF metadata and -//14. you can add your own DublinCore Elements metadata to scans. -//15. A very powerful feature is FileTrove's ability to consume YARA-X rule files. -//16. FileTrove also checks if the file is in the NSRL. -//*/ -//func (t *FileMetaDB) createTable() { -// sqlStmt := ` -// CREATE TABLE IF NOT EXISTS FileMetadata ( -// id INTEGER PRIMARY KEY AUTOINCREMENT, -- 내부 ID, 자동 증가 -// uuid TEXT NOT NULL unique, -- UUIDv4 -// path TEXT NOT NULL, -- 파일 경로 -// name TEXT NOT NULL, -- 파일명 -// type TEXT NOT NULL, -- 파일 유형 (파일 또는 디렉터리) -// mime_type TEXT, -- MIME 타입 -// pronom_id TEXT, -- PRONOM 식별자 -// format_version TEXT, -- 포맷 버전 -// identification_proof TEXT, -- 식별 증거 및 노트 -// file_extension TEXT, -- 파일 확장자 -// file_size INTEGER, -- 파일 크기 (바이트) -// creation_time DATETIME, -- 파일 생성 시간 -// modification_time DATETIME, -- 파일 수정 시간 -// access_time DATETIME, -- 파일 접근 시간 -// md5 TEXT, -- MD5 해시 -// sha256 TEXT, -- SHA-256 해시 -// entropy REAL, -- 엔트로피 (최대 1GB 파일) -// exif_metadata TEXT, -- EXIF 메타데이터 -// dublin_core TEXT, -- Dublin Core 메타데이터 -// yara_x_result TEXT, -- YARA-X 룰 결과 -// nsrl_check BOOLEAN, -- NSRL 체크 여부 -// scan_date DATETIME DEFAULT CURRENT_TIMESTAMP -- 스캔 날짜 -// ); -// ` -// -// _, err := t.db.Exec(sqlStmt) -// if err != nil { -// log.Fatal(err) -// } -//} diff --git a/Model/JobData.go b/Model/JobData.go new file mode 100644 index 0000000..07200d0 --- /dev/null +++ b/Model/JobData.go @@ -0,0 +1,227 @@ +package Model + +import ( + "database/sql" + "fmt" + "time" +) + +type JobData struct { + Id int `json:"id"` + ProcedureID string `json:"procedureID"` + AgentUUID string `json:"agentUUID"` + MessageUUID string `json:"messageUUID"` + CreateAt time.Time `json:"createAt"` +} + +/** +Chatgpt 이용해서 생성 +*/ + +type JobDB struct { + dbName string +} + +// NewJobDB: SQLite DB를 초기화하고 테이블을 생성하는 함수 +func NewJobDB() (*JobDB, error) { + jd := &JobDB{dbName: "jobs"} + err := jd.createTableIfNotExists() + if err != nil { + return nil, err + } + return jd, nil +} + +// createTableIfNotExists: jobs 테이블이 없으면 생성 +func (jd *JobDB) createTableIfNotExists() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + createTableSQL := ` + CREATE TABLE IF NOT EXISTS jobs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ProcedureID TEXT, + AgentUUID TEXT, + MessageUUID TEXT, + CreateAt DATETIME + );` + + _, err = db.Exec(createTableSQL) + if err != nil { + return fmt.Errorf("create table failed: %w", err) + } + return nil +} + +func (jd *JobDB) InsertJobData(jobData *JobData) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + jobData.CreateAt = time.Now() + insertSQL := `INSERT INTO jobs (ProcedureID, AgentUUID, MessageUUID, CreateAt) + VALUES (?, ?, ?, ?)` + _, err = db.Exec(insertSQL, jobData.ProcedureID, jobData.AgentUUID, jobData.MessageUUID, jobData.CreateAt) + fmt.Println(jobData) + if err != nil { + return fmt.Errorf("insert job failed: %w", err) + } + return nil +} + +// GetJobDataByAgentUUID: AgentUUID 기반으로 JobData 조회 +func (jd *JobDB) SelectJobDataByAgentUUID(agentUUID string) (*JobData, error, bool) { + db, err := getDBPtr() + if err != nil { + return nil, err, false + } + defer db.Close() + + selectSQL := `SELECT id, ProcedureID, AgentUUID, MessageUUID, CreateAt FROM jobs WHERE AgentUUID = ?` + rows, err := db.Query(selectSQL, agentUUID) + if err != nil { + return nil, err, false + } + defer rows.Close() + if rows.Next() == false { + // 결과가 없다면, + return &JobData{}, nil, false + } + + var job *JobData = &JobData{} + err = rows.Scan(&job.ProcedureID, &job.Id, &job.AgentUUID, &job.MessageUUID, &job.CreateAt) // 첫 행에만 적용 + if err != nil { + return nil, err, true + } + + return job, nil, true +} + +func (jd *JobDB) SelectAllJobData() ([]JobData, error) { + + db, err := getDBPtr() + if err != nil { + return nil, err + } + defer db.Close() + + selectSQL := `SELECT id, ProcedureID, AgentUUID, MessageUUID, CreateAt FROM jobs` + + rows, err := db.Query(selectSQL) + defer rows.Close() + if err != nil { + return nil, err + } + if rows.Next() == false { + return []JobData{}, nil + } + + jobs := []JobData{} + + var job_init *JobData = &JobData{} + err = rows.Scan(&job_init.Id, &job_init.ProcedureID, &job_init.AgentUUID, &job_init.MessageUUID, &job_init.CreateAt) // 첫 행에만 적용 + if err != nil { + return nil, err + } + jobs = append(jobs, *job_init) + + for rows.Next() == true { + var job *JobData = &JobData{} + err = rows.Scan(&job.Id, &job.ProcedureID, &job.AgentUUID, &job.MessageUUID, &job.CreateAt) // 첫 행에만 적용 + if err != nil { + return nil, err + } + jobs = append(jobs, *job) + } + + return jobs, nil +} + +func (jd *JobDB) PopbyAgentUUID(agentUUID string) (*JobData, error, bool) { + db, err := getDBPtr() + if err != nil { + return nil, err, false + } + defer db.Close() + + // 쿼리문 수정: WHERE 절을 추가하고 ORDER BY를 사용하여 CreateAt을 기준으로 내림차순 정렬 + selectSQL := ` + SELECT id, ProcedureID, AgentUUID, MessageUUID, CreateAt + FROM jobs + WHERE AgentUUID = ? + ORDER BY CreateAt DESC + LIMIT 1 + ` + + // QueryRow를 사용하여 한 행만 가져옵니다. + row := db.QueryRow(selectSQL, agentUUID) + + var job JobData + err = row.Scan(&job.Id, &job.ProcedureID, &job.AgentUUID, &job.MessageUUID, &job.CreateAt) + if err != nil { + if err == sql.ErrNoRows { + // 일치하는 행이 없는 경우 + return &JobData{}, nil, false + } + return nil, err, false + } + + // 해당 JobData를 삭제 + err = jd.DeleteJobDataById(job.Id) + if err != nil { + return nil, err, false + } + + return &job, nil, true +} + +// DeleteJobDataByMessageUUID: MessageUUID 기반으로 JobData 삭제 +func (jd *JobDB) DeleteJobDataByMessageUUID(messageUUID string) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + deleteSQL := `DELETE FROM jobs WHERE MessageUUID = ?` + _, err = db.Exec(deleteSQL, messageUUID) + if err != nil { + return fmt.Errorf("delete job failed: %w", err) + } + return nil +} + +func (jd *JobDB) DeleteJobDataById(id int) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + deleteSQL := `DELETE FROM jobs WHERE id = ?` + _, err = db.Exec(deleteSQL, id) + if err != nil { + return fmt.Errorf("delete job failed: %w", err) + } + return nil +} + +// DeleteJobDataByMessageUUID: MessageUUID 기반으로 JobData 삭제 +func (jd *JobDB) DeleteAllJobData() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + deleteSQL := `DELETE FROM jobs` + _, err = db.Exec(deleteSQL) + if err != nil { + return fmt.Errorf("delete job failed: %w", err) + } + return nil +} diff --git a/Model/OperationLog.go b/Model/OperationLog.go new file mode 100644 index 0000000..aebc74f --- /dev/null +++ b/Model/OperationLog.go @@ -0,0 +1,147 @@ +package Model + +import ( + "context" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "time" +) + +/* +빠른 개발을 위해 chatgpt 를 사용한 개발 코드입니다. +검토자 : 허남정 +*/ + +// OperationLogDocument 구조체 정의 +type OperationLogDocument struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + AgentUUID string `bson:"agentUUID"` + ProcedureID string `bson:"procedureID"` + MessageUUID string `bson:"messageUUID"` + ConductAt time.Time `bson:"conductAt"` + ExitCode int `bson:"exitCode"` + Log string `bson:"log"` + Command string `bson:"command"` // Command 필드로 변경 +} + +const ( + EXIT_SUCCESS = 1 + EXIT_Unknown = 0 + EXIT_FAIL = -1 +) + +type OperationLogDB struct { + DBNAME string +} + +func NewOperationLogDB() (*OperationLogDB, error) { + return &OperationLogDB{DBNAME: "execLog"}, nil +} + +func (repo *OperationLogDB) InsertDocument(log *OperationLogDocument) (*mongo.InsertOneResult, error) { + db, err := getCollectionPtr() + if err != nil { + return nil, err + } + ptrdb := db.Collection(repo.DBNAME) + // Command 필드를 기본값으로 설정 (필요에 따라 변경) + result, err := ptrdb.InsertOne(context.TODO(), log) + fmt.Println(log) + if err != nil { + return nil, err + } + + fmt.Println("Inserted document with ID:", result.InsertedID) + return result, nil +} + +func (repo *OperationLogDB) SelectDocumentById(id string) (*OperationLogDocument, error) { + db, err := getCollectionPtr() + if err != nil { + return nil, err + } + ptrdb := db.Collection(repo.DBNAME) + + var OperationLogDocument OperationLogDocument + filter := bson.M{"messageUUID": id} + + err = ptrdb.FindOne(context.TODO(), filter).Decode(&OperationLogDocument) + if err != nil { + return nil, err + } + + return &OperationLogDocument, nil +} + +func (repo *OperationLogDB) SelectAllDocuments() ([]OperationLogDocument, error) { + documents := []OperationLogDocument{} + db, err := getCollectionPtr() + if err != nil { + return documents, err + } + ptrdb := db.Collection(repo.DBNAME) + + filter := bson.M{} + + cursor, err := ptrdb.Find(context.TODO(), filter) + if err != nil { + return documents, err + } + + defer cursor.Close(context.TODO()) + + for cursor.Next(context.TODO()) { + var doc OperationLogDocument + if err := cursor.Decode(&doc); err != nil { + return nil, err + } + documents = append(documents, doc) + } + + if err := cursor.Err(); err != nil { + return documents, err + } + + return documents, nil +} + +func (repo *OperationLogDB) UpdateDocumentByInstID(id string, updateData bson.M) (*mongo.UpdateResult, error) { + db, err := getCollectionPtr() + if err != nil { + return nil, err + } + ptrdb := db.Collection(repo.DBNAME) + + filter := bson.M{"messageUUID": id} + update := bson.M{ + "$set": updateData, + } + + result, err := ptrdb.UpdateOne(context.TODO(), filter, update) + if err != nil { + return nil, err + } + + fmt.Println("Updated document count:", result.ModifiedCount) + return result, nil +} + +func (repo *OperationLogDB) DeleteAllDocument() (*mongo.DeleteResult, error) { + db, err := getCollectionPtr() + if err != nil { + return nil, err + } + ptrdb := db.Collection(repo.DBNAME) + + filter := bson.M{} + + result, err := ptrdb.DeleteMany(context.TODO(), filter) + if err != nil { + return nil, err + } + + fmt.Println("Deleted document count:", result.DeletedCount) + return result, nil +} diff --git a/Model/ProgrameNameDB.go b/Model/ProgrameNameDB.go new file mode 100644 index 0000000..7e13a47 --- /dev/null +++ b/Model/ProgrameNameDB.go @@ -0,0 +1,211 @@ +package Model + +import ( + "encoding/json" + "fmt" + "time" +) + +type ProgramsDB struct { + dbName string +} + +func NewProgramsDB() (*ProgramsDB, error) { + db := &ProgramsDB{dbName: "Programs"} + err := db.CreateTable() + if err != nil { + return nil, err + } + return db, nil +} + +type ProgramsRecord struct { + ID int `json:"id"` + AgentUUID string `json:"agent_uuid"` + FileName string `json:"file_name"` + CreateAt time.Time `json:"create_at"` + UpdateAt time.Time `json:"update_at"` + DeletedAt time.Time `json:"deleted_at"` +} + +func (a *ProgramsDB) CreateTable() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + sqlStmt := ` + CREATE TABLE IF NOT EXISTS %s ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 내부 ID, 자동 증가 + AgentUUID VARCHAR(255), + FileName VARCHAR(255), + createAt DATETIME DEFAULT CURRENT_TIMESTAMP, -- 레코드 생성 시간 + updateAt DATETIME DEFAULT CURRENT_TIMESTAMP, -- 레코드 업데이트 시간 + deletedAt DATETIME DEFAULT CURRENT_TIMESTAMP -- 제거된 시간 + ); + ` + sqlStmt = fmt.Sprintf(sqlStmt, a.dbName) + + _, err = db.Exec(sqlStmt) + if err != nil { + return err + } + + sqlModifyTrigger := fmt.Sprintf(` + CREATE TRIGGER IF NOT EXISTS update_ModificationTime + AFTER UPDATE ON %s + FOR EACH ROW + BEGIN + UPDATE %s SET + updateAt = CURRENT_TIMESTAMP + WHERE id = NEW.id; + END; + `, a.dbName, a.dbName) + + _, err = db.Exec(sqlModifyTrigger) + if err != nil { + return err + } + + return nil +} + +// InsertRecord: 레코드 삽입 +func (a *ProgramsDB) InsertRecord(agentUUID string, fileName string) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`INSERT INTO %s (AgentUUID, FileName) VALUES (?, ?)`, a.dbName) + _, err = db.Exec(query, agentUUID, fileName) + if err != nil { + return err + } + + return nil +} + +// SelectAllRecords: 모든 레코드를 선택 +func (a *ProgramsDB) SelectAllRecords() ([]ProgramsRecord, error) { + db, err := getDBPtr() + if err != nil { + return nil, err + } + defer db.Close() + + query := fmt.Sprintf(`SELECT id, AgentUUID, FileName, createAt, updateAt, deletedAt FROM %s`, a.dbName) + rows, err := db.Query(query) + if err != nil { + return nil, err + } + defer rows.Close() + + var records []ProgramsRecord + for rows.Next() { + var record ProgramsRecord + err = rows.Scan(&record.ID, &record.AgentUUID, &record.FileName, &record.CreateAt, &record.UpdateAt, &record.DeletedAt) + if err != nil { + return nil, err + } + records = append(records, record) + } + + return records, nil +} + +// SelectRecordsByUUID: 특정 AgentUUID에 따른 레코드를 선택 +func (a *ProgramsDB) SelectRecordsByUUID(agentUUID string) ([]ProgramsRecord, error) { + db, err := getDBPtr() // 데이터베이스 연결 + if err != nil { + return nil, err + } + defer db.Close() + + // 쿼리문 작성 (AgentUUID에 따라 필터링) + query := fmt.Sprintf(`SELECT id, AgentUUID, FileName, createAt, updateAt, deletedAt FROM %s WHERE AgentUUID = ?`, a.dbName) + rows, err := db.Query(query, agentUUID) + if err != nil { + return nil, err + } + defer rows.Close() + + var records []ProgramsRecord + for rows.Next() { + var record ProgramsRecord + // 각 필드의 값을 스캔하여 구조체에 저장 + err = rows.Scan(&record.ID, &record.AgentUUID, &record.FileName, &record.CreateAt, &record.UpdateAt, &record.DeletedAt) + if err != nil { + return nil, err + } + records = append(records, record) + } + + return records, nil +} + +// UpdateRecordByID: ID를 기준으로 레코드 업데이트 +func (a *ProgramsDB) UpdateRecordByID(id int, newFileName string) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`UPDATE %s SET FileName = ?, updateAt = ? WHERE id = ?`, a.dbName) + _, err = db.Exec(query, newFileName, time.Now(), id) + if err != nil { + return err + } + + return nil +} + +// DeleteRecordByID: ID를 기준으로 레코드 삭제 +func (a *ProgramsDB) DeleteRecordByID(id int) error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`DELETE FROM %s WHERE id = ?`, a.dbName) + _, err = db.Exec(query, id) + if err != nil { + return err + } + + return nil +} + +func (a *ProgramsDB) DeleteAllRecords() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`DELETE FROM %s`, a.dbName) + _, err = db.Exec(query) + if err != nil { + return err + } + + return nil +} + +// ToJSON: ProgramNameDB 레코드 리스트를 JSON 바이트로 변환 +func (s *ProgramsDB) ToJSON(data []ProgramsRecord) ([]byte, error) { + // JSON 마샬링하여 []byte로 반환 + return json.Marshal(data) +} + +// FromJSON: JSON 바이트를 ProgramNameDB 레코드 리스트로 변환 +func (s *ProgramsDB) FromJSON(data []byte) ([]ProgramsRecord, error) { + var result []ProgramsRecord + // JSON 언마샬링하여 구조체로 변환 + err := json.Unmarshal(data, &result) + return result, err +} diff --git a/Model/SystemInfoDB.go b/Model/SystemInfoDB.go index 1205243..8fd63a8 100644 --- a/Model/SystemInfoDB.go +++ b/Model/SystemInfoDB.go @@ -1,6 +1,7 @@ package Model import ( + "encoding/json" "errors" "fmt" "time" @@ -11,25 +12,31 @@ type SystemInfoDB struct { } type DsystemInfoDB struct { - id int - Uuid string - HostName string - OsName string - OsVersion string - Family string - Architecture string - KernelVersion string - BootTime time.Time - createAt time.Time - updateAt time.Time + ID int `json:"id"` + Uuid string `json:"uuid"` + HostName string `json:"host_name"` + OsName string `json:"os_name"` + OsVersion string `json:"os_version"` + Family string `json:"family"` + Architecture string `json:"architecture"` + KernelVersion string `json:"kernel_version"` + BootTime time.Time `json:"boot_time"` + IP string `json:"ip"` // IP 추가 + MAC string `json:"mac"` // MAC 추가 + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } -func NewSystemInfoDB() *SystemInfoDB { +func NewSystemInfoDB() (*SystemInfoDB, error) { sysDB := &SystemInfoDB{"SystemInfo"} - return sysDB + err := sysDB.CreateTable() + if err != nil { + return nil, err + } + return sysDB, nil } -func (s *SystemInfoDB) createTable() error { +func (s *SystemInfoDB) CreateTable() error { db, err := getDBPtr() if err != nil { return err @@ -40,7 +47,6 @@ func (s *SystemInfoDB) createTable() error { CREATE TABLE IF NOT EXISTS %s ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 내부 ID, 자동 증가 uuid TEXT NOT NULL unique, -- UUIDv4 - AgentUUID VARCHAR(255), HostName string, OsName string, OsVersion string, @@ -48,6 +54,8 @@ func (s *SystemInfoDB) createTable() error { Architecture string, KernelVersion string, BootTime DATETIME, + IP string, -- IP 추가 + MAC string, -- MAC 추가 createAt DATETIME DEFAULT CURRENT_TIMESTAMP, updateAt DATETIME DEFAULT CURRENT_TIMESTAMP ); @@ -78,14 +86,14 @@ func (s *SystemInfoDB) createTable() error { return nil } -func (s *SystemInfoDB) insertRecord(data *DsystemInfoDB) error { +func (s *SystemInfoDB) InsertRecord(data *DsystemInfoDB) error { // 데이터 베이스에는 단 하나의 Row 만을 보장해야함 - isExist, err := s.existRecord() + isExist, err := s.ExistRecord() if err != nil { return err } if isExist == true { - err = s.updateRecord(data) + err = s.UpdateRecord(data) if err != nil { return err } @@ -98,21 +106,16 @@ func (s *SystemInfoDB) insertRecord(data *DsystemInfoDB) error { } defer db.Close() - query := fmt.Sprintf(`INSERT INTO %s (uuid, HostName, - OsName, OsVersion, Family, Architecture, KernelVersion, - BootTime) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, s.dbName) + query := fmt.Sprintf(`INSERT INTO %s (uuid, HostName, OsName, OsVersion, Family, Architecture, KernelVersion, BootTime, IP, MAC) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, s.dbName) stmt, err := db.Prepare(query) - fmt.Println(query) defer stmt.Close() + if err != nil { return err } - _, err = stmt.Exec(data.Uuid, data.HostName, data.OsName, - data.OsVersion, data.Family, data.Architecture, - data.KernelVersion, data.BootTime) - //fmt.Println(rst.LastInsertId()) - //fmt.Println("debug=-===============") + _, err = stmt.Exec(data.Uuid, data.HostName, data.OsName, data.OsVersion, data.Family, data.Architecture, data.KernelVersion, data.BootTime, data.IP, data.MAC) if err != nil { return err @@ -121,18 +124,14 @@ func (s *SystemInfoDB) insertRecord(data *DsystemInfoDB) error { return nil } -/* -selectRecords()를 통해 반환된 DsystemInfoDB 객체의 값을 수정한 후, -수정된 객체를 updateRecord 함수의 매개변수로 전달합시오 -*/ -func (s *SystemInfoDB) updateRecord(data *DsystemInfoDB) error { +func (s *SystemInfoDB) UpdateRecord(data *DsystemInfoDB) error { db, err := getDBPtr() if err != nil { return err } defer db.Close() - rows, err := s.selectRecords() + rows, err := s.SelectAllRecords() if err != nil { return err } @@ -142,8 +141,8 @@ func (s *SystemInfoDB) updateRecord(data *DsystemInfoDB) error { row := rows[0] data.Uuid = row.Uuid - query := fmt.Sprintf(`UPDATE %s SET HostName = ?, OsName = ?, OsVersion = ?, Family = ?, Architecture = ?, KernelVersion = ?, BootTime = ?`, s.dbName) - _, err = db.Exec(query, data.HostName, data.OsName, data.OsVersion, data.Family, data.Architecture, data.KernelVersion, data.BootTime) + query := fmt.Sprintf(`UPDATE %s SET HostName = ?, OsName = ?, OsVersion = ?, Family = ?, Architecture = ?, KernelVersion = ?, BootTime = ?, IP = ?, MAC = ?`, s.dbName) + _, err = db.Exec(query, data.HostName, data.OsName, data.OsVersion, data.Family, data.Architecture, data.KernelVersion, data.BootTime, data.IP, data.MAC) if err != nil { return err } @@ -151,7 +150,7 @@ func (s *SystemInfoDB) updateRecord(data *DsystemInfoDB) error { return nil } -func (s *SystemInfoDB) deleteRecord(uuid string) error { +func (s *SystemInfoDB) DeleteRecordByUUID(uuid string) error { db, err := getDBPtr() if err != nil { return err @@ -167,7 +166,23 @@ func (s *SystemInfoDB) deleteRecord(uuid string) error { return nil } -func (s *SystemInfoDB) selectRecords() ([]DsystemInfoDB, error) { +func (s *SystemInfoDB) DeleteAllRecord() error { + db, err := getDBPtr() + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf(`DELETE FROM %s`, s.dbName) + _, err = db.Exec(query) + if err != nil { + return err + } + + return nil +} + +func (s *SystemInfoDB) SelectAllRecords() ([]DsystemInfoDB, error) { db, err := getDBPtr() if err != nil { return nil, err @@ -176,18 +191,17 @@ func (s *SystemInfoDB) selectRecords() ([]DsystemInfoDB, error) { query := fmt.Sprintf(`SELECT * FROM %s`, s.dbName) row, err := db.Query(query) + defer row.Close() if err != nil { return nil, err } - var rows []DsystemInfoDB + rows := []DsystemInfoDB{} for row.Next() { var data DsystemInfoDB - err = row.Scan(&data.id, &data.Uuid, &data.HostName, &data.OsName, - &data.OsVersion, &data.Family, &data.Architecture, &data.KernelVersion, - &data.BootTime, &data.createAt, &data.updateAt) + err = row.Scan(&data.ID, &data.Uuid, &data.HostName, &data.OsName, &data.OsVersion, &data.Family, &data.Architecture, &data.KernelVersion, &data.BootTime, &data.IP, &data.MAC, &data.CreatedAt, &data.UpdatedAt) if err != nil { return nil, err } @@ -197,11 +211,34 @@ func (s *SystemInfoDB) selectRecords() ([]DsystemInfoDB, error) { return rows, nil } -/* -* -하나 이상의 row 행이 있는지 검사한다. -*/ -func (s *SystemInfoDB) existRecord() (bool, error) { +func (s *SystemInfoDB) SelectRecordByUUID(uuid string) ([]DsystemInfoDB, error) { + db, err := getDBPtr() + if err != nil { + return nil, err + } + defer db.Close() + + query := fmt.Sprintf(`SELECT * FROM %s WHERE uuid = ?`, s.dbName) + rows, err := db.Query(query, uuid) + if err != nil { + return nil, err + } + defer rows.Close() + + results := []DsystemInfoDB{} + for rows.Next() { + data := DsystemInfoDB{} + err = rows.Scan(&data.ID, &data.Uuid, &data.HostName, &data.OsName, &data.OsVersion, &data.Family, &data.Architecture, &data.KernelVersion, &data.BootTime, &data.IP, &data.MAC, &data.CreatedAt, &data.UpdatedAt) + if err != nil { + return nil, err + } + results = append(results, data) + } + + return results, nil +} + +func (s *SystemInfoDB) ExistRecord() (bool, error) { db, err := getDBPtr() if err != nil { return false, err @@ -217,3 +254,20 @@ func (s *SystemInfoDB) existRecord() (bool, error) { return exists, nil } + +func (s *SystemInfoDB) Unmarshal(data []byte) (*DsystemInfoDB, error) { + + var DsysInfo DsystemInfoDB + err := json.Unmarshal(data, &DsysInfo) + if err != nil { + fmt.Println("Error unmarshaling JSON:", err) + return &DsysInfo, err + } + + err = s.InsertRecord(&DsysInfo) + if err != nil { + return &DsysInfo, err + } + + return &DsysInfo, err +} diff --git a/Model/SystemInfoDB_test.go b/Model/SystemInfoDB_test.go deleted file mode 100644 index 4aee3df..0000000 --- a/Model/SystemInfoDB_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package Model - -import ( - "fmt" - "github.com/stretchr/testify/assert" - "testing" -) - -type sqlite_master struct { - Type string - name string - tbl_name string - rootpage string - sql string -} - -func TestSystemInfoDB_createTable(t *testing.T) { - tests := []struct { - name string - wantErr bool - }{ - {name: "create DB test"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var err error - s := NewSystemInfoDB() - s.createTable() - if err != nil { - t.Fatalf(s.dbName + " : DB를 생성할 수 없습니다. \n" + err.Error()) - } - - // ========== 검증 ============= - dbPtr, err := getDBPtr() - if err != nil { - t.Fatalf(s.dbName + " : DB 포인터를 가져올 수 없습니다. getDBPtr() 함수 오류\n" + err.Error()) - } - - query := fmt.Sprintf("select * from sqlite_master where name = '%s'", s.dbName) - - dsys := &sqlite_master{} - - rst := dbPtr.QueryRow(query).Scan(&dsys.Type, &dsys.name, &dsys.tbl_name, &dsys.rootpage, &dsys.sql) - if rst != nil { - t.Fatalf(s.dbName + " : 생성된 테이블이 존재하지 않습니다.") - } - assert.Equal(t, dsys.name, s.dbName) - - }) - } -} - -func TestNewSystemInfoDB_selectRecord(t *testing.T) { - type fields struct { - dbName string - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - {name: "Select Record in DB"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var err error - s := NewSystemInfoDB() - - data, err := s.selectRecords() - - if err != nil { - t.Fatalf(s.dbName + " : select 오류. Query 재 확인 \n" + err.Error()) - } - - assert.Equal(t, 1, len(data), "Row를 하나 이상 채우고 재시도를 하시오") - fmt.Println("sql select result : \n", data) - - }) - } -} diff --git a/Model/db.go b/Model/db.go index 1d33863..002e54b 100644 --- a/Model/db.go +++ b/Model/db.go @@ -3,6 +3,7 @@ package Model import ( "database/sql" "errors" + _ "modernc.org/sqlite" "time" ) @@ -15,6 +16,7 @@ db파일 : https://medium.com/@SlackBeck/golang-database-sql-패키지-삽질기 */ func getDBPtr() (*sql.DB, error) { dbPath := "file:db.db?cache=shared" + //dbPath := "db.db" db, err := sql.Open("sqlite", dbPath) if err != nil { diff --git a/Model/nosqldb.go b/Model/nosqldb.go new file mode 100644 index 0000000..aa77437 --- /dev/null +++ b/Model/nosqldb.go @@ -0,0 +1,55 @@ +package Model + +import ( + "context" + "fmt" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "log" + "os" + "time" +) + +// Chatgpt 이용함. +/* +MongoDB는 100개의 커넥션 풀을 지원한다. +- 최대 풀 크기 50, 최소 풀 크기 10으로 설정. +- 커넥션 최대 유휴 시간을 30초로 설정. +*/ +func getCollectionPtr() (*mongo.Database, error) { + MONGOID := os.Getenv("MONGODBID") + MONGOPW := os.Getenv("MONGODBPW") + clientOptions := options.Client(). + ApplyURI("mongodb://" + MONGOID + ":" + MONGOPW + "@uskawjdu.iptime.org:17017/"). // MongoDB URI + SetMaxPoolSize(50). // 최대 풀 크기 + SetMinPoolSize(10). // 최소 풀 크기 + SetMaxConnIdleTime(60 * time.Second) // 최대 유휴 시간 + //fmt.Println("mongodb://" + MONGOID + ":" + MONGOPW + "@uskawjdu.iptime.org:17017/") + // 클라이언트 생성 + client, err := mongo.NewClient(clientOptions) + if err != nil { + log.Fatal("Error creating MongoDB client: ", err) + return nil, err + } + + // 연결 설정 + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + + // MongoDB에 연결 + err = client.Connect(ctx) + if err != nil { + log.Fatal("Error connecting to MongoDB: ", err) + return nil, err + } + + database := client.Database("httpsAgent") + + err = client.Ping(context.TODO(), nil) + if err != nil { + return nil, fmt.Errorf("failed to connect to MongoDB: %v", err) + } + + // 클라이언트 연결 반환 + return database, nil +} diff --git a/Model/test.py b/Model/test.py new file mode 100644 index 0000000..c973b34 --- /dev/null +++ b/Model/test.py @@ -0,0 +1,26 @@ +import requests +import json + +# 요청할 URL +url = 'http://127.0.0.1/postInstruction' + +# 전송할 데이터 +data = { + "agentUUID": "12342", + "procedureID": "P_Collection_Kimsuky_001", + "messageUUID": "f5556669-ffbe-4d24-b833-fc9888fdeaef" +} + +# 헤더 설정 +headers = { + 'Content-Type': 'application/json' +} + +# POST 요청 전송 +response = requests.post(url, headers=headers, data=json.dumps(data)) + +# 응답 출력 +if response.status_code == 200: + print("Request succeeded:", response.json()) +else: + print(f"Request failed with status code {response.status_code}: {response.text}") diff --git a/app.go b/app.go deleted file mode 100644 index 31dfb69..0000000 --- a/app.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "github.com/gofiber/fiber/v3" - "github.com/joho/godotenv" - "log" -) - -func main() { - err := godotenv.Load() - if err != nil { - panic("Error loading .env file") - } - - // Initialize a new Fiber app - app := fiber.New() - - // Define a route for the GET method on the root path '/' - app.Get("/", func(c fiber.Ctx) error { - // Send a string response to the client - return c.SendString("Hello, World 👋!") - }) - - // Start the server on port 3000 - log.Fatal(app.Listen(":3000")) - -} diff --git a/db.db b/db.db new file mode 100644 index 0000000..524d8d2 Binary files /dev/null and b/db.db differ diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..d77a6a3 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,89 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/postInst": { + "post": { + "description": "get struct array by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "operationId": "get-struct-array2-by-string", + "parameters": [ + { + "description": "request job", + "name": "loginUserRequest", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/router.InstructionD" + } + } + ], + "responses": {} + } + } + }, + "definitions": { + "router.InstructionD": { + "type": "object", + "properties": { + "agentUUID": { + "type": "string", + "default": "09a4e53c7a1c4b4e9a519f36df29d8a2" + }, + "messageUUID": { + "type": "string", + "default": "32a2833486414af9bc4596caef585538" + }, + "procedureID": { + "type": "string", + "default": "P_DefenseEvasion_Kimsuky_001" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "localhost", + BasePath: "/", + Schemes: []string{}, + Title: "Swagger Example API", + Description: "This is a sample server Petstore server.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..a242eed --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,65 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "host": "localhost", + "basePath": "/", + "paths": { + "/api/postInst": { + "post": { + "description": "get struct array by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "operationId": "get-struct-array2-by-string", + "parameters": [ + { + "description": "request job", + "name": "loginUserRequest", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/router.InstructionD" + } + } + ], + "responses": {} + } + } + }, + "definitions": { + "router.InstructionD": { + "type": "object", + "properties": { + "agentUUID": { + "type": "string", + "default": "09a4e53c7a1c4b4e9a519f36df29d8a2" + }, + "messageUUID": { + "type": "string", + "default": "32a2833486414af9bc4596caef585538" + }, + "procedureID": { + "type": "string", + "default": "P_DefenseEvasion_Kimsuky_001" + } + } + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..c4627bb --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,45 @@ +basePath: / +definitions: + router.InstructionD: + properties: + agentUUID: + default: 09a4e53c7a1c4b4e9a519f36df29d8a2 + type: string + messageUUID: + default: 32a2833486414af9bc4596caef585538 + type: string + procedureID: + default: P_DefenseEvasion_Kimsuky_001 + type: string + type: object +host: localhost +info: + contact: + email: support@swagger.io + name: API Support + url: http://www.swagger.io/support + description: This is a sample server Petstore server. + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + termsOfService: http://swagger.io/terms/ + title: Swagger Example API + version: "1.0" +paths: + /api/postInst: + post: + consumes: + - application/json + description: get struct array by ID + operationId: get-struct-array2-by-string + parameters: + - description: request job + in: body + name: loginUserRequest + required: true + schema: + $ref: '#/definitions/router.InstructionD' + produces: + - application/json + responses: {} +swagger: "2.0" diff --git a/go.mod b/go.mod index 3ef6576..ea7746f 100644 --- a/go.mod +++ b/go.mod @@ -3,32 +3,61 @@ module github.com/your/repo go 1.23.0 require ( + github.com/HTTPs-omma/HTTPsBAS-HSProtocol v1.0.12 + github.com/gin-gonic/gin v1.10.0 + github.com/gofiber/fiber/v3 v3.0.0-beta.3 + github.com/joho/godotenv v1.5.1 github.com/stretchr/testify v1.9.0 - github.com/yusufpapurcu/wmi v1.2.4 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.3 + go.mongodb.org/mongo-driver v1.16.1 + gopkg.in/yaml.v2 v2.4.0 + modernc.org/sqlite v1.33.0 ) require ( + github.com/KyleBanks/depth v1.2.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/bytedance/sonic v1.12.2 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/elastic/go-sysinfo v1.14.1 // indirect - github.com/elastic/go-windows v1.0.0 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/gofiber/fiber/v3 v3.0.0-beta.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/gin-contrib/cors v1.3.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/gofiber/utils/v2 v2.0.0-beta.6 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/joho/godotenv v1.5.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.55.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect @@ -36,18 +65,18 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect - go.mongodb.org/mongo-driver v1.16.1 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/arch v0.10.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/tools v0.25.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.32.0 // indirect modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 11f3f48..52d77b5 100644 --- a/go.sum +++ b/go.sum @@ -1,56 +1,146 @@ +github.com/HTTPs-omma/HTTPsBAS-HSProtocol v1.0.9 h1:LmGvK4r5RyPb3QaJ//3eqmmufu3W/aikssoWsmQLHhw= +github.com/HTTPs-omma/HTTPsBAS-HSProtocol v1.0.9/go.mod h1:7Kv1PDaclTyg/VJ3xPahHfJiYF1GqXIWsbYlGKlUV2c= +github.com/HTTPs-omma/HTTPsBAS-HSProtocol v1.0.11 h1:vHgJG5BhEyxfbNZzXw1DXyQh6x9OU5jIsSHcVRXH+cA= +github.com/HTTPs-omma/HTTPsBAS-HSProtocol v1.0.11/go.mod h1:7Kv1PDaclTyg/VJ3xPahHfJiYF1GqXIWsbYlGKlUV2c= +github.com/HTTPs-omma/HTTPsBAS-HSProtocol v1.0.12 h1:6PXYMZk1DCBQnbiasXXNj2My5p827wFyvP0QZX438Vw= +github.com/HTTPs-omma/HTTPsBAS-HSProtocol v1.0.12/go.mod h1:7Kv1PDaclTyg/VJ3xPahHfJiYF1GqXIWsbYlGKlUV2c= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg= +github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/elastic/go-sysinfo v1.14.1 h1:BpY/Utfz75oKSpsQnbAJmmlnT3gBV9WFsopBEYgjhZY= -github.com/elastic/go-sysinfo v1.14.1/go.mod h1:FKUXnZWhnYI0ueO7jhsGV3uQJ5hiz8OqM5b3oGyaRr8= -github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= -github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/gin-contrib/cors v1.3.0 h1:PolezCc89peu+NgkIWt9OB01Kbzt6IP0J/JvkG6xxlg= +github.com/gin-contrib/cors v1.3.0/go.mod h1:artPvLlhkF7oG06nK8v3U8TNz6IeX+w1uzCSEId5/Vc= +github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofiber/fiber/v3 v3.0.0-beta.3 h1:7Q2I+HsIqnIEEDB+9oe7Gadpakh6ZLhXpTYz/L20vrg= github.com/gofiber/fiber/v3 v3.0.0-beta.3/go.mod h1:kcMur0Dxqk91R7p4vxEpJfDWZ9u5IfvrtQc8Bvv/JmY= github.com/gofiber/utils/v2 v2.0.0-beta.6 h1:ED62bOmpRXdgviPlfTmf0Q+AXzhaTUAFtdWjgx+XkYI= github.com/gofiber/utils/v2 v2.0.0-beta.6/go.mod h1:3Kz8Px3jInKFvqxDzDeoSygwEOO+3uyubTmUa6PqY+0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= @@ -66,64 +156,82 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8= go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= +golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= +golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= -howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= -modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= -modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk= +modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/main.go b/main.go new file mode 100644 index 0000000..d6c1c6b --- /dev/null +++ b/main.go @@ -0,0 +1,181 @@ +package main + +import ( + "fmt" + "github.com/HTTPs-omma/HTTPsBAS-HSProtocol/HSProtocol" + cors2 "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" + "github.com/joho/godotenv" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "github.com/your/repo/Core" + _ "github.com/your/repo/docs" + "github.com/your/repo/router" + "net" + "os" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @termsOfService http://swagger.io/terms/ +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html +// @host localhost +// @BasePath / +// @Path api +var testCommand string = "dir /" + +func main() { + err := godotenv.Load() + if err != nil { + panic("Error loading .env file") + } + + if err != nil { + panic("큐 생성 에러") + } + + // tcp + go TCPServer() + + // Swagger + go Swagger() + + // HTTP + HTTPServer() + +} + +// https://zzihyeon.tistory.com/76 +func Swagger() { + r := gin.Default() + + // Swagger 엔드포인트 + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + r.Use(cors2.New(cors2.Config{ + AllowOrigins: []string{"*"}, // 모든 도메인 허용, 보안 상 필요한 경우 특정 도메인만 허용해야 함 + AllowMethods: []string{"GET", "POST", "PUT", "DELETE"}, // 허용할 HTTP 메서드 + AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "Access-Control-Allow-Origin", "Connection", "Accept-Encoding"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, + })) + r.Run("localhost:8000") + +} + +func TCPServer() { + listener, err := net.Listen("tcp", "localhost:8080") + if err != nil { + fmt.Println("Error starting TCP server:", err) + return + } + defer listener.Close() + + fmt.Println("TCP server listening on port 8080") + + for { + conn, err := listener.Accept() + if err != nil { + fmt.Println("Error accepting connection:", err) + continue + } + + go handleTCPConnection(conn) + } + +} + +func handleTCPConnection(conn net.Conn) { + defer conn.Close() // 함수 호출 종료 후 Close + + buffer := make([]byte, 1024*1024) + for { + n, err := conn.Read(buffer) + if err != nil { + fmt.Println("Error reading from connection:", err) + break + } + if n < 1 { + continue + } + + HSMgr := HSProtocol.NewHSProtocolManager() + hs, err := HSMgr.Parsing(buffer) + if err != nil { + fmt.Println("Error parsing:", err) + ack := &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ERROR_ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + } + rstb, _ := HSMgr.ToBytes(ack) + conn.Write(rstb) + return + } + + //fmt.Println("hs.uuid : ", hs.UUID) + dipt := Core.CommandDispatcher{} + ack, err := dipt.Action(hs) + if err != nil { + ack := &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ERROR_ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + } + rstb, _ := HSMgr.ToBytes(ack) + fmt.Println(err) + conn.Write(rstb) + return + } + rstb, err := HSMgr.ToBytes(ack) + conn.Write(rstb) + return + } +} + +func HTTPServer() { + app := fiber.New() + app.Get("/view/db", func(c fiber.Ctx) error { + // HTML 파일을 읽어서 응답으로 반환 + htmlData, err := os.ReadFile("./view/html/viewdata.html") + if err != nil { + return c.Status(500).SendString("Error loading page") + } + c.Set("Content-Type", "text/html") + return c.Send(htmlData) + }) + + // 효과적인 Cors 에러 해결 + //app.Use(cors.New(cors.Config{ + // AllowCredentials: true, + // AllowOriginsFunc: func(origin string) bool { return true }, + //})) + app.Use(cors.New(cors.Config{ + AllowOrigins: []string{"*", "http://localhost/*"}, + AllowHeaders: []string{"Origin", "Content-Type", "Accept"}, + })) + + router.SetupAPIRoutes(app) + router.SetupViewRoutes(app) + + fmt.Println("HTTP server listening on port 80") + err := app.Listen(":80") + if err != nil { + fmt.Println("Error starting HTTP server:", err) + } + +} diff --git a/router/api.go b/router/api.go new file mode 100644 index 0000000..5d28433 --- /dev/null +++ b/router/api.go @@ -0,0 +1,109 @@ +package router + +import ( + "fmt" + "github.com/HTTPs-omma/HTTPsBAS-HSProtocol/HSProtocol" + "github.com/gofiber/fiber/v3" + "github.com/your/repo/Core" + "github.com/your/repo/Model" + _ "github.com/your/repo/docs" + "time" +) + +// swagger:parameters Request +type InstructionD struct { + ProcedureID string `json:"procedureID" default:"P_DefenseEvasion_Kimsuky_001"` + AgentUUID string `json:"agentUUID" default:"09a4e53c7a1c4b4e9a519f36df29d8a2"` + MessageUUID string `json:"messageUUID" default:"32a2833486414af9bc4596caef585538"` +} + +// @title ManagingServer API +// @version 1.0 +// @description This is a sample server for the ManagingServer project. +// @termsOfService http://managingserver.io/terms/ +// @contact.name ManagingServer API Support +// @contact.url http://managingserver.io/support +// @contact.email support@managingserver.io +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html +// @host localhost:80 +// @BasePath / +// @Path /api +func SetupAPIRoutes(app *fiber.App) { + + app.Post("/api/checkInstReq", checkInstReq) + app.Post("/api/postInst", postInst) +} + +func checkInstReq(ctx fiber.Ctx) error { + req := ctx.Body() + HSMgr := HSProtocol.NewHSProtocolManager() + hs, err := HSMgr.Parsing(req) + if err != nil { + fmt.Println(err) + ctx.Status(404) + return fmt.Errorf("Error parsing:", err) + } + + //fmt.Println("hs.uuid : ", hs.UUID) + dipt := Core.CommandDispatcher{} + ack, err := dipt.Action(hs) + if err != nil { + ack := &HSProtocol.HS{ // HSProtocol.ACK + ProtocolID: hs.ProtocolID, + Command: HSProtocol.ERROR_ACK, + UUID: hs.UUID, + HealthStatus: hs.HealthStatus, + Identification: hs.Identification, + TotalLength: hs.TotalLength, + Data: []byte{}, + } + rstb, _ := HSMgr.ToBytes(ack) + fmt.Println(err) + return ctx.Send(rstb) + } + + rstb, err := HSMgr.ToBytes(ack) + return ctx.Send(rstb) +} + +// postInst example +// +// @Description get struct array by ID +// @ID get-struct-array2-by-string +// @Accept json +// @Produce json +// @Param loginUserRequest body InstructionD true "request job" +// @Router /api/postInst [post] +func postInst(ctx fiber.Ctx) error { + // https://github.com/gofiber/fiber/issues/2958 + InstD := new(InstructionD) + err := ctx.Bind().JSON(InstD) + if err != nil { + fmt.Println("Error marshaling to JSON:", err) + ctx.Status(404) + return ctx.Send([]byte(err.Error())) + } + jobdb, err := Model.NewJobDB() + if err != nil { + return ctx.Send([]byte(err.Error())) + } + //fmt.Println("test : ", InstD.ProcedureID, InstD.AgentUUID, InstD.MessageUUID) + + err = jobdb.InsertJobData(&Model.JobData{ + 0, + InstD.ProcedureID, + InstD.AgentUUID, + InstD.MessageUUID, + time.Now(), + }) + if err != nil { + fmt.Println("Error inserting data:", err) + return fmt.Errorf("Error inserting data into job manager: %v", err) + } + + ctx.Status(200) + return ctx.JSON(fiber.Map{ + "status": true, + }) +} diff --git a/router/view.go b/router/view.go new file mode 100644 index 0000000..9550f4c --- /dev/null +++ b/router/view.go @@ -0,0 +1,248 @@ +package router + +import ( + "fmt" + "github.com/gofiber/fiber/v3" + "github.com/your/repo/Model" +) + +// 통합 구조체 정의 +type CombinedData struct { + OperationLog []Model.OperationLogDocument `json:"operation_log"` + AgentStatus []Model.AgentStatusRecord `json:"agent_status"` + Application []Model.DapplicationDB `json:"application"` + JobData []Model.JobData `json:"job_data"` + SystemInfo []Model.DsystemInfoDB `json:"system_info"` +} + +func SetupViewRoutes(app *fiber.App) { + app.Get("/combined-data", func(ctx fiber.Ctx) error { + dbOperationLog, err := Model.NewOperationLogDB() + if err != nil { + return ctx.Status(404).SendString("Error : " + err.Error()) + } + // 모든 문서 조회 + var dataOL []Model.OperationLogDocument + dataOL, err = dbOperationLog.SelectAllDocuments() + if err != nil { + //dataOL = make([]Model.OperationLogDocument, 0) + //fmt.Println("SelectAllDocuments Error : " + err.Error()) + //return ctx.Status(404).SendString("Error : " + err.Error()) + } + + dbAgentStatus, err := Model.NewAgentStatusDB() + if err != nil { + return ctx.Status(404).SendString("Error : " + err.Error()) + } + dataAS, err := dbAgentStatus.SelectAllRecords() + if err != nil { + fmt.Println("Error selecting records:", err) + return ctx.Status(404).SendString("Error : " + err.Error()) + } + + appdb, err := Model.NewApplicationDB() + if err != nil { + return err + } + dataAPP, err := appdb.SelectAllRecords() + if err != nil { + fmt.Println("Error selecting records:", err) + ctx.Status(404) + return ctx.SendString("Error : " + err.Error()) + } + + dbJob, err := Model.NewJobDB() + if err != nil { + return err + } + dataJD, err := dbJob.SelectAllJobData() + if err != nil { + fmt.Println("Error selecting records:", err) + ctx.Status(404) + return ctx.SendString("Error : " + err.Error()) + } + + dbSysInfo, err := Model.NewSystemInfoDB() + if err != nil { + return err + } + dataSys, err := dbSysInfo.SelectAllRecords() + if err != nil { + fmt.Println("Error selecting records:", err) + ctx.Status(404) + return ctx.SendString("Error : " + err.Error()) + } + + combined := CombinedData{ + OperationLog: dataOL, + AgentStatus: dataAS, + Application: dataAPP, + JobData: dataJD, + SystemInfo: dataSys, + } + + return ctx.JSON(combined) + }) + + // /view/OperationLogDB 라우트 정의 + app.Get("/view/OperationLogDB", func(ctx fiber.Ctx) error { + db, err := Model.NewOperationLogDB() + if err != nil { + ctx.Status(500) + return ctx.SendString("Failed to connect to the database") + } + + // 모든 문서 조회 + datas, err := db.SelectAllDocuments() + if err != nil { + return ctx.Status(404).JSON(datas) + } + + return ctx.Status(200).JSON(datas) + }) + + app.Get("/view/agentStatus", func(ctx fiber.Ctx) error { + db, err := Model.NewAgentStatusDB() + uuid := ctx.Params("uuid") + var datas []Model.AgentStatusRecord + if uuid != "" { + datas, err = db.SelectRecordByUUID(uuid) + } else { + datas, err = db.SelectAllRecords() + } + if err != nil { + fmt.Println("Error selecting records:", err) + ctx.Status(404) + return ctx.Status(404).SendString("Error : " + err.Error()) + } + + return ctx.Status(200).JSON(datas) + }) + + app.Get("/view/ApplicationDB", func(ctx fiber.Ctx) error { + fmt.Println("ApplicationDB loging") + db, err := Model.NewApplicationDB() + if err != nil { + return err + } + + uuid := ctx.Query("uuid") + var datas []Model.DapplicationDB + fmt.Println(uuid) + if uuid != "" { + datas, err = db.SelectRecordByUUID(uuid) + } else { + datas, err = db.SelectAllRecords() + } + if err != nil { + fmt.Println("Error selecting records:", err) + return ctx.Status(404).JSON(datas) + } + return ctx.Status(200).JSON(datas) + }) + + app.Get("/view/SystemInfoDB", func(ctx fiber.Ctx) error { + //data := ctx.Body() + db, err := Model.NewSystemInfoDB() + if err != nil { + return err + } + + uuid := ctx.Query("uuid") + var datas []Model.DsystemInfoDB + if uuid != "" { + datas, err = db.SelectRecordByUUID(uuid) + } else { + datas, err = db.SelectAllRecords() + } + if err != nil { + fmt.Println("Error selecting records:", err) + return ctx.Status(404).JSON(datas) + } + return ctx.Status(200).JSON(datas) + }) + + app.Get("/view/JobDataDB", func(ctx fiber.Ctx) error { + db, err := Model.NewJobDB() + if err != nil { + return err + } + datas, err := db.SelectAllJobData() + if err != nil { + fmt.Println("Error selecting records:", err) + return ctx.Status(404).JSON(datas) + } + ctx.Status(200) + return ctx.JSON(datas) + }) + + app.Get("/deleted/JobDataDB", func(ctx fiber.Ctx) error { + db, err := Model.NewJobDB() + if err != nil { + return err + } + err = db.DeleteAllJobData() + if err != nil { + fmt.Println("Error Deleted records:", err) + ctx.Status(404) + return nil + } + return nil + }) + + app.Get("/deleted/OperationLog", func(ctx fiber.Ctx) error { + db, err := Model.NewOperationLogDB() + if err != nil { + return err + } + _, err = db.DeleteAllDocument() + if err != nil { + fmt.Println("Error Deleted records:", err) + ctx.Status(404) + return nil + } + return nil + }) + + app.Get("/deleted/SystemInfoDB", func(ctx fiber.Ctx) error { + db, err := Model.NewSystemInfoDB() + if err != nil { + return err + } + err = db.DeleteAllRecord() + if err != nil { + fmt.Println("Error Deleted records:", err) + ctx.Status(404) + return nil + } + return nil + }) + + app.Get("/deleted/ApplicationDB", func(ctx fiber.Ctx) error { + db, err := Model.NewApplicationDB() + if err != nil { + return err + } + err = db.DeleteAllRecords() + if err != nil { + fmt.Println("Error Deleted records:", err) + ctx.Status(404) + return nil + } + return nil + }) + + app.Get("/deleted/AgentStatusDB", func(ctx fiber.Ctx) error { + db, err := Model.NewAgentStatusDB() + if err != nil { + return err + } + err = db.DeleteAllRecord() + if err != nil { + fmt.Println("Error Deleted records:", err) + ctx.Status(404) + return nil + } + return nil + }) +} diff --git a/view/html/viewdata.html b/view/html/viewdata.html new file mode 100644 index 0000000..3543dba --- /dev/null +++ b/view/html/viewdata.html @@ -0,0 +1,319 @@ + + + + + + Combined Data + + + + +

Combined Data Tables

+ + +
+
Operation Log
+
+ + + + + + + + + + + + + + +
IDAgentUUIDProcedureIDMessageUUIDConductAtExitCodeLogCommand
+
+ +
+ + +
+
Agent Status
+
+ + + + + + + + + + + + +
IDUUIDStatusProtocolCreatedAtUpdatedAt
+
+ +
+ + +
+
Application
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
IDAgentUUIDNameVersionLanguageVendorInstallDateInstallLocationInstallSourcePackageNamePackageCodeRegCompanyRegOwnerURLInfoAboutDescriptionIsDeletedCreateAtUpdateAtDeletedAt
+
+ +
+ + +
+
Job Data
+
+ + + + + + + + + + + +
IDProcedureIDAgentUUIDMessageUUIDCreatedAt
+
+ +
+ + +
+
System Info
+
+ + + + + + + + + + + + + + + + + +
IDUUIDHostNameOsNameOsVersionFamilyArchitectureKernelVersionBootTimeCreatedAtUpdatedAt
+
+ +
+ + + + +