From 648c2d0a999202901e417129e5f462e4d24e0185 Mon Sep 17 00:00:00 2001 From: lorsan Date: Mon, 13 Apr 2026 16:37:54 +0300 Subject: [PATCH 1/8] create register agent in agent --- .idea/inspectionProfiles/Project_Default.xml | 6 ++ ....sync-conflict-20260413-162916-XNSB2YU.xml | 74 +++++++++++++++++++ cmd/agent/main.go | 2 +- internal/agent/app/app.go | 13 +++- internal/agent/rpc/mapper.go | 4 + internal/agent/service/agent_service/agent.go | 53 +++++++++++++ internal/agent/utils/config_yaml/config.go | 4 +- internal/hub/rpc/server.go | 4 + 8 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/workspace.sync-conflict-20260413-162916-XNSB2YU.xml create mode 100644 internal/agent/service/agent_service/agent.go diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..5cb71ef --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/workspace.sync-conflict-20260413-162916-XNSB2YU.xml b/.idea/workspace.sync-conflict-20260413-162916-XNSB2YU.xml new file mode 100644 index 0000000..24c3faf --- /dev/null +++ b/.idea/workspace.sync-conflict-20260413-162916-XNSB2YU.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + { + "associatedIndex": 6 +} + + + + + + + + + + + + + + 1775479991162 + + + + + + \ No newline at end of file diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 3a3986a..9370bec 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -1,6 +1,6 @@ package main -import "github.com/lorsanstand/HomeOps-Hub/internal/hub/app" +import "github.com/lorsanstand/HomeOps-Hub/internal/agent/app" func main() { start := app.NewApp() diff --git a/internal/agent/app/app.go b/internal/agent/app/app.go index e16df09..d87f5c8 100644 --- a/internal/agent/app/app.go +++ b/internal/agent/app/app.go @@ -1,16 +1,19 @@ package app import ( + "context" standartlog "log" "github.com/docker/docker/client" "github.com/lorsanstand/HomeOps-Hub/internal/agent/rpc" + "github.com/lorsanstand/HomeOps-Hub/internal/agent/service/agent_service" "github.com/lorsanstand/HomeOps-Hub/internal/agent/service/collector" "github.com/lorsanstand/HomeOps-Hub/internal/agent/service/docker_service" "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/config_yaml" log2 "github.com/lorsanstand/HomeOps-Hub/internal/shared/log" "github.com/rs/zerolog" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) type App struct { @@ -20,6 +23,7 @@ type App struct { } func NewApp() *App { + cfg, err := config_yaml.NewConfig() if err != nil { standartlog.Fatalf("failed get config: %v", err) @@ -31,7 +35,9 @@ func NewApp() *App { } func (a *App) Run() { - GRPCConn, err := grpc.NewClient(a.cfg.GetGRPCAddress()) + ctx := context.Background() + + GRPCConn, err := grpc.NewClient(a.cfg.GetGRPCAddress(), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { a.log.Error().Err(err).Msg("failed to get hub connections") return @@ -50,7 +56,8 @@ func (a *App) Run() { DockerService = docker_service.NewDockerService(DockerClient, a.log) } - Collector := collector.NewCollector(DockerService, a.log) + collect := collector.NewCollector(DockerService, a.log) - Collector.GatherInfoSystem() + agent := agent_service.NewAgentService(collect, conn, "", a.cfg, a.log) + agent.RegisterAgentConn(ctx) } diff --git a/internal/agent/rpc/mapper.go b/internal/agent/rpc/mapper.go index 79c45dc..f4d729a 100644 --- a/internal/agent/rpc/mapper.go +++ b/internal/agent/rpc/mapper.go @@ -33,6 +33,10 @@ func toGRPCCapability(caps []domain.Capability) []*pb.Capability { } func toAgentRegisterDataResponse(response *pb.RegisterAgentResponse) domain.RegisterAgentDataResponse { + if response == nil { + return domain.RegisterAgentDataResponse{} + } + return domain.RegisterAgentDataResponse{ AgentID: response.AgentId, Heartbeat: int(response.HeartbeatIntervalSecond), diff --git a/internal/agent/service/agent_service/agent.go b/internal/agent/service/agent_service/agent.go new file mode 100644 index 0000000..2f28a1c --- /dev/null +++ b/internal/agent/service/agent_service/agent.go @@ -0,0 +1,53 @@ +package agent_service + +import ( + "context" + "fmt" + + "github.com/lorsanstand/HomeOps-Hub/internal/agent/domain" + "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/config_yaml" + "github.com/rs/zerolog" +) + +type Collector interface { + GatherInfoSystem() (domain.HostInfo, []domain.Capability) +} + +type HubConnection interface { + RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentData) (domain.RegisterAgentDataResponse, error) +} + +type AgentService struct { + collect Collector + conn HubConnection + log zerolog.Logger + cfg *config_yaml.AgentConfig + heartBeat int + agentID string +} + +func NewAgentService( + collector Collector, + conn HubConnection, + AgentID string, + cfg *config_yaml.AgentConfig, + logger zerolog.Logger, +) *AgentService { + logger = logger.With().Str("component", "agent.service.agent_serivce").Logger() + + return &AgentService{collect: collector, conn: conn, cfg: cfg, log: logger, agentID: AgentID} +} + +func (a *AgentService) RegisterAgentConn(ctx context.Context) { + info, caps := a.collect.GatherInfoSystem() + AgentID := a.agentID + AgentName := a.cfg.AppName + AgentData := domain.RegisterAgentData{AgentId: AgentID, AgentName: AgentName, Host: info, Capabilities: caps} + + data, err := a.conn.RegisterAgent(ctx, AgentData) + if err != nil { + a.log.Error().Err(err).Msg("failed register agent") + return + } + fmt.Println(data) +} diff --git a/internal/agent/utils/config_yaml/config.go b/internal/agent/utils/config_yaml/config.go index 5472b34..6bccab3 100644 --- a/internal/agent/utils/config_yaml/config.go +++ b/internal/agent/utils/config_yaml/config.go @@ -18,7 +18,7 @@ type AgentConfig struct { } func NewConfig() (*AgentConfig, error) { - yamlFile, err := os.ReadFile("config.yaml") + yamlFile, err := os.ReadFile("agent.dev.yaml") if err != nil { return nil, fmt.Errorf("failed open file: %v", err) } @@ -41,7 +41,7 @@ func (c *AgentConfig) GetLogLevel() zerolog.Level { } func (c *AgentConfig) GetMode() string { - return "PROD" + return "DEV" } func (c *AgentConfig) GetGRPCAddress() string { diff --git a/internal/hub/rpc/server.go b/internal/hub/rpc/server.go index 0692d17..3e5429d 100644 --- a/internal/hub/rpc/server.go +++ b/internal/hub/rpc/server.go @@ -30,3 +30,7 @@ func (h *HubHandler) Ping(ctx context.Context, _ *emptypb.Empty) (*pb.PongRespon h.log.Info().Msg("pong request") return &pb.PongResponse{Pong: "Pong"}, nil } + +func (h *HubHandler) RegisterAgent(ctx context.Context, request *pb.RegisterAgentRequest) (*pb.RegisterAgentResponse, error) { + return &pb.RegisterAgentResponse{AgentId: "12234", HeartbeatIntervalSecond: 2}, nil +} From 76fe95400c82aaa5a2f1cd7e51bbd7b0946dc991 Mon Sep 17 00:00:00 2001 From: lorsan Date: Mon, 13 Apr 2026 20:53:40 +0300 Subject: [PATCH 2/8] create system register agent id --- cmd/agent/main.go | 5 +- internal/agent/app/app.go | 24 +++++-- internal/agent/service/agent_service/agent.go | 13 ++-- internal/agent/utils/config_yaml/config.go | 3 +- internal/agent/utils/settings/settings.go | 68 +++++++++++++++++++ .../{agent/domain/agent.go => domain/info.go} | 0 6 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 internal/agent/utils/settings/settings.go rename internal/{agent/domain/agent.go => domain/info.go} (100%) diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 9370bec..67febdd 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -3,6 +3,9 @@ package main import "github.com/lorsanstand/HomeOps-Hub/internal/agent/app" func main() { - start := app.NewApp() + start, err := app.NewApp() + if err != nil { + return + } start.Run() } diff --git a/internal/agent/app/app.go b/internal/agent/app/app.go index d87f5c8..a588b6d 100644 --- a/internal/agent/app/app.go +++ b/internal/agent/app/app.go @@ -10,6 +10,7 @@ import ( "github.com/lorsanstand/HomeOps-Hub/internal/agent/service/collector" "github.com/lorsanstand/HomeOps-Hub/internal/agent/service/docker_service" "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/config_yaml" + "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/settings" log2 "github.com/lorsanstand/HomeOps-Hub/internal/shared/log" "github.com/rs/zerolog" "google.golang.org/grpc" @@ -17,21 +18,30 @@ import ( ) type App struct { - log zerolog.Logger - cfg *config_yaml.AgentConfig - hubConn *rpc.Connection + log zerolog.Logger + cfg *config_yaml.AgentConfig + settings *settings.Settings + hubConn *rpc.Connection } -func NewApp() *App { +func NewApp() (*App, error) { cfg, err := config_yaml.NewConfig() if err != nil { - standartlog.Fatalf("failed get config: %v", err) + standartlog.Fatalf("failed to get config: %v", err) + return nil, err } log := log2.NewLogger(cfg) + log = log.With().Str("component", "agent.app").Logger() - return &App{cfg: cfg, log: log} + sett, err := settings.ReadSettings(cfg.SettingsPath) + if err != nil { + log.Error().Err(err).Msg("failed to get settings") + return nil, err + } + + return &App{cfg: cfg, log: log, settings: sett}, nil } func (a *App) Run() { @@ -58,6 +68,6 @@ func (a *App) Run() { collect := collector.NewCollector(DockerService, a.log) - agent := agent_service.NewAgentService(collect, conn, "", a.cfg, a.log) + agent := agent_service.NewAgentService(collect, conn, a.settings, a.cfg, a.log) agent.RegisterAgentConn(ctx) } diff --git a/internal/agent/service/agent_service/agent.go b/internal/agent/service/agent_service/agent.go index 2f28a1c..31f89a9 100644 --- a/internal/agent/service/agent_service/agent.go +++ b/internal/agent/service/agent_service/agent.go @@ -6,6 +6,7 @@ import ( "github.com/lorsanstand/HomeOps-Hub/internal/agent/domain" "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/config_yaml" + "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/settings" "github.com/rs/zerolog" ) @@ -23,24 +24,24 @@ type AgentService struct { log zerolog.Logger cfg *config_yaml.AgentConfig heartBeat int - agentID string + settings *settings.Settings } func NewAgentService( collector Collector, conn HubConnection, - AgentID string, + settings *settings.Settings, cfg *config_yaml.AgentConfig, logger zerolog.Logger, ) *AgentService { logger = logger.With().Str("component", "agent.service.agent_serivce").Logger() - return &AgentService{collect: collector, conn: conn, cfg: cfg, log: logger, agentID: AgentID} + return &AgentService{collect: collector, conn: conn, cfg: cfg, log: logger, settings: settings} } func (a *AgentService) RegisterAgentConn(ctx context.Context) { info, caps := a.collect.GatherInfoSystem() - AgentID := a.agentID + AgentID := a.settings.AgentID AgentName := a.cfg.AppName AgentData := domain.RegisterAgentData{AgentId: AgentID, AgentName: AgentName, Host: info, Capabilities: caps} @@ -49,5 +50,9 @@ func (a *AgentService) RegisterAgentConn(ctx context.Context) { a.log.Error().Err(err).Msg("failed register agent") return } + + if err = a.settings.Insert(settings.Settings{AgentID: data.AgentID}); err != nil { + a.log.Warn().Err(err).Msg("failed to save agent id") + } fmt.Println(data) } diff --git a/internal/agent/utils/config_yaml/config.go b/internal/agent/utils/config_yaml/config.go index 6bccab3..9d24106 100644 --- a/internal/agent/utils/config_yaml/config.go +++ b/internal/agent/utils/config_yaml/config.go @@ -14,7 +14,8 @@ type AgentConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` } `yaml:"hub"` - LogLevel string `yaml:"log_level"` + LogLevel string `yaml:"log_level"` + SettingsPath string `yaml:"settings_path"` } func NewConfig() (*AgentConfig, error) { diff --git a/internal/agent/utils/settings/settings.go b/internal/agent/utils/settings/settings.go new file mode 100644 index 0000000..9be1cb0 --- /dev/null +++ b/internal/agent/utils/settings/settings.go @@ -0,0 +1,68 @@ +package settings + +import ( + "encoding/json" + "errors" + "io" + "os" + "path/filepath" +) + +type Settings struct { + AgentID string `json:"agent_id"` + path string +} + +func ReadSettings(path string) (*Settings, error) { + if path == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, err + } + path = filepath.Join(homeDir, ".config", "homeops") + } + + err := os.Mkdir(path, 0755) + if err != nil { + if !errors.Is(err, os.ErrExist) { + return nil, err + } + err = nil + } + + file, err := os.Create(path + "/settings.json") + if err != nil { + if !errors.Is(err, os.ErrExist) { + return nil, err + } + err = nil + } + defer file.Close() + + var settings Settings + + err = json.NewDecoder(file).Decode(&settings) + if err != nil { + if !errors.Is(err, io.EOF) { + return nil, err + } + } + + settings.path = path + "/settings.json" + + return &settings, nil +} + +func (s *Settings) Insert(sett Settings) error { + file, err := os.OpenFile(s.path, os.O_RDWR, 0755) + if err != nil { + return err + } + defer file.Close() + + if err = json.NewEncoder(file).Encode(sett); err != nil { + return err + } + + return nil +} diff --git a/internal/agent/domain/agent.go b/internal/domain/info.go similarity index 100% rename from internal/agent/domain/agent.go rename to internal/domain/info.go From 0b955bb7142365419e3e0bdf91934f007653c54f Mon Sep 17 00:00:00 2001 From: lorsan Date: Tue, 14 Apr 2026 19:48:16 +0300 Subject: [PATCH 3/8] refactor: move domain structure and mapper --- internal/agent/rpc/client.go | 8 +- internal/agent/rpc/mapper.go | 44 ----------- internal/agent/service/agent_service/agent.go | 6 +- internal/agent/service/collector/collector.go | 2 +- internal/agent/service/docker_service/bad.go | 4 +- .../agent/service/docker_service/docker.go | 2 +- internal/domain/mapper.go | 79 +++++++++++++++++++ internal/domain/{info.go => structure.go} | 4 +- internal/hub/app/app.go | 2 +- internal/hub/rpc/server.go | 6 +- internal/hub/service/hub_service/hub.go | 12 +++ 11 files changed, 111 insertions(+), 58 deletions(-) delete mode 100644 internal/agent/rpc/mapper.go create mode 100644 internal/domain/mapper.go rename internal/domain/{info.go => structure.go} (82%) create mode 100644 internal/hub/service/hub_service/hub.go diff --git a/internal/agent/rpc/client.go b/internal/agent/rpc/client.go index e03166c..a945085 100644 --- a/internal/agent/rpc/client.go +++ b/internal/agent/rpc/client.go @@ -4,7 +4,7 @@ import ( "context" pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/domain" + "github.com/lorsanstand/HomeOps-Hub/internal/domain" "github.com/rs/zerolog" "google.golang.org/grpc" ) @@ -32,8 +32,8 @@ func (c *Connection) Hub() pb.HubClient { return c.hub } -func (c *Connection) RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentData) (domain.RegisterAgentDataResponse, error) { - ResponseData, err := c.Hub().RegisterAgent(ctx, new(toAgentRegisterRequest(RegisterData))) +func (c *Connection) RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) { + ResponseData, err := c.Hub().RegisterAgent(ctx, new(domain.ToGRPCAgentRequest(RegisterData))) c.log.Info().Msg("register agent") - return toAgentRegisterDataResponse(ResponseData), err + return domain.ToDomainAgentResponse(ResponseData), err } diff --git a/internal/agent/rpc/mapper.go b/internal/agent/rpc/mapper.go deleted file mode 100644 index f4d729a..0000000 --- a/internal/agent/rpc/mapper.go +++ /dev/null @@ -1,44 +0,0 @@ -package rpc - -import ( - pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/domain" -) - -func toAgentRegisterRequest(request domain.RegisterAgentData) pb.RegisterAgentRequest { - return pb.RegisterAgentRequest{ - AgentId: request.AgentId, - AgentName: request.AgentName, - Host: &pb.HostInfo{ - Hostname: request.Host.Hostname, - Arch: request.Host.Arch, - System: request.Host.System, - }, - Version: request.AgentVersion, - Capability: toGRPCCapability(request.Capabilities), - } -} - -func toGRPCCapability(caps []domain.Capability) []*pb.Capability { - var capability []*pb.Capability - for _, capi := range caps { - capability = append(capability, &pb.Capability{ - Name: capi.Name, - Available: capi.Available, - Version: capi.Version, - Reason: capi.Reason, - }) - } - return capability -} - -func toAgentRegisterDataResponse(response *pb.RegisterAgentResponse) domain.RegisterAgentDataResponse { - if response == nil { - return domain.RegisterAgentDataResponse{} - } - - return domain.RegisterAgentDataResponse{ - AgentID: response.AgentId, - Heartbeat: int(response.HeartbeatIntervalSecond), - } -} diff --git a/internal/agent/service/agent_service/agent.go b/internal/agent/service/agent_service/agent.go index 31f89a9..391c62d 100644 --- a/internal/agent/service/agent_service/agent.go +++ b/internal/agent/service/agent_service/agent.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/domain" "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/config_yaml" "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/settings" + "github.com/lorsanstand/HomeOps-Hub/internal/domain" "github.com/rs/zerolog" ) @@ -15,7 +15,7 @@ type Collector interface { } type HubConnection interface { - RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentData) (domain.RegisterAgentDataResponse, error) + RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) } type AgentService struct { @@ -43,7 +43,7 @@ func (a *AgentService) RegisterAgentConn(ctx context.Context) { info, caps := a.collect.GatherInfoSystem() AgentID := a.settings.AgentID AgentName := a.cfg.AppName - AgentData := domain.RegisterAgentData{AgentId: AgentID, AgentName: AgentName, Host: info, Capabilities: caps} + AgentData := domain.RegisterAgentRequest{AgentId: AgentID, AgentName: AgentName, Host: info, Capabilities: caps} data, err := a.conn.RegisterAgent(ctx, AgentData) if err != nil { diff --git a/internal/agent/service/collector/collector.go b/internal/agent/service/collector/collector.go index 4b613d9..93cea10 100644 --- a/internal/agent/service/collector/collector.go +++ b/internal/agent/service/collector/collector.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/domain" + "github.com/lorsanstand/HomeOps-Hub/internal/domain" "github.com/rs/zerolog" ) diff --git a/internal/agent/service/docker_service/bad.go b/internal/agent/service/docker_service/bad.go index bbd22ca..6c48587 100644 --- a/internal/agent/service/docker_service/bad.go +++ b/internal/agent/service/docker_service/bad.go @@ -1,6 +1,8 @@ package docker_service -import "github.com/lorsanstand/HomeOps-Hub/internal/agent/domain" +import ( + "github.com/lorsanstand/HomeOps-Hub/internal/domain" +) type BadDocker struct { reason string diff --git a/internal/agent/service/docker_service/docker.go b/internal/agent/service/docker_service/docker.go index 991d9e7..487b1e1 100644 --- a/internal/agent/service/docker_service/docker.go +++ b/internal/agent/service/docker_service/docker.go @@ -5,7 +5,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/domain" + "github.com/lorsanstand/HomeOps-Hub/internal/domain" "github.com/rs/zerolog" ) diff --git a/internal/domain/mapper.go b/internal/domain/mapper.go new file mode 100644 index 0000000..9f7a128 --- /dev/null +++ b/internal/domain/mapper.go @@ -0,0 +1,79 @@ +package domain + +import ( + pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" +) + +func ToDomainAgentRequest(request *pb.RegisterAgentRequest) RegisterAgentData { + if request == nil { + return RegisterAgentData{} + } + + return RegisterAgentData{ + AgentId: request.AgentId, + AgentName: request.AgentName, + Host: HostInfo{ + System: request.Host.System, + Hostname: request.Host.Hostname, + Arch: request.Host.Arch, + }, + Capabilities: ToDomainCapabilities(request.Capability), + } +} + +func ToDomainAgentResponse(response *pb.RegisterAgentResponse) RegisterAgentDataResponse { + if response == nil { + return RegisterAgentDataResponse{} + } + + return RegisterAgentDataResponse{ + AgentID: response.AgentId, + Heartbeat: int(response.HeartbeatIntervalSecond), + } +} + +func ToDomainCapabilities(capability []*pb.Capability) []Capability { + var caps []Capability + + for _, capa := range capability { + if capa == nil { + continue + } + + caps = append(caps, Capability{ + Name: capa.Name, + Version: capa.Version, + Reason: capa.Reason, + Available: capa.Available, + }) + } + + return caps +} + +func ToGRPCAgentRequest(request RegisterAgentData) pb.RegisterAgentRequest { + return pb.RegisterAgentRequest{ + AgentId: request.AgentId, + AgentName: request.AgentName, + Host: &pb.HostInfo{ + Hostname: request.Host.Hostname, + Arch: request.Host.Arch, + System: request.Host.System, + }, + Version: request.AgentVersion, + Capability: ToGRPCCapability(request.Capabilities), + } +} + +func ToGRPCCapability(caps []Capability) []*pb.Capability { + var capability []*pb.Capability + for _, capi := range caps { + capability = append(capability, &pb.Capability{ + Name: capi.Name, + Available: capi.Available, + Version: capi.Version, + Reason: capi.Reason, + }) + } + return capability +} diff --git a/internal/domain/info.go b/internal/domain/structure.go similarity index 82% rename from internal/domain/info.go rename to internal/domain/structure.go index cc21078..8a5f062 100644 --- a/internal/domain/info.go +++ b/internal/domain/structure.go @@ -1,6 +1,6 @@ package domain -type RegisterAgentData struct { +type RegisterAgentRequest struct { AgentId string AgentName string AgentVersion string @@ -21,7 +21,7 @@ type Capability struct { Reason string } -type RegisterAgentDataResponse struct { +type RegisterAgentResponse struct { Heartbeat int AgentID string } diff --git a/internal/hub/app/app.go b/internal/hub/app/app.go index 0db6be1..6f2ac61 100644 --- a/internal/hub/app/app.go +++ b/internal/hub/app/app.go @@ -33,7 +33,7 @@ func NewApp() *App { func (a *App) Run() { err := a.hubServe() if err != nil { - a.log.Error().Err(err).Msg("failed start server") + a.log.Error().Err(err).Msg("failed to start the server") } } diff --git a/internal/hub/rpc/server.go b/internal/hub/rpc/server.go index 3e5429d..218f1ea 100644 --- a/internal/hub/rpc/server.go +++ b/internal/hub/rpc/server.go @@ -2,8 +2,10 @@ package rpc import ( "context" + "fmt" pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" + "github.com/lorsanstand/HomeOps-Hub/internal/domain" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/emptypb" @@ -32,5 +34,7 @@ func (h *HubHandler) Ping(ctx context.Context, _ *emptypb.Empty) (*pb.PongRespon } func (h *HubHandler) RegisterAgent(ctx context.Context, request *pb.RegisterAgentRequest) (*pb.RegisterAgentResponse, error) { - return &pb.RegisterAgentResponse{AgentId: "12234", HeartbeatIntervalSecond: 2}, nil + data := domain.ToDomainAgentRequest(request) + fmt.Println(data) + return &pb.RegisterAgentResponse{}, nil } diff --git a/internal/hub/service/hub_service/hub.go b/internal/hub/service/hub_service/hub.go new file mode 100644 index 0000000..15e89d5 --- /dev/null +++ b/internal/hub/service/hub_service/hub.go @@ -0,0 +1,12 @@ +package hub_service + +import ( + "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/rs/zerolog" +) + +type HubService struct { + log zerolog.Logger +} + +func NewHubService(request domain.RegisterAgentRequest) From 97e94587007de214c29fa9742bab1c02775c8ad4 Mon Sep 17 00:00:00 2001 From: lorsan Date: Wed, 15 Apr 2026 17:55:34 +0300 Subject: [PATCH 4/8] feat: new hub service in hub --- internal/agent/utils/settings/settings.go | 24 +++++++++----------- internal/domain/mapper.go | 18 +++++++++------ internal/hub/app/app.go | 16 ++++++++------ internal/hub/rpc/server.go | 14 +++++++----- internal/hub/service/hub_service/hub.go | 19 +++++++++++++++- internal/hub/utils/hasher/id.go | 27 +++++++++++++++++++++++ 6 files changed, 85 insertions(+), 33 deletions(-) create mode 100644 internal/hub/utils/hasher/id.go diff --git a/internal/agent/utils/settings/settings.go b/internal/agent/utils/settings/settings.go index 9be1cb0..1632780 100644 --- a/internal/agent/utils/settings/settings.go +++ b/internal/agent/utils/settings/settings.go @@ -30,31 +30,29 @@ func ReadSettings(path string) (*Settings, error) { err = nil } - file, err := os.Create(path + "/settings.json") - if err != nil { - if !errors.Is(err, os.ErrExist) { - return nil, err - } - err = nil - } - defer file.Close() - + settingsPath := filepath.Join(path, "settings.json") var settings Settings - err = json.NewDecoder(file).Decode(&settings) + file, err := os.Open(settingsPath) if err != nil { - if !errors.Is(err, io.EOF) { + if !errors.Is(err, os.ErrNotExist) { + return nil, err + } + } else { + defer file.Close() + err = json.NewDecoder(file).Decode(&settings) + if err != nil && !errors.Is(err, io.EOF) { return nil, err } } - settings.path = path + "/settings.json" + settings.path = settingsPath return &settings, nil } func (s *Settings) Insert(sett Settings) error { - file, err := os.OpenFile(s.path, os.O_RDWR, 0755) + file, err := os.OpenFile(s.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } diff --git a/internal/domain/mapper.go b/internal/domain/mapper.go index 9f7a128..2812e8e 100644 --- a/internal/domain/mapper.go +++ b/internal/domain/mapper.go @@ -4,12 +4,12 @@ import ( pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" ) -func ToDomainAgentRequest(request *pb.RegisterAgentRequest) RegisterAgentData { +func ToDomainAgentRequest(request *pb.RegisterAgentRequest) RegisterAgentRequest { if request == nil { - return RegisterAgentData{} + return RegisterAgentRequest{} } - return RegisterAgentData{ + return RegisterAgentRequest{ AgentId: request.AgentId, AgentName: request.AgentName, Host: HostInfo{ @@ -21,12 +21,12 @@ func ToDomainAgentRequest(request *pb.RegisterAgentRequest) RegisterAgentData { } } -func ToDomainAgentResponse(response *pb.RegisterAgentResponse) RegisterAgentDataResponse { +func ToDomainAgentResponse(response *pb.RegisterAgentResponse) RegisterAgentResponse { if response == nil { - return RegisterAgentDataResponse{} + return RegisterAgentResponse{} } - return RegisterAgentDataResponse{ + return RegisterAgentResponse{ AgentID: response.AgentId, Heartbeat: int(response.HeartbeatIntervalSecond), } @@ -51,7 +51,7 @@ func ToDomainCapabilities(capability []*pb.Capability) []Capability { return caps } -func ToGRPCAgentRequest(request RegisterAgentData) pb.RegisterAgentRequest { +func ToGRPCAgentRequest(request RegisterAgentRequest) pb.RegisterAgentRequest { return pb.RegisterAgentRequest{ AgentId: request.AgentId, AgentName: request.AgentName, @@ -65,6 +65,10 @@ func ToGRPCAgentRequest(request RegisterAgentData) pb.RegisterAgentRequest { } } +func ToGRPCAgentResponse(response RegisterAgentResponse) *pb.RegisterAgentResponse { + return &pb.RegisterAgentResponse{AgentId: response.AgentID, HeartbeatIntervalSecond: int64(response.Heartbeat)} +} + func ToGRPCCapability(caps []Capability) []*pb.Capability { var capability []*pb.Capability for _, capi := range caps { diff --git a/internal/hub/app/app.go b/internal/hub/app/app.go index 6f2ac61..86ab0ef 100644 --- a/internal/hub/app/app.go +++ b/internal/hub/app/app.go @@ -6,15 +6,15 @@ import ( "net" grpcserv "github.com/lorsanstand/HomeOps-Hub/internal/hub/rpc" + "github.com/lorsanstand/HomeOps-Hub/internal/hub/service/hub_service" "github.com/lorsanstand/HomeOps-Hub/internal/shared/config" "github.com/lorsanstand/HomeOps-Hub/internal/shared/log" "github.com/rs/zerolog" ) type App struct { - cfg *config.Config - log zerolog.Logger - server *grpcserv.HubHandler + cfg *config.Config + log zerolog.Logger } func NewApp() *App { @@ -25,9 +25,7 @@ func NewApp() *App { logger := log.NewLogger(cfg) - server := grpcserv.NewHubHandler(logger) - - return &App{cfg: cfg, log: logger, server: server} + return &App{cfg: cfg, log: logger} } func (a *App) Run() { @@ -41,12 +39,16 @@ func (a *App) hubServe() error { address := fmt.Sprintf("0.0.0.0:%v", a.cfg.Port) a.log.Info().Str("address", "http://"+address).Msg("start GRPC server") + hub := hub_service.NewHubService(a.log) + + server := grpcserv.NewHubHandler(hub, a.log) + lis, err := net.Listen("tcp", address) if err != nil { return err } - err = a.server.GrpcServer.Serve(lis) + err = server.GrpcServer.Serve(lis) if err != nil { return err } diff --git a/internal/hub/rpc/server.go b/internal/hub/rpc/server.go index 218f1ea..29fac52 100644 --- a/internal/hub/rpc/server.go +++ b/internal/hub/rpc/server.go @@ -2,7 +2,6 @@ package rpc import ( "context" - "fmt" pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" "github.com/lorsanstand/HomeOps-Hub/internal/domain" @@ -11,14 +10,19 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) +type HubService interface { + RegisterAgent(data domain.RegisterAgentRequest) domain.RegisterAgentResponse +} + type HubHandler struct { pb.UnimplementedHubServer log zerolog.Logger GrpcServer *grpc.Server + hub HubService } -func NewHubHandler(logger zerolog.Logger) *HubHandler { - hub := &HubHandler{log: logger} +func NewHubHandler(HubServ HubService, logger zerolog.Logger) *HubHandler { + hub := &HubHandler{log: logger, hub: HubServ} grpcServer := grpc.NewServer() pb.RegisterHubServer(grpcServer, hub) @@ -35,6 +39,6 @@ func (h *HubHandler) Ping(ctx context.Context, _ *emptypb.Empty) (*pb.PongRespon func (h *HubHandler) RegisterAgent(ctx context.Context, request *pb.RegisterAgentRequest) (*pb.RegisterAgentResponse, error) { data := domain.ToDomainAgentRequest(request) - fmt.Println(data) - return &pb.RegisterAgentResponse{}, nil + resp := h.hub.RegisterAgent(data) + return domain.ToGRPCAgentResponse(resp), nil } diff --git a/internal/hub/service/hub_service/hub.go b/internal/hub/service/hub_service/hub.go index 15e89d5..712c3a2 100644 --- a/internal/hub/service/hub_service/hub.go +++ b/internal/hub/service/hub_service/hub.go @@ -2,6 +2,7 @@ package hub_service import ( "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/internal/hub/utils/hasher" "github.com/rs/zerolog" ) @@ -9,4 +10,20 @@ type HubService struct { log zerolog.Logger } -func NewHubService(request domain.RegisterAgentRequest) +func NewHubService(logger zerolog.Logger) *HubService { + return &HubService{log: logger} +} + +func (h *HubService) RegisterAgent(data domain.RegisterAgentRequest) domain.RegisterAgentResponse { + AgentID := data.AgentId + if data.AgentId == "" { + var err error + AgentID, err = hasher.MakeID(data.Host, data.AgentName) + if err != nil { + h.log.Error().Err(err).Msg("failed create agent id") + AgentID = "" + } + } + + return domain.RegisterAgentResponse{AgentID: AgentID, Heartbeat: 5} +} diff --git a/internal/hub/utils/hasher/id.go b/internal/hub/utils/hasher/id.go new file mode 100644 index 0000000..e28b504 --- /dev/null +++ b/internal/hub/utils/hasher/id.go @@ -0,0 +1,27 @@ +package hasher + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + + "github.com/lorsanstand/HomeOps-Hub/internal/domain" +) + +func newSalt(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + return b, err +} + +func MakeID(info domain.HostInfo, AgentName string) (string, error) { + salt, err := newSalt(10) + if err != nil { + return "", err + } + + s := fmt.Sprintf("v1|host=%s|distro=%s|name=%s|", info.Hostname, info.Arch, AgentName) + h := sha256.Sum256(append([]byte(s), salt...)) + return hex.EncodeToString(h[:16]), nil +} From 4e6e83795a9402926f391992f4406f69a86d3d75 Mon Sep 17 00:00:00 2001 From: lorsan Date: Wed, 15 Apr 2026 18:17:07 +0300 Subject: [PATCH 5/8] feat: add scripts generate proto --- scripts/gen_proto.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100755 scripts/gen_proto.sh diff --git a/scripts/gen_proto.sh b/scripts/gen_proto.sh new file mode 100755 index 0000000..9776632 --- /dev/null +++ b/scripts/gen_proto.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +protoc \ + -I api/proto \ + --go_out=api/gen --go_opt=paths=source_relative \ + --go-grpc_out=api/gen --go-grpc_opt=paths=source_relative \ + api/proto/homeops/*.proto \ No newline at end of file From 65d8f883dca3c46e6ececada1eb8dc386374b5db Mon Sep 17 00:00:00 2001 From: lorsan Date: Wed, 15 Apr 2026 19:03:51 +0300 Subject: [PATCH 6/8] feat: create migrator --- go.mod | 3 +- go.sum | 6 +++ internal/hub/app/app.go | 24 +++++++++- internal/hub/embed.go | 6 +++ ...20260415151037_create_agent_table.down.sql | 4 ++ .../20260415151037_create_agent_table.up.sql | 14 ++++++ internal/hub/migrator/migrator.go | 45 +++++++++++++++++++ 7 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 internal/hub/embed.go create mode 100644 internal/hub/migrations/20260415151037_create_agent_table.down.sql create mode 100644 internal/hub/migrations/20260415151037_create_agent_table.up.sql create mode 100644 internal/hub/migrator/migrator.go diff --git a/go.mod b/go.mod index 0ea2513..43ffb6b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( require ( github.com/BurntSushi/toml v1.2.1 // indirect - github.com/Microsoft/go-winio v0.4.21 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect @@ -25,6 +25,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang-migrate/migrate/v4 v4.19.1 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 3ab14ab..ba537f0 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Microsoft/go-winio v0.4.21 h1:+6mVbXh4wPzUrl1COX9A+ZCvEpYsOBZ6/+kwDnvLyro= github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -16,6 +18,7 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= @@ -31,6 +34,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= +github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -71,6 +76,7 @@ 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= diff --git a/internal/hub/app/app.go b/internal/hub/app/app.go index 86ab0ef..1349cf4 100644 --- a/internal/hub/app/app.go +++ b/internal/hub/app/app.go @@ -1,10 +1,13 @@ package app import ( + "database/sql" "fmt" standartlog "log" "net" + hubdir "github.com/lorsanstand/HomeOps-Hub/internal/hub" + "github.com/lorsanstand/HomeOps-Hub/internal/hub/migrator" grpcserv "github.com/lorsanstand/HomeOps-Hub/internal/hub/rpc" "github.com/lorsanstand/HomeOps-Hub/internal/hub/service/hub_service" "github.com/lorsanstand/HomeOps-Hub/internal/shared/config" @@ -29,9 +32,28 @@ func NewApp() *App { } func (a *App) Run() { - err := a.hubServe() + migratePGConn, err := sql.Open("pgx", a.cfg.GetURLPostgres()) + if err != nil { + a.log.Error().Err(err).Msg("failed to connect to the database") + return + } + defer migratePGConn.Close() + + mgrt, err := migrator.NewMigrator(hubdir.MigrationsFS, "migrations") + if err != nil { + a.log.Error().Err(err).Msg("failed create migrator") + return + } + + if err = mgrt.ApplyMigrations(migratePGConn); err != nil { + a.log.Error().Err(err).Msg("migrations were not applied") + } + migratePGConn.Close() + + err = a.hubServe() if err != nil { a.log.Error().Err(err).Msg("failed to start the server") + return } } diff --git a/internal/hub/embed.go b/internal/hub/embed.go new file mode 100644 index 0000000..1938b80 --- /dev/null +++ b/internal/hub/embed.go @@ -0,0 +1,6 @@ +package hub + +import "embed" + +//go:embed migrations/*.sql +var MigrationsFS embed.FS diff --git a/internal/hub/migrations/20260415151037_create_agent_table.down.sql b/internal/hub/migrations/20260415151037_create_agent_table.down.sql new file mode 100644 index 0000000..7bd2985 --- /dev/null +++ b/internal/hub/migrations/20260415151037_create_agent_table.down.sql @@ -0,0 +1,4 @@ +DROP INDEX idx_agent_id_id; +DROP INDEX idx_agent_id; + +DROP TABLE agents IF EXISTS agents; \ No newline at end of file diff --git a/internal/hub/migrations/20260415151037_create_agent_table.up.sql b/internal/hub/migrations/20260415151037_create_agent_table.up.sql new file mode 100644 index 0000000..1e7cf7e --- /dev/null +++ b/internal/hub/migrations/20260415151037_create_agent_table.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE agents ( + id BIGINT UNIQUE PRIMARY KEY, + agent_id VARCHAR(32) UNIQUE NOT NULL, + agent_name VARCHAR(255), + architecture VARCHAR(10) NOT NULL, + system VARCHAR(10) NOT NULL, + hostname VARCHAR(100) NOT NULL, + version VARCHAR(10) NOT NULL, + capabilities JSON, + registered_at timestamp without time zone DEFAULT now() NOT NULL +); + +CREATE UNIQUE INDEX idx_agent_id ON agent (id); +CREATE UNIQUE INDEX idx_agent_id_id On agent (agent_id) \ No newline at end of file diff --git a/internal/hub/migrator/migrator.go b/internal/hub/migrator/migrator.go new file mode 100644 index 0000000..39a5830 --- /dev/null +++ b/internal/hub/migrator/migrator.go @@ -0,0 +1,45 @@ +package migrator + +import ( + "database/sql" + "embed" + "errors" + "fmt" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source" + "github.com/golang-migrate/migrate/v4/source/iofs" +) + +type Migrator struct { + srcDriver source.Driver +} + +func NewMigrator(sqlFiles embed.FS, dirname string) (*Migrator, error) { + d, err := iofs.New(sqlFiles, dirname) + if err != nil { + return nil, fmt.Errorf("failed to initialize migration driver: %w", err) + } + return &Migrator{srcDriver: d}, nil +} + +func (m *Migrator) ApplyMigrations(db *sql.DB) error { + driver, err := postgres.WithInstance(db, &postgres.Config{}) + if err != nil { + return fmt.Errorf("unable to create db instance: %w", err) + } + + migrator, err := migrate.NewWithInstance("migration_embeded_sql_files", m.srcDriver, "psql_db", driver) + if err != nil { + return fmt.Errorf("unable to create migration: %w", err) + } + + defer migrator.Close() + + if err = migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { + return fmt.Errorf("unable to apply migrations: %w", err) + } + + return nil +} From 52766cf7e86a2fcbae4149d58245558b41915895 Mon Sep 17 00:00:00 2001 From: lorsan Date: Thu, 16 Apr 2026 13:38:35 +0300 Subject: [PATCH 7/8] feat: add register agent in db --- .../compose/dev/hub.docker-compose.yaml | 24 ++++++++++++ .../compose/prod/hub.docker-compose.yaml | 0 go.mod | 9 ++++- go.sum | 33 ++++++++++------ internal/hub/app/app.go | 23 ++++++++--- .../20260415151037_create_agent_table.up.sql | 6 +-- internal/hub/migrator/migrator.go | 1 + internal/hub/rpc/server.go | 6 +-- internal/hub/service/hub_service/hub.go | 34 ++++++++++++++--- internal/hub/store/sqlc/gen/agent.sql.go | 38 +++++++++++++++++++ internal/hub/store/sqlc/gen/db.go | 32 ++++++++++++++++ internal/hub/store/sqlc/gen/models.go | 21 ++++++++++ internal/hub/store/sqlc/queries/agent.sql | 3 ++ internal/hub/store/store.go | 21 ++++++++++ internal/hub/store/structure.go | 37 ++++++++++++++++++ sqlc.yaml | 12 ++++++ 16 files changed, 271 insertions(+), 29 deletions(-) create mode 100644 deployments/compose/dev/hub.docker-compose.yaml create mode 100644 deployments/compose/prod/hub.docker-compose.yaml create mode 100644 internal/hub/store/sqlc/gen/agent.sql.go create mode 100644 internal/hub/store/sqlc/gen/db.go create mode 100644 internal/hub/store/sqlc/gen/models.go create mode 100644 internal/hub/store/sqlc/queries/agent.sql create mode 100644 internal/hub/store/store.go create mode 100644 internal/hub/store/structure.go create mode 100644 sqlc.yaml diff --git a/deployments/compose/dev/hub.docker-compose.yaml b/deployments/compose/dev/hub.docker-compose.yaml new file mode 100644 index 0000000..709e12d --- /dev/null +++ b/deployments/compose/dev/hub.docker-compose.yaml @@ -0,0 +1,24 @@ +services: + postgres-db: + image: postgres:latest + volumes: + - pgdata:/var/lib/postgresql + environment: + POSTGRES_DB: "${DB_NAME}" + POSTGRES_USER: "${DB_USER}" + POSTGRES_PASSWORD: "${DB_PASS}" + networks: + - homeops-dev + ports: + - "${DB_PORT}:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"] + interval: 5s + retries: 5 + restart: always + +volumes: + pgdata: + +networks: + homeops-dev: \ No newline at end of file diff --git a/deployments/compose/prod/hub.docker-compose.yaml b/deployments/compose/prod/hub.docker-compose.yaml new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod index 43ffb6b..874ab6b 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ go 1.26.1 require ( github.com/docker/docker v28.5.2+incompatible + github.com/golang-migrate/migrate/v4 v4.19.1 github.com/ilyakaznacheev/cleanenv v1.5.0 - github.com/moby/moby v28.5.2+incompatible + github.com/jackc/pgx/v5 v5.9.1 github.com/rs/zerolog v1.35.0 google.golang.org/grpc v1.80.0 google.golang.org/protobuf v1.36.11 @@ -25,8 +26,11 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang-migrate/migrate/v4 v4.19.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -43,6 +47,7 @@ require ( go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.15.0 // indirect diff --git a/go.sum b/go.sum index ba537f0..c265e8f 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/Microsoft/go-winio v0.4.21 h1:+6mVbXh4wPzUrl1COX9A+ZCvEpYsOBZ6/+kwDnvLyro= -github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -16,9 +14,11 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -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.0/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/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= +github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= @@ -46,20 +46,28 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 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/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/moby v28.5.2+incompatible h1:hIn6qcenb3JY1E3STwqEbBvJ8bha+u1LpqjX4CBvNCk= -github.com/moby/moby v28.5.2+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -74,17 +82,18 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -109,8 +118,8 @@ go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpu go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= @@ -120,6 +129,7 @@ golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= @@ -131,6 +141,7 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/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= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= diff --git a/internal/hub/app/app.go b/internal/hub/app/app.go index 1349cf4..0153c0f 100644 --- a/internal/hub/app/app.go +++ b/internal/hub/app/app.go @@ -1,15 +1,18 @@ package app import ( + "context" "database/sql" "fmt" standartlog "log" "net" + "github.com/jackc/pgx/v5/pgxpool" hubdir "github.com/lorsanstand/HomeOps-Hub/internal/hub" "github.com/lorsanstand/HomeOps-Hub/internal/hub/migrator" grpcserv "github.com/lorsanstand/HomeOps-Hub/internal/hub/rpc" "github.com/lorsanstand/HomeOps-Hub/internal/hub/service/hub_service" + "github.com/lorsanstand/HomeOps-Hub/internal/hub/store" "github.com/lorsanstand/HomeOps-Hub/internal/shared/config" "github.com/lorsanstand/HomeOps-Hub/internal/shared/log" "github.com/rs/zerolog" @@ -32,6 +35,7 @@ func NewApp() *App { } func (a *App) Run() { + ctx := context.Background() migratePGConn, err := sql.Open("pgx", a.cfg.GetURLPostgres()) if err != nil { a.log.Error().Err(err).Msg("failed to connect to the database") @@ -47,23 +51,32 @@ func (a *App) Run() { if err = mgrt.ApplyMigrations(migratePGConn); err != nil { a.log.Error().Err(err).Msg("migrations were not applied") + return } migratePGConn.Close() - err = a.hubServe() + pool, err := pgxpool.New(ctx, a.cfg.GetURLPostgres()) + if err != nil { + a.log.Error().Err(err).Msg("failed create db pool") + return + } + + hubStore := store.NewHubStore(pool) + + hubService := hub_service.NewHubService(hubStore, a.log) + + err = a.hubServe(hubService) if err != nil { a.log.Error().Err(err).Msg("failed to start the server") return } } -func (a *App) hubServe() error { +func (a *App) hubServe(hubService *hub_service.HubService) error { address := fmt.Sprintf("0.0.0.0:%v", a.cfg.Port) a.log.Info().Str("address", "http://"+address).Msg("start GRPC server") - hub := hub_service.NewHubService(a.log) - - server := grpcserv.NewHubHandler(hub, a.log) + server := grpcserv.NewHubHandler(hubService, a.log) lis, err := net.Listen("tcp", address) if err != nil { diff --git a/internal/hub/migrations/20260415151037_create_agent_table.up.sql b/internal/hub/migrations/20260415151037_create_agent_table.up.sql index 1e7cf7e..dbba8b9 100644 --- a/internal/hub/migrations/20260415151037_create_agent_table.up.sql +++ b/internal/hub/migrations/20260415151037_create_agent_table.up.sql @@ -1,5 +1,5 @@ CREATE TABLE agents ( - id BIGINT UNIQUE PRIMARY KEY, + id SERIAL PRIMARY KEY, agent_id VARCHAR(32) UNIQUE NOT NULL, agent_name VARCHAR(255), architecture VARCHAR(10) NOT NULL, @@ -10,5 +10,5 @@ CREATE TABLE agents ( registered_at timestamp without time zone DEFAULT now() NOT NULL ); -CREATE UNIQUE INDEX idx_agent_id ON agent (id); -CREATE UNIQUE INDEX idx_agent_id_id On agent (agent_id) \ No newline at end of file +CREATE UNIQUE INDEX idx_agent_id ON agents (id); +CREATE UNIQUE INDEX idx_agent_id_id On agents (agent_id) \ No newline at end of file diff --git a/internal/hub/migrator/migrator.go b/internal/hub/migrator/migrator.go index 39a5830..22338f8 100644 --- a/internal/hub/migrator/migrator.go +++ b/internal/hub/migrator/migrator.go @@ -5,6 +5,7 @@ import ( "embed" "errors" "fmt" + _ "github.com/jackc/pgx/v5/stdlib" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/postgres" diff --git a/internal/hub/rpc/server.go b/internal/hub/rpc/server.go index 29fac52..bc629b3 100644 --- a/internal/hub/rpc/server.go +++ b/internal/hub/rpc/server.go @@ -11,7 +11,7 @@ import ( ) type HubService interface { - RegisterAgent(data domain.RegisterAgentRequest) domain.RegisterAgentResponse + RegisterAgent(ctx context.Context, data domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) } type HubHandler struct { @@ -39,6 +39,6 @@ func (h *HubHandler) Ping(ctx context.Context, _ *emptypb.Empty) (*pb.PongRespon func (h *HubHandler) RegisterAgent(ctx context.Context, request *pb.RegisterAgentRequest) (*pb.RegisterAgentResponse, error) { data := domain.ToDomainAgentRequest(request) - resp := h.hub.RegisterAgent(data) - return domain.ToGRPCAgentResponse(resp), nil + resp, err := h.hub.RegisterAgent(ctx, data) + return domain.ToGRPCAgentResponse(resp), err } diff --git a/internal/hub/service/hub_service/hub.go b/internal/hub/service/hub_service/hub.go index 712c3a2..686c505 100644 --- a/internal/hub/service/hub_service/hub.go +++ b/internal/hub/service/hub_service/hub.go @@ -1,20 +1,29 @@ package hub_service import ( + "context" + "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/internal/hub/store" "github.com/lorsanstand/HomeOps-Hub/internal/hub/utils/hasher" "github.com/rs/zerolog" ) +type Store interface { + NewAgent(ctx context.Context, agent store.Agent) error +} + type HubService struct { - log zerolog.Logger + store Store + log zerolog.Logger } -func NewHubService(logger zerolog.Logger) *HubService { - return &HubService{log: logger} +func NewHubService(store Store, logger zerolog.Logger) *HubService { + return &HubService{log: logger, store: store} } -func (h *HubService) RegisterAgent(data domain.RegisterAgentRequest) domain.RegisterAgentResponse { +func (h *HubService) RegisterAgent(ctx context.Context, data domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) { + h.log.Debug().Msg("registered new agent") AgentID := data.AgentId if data.AgentId == "" { var err error @@ -25,5 +34,20 @@ func (h *HubService) RegisterAgent(data domain.RegisterAgentRequest) domain.Regi } } - return domain.RegisterAgentResponse{AgentID: AgentID, Heartbeat: 5} + agentStore := store.Agent{ + AgentID: AgentID, + AgentName: data.AgentName, + Architecture: data.Host.Arch, + System: data.Host.System, + Hostname: data.Host.Hostname, + Version: data.AgentVersion, + Capabilities: data.Capabilities, + } + + if err := h.store.NewAgent(ctx, agentStore); err != nil { + h.log.Warn().Err(err).Msg("failed add new agent in db") + return domain.RegisterAgentResponse{}, err + } + + return domain.RegisterAgentResponse{AgentID: AgentID, Heartbeat: 5}, nil } diff --git a/internal/hub/store/sqlc/gen/agent.sql.go b/internal/hub/store/sqlc/gen/agent.sql.go new file mode 100644 index 0000000..f4d94a7 --- /dev/null +++ b/internal/hub/store/sqlc/gen/agent.sql.go @@ -0,0 +1,38 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: agent.sql + +package gen + +import ( + "context" +) + +const createAgent = `-- name: CreateAgent :exec +INSERT INTO agents (agent_id, agent_name, architecture, system, hostname, version, capabilities) +VALUES ($1, $2, $3, $4, $5, $6, $7) +` + +type CreateAgentParams struct { + AgentID string + AgentName *string + Architecture string + System string + Hostname string + Version string + Capabilities []byte +} + +func (q *Queries) CreateAgent(ctx context.Context, arg CreateAgentParams) error { + _, err := q.db.Exec(ctx, createAgent, + arg.AgentID, + arg.AgentName, + arg.Architecture, + arg.System, + arg.Hostname, + arg.Version, + arg.Capabilities, + ) + return err +} diff --git a/internal/hub/store/sqlc/gen/db.go b/internal/hub/store/sqlc/gen/db.go new file mode 100644 index 0000000..7593a67 --- /dev/null +++ b/internal/hub/store/sqlc/gen/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package gen + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/hub/store/sqlc/gen/models.go b/internal/hub/store/sqlc/gen/models.go new file mode 100644 index 0000000..dff6f06 --- /dev/null +++ b/internal/hub/store/sqlc/gen/models.go @@ -0,0 +1,21 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package gen + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type Agent struct { + ID int64 + AgentID string + AgentName *string + Architecture string + System string + Hostname string + Version string + Capabilities []byte + RegisteredAt pgtype.Timestamp +} diff --git a/internal/hub/store/sqlc/queries/agent.sql b/internal/hub/store/sqlc/queries/agent.sql new file mode 100644 index 0000000..900c564 --- /dev/null +++ b/internal/hub/store/sqlc/queries/agent.sql @@ -0,0 +1,3 @@ +-- name: CreateAgent :exec +INSERT INTO agents (agent_id, agent_name, architecture, system, hostname, version, capabilities) +VALUES ($1, $2, $3, $4, $5, $6, $7); \ No newline at end of file diff --git a/internal/hub/store/store.go b/internal/hub/store/store.go new file mode 100644 index 0000000..a1ac28d --- /dev/null +++ b/internal/hub/store/store.go @@ -0,0 +1,21 @@ +package store + +import ( + "context" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/lorsanstand/HomeOps-Hub/internal/hub/store/sqlc/gen" +) + +type HubStore struct { + queries *gen.Queries +} + +func NewHubStore(db *pgxpool.Pool) *HubStore { + queries := gen.New(db) + return &HubStore{queries} +} + +func (h *HubStore) NewAgent(ctx context.Context, agent Agent) error { + return h.queries.CreateAgent(ctx, toDBAgent(agent)) +} diff --git a/internal/hub/store/structure.go b/internal/hub/store/structure.go new file mode 100644 index 0000000..b2eaa36 --- /dev/null +++ b/internal/hub/store/structure.go @@ -0,0 +1,37 @@ +package store + +import ( + "encoding/json" + + "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/internal/hub/store/sqlc/gen" +) + +type Agent struct { + AgentID string + AgentName string + Architecture string + System string + Hostname string + Version string + Capabilities []domain.Capability +} + +func toDBAgent(agent Agent) gen.CreateAgentParams { + return gen.CreateAgentParams{ + AgentID: agent.AgentID, + AgentName: &agent.AgentName, + Architecture: agent.Architecture, + System: agent.System, + Version: agent.Version, + Capabilities: toJsonCapabilities(agent.Capabilities), + } +} + +func toJsonCapabilities(caps []domain.Capability) []byte { + data, err := json.Marshal(caps) + if err != nil { + return []byte{} + } + return data +} diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..4e02b14 --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,12 @@ +version: "2" + +sql: + - engine: "postgresql" + queries: "internal/hub/store/sqlc/queries" + schema: "./internal/hub/migrations/" + gen: + go: + sql_package: "pgx/v5" + package: "gen" + out: "internal/hub/store/sqlc/gen" + emit_pointers_for_null_types: true \ No newline at end of file From e289365ce84943e4ef039d1b96250f7a7979bc7a Mon Sep 17 00:00:00 2001 From: lorsan Date: Sun, 19 Apr 2026 16:16:03 +0300 Subject: [PATCH 8/8] feat: create save new agent in db --- internal/agent/service/agent_service/agent.go | 4 +- .../agent/service/agent_service/agent_test.go | 25 +++++++ internal/domain/{structure.go => agent.go} | 0 internal/hub/app/app.go | 24 ++++-- internal/hub/domain/structure.go | 29 +++++++ internal/hub/rpc/server.go | 10 ++- internal/hub/service/hub_service/hub.go | 61 +++++++++------ internal/hub/service/hub_service/mapper.go | 18 +++++ internal/hub/store/mapper.go | 69 +++++++++++++++++ internal/hub/store/sqlc/gen/agent.sql.go | 75 +++++++++++++++++++ internal/hub/store/sqlc/gen/models.go | 2 +- internal/hub/store/sqlc/queries/agent.sql | 15 +++- internal/hub/store/store.go | 17 ++++- internal/hub/store/structure.go | 37 --------- 14 files changed, 314 insertions(+), 72 deletions(-) create mode 100644 internal/agent/service/agent_service/agent_test.go rename internal/domain/{structure.go => agent.go} (100%) create mode 100644 internal/hub/domain/structure.go create mode 100644 internal/hub/service/hub_service/mapper.go create mode 100644 internal/hub/store/mapper.go delete mode 100644 internal/hub/store/structure.go diff --git a/internal/agent/service/agent_service/agent.go b/internal/agent/service/agent_service/agent.go index 391c62d..af9f050 100644 --- a/internal/agent/service/agent_service/agent.go +++ b/internal/agent/service/agent_service/agent.go @@ -10,6 +10,8 @@ import ( "github.com/rs/zerolog" ) +const AgentVersion = "0.0" + type Collector interface { GatherInfoSystem() (domain.HostInfo, []domain.Capability) } @@ -43,7 +45,7 @@ func (a *AgentService) RegisterAgentConn(ctx context.Context) { info, caps := a.collect.GatherInfoSystem() AgentID := a.settings.AgentID AgentName := a.cfg.AppName - AgentData := domain.RegisterAgentRequest{AgentId: AgentID, AgentName: AgentName, Host: info, Capabilities: caps} + AgentData := domain.RegisterAgentRequest{AgentId: AgentID, AgentName: AgentName, Host: info, Capabilities: caps, AgentVersion: AgentVersion} data, err := a.conn.RegisterAgent(ctx, AgentData) if err != nil { diff --git a/internal/agent/service/agent_service/agent_test.go b/internal/agent/service/agent_service/agent_test.go new file mode 100644 index 0000000..b6852ac --- /dev/null +++ b/internal/agent/service/agent_service/agent_test.go @@ -0,0 +1,25 @@ +package agent_service + +import ( + "context" + + "github.com/lorsanstand/HomeOps-Hub/internal/domain" +) + +type CollectorMock struct { + host domain.HostInfo + caps []domain.Capability +} + +func (c *CollectorMock) GatherInfoSystem() (domain.HostInfo, []domain.Capability) { + return c.host, c.caps +} + +type ConnectionMock struct { + regAgentErr error + regResp domain.RegisterAgentResponse +} + +func (c *ConnectionMock) RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) { + return c.regResp, c.regAgentErr +} diff --git a/internal/domain/structure.go b/internal/domain/agent.go similarity index 100% rename from internal/domain/structure.go rename to internal/domain/agent.go diff --git a/internal/hub/app/app.go b/internal/hub/app/app.go index 0153c0f..56652b0 100644 --- a/internal/hub/app/app.go +++ b/internal/hub/app/app.go @@ -36,55 +36,65 @@ func NewApp() *App { func (a *App) Run() { ctx := context.Background() + a.log.Info().Str("host", a.cfg.DBHost).Int("port", a.cfg.DBPort).Msg("connecting to database") migratePGConn, err := sql.Open("pgx", a.cfg.GetURLPostgres()) if err != nil { - a.log.Error().Err(err).Msg("failed to connect to the database") + a.log.Error().Err(err).Msg("failed to connect to the database for migrations") return } defer migratePGConn.Close() mgrt, err := migrator.NewMigrator(hubdir.MigrationsFS, "migrations") if err != nil { - a.log.Error().Err(err).Msg("failed create migrator") + a.log.Error().Err(err).Msg("failed to create migrator") return } + a.log.Info().Msg("applying database migrations") if err = mgrt.ApplyMigrations(migratePGConn); err != nil { - a.log.Error().Err(err).Msg("migrations were not applied") + a.log.Error().Err(err).Msg("migrations failed to apply") return } + a.log.Info().Msg("migrations applied successfully") migratePGConn.Close() + a.log.Info().Msg("creating database connection pool") pool, err := pgxpool.New(ctx, a.cfg.GetURLPostgres()) if err != nil { - a.log.Error().Err(err).Msg("failed create db pool") + a.log.Error().Err(err).Msg("failed to create database connection pool") return } + defer pool.Close() + a.log.Info().Msg("database connection pool created") hubStore := store.NewHubStore(pool) - hubService := hub_service.NewHubService(hubStore, a.log) + a.log.Info().Msg("starting hub service") err = a.hubServe(hubService) if err != nil { - a.log.Error().Err(err).Msg("failed to start the server") + a.log.Error().Err(err).Msg("hub service failed to start") return } } func (a *App) hubServe(hubService *hub_service.HubService) error { address := fmt.Sprintf("0.0.0.0:%v", a.cfg.Port) - a.log.Info().Str("address", "http://"+address).Msg("start GRPC server") + a.log.Info().Str("address", address).Msg("starting gRPC server") server := grpcserv.NewHubHandler(hubService, a.log) lis, err := net.Listen("tcp", address) if err != nil { + a.log.Error().Err(err).Str("address", address).Msg("failed to listen on address") return err } + a.log.Info().Str("address", address).Msg("listening on address") + a.log.Info().Msg("gRPC server is running") err = server.GrpcServer.Serve(lis) if err != nil { + a.log.Error().Err(err).Msg("gRPC server error") return err } diff --git a/internal/hub/domain/structure.go b/internal/hub/domain/structure.go new file mode 100644 index 0000000..795fe89 --- /dev/null +++ b/internal/hub/domain/structure.go @@ -0,0 +1,29 @@ +package domain + +import ( + "time" + + "github.com/lorsanstand/HomeOps-Hub/internal/domain" +) + +type CreateAgentModel struct { + AgentID string + AgentName string + Architecture string + System string + Hostname string + Version string + Capabilities []domain.Capability +} + +type AgentModel struct { + ID int + AgentID string + AgentName string + Architecture string + System string + Hostname string + Version string + Capabilities []domain.Capability + RegisteredAt time.Time +} diff --git a/internal/hub/rpc/server.go b/internal/hub/rpc/server.go index bc629b3..1a5aade 100644 --- a/internal/hub/rpc/server.go +++ b/internal/hub/rpc/server.go @@ -33,12 +33,18 @@ func NewHubHandler(HubServ HubService, logger zerolog.Logger) *HubHandler { } func (h *HubHandler) Ping(ctx context.Context, _ *emptypb.Empty) (*pb.PongResponse, error) { - h.log.Info().Msg("pong request") + h.log.Debug().Msg("ping request received") return &pb.PongResponse{Pong: "Pong"}, nil } func (h *HubHandler) RegisterAgent(ctx context.Context, request *pb.RegisterAgentRequest) (*pb.RegisterAgentResponse, error) { + h.log.Debug().Str("agentId", request.AgentId).Str("agentName", request.AgentName).Msg("register agent request received") data := domain.ToDomainAgentRequest(request) resp, err := h.hub.RegisterAgent(ctx, data) - return domain.ToGRPCAgentResponse(resp), err + if err != nil { + h.log.Error().Err(err).Str("agentId", request.AgentId).Msg("register agent request failed") + return domain.ToGRPCAgentResponse(resp), err + } + h.log.Debug().Str("agentId", resp.AgentID).Msg("register agent request completed") + return domain.ToGRPCAgentResponse(resp), nil } diff --git a/internal/hub/service/hub_service/hub.go b/internal/hub/service/hub_service/hub.go index 686c505..aa86b1a 100644 --- a/internal/hub/service/hub_service/hub.go +++ b/internal/hub/service/hub_service/hub.go @@ -2,15 +2,20 @@ package hub_service import ( "context" + "database/sql" + "errors" + "fmt" "github.com/lorsanstand/HomeOps-Hub/internal/domain" - "github.com/lorsanstand/HomeOps-Hub/internal/hub/store" + domainHub "github.com/lorsanstand/HomeOps-Hub/internal/hub/domain" "github.com/lorsanstand/HomeOps-Hub/internal/hub/utils/hasher" "github.com/rs/zerolog" ) type Store interface { - NewAgent(ctx context.Context, agent store.Agent) error + NewAgent(ctx context.Context, agent domainHub.CreateAgentModel) error + GetAgentByAgentID(ctx context.Context, AgentID string) (domainHub.AgentModel, error) + UpdateAgentByID(ctx context.Context, ID int, updateAgent domainHub.CreateAgentModel) error } type HubService struct { @@ -23,31 +28,43 @@ func NewHubService(store Store, logger zerolog.Logger) *HubService { } func (h *HubService) RegisterAgent(ctx context.Context, data domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) { - h.log.Debug().Msg("registered new agent") - AgentID := data.AgentId - if data.AgentId == "" { - var err error - AgentID, err = hasher.MakeID(data.Host, data.AgentName) - if err != nil { - h.log.Error().Err(err).Msg("failed create agent id") - AgentID = "" + h.log.Debug().Str("agentId", data.AgentId).Str("agentName", data.AgentName).Msg("started registering agent") + agent, err := h.store.GetAgentByAgentID(ctx, data.AgentId) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + h.log.Error().Err(err).Str("agentId", data.AgentId).Msg("failed to get agent from database") + return domain.RegisterAgentResponse{}, fmt.Errorf("failed select agent to db: %w", err) + } + + if data.AgentId != "" && !errors.Is(err, sql.ErrNoRows) { + h.log.Debug().Str("agentId", agent.AgentID).Str("agentName", data.AgentName).Msg("agent exists, updating") + + data.AgentId = agent.AgentID + + agentStore := toCreateAgentModel(data) + + if err := h.store.UpdateAgentByID(ctx, agent.ID, agentStore); err != nil { + h.log.Error().Err(err).Str("agentId", agent.AgentID).Msg("failed to update agent in database") + return domain.RegisterAgentResponse{}, err } + h.log.Info().Str("agentId", agent.AgentID).Msg("agent updated successfully") + return domain.RegisterAgentResponse{AgentID: agent.AgentID, Heartbeat: 5}, nil } - agentStore := store.Agent{ - AgentID: AgentID, - AgentName: data.AgentName, - Architecture: data.Host.Arch, - System: data.Host.System, - Hostname: data.Host.Hostname, - Version: data.AgentVersion, - Capabilities: data.Capabilities, - } - - if err := h.store.NewAgent(ctx, agentStore); err != nil { - h.log.Warn().Err(err).Msg("failed add new agent in db") + AgentID, err := hasher.MakeID(data.Host, data.AgentName) + if err != nil { + h.log.Error().Err(err).Str("agentName", data.AgentName).Str("hostname", data.Host.Hostname).Msg("failed to generate agent id") return domain.RegisterAgentResponse{}, err } + data.AgentId = AgentID + + agentStore := toCreateAgentModel(data) + + if err := h.store.NewAgent(ctx, agentStore); err != nil { + h.log.Error().Err(err).Str("agentId", AgentID).Str("agentName", data.AgentName).Msg("failed to create new agent in database") + return domain.RegisterAgentResponse{}, err + } + + h.log.Info().Str("agentId", AgentID).Str("agentName", data.AgentName).Str("hostname", data.Host.Hostname).Msg("agent registered successfully") return domain.RegisterAgentResponse{AgentID: AgentID, Heartbeat: 5}, nil } diff --git a/internal/hub/service/hub_service/mapper.go b/internal/hub/service/hub_service/mapper.go new file mode 100644 index 0000000..6a020d8 --- /dev/null +++ b/internal/hub/service/hub_service/mapper.go @@ -0,0 +1,18 @@ +package hub_service + +import ( + "github.com/lorsanstand/HomeOps-Hub/internal/domain" + domainHub "github.com/lorsanstand/HomeOps-Hub/internal/hub/domain" +) + +func toCreateAgentModel(agent domain.RegisterAgentRequest) domainHub.CreateAgentModel { + return domainHub.CreateAgentModel{ + AgentID: agent.AgentId, + AgentName: agent.AgentName, + Architecture: agent.Host.Arch, + System: agent.Host.System, + Hostname: agent.Host.Hostname, + Version: agent.AgentVersion, + Capabilities: agent.Capabilities, + } +} diff --git a/internal/hub/store/mapper.go b/internal/hub/store/mapper.go new file mode 100644 index 0000000..cbce0c3 --- /dev/null +++ b/internal/hub/store/mapper.go @@ -0,0 +1,69 @@ +package store + +import ( + "encoding/json" + + "github.com/lorsanstand/HomeOps-Hub/internal/domain" + domainHub "github.com/lorsanstand/HomeOps-Hub/internal/hub/domain" + "github.com/lorsanstand/HomeOps-Hub/internal/hub/store/sqlc/gen" +) + +func toDBAgent(agent domainHub.CreateAgentModel) gen.CreateAgentParams { + return gen.CreateAgentParams{ + AgentID: agent.AgentID, + AgentName: &agent.AgentName, + Architecture: agent.Architecture, + System: agent.System, + Hostname: agent.Hostname, + Version: agent.Version, + Capabilities: toJsonCapabilities(agent.Capabilities), + } +} + +func toUpdateDBAgent(agent domainHub.CreateAgentModel) gen.UpdateAgentByIDParams { + return gen.UpdateAgentByIDParams{ + AgentID: agent.AgentID, + AgentName: &agent.AgentName, + Architecture: agent.Architecture, + System: agent.System, + Hostname: agent.Hostname, + Version: agent.Version, + Capabilities: toJsonCapabilities(agent.Capabilities), + } +} + +func toJsonCapabilities(caps []domain.Capability) []byte { + data, err := json.Marshal(caps) + if err != nil { + // Note: Error is silently handled - consider logging in production + return []byte{} + } + return data +} + +func toAgentModel(dbAgent gen.Agent) domainHub.AgentModel { + var dbAgentName string + if dbAgent.AgentName != nil { + dbAgentName = *dbAgent.AgentName + } + + return domainHub.AgentModel{ + ID: int(dbAgent.ID), + AgentID: dbAgent.AgentID, + AgentName: dbAgentName, + Architecture: dbAgent.Architecture, + System: dbAgent.System, + Hostname: dbAgent.Hostname, + Capabilities: toDomainCapabilities(dbAgent.Capabilities), + } +} + +func toDomainCapabilities(caps []byte) []domain.Capability { + var capabilities []domain.Capability + err := json.Unmarshal(caps, &capabilities) + if err != nil { + // Note: Error is silently handled - consider logging in production + return []domain.Capability{} + } + return capabilities +} diff --git a/internal/hub/store/sqlc/gen/agent.sql.go b/internal/hub/store/sqlc/gen/agent.sql.go index f4d94a7..6a6e23c 100644 --- a/internal/hub/store/sqlc/gen/agent.sql.go +++ b/internal/hub/store/sqlc/gen/agent.sql.go @@ -36,3 +36,78 @@ func (q *Queries) CreateAgent(ctx context.Context, arg CreateAgentParams) error ) return err } + +const getAgentByAgentID = `-- name: GetAgentByAgentID :one +SELECT id, agent_id, agent_name, architecture, system, hostname, version, capabilities, registered_at from agents +WHERE agent_id=$1 +` + +func (q *Queries) GetAgentByAgentID(ctx context.Context, agentID string) (Agent, error) { + row := q.db.QueryRow(ctx, getAgentByAgentID, agentID) + var i Agent + err := row.Scan( + &i.ID, + &i.AgentID, + &i.AgentName, + &i.Architecture, + &i.System, + &i.Hostname, + &i.Version, + &i.Capabilities, + &i.RegisteredAt, + ) + return i, err +} + +const getAgentByID = `-- name: GetAgentByID :one +SELECT id, agent_id, agent_name, architecture, system, hostname, version, capabilities, registered_at from agents +WHERE id=$1 +` + +func (q *Queries) GetAgentByID(ctx context.Context, id int32) (Agent, error) { + row := q.db.QueryRow(ctx, getAgentByID, id) + var i Agent + err := row.Scan( + &i.ID, + &i.AgentID, + &i.AgentName, + &i.Architecture, + &i.System, + &i.Hostname, + &i.Version, + &i.Capabilities, + &i.RegisteredAt, + ) + return i, err +} + +const updateAgentByID = `-- name: UpdateAgentByID :exec +UPDATE agents +SET agent_id=$1, agent_name=$2, architecture=$3, system=$4, hostname=$5, version=$6, capabilities=$7 +WHERE id=$8 +` + +type UpdateAgentByIDParams struct { + AgentID string + AgentName *string + Architecture string + System string + Hostname string + Version string + Capabilities []byte + ID int32 +} + +func (q *Queries) UpdateAgentByID(ctx context.Context, arg UpdateAgentByIDParams) error { + _, err := q.db.Exec(ctx, updateAgentByID, + arg.AgentID, + arg.AgentName, + arg.Architecture, + arg.System, + arg.Hostname, + arg.Version, + arg.Capabilities, + arg.ID, + ) + return err +} diff --git a/internal/hub/store/sqlc/gen/models.go b/internal/hub/store/sqlc/gen/models.go index dff6f06..c5e077d 100644 --- a/internal/hub/store/sqlc/gen/models.go +++ b/internal/hub/store/sqlc/gen/models.go @@ -9,7 +9,7 @@ import ( ) type Agent struct { - ID int64 + ID int32 AgentID string AgentName *string Architecture string diff --git a/internal/hub/store/sqlc/queries/agent.sql b/internal/hub/store/sqlc/queries/agent.sql index 900c564..b0e53ca 100644 --- a/internal/hub/store/sqlc/queries/agent.sql +++ b/internal/hub/store/sqlc/queries/agent.sql @@ -1,3 +1,16 @@ -- name: CreateAgent :exec INSERT INTO agents (agent_id, agent_name, architecture, system, hostname, version, capabilities) -VALUES ($1, $2, $3, $4, $5, $6, $7); \ No newline at end of file +VALUES ($1, $2, $3, $4, $5, $6, $7); + +-- name: GetAgentByID :one +SELECT * from agents +WHERE id=$1; + +-- name: GetAgentByAgentID :one +SELECT * from agents +WHERE agent_id=$1; + +-- name: UpdateAgentByID :exec +UPDATE agents +SET agent_id=$1, agent_name=$2, architecture=$3, system=$4, hostname=$5, version=$6, capabilities=$7 +WHERE id=$8; \ No newline at end of file diff --git a/internal/hub/store/store.go b/internal/hub/store/store.go index a1ac28d..02936d4 100644 --- a/internal/hub/store/store.go +++ b/internal/hub/store/store.go @@ -4,6 +4,7 @@ import ( "context" "github.com/jackc/pgx/v5/pgxpool" + domainHub "github.com/lorsanstand/HomeOps-Hub/internal/hub/domain" "github.com/lorsanstand/HomeOps-Hub/internal/hub/store/sqlc/gen" ) @@ -16,6 +17,20 @@ func NewHubStore(db *pgxpool.Pool) *HubStore { return &HubStore{queries} } -func (h *HubStore) NewAgent(ctx context.Context, agent Agent) error { +func (h *HubStore) NewAgent(ctx context.Context, agent domainHub.CreateAgentModel) error { return h.queries.CreateAgent(ctx, toDBAgent(agent)) } + +func (h *HubStore) GetAgentByAgentID(ctx context.Context, AgentID string) (domainHub.AgentModel, error) { + data, err := h.queries.GetAgentByAgentID(ctx, AgentID) + if err != nil { + return domainHub.AgentModel{}, err + } + return toAgentModel(data), nil +} + +func (h *HubStore) UpdateAgentByID(ctx context.Context, ID int, updateAgent domainHub.CreateAgentModel) error { + data := toUpdateDBAgent(updateAgent) + data.ID = int32(ID) + return h.queries.UpdateAgentByID(ctx, data) +} diff --git a/internal/hub/store/structure.go b/internal/hub/store/structure.go deleted file mode 100644 index b2eaa36..0000000 --- a/internal/hub/store/structure.go +++ /dev/null @@ -1,37 +0,0 @@ -package store - -import ( - "encoding/json" - - "github.com/lorsanstand/HomeOps-Hub/internal/domain" - "github.com/lorsanstand/HomeOps-Hub/internal/hub/store/sqlc/gen" -) - -type Agent struct { - AgentID string - AgentName string - Architecture string - System string - Hostname string - Version string - Capabilities []domain.Capability -} - -func toDBAgent(agent Agent) gen.CreateAgentParams { - return gen.CreateAgentParams{ - AgentID: agent.AgentID, - AgentName: &agent.AgentName, - Architecture: agent.Architecture, - System: agent.System, - Version: agent.Version, - Capabilities: toJsonCapabilities(agent.Capabilities), - } -} - -func toJsonCapabilities(caps []domain.Capability) []byte { - data, err := json.Marshal(caps) - if err != nil { - return []byte{} - } - return data -}