From ea1eeb5c21137aba85da7cbfed5a19d5dbd6425e Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Tue, 13 Jan 2026 17:05:14 +1100 Subject: [PATCH] update to support postgresql and add godocs --- README.md | 19 +++++ db/db.go | 85 ++++++++++++++----- db/local.go | 22 ++++- .../20240912012927_init.sql | 38 +++++++++ .../20240912231739_extend_updates.sql | 11 +++ .../20240913021038_events.sql | 21 +++++ .../20240913043145_extend_events.sql | 17 ++++ .../20240915015710_events_type.sql | 9 ++ .../20240915232747_extend_inventory.sql | 11 +++ .../20240916041639_rename_eventid.sql | 9 ++ .../20240916045259_updates_disk.sql | 9 ++ .../20240926022714_updates_user.sql | 9 ++ .../20240927002029_change_inventory.sql | 55 ++++++++++++ .../20240930012450_add_uuid.sql | 9 ++ .../20240930031506_add_table.sql | 18 ++++ .../20241001222729_add_placeholder.sql | 9 ++ .../20241002032439_add_name.sql | 9 ++ .../20241002033323_add_change.sql | 9 ++ db/postgres.go | 79 +++++++++++++++++ db/querier.go | 35 ++++++++ db/queries/query.sql | 4 +- db/queries/query.sql.go | 2 +- go.mod | 4 + go.sum | 13 +++ main.go | 18 +++- server/handler/encryptData.go | 10 +++ server/handler/home.go | 9 +- server/handler/reportDownload.go | 16 ++++ server/handler/updateCleanup.go | 9 +- server/handler/vcCleanup.go | 10 ++- server/handler/vmCleanup.go | 11 ++- server/handler/vmCreateEvent.go | 12 ++- server/handler/vmDeleteEvent.go | 12 ++- server/handler/vmImport.go | 11 ++- server/handler/vmModifyEvent.go | 12 ++- server/handler/vmMoveEvent.go | 11 +++ server/handler/vmUpdateDetails.go | 9 +- 37 files changed, 618 insertions(+), 38 deletions(-) create mode 100644 db/migrations_postgres/20240912012927_init.sql create mode 100644 db/migrations_postgres/20240912231739_extend_updates.sql create mode 100644 db/migrations_postgres/20240913021038_events.sql create mode 100644 db/migrations_postgres/20240913043145_extend_events.sql create mode 100644 db/migrations_postgres/20240915015710_events_type.sql create mode 100644 db/migrations_postgres/20240915232747_extend_inventory.sql create mode 100644 db/migrations_postgres/20240916041639_rename_eventid.sql create mode 100644 db/migrations_postgres/20240916045259_updates_disk.sql create mode 100644 db/migrations_postgres/20240926022714_updates_user.sql create mode 100644 db/migrations_postgres/20240927002029_change_inventory.sql create mode 100644 db/migrations_postgres/20240930012450_add_uuid.sql create mode 100644 db/migrations_postgres/20240930031506_add_table.sql create mode 100644 db/migrations_postgres/20241001222729_add_placeholder.sql create mode 100644 db/migrations_postgres/20241002032439_add_name.sql create mode 100644 db/migrations_postgres/20241002033323_add_change.sql create mode 100644 db/postgres.go create mode 100644 db/querier.go diff --git a/README.md b/README.md index 49b6871..ab7786b 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,25 @@ Create a new up/down migration file with this command goose -dir db/migrations sqlite3 ./db.sqlite3 create init sql ``` +#### Database Configuration +By default the app uses SQLite and creates/opens `db.sqlite3`. You can opt into PostgreSQL +by setting environment variables: + +- `DB_DRIVER`: `sqlite` (default) or `postgres` +- `DB_URL`: SQLite file path/DSN or PostgreSQL DSN + +Examples: +```shell +# SQLite (default) +DB_DRIVER=sqlite DB_URL=./db.sqlite3 + +# PostgreSQL +DB_DRIVER=postgres DB_URL=postgres://user:pass@localhost:5432/vctp?sslmode=disable +``` + +PostgreSQL migrations live in `db/migrations_postgres`, while SQLite migrations remain in +`db/migrations`. + ### Dist This is where your assets live. Any Javascript, images, or styling needs to go in the diff --git a/db/db.go b/db/db.go index 0615500..2156f88 100644 --- a/db/db.go +++ b/db/db.go @@ -3,46 +3,79 @@ package db import ( "database/sql" "embed" + "fmt" "log/slog" "reflect" - "vctp/db/queries" + "strings" "github.com/jmoiron/sqlx" "github.com/pressly/goose/v3" ) -//go:embed migrations/*.sql +//go:embed migrations migrations_postgres var migrations embed.FS type Database interface { DB() *sqlx.DB - Queries() *queries.Queries + Queries() Querier Logger() *slog.Logger Close() error } -func New(logger *slog.Logger, url string) (Database, error) { - db, err := newLocalDB(logger, url) - if err != nil { - return nil, err - } - if err = db.db.Ping(); err != nil { - return nil, err - } - return db, nil +type Config struct { + Driver string + DSN string } -// Migrate runs the migrations on the database. Assumes the database is SQLite. -func Migrate(db Database) error { +func New(logger *slog.Logger, cfg Config) (Database, error) { + driver := normalizeDriver(cfg.Driver) + switch driver { + case "sqlite": + db, err := newLocalDB(logger, cfg.DSN) + if err != nil { + return nil, err + } + if err = db.db.Ping(); err != nil { + return nil, err + } + return db, nil + case "postgres": + db, err := newPostgresDB(logger, cfg.DSN) + if err != nil { + return nil, err + } + if err = db.db.Ping(); err != nil { + return nil, err + } + return db, nil + default: + return nil, fmt.Errorf("unsupported database driver: %s", cfg.Driver) + } +} + +// Migrate runs the migrations on the database. +func Migrate(db Database, driver string) error { + driver = normalizeDriver(driver) goose.SetBaseFS(migrations) - if err := goose.SetDialect("sqlite3"); err != nil { - panic(err) - } - - if err := goose.Up(db.DB().DB, "migrations"); err != nil { - panic(err) + switch driver { + case "sqlite": + if err := goose.SetDialect("sqlite3"); err != nil { + return fmt.Errorf("failed to set sqlite dialect: %w", err) + } + if err := goose.Up(db.DB().DB, "migrations"); err != nil { + return fmt.Errorf("failed to run sqlite migrations: %w", err) + } + case "postgres": + if err := goose.SetDialect("postgres"); err != nil { + return fmt.Errorf("failed to set postgres dialect: %w", err) + } + if err := goose.Up(db.DB().DB, "migrations_postgres"); err != nil { + return fmt.Errorf("failed to run postgres migrations: %w", err) + } + default: + return fmt.Errorf("unsupported database driver: %s", driver) } // TODO - replace with goose @@ -69,6 +102,18 @@ func Migrate(db Database) error { return nil } +func normalizeDriver(driver string) string { + normalized := strings.ToLower(strings.TrimSpace(driver)) + switch normalized { + case "", "sqlite3": + return "sqlite" + case "postgresql": + return "postgres" + default: + return normalized + } +} + // ConvertToSQLParams is a utility function that generically converts a struct to a corresponding sqlc-generated struct func ConvertToSQLParams(input interface{}, output interface{}) { inputVal := reflect.ValueOf(input).Elem() diff --git a/db/local.go b/db/local.go index a064e15..837cca9 100644 --- a/db/local.go +++ b/db/local.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "log/slog" + "strings" "vctp/db/queries" //_ "github.com/tursodatabase/libsql-client-go/libsql" @@ -28,7 +29,7 @@ func (d *LocalDB) DB() *sqlx.DB { return d.db } -func (d *LocalDB) Queries() *queries.Queries { +func (d *LocalDB) Queries() Querier { return d.queries } @@ -42,7 +43,7 @@ func (d *LocalDB) Close() error { return d.db.Close() } -func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) { +func newLocalDB(logger *slog.Logger, dsn string) (*LocalDB, error) { // TODO - work out if https://kerkour.com/sqlite-for-servers is possible without using sqlx /* @@ -62,8 +63,9 @@ func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) { readDB.SetMaxOpenConns(max(4, runtime.NumCPU())) */ - //db, err := sql.Open("libsql", "file:"+path) - db, err := sqlx.Open("sqlite", "file:"+path) + normalizedDSN := normalizeSqliteDSN(dsn) + //db, err := sql.Open("libsql", normalizedDSN) + db, err := sqlx.Open("sqlite", normalizedDSN) if err != nil { logger.Error("can't open database connection", "error", err) return nil, err @@ -92,3 +94,15 @@ func newLocalDB(logger *slog.Logger, path string) (*LocalDB, error) { return &LocalDB{logger: logger, db: db, queries: queries.New(db)}, nil } + +func normalizeSqliteDSN(dsn string) string { + trimmed := strings.TrimSpace(dsn) + if trimmed == "" { + return "file:db.sqlite3" + } + lower := strings.ToLower(trimmed) + if strings.HasPrefix(lower, "file:") || strings.HasPrefix(lower, "file::memory:") || trimmed == ":memory:" { + return trimmed + } + return "file:" + trimmed +} diff --git a/db/migrations_postgres/20240912012927_init.sql b/db/migrations_postgres/20240912012927_init.sql new file mode 100644 index 0000000..170c09f --- /dev/null +++ b/db/migrations_postgres/20240912012927_init.sql @@ -0,0 +1,38 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS "Inventory" ( + "Iid" BIGSERIAL PRIMARY KEY, + "Name" TEXT NOT NULL, + "Vcenter" TEXT NOT NULL, + "VmId" TEXT, + "EventKey" TEXT, + "EventId" TEXT, + "CreationTime" BIGINT, + "DeletionTime" BIGINT, + "ResourcePool" TEXT, + "VmType" TEXT, + "Datacenter" TEXT, + "Cluster" TEXT, + "Folder" TEXT, + "ProvisionedDisk" REAL, + "InitialVcpus" INTEGER, + "InitialRam" INTEGER, + "SrmPlaceholder" INTEGER +); + +CREATE TABLE IF NOT EXISTS "Updates" ( + "Uid" BIGSERIAL PRIMARY KEY, + "InventoryId" INTEGER, + "UpdateTime" BIGINT, + "UpdateType" TEXT NOT NULL, + "NewVcpus" INTEGER, + "NewRam" INTEGER, + "NewResourcePool" TEXT +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE "Inventory"; +DROP TABLE "Updates"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240912231739_extend_updates.sql b/db/migrations_postgres/20240912231739_extend_updates.sql new file mode 100644 index 0000000..d48503f --- /dev/null +++ b/db/migrations_postgres/20240912231739_extend_updates.sql @@ -0,0 +1,11 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Updates" ADD COLUMN "EventKey" TEXT; +ALTER TABLE "Updates" ADD COLUMN "EventId" TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Updates" DROP COLUMN "EventKey"; +ALTER TABLE "Updates" DROP COLUMN "EventId"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240913021038_events.sql b/db/migrations_postgres/20240913021038_events.sql new file mode 100644 index 0000000..d6d536b --- /dev/null +++ b/db/migrations_postgres/20240913021038_events.sql @@ -0,0 +1,21 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS "Events" ( + "Eid" BIGSERIAL PRIMARY KEY, + "CloudId" TEXT NOT NULL, + "Source" TEXT NOT NULL, + "EventTime" BIGINT, + "ChainId" TEXT NOT NULL, + "VmId" TEXT, + "EventKey" TEXT, + "Datacenter" TEXT, + "ComputeResource" TEXT, + "UserName" TEXT, + "Processed" INTEGER NOT NULL DEFAULT 0 +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE "Events"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240913043145_extend_events.sql b/db/migrations_postgres/20240913043145_extend_events.sql new file mode 100644 index 0000000..6280bfd --- /dev/null +++ b/db/migrations_postgres/20240913043145_extend_events.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Events" RENAME COLUMN "Datacenter" TO "DatacenterName"; +ALTER TABLE "Events" RENAME COLUMN "ComputeResource" TO "ComputeResourceName"; +ALTER TABLE "Events" ADD COLUMN "DatacenterId" TEXT; +ALTER TABLE "Events" ADD COLUMN "ComputeResourceId" TEXT; +ALTER TABLE "Events" ADD COLUMN "VmName" TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Events" DROP COLUMN "VmName"; +ALTER TABLE "Events" DROP COLUMN "ComputeResourceId"; +ALTER TABLE "Events" DROP COLUMN "DatacenterId"; +ALTER TABLE "Events" RENAME COLUMN "ComputeResourceName" TO "ComputeResource"; +ALTER TABLE "Events" RENAME COLUMN "DatacenterName" TO "Datacenter"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240915015710_events_type.sql b/db/migrations_postgres/20240915015710_events_type.sql new file mode 100644 index 0000000..2460c19 --- /dev/null +++ b/db/migrations_postgres/20240915015710_events_type.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Events" ADD COLUMN "EventType" TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Updates" DROP COLUMN "EventType"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240915232747_extend_inventory.sql b/db/migrations_postgres/20240915232747_extend_inventory.sql new file mode 100644 index 0000000..46b196f --- /dev/null +++ b/db/migrations_postgres/20240915232747_extend_inventory.sql @@ -0,0 +1,11 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Inventory" ADD COLUMN "IsTemplate" INTEGER; +ALTER TABLE "Inventory" ADD COLUMN "PowerState" INTEGER; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Inventory" DROP COLUMN "PowerState"; +ALTER TABLE "Inventory" DROP COLUMN "IsTemplate"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240916041639_rename_eventid.sql b/db/migrations_postgres/20240916041639_rename_eventid.sql new file mode 100644 index 0000000..f014f73 --- /dev/null +++ b/db/migrations_postgres/20240916041639_rename_eventid.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Inventory" RENAME COLUMN "EventId" TO "CloudId"; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Inventory" RENAME COLUMN "CloudId" TO "EventId"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240916045259_updates_disk.sql b/db/migrations_postgres/20240916045259_updates_disk.sql new file mode 100644 index 0000000..9938ffc --- /dev/null +++ b/db/migrations_postgres/20240916045259_updates_disk.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Updates" ADD COLUMN "NewProvisionedDisk" REAL; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Updates" DROP COLUMN "NewProvisionedDisk"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240926022714_updates_user.sql b/db/migrations_postgres/20240926022714_updates_user.sql new file mode 100644 index 0000000..b328e68 --- /dev/null +++ b/db/migrations_postgres/20240926022714_updates_user.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Updates" ADD COLUMN "UserName" TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Updates" DROP COLUMN "UserName"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240927002029_change_inventory.sql b/db/migrations_postgres/20240927002029_change_inventory.sql new file mode 100644 index 0000000..3516f47 --- /dev/null +++ b/db/migrations_postgres/20240927002029_change_inventory.sql @@ -0,0 +1,55 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Inventory" RENAME COLUMN "IsTemplate" TO "IsTemplate_old"; +ALTER TABLE "Inventory" RENAME COLUMN "PowerState" TO "PowerState_old"; +ALTER TABLE "Inventory" RENAME COLUMN "SrmPlaceholder" TO "SrmPlaceholder_old"; +ALTER TABLE "Inventory" ADD COLUMN "IsTemplate" TEXT NOT NULL DEFAULT 'FALSE'; +ALTER TABLE "Inventory" ADD COLUMN "PoweredOn" TEXT NOT NULL DEFAULT 'FALSE'; +ALTER TABLE "Inventory" ADD COLUMN "SrmPlaceholder" TEXT NOT NULL DEFAULT 'FALSE'; +UPDATE "Inventory" +SET "IsTemplate" = CASE + WHEN "IsTemplate_old" = 1 THEN 'TRUE' + ELSE 'FALSE' +END; +UPDATE "Inventory" +SET "PoweredOn" = CASE + WHEN "PowerState_old" = 1 THEN 'TRUE' + ELSE 'FALSE' +END; +UPDATE "Inventory" +SET "SrmPlaceholder" = CASE + WHEN "SrmPlaceholder_old" = 1 THEN 'TRUE' + ELSE 'FALSE' +END; +ALTER TABLE "Inventory" DROP COLUMN "IsTemplate_old"; +ALTER TABLE "Inventory" DROP COLUMN "PowerState_old"; +ALTER TABLE "Inventory" DROP COLUMN "SrmPlaceholder_old"; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Inventory" RENAME COLUMN "IsTemplate" TO "IsTemplate_old"; +ALTER TABLE "Inventory" RENAME COLUMN "PoweredOn" TO "PoweredOn_old"; +ALTER TABLE "Inventory" RENAME COLUMN "SrmPlaceholder" TO "SrmPlaceholder_old"; +ALTER TABLE "Inventory" ADD COLUMN "IsTemplate" INTEGER; +ALTER TABLE "Inventory" ADD COLUMN "PowerState" INTEGER; +ALTER TABLE "Inventory" ADD COLUMN "SrmPlaceholder" INTEGER; +UPDATE "Inventory" +SET "IsTemplate" = CASE + WHEN "IsTemplate_old" = 'TRUE' THEN 1 + ELSE 0 +END; +UPDATE "Inventory" +SET "PowerState" = CASE + WHEN "PoweredOn_old" = 'TRUE' THEN 1 + ELSE 0 +END; +UPDATE "Inventory" +SET "SrmPlaceholder" = CASE + WHEN "SrmPlaceholder_old" = 'TRUE' THEN 1 + ELSE 0 +END; +ALTER TABLE "Inventory" DROP COLUMN "IsTemplate_old"; +ALTER TABLE "Inventory" DROP COLUMN "PoweredOn_old"; +ALTER TABLE "Inventory" DROP COLUMN "SrmPlaceholder_old"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240930012450_add_uuid.sql b/db/migrations_postgres/20240930012450_add_uuid.sql new file mode 100644 index 0000000..8473705 --- /dev/null +++ b/db/migrations_postgres/20240930012450_add_uuid.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Inventory" ADD COLUMN "VmUuid" TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Inventory" DROP COLUMN "VmUuid"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20240930031506_add_table.sql b/db/migrations_postgres/20240930031506_add_table.sql new file mode 100644 index 0000000..4470e51 --- /dev/null +++ b/db/migrations_postgres/20240930031506_add_table.sql @@ -0,0 +1,18 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS "InventoryHistory" ( + "Hid" BIGSERIAL PRIMARY KEY, + "InventoryId" INTEGER, + "ReportDate" BIGINT, + "UpdateTime" BIGINT, + "PreviousVcpus" INTEGER, + "PreviousRam" INTEGER, + "PreviousResourcePool" TEXT, + "PreviousProvisionedDisk" REAL +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE "InventoryHistory"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20241001222729_add_placeholder.sql b/db/migrations_postgres/20241001222729_add_placeholder.sql new file mode 100644 index 0000000..fdf3404 --- /dev/null +++ b/db/migrations_postgres/20241001222729_add_placeholder.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Updates" ADD COLUMN "PlaceholderChange" TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Updates" DROP COLUMN "PlaceholderChange"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20241002032439_add_name.sql b/db/migrations_postgres/20241002032439_add_name.sql new file mode 100644 index 0000000..f62fc45 --- /dev/null +++ b/db/migrations_postgres/20241002032439_add_name.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Updates" ADD COLUMN "Name" TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Updates" DROP COLUMN "Name"; +-- +goose StatementEnd diff --git a/db/migrations_postgres/20241002033323_add_change.sql b/db/migrations_postgres/20241002033323_add_change.sql new file mode 100644 index 0000000..1e867cc --- /dev/null +++ b/db/migrations_postgres/20241002033323_add_change.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE "Updates" ADD COLUMN "RawChangeString" BYTEA; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE "Updates" DROP COLUMN "RawChangeString"; +-- +goose StatementEnd diff --git a/db/postgres.go b/db/postgres.go new file mode 100644 index 0000000..5e884c0 --- /dev/null +++ b/db/postgres.go @@ -0,0 +1,79 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + "log/slog" + "regexp" + "strings" + "vctp/db/queries" + + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/jmoiron/sqlx" +) + +type PostgresDB struct { + logger *slog.Logger + db *sqlx.DB + queries *queries.Queries +} + +var _ Database = (*PostgresDB)(nil) + +func (d *PostgresDB) DB() *sqlx.DB { + return d.db +} + +func (d *PostgresDB) Queries() Querier { + return d.queries +} + +func (d *PostgresDB) Logger() *slog.Logger { + return d.logger +} + +func (d *PostgresDB) Close() error { + return d.db.Close() +} + +func newPostgresDB(logger *slog.Logger, dsn string) (*PostgresDB, error) { + if strings.TrimSpace(dsn) == "" { + return nil, fmt.Errorf("postgres DSN is required") + } + db, err := sqlx.Open("pgx", dsn) + if err != nil { + return nil, err + } + db.SetMaxOpenConns(10) + + rebindDB := rebindDBTX{db: db} + return &PostgresDB{logger: logger, db: db, queries: queries.New(rebindDB)}, nil +} + +type rebindDBTX struct { + db *sqlx.DB +} + +func (r rebindDBTX) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + return r.db.ExecContext(ctx, rebindQuery(query), args...) +} + +func (r rebindDBTX) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { + return r.db.PrepareContext(ctx, rebindQuery(query)) +} + +func (r rebindDBTX) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + return r.db.QueryContext(ctx, rebindQuery(query), args...) +} + +func (r rebindDBTX) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + return r.db.QueryRowContext(ctx, rebindQuery(query), args...) +} + +var numberedPlaceholderRe = regexp.MustCompile(`\?\d+`) + +func rebindQuery(query string) string { + unindexed := numberedPlaceholderRe.ReplaceAllString(query, "?") + return sqlx.Rebind(sqlx.DOLLAR, unindexed) +} diff --git a/db/querier.go b/db/querier.go new file mode 100644 index 0000000..d292ba2 --- /dev/null +++ b/db/querier.go @@ -0,0 +1,35 @@ +package db + +import ( + "context" + "database/sql" + "vctp/db/queries" +) + +// Querier abstracts sqlc-generated queries so multiple database backends can share call sites. +type Querier interface { + CleanupUpdates(ctx context.Context, arg queries.CleanupUpdatesParams) error + CleanupUpdatesNullVm(ctx context.Context) error + CreateEvent(ctx context.Context, arg queries.CreateEventParams) (queries.Events, error) + CreateInventory(ctx context.Context, arg queries.CreateInventoryParams) (queries.Inventory, error) + CreateInventoryHistory(ctx context.Context, arg queries.CreateInventoryHistoryParams) (queries.InventoryHistory, error) + CreateUpdate(ctx context.Context, arg queries.CreateUpdateParams) (queries.Updates, error) + GetInventoryByName(ctx context.Context, name string) ([]queries.Inventory, error) + GetInventoryByVcenter(ctx context.Context, vcenter string) ([]queries.Inventory, error) + GetInventoryEventId(ctx context.Context, cloudid sql.NullString) (queries.Inventory, error) + GetInventoryVcUrl(ctx context.Context, vc string) ([]queries.Inventory, error) + GetInventoryVmId(ctx context.Context, arg queries.GetInventoryVmIdParams) (queries.Inventory, error) + GetInventoryVmUuid(ctx context.Context, arg queries.GetInventoryVmUuidParams) (queries.Inventory, error) + GetReportInventory(ctx context.Context) ([]queries.Inventory, error) + GetReportUpdates(ctx context.Context) ([]queries.Updates, error) + GetVmUpdates(ctx context.Context, arg queries.GetVmUpdatesParams) ([]queries.Updates, error) + InventoryCleanup(ctx context.Context, arg queries.InventoryCleanupParams) error + InventoryCleanupTemplates(ctx context.Context) error + InventoryCleanupVcenter(ctx context.Context, vc string) error + InventoryMarkDeleted(ctx context.Context, arg queries.InventoryMarkDeletedParams) error + InventoryUpdate(ctx context.Context, arg queries.InventoryUpdateParams) error + ListEvents(ctx context.Context) ([]queries.Events, error) + ListInventory(ctx context.Context) ([]queries.Inventory, error) + ListUnprocessedEvents(ctx context.Context, eventtime sql.NullInt64) ([]queries.Events, error) + UpdateEventsProcessed(ctx context.Context, eid int64) error +} diff --git a/db/queries/query.sql b/db/queries/query.sql index 13475eb..428d312 100644 --- a/db/queries/query.sql +++ b/db/queries/query.sql @@ -60,7 +60,7 @@ RETURNING *; -- name: InventoryCleanupTemplates :exec DELETE FROM "Inventory" -WHERE "IsTemplate" = "TRUE" +WHERE "IsTemplate" = 'TRUE' RETURNING *; -- name: CreateUpdate :one @@ -118,4 +118,4 @@ INSERT INTO "InventoryHistory" ( ) VALUES( ?, ?, ?, ?, ?, ?, ? ) -RETURNING *; \ No newline at end of file +RETURNING *; diff --git a/db/queries/query.sql.go b/db/queries/query.sql.go index 3d59c2b..104071c 100644 --- a/db/queries/query.sql.go +++ b/db/queries/query.sql.go @@ -694,7 +694,7 @@ func (q *Queries) InventoryCleanup(ctx context.Context, arg InventoryCleanupPara const inventoryCleanupTemplates = `-- name: InventoryCleanupTemplates :exec DELETE FROM "Inventory" -WHERE "IsTemplate" = "TRUE" +WHERE "IsTemplate" = 'TRUE' RETURNING Iid, Name, Vcenter, VmId, EventKey, CloudId, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, IsTemplate, PoweredOn, SrmPlaceholder, VmUuid ` diff --git a/go.mod b/go.mod index 4f1cfbd..a385cc8 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.25.0 require ( github.com/a-h/templ v0.3.977 github.com/go-co-op/gocron/v2 v2.19.0 + github.com/jackc/pgx/v5 v5.8.0 github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 github.com/pressly/goose/v3 v3.26.0 @@ -17,6 +18,9 @@ require ( require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // 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/jonboulle/clockwork v0.5.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mfridman/interpolate v0.0.2 // indirect diff --git a/go.sum b/go.sum index c940263..4496990 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= +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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -19,6 +20,14 @@ 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/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.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +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/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -55,6 +64,9 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +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= github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44= @@ -95,6 +107,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= diff --git a/main.go b/main.go index 94be0e9..6da2b1f 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "log/slog" "os" "runtime" + "strings" "time" "vctp/db" "vctp/internal/secrets" @@ -46,7 +47,20 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) // Configure database - database, err := db.New(logger, utils.GetFilePath("db.sqlite3")) + dbDriver := os.Getenv("DB_DRIVER") + if dbDriver == "" { + dbDriver = "sqlite" + } + normalizedDriver := strings.ToLower(strings.TrimSpace(dbDriver)) + if normalizedDriver == "" || normalizedDriver == "sqlite3" { + normalizedDriver = "sqlite" + } + dbURL := os.Getenv("DB_URL") + if dbURL == "" && normalizedDriver == "sqlite" { + dbURL = utils.GetFilePath("db.sqlite3") + } + + database, err := db.New(logger, db.Config{Driver: dbDriver, DSN: dbURL}) if err != nil { logger.Error("Failed to create database", "error", err) os.Exit(1) @@ -54,7 +68,7 @@ func main() { defer database.Close() //defer database.DB().Close() - if err = db.Migrate(database); err != nil { + if err = db.Migrate(database, dbDriver); err != nil { logger.Error("failed to migrate database", "error", err) os.Exit(1) } diff --git a/server/handler/encryptData.go b/server/handler/encryptData.go index ab16610..80c3a43 100644 --- a/server/handler/encryptData.go +++ b/server/handler/encryptData.go @@ -7,6 +7,16 @@ import ( "net/http" ) +// EncryptData encrypts a plaintext value and returns the ciphertext. +// @Summary Encrypt data +// @Description Encrypts a plaintext value and returns the ciphertext. +// @Tags crypto +// @Accept json +// @Produce json +// @Param payload body map[string]string true "Plaintext payload" +// @Success 200 {object} map[string]string "Ciphertext response" +// @Failure 500 {object} map[string]string "Server error" +// @Router /api/encrypt [post] func (h *Handler) EncryptData(w http.ResponseWriter, r *http.Request) { //ctx := context.Background() var cipherText string diff --git a/server/handler/home.go b/server/handler/home.go index 853e06a..eb5bfac 100644 --- a/server/handler/home.go +++ b/server/handler/home.go @@ -5,7 +5,14 @@ import ( "vctp/components/views" ) -// Home handles the home page. +// Home renders the web UI home page. +// @Summary Home page +// @Description Renders the main UI page. +// @Tags ui +// @Produce text/html +// @Success 200 {string} string "HTML page" +// @Failure 500 {string} string "Render failed" +// @Router / [get] func (h *Handler) Home(w http.ResponseWriter, r *http.Request) { //h.html(r.Context(), w, http.StatusOK, core.HTML("Example Site", home.Home())) diff --git a/server/handler/reportDownload.go b/server/handler/reportDownload.go index 36f7623..da689a8 100644 --- a/server/handler/reportDownload.go +++ b/server/handler/reportDownload.go @@ -8,6 +8,14 @@ import ( "vctp/internal/report" ) +// InventoryReportDownload returns the inventory report as an XLSX download. +// @Summary Download inventory report +// @Description Generates an inventory XLSX report and returns it as a file download. +// @Tags reports +// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet +// @Success 200 {file} file "Inventory XLSX report" +// @Failure 500 {object} map[string]string "Report generation failed" +// @Router /api/report/inventory [get] func (h *Handler) InventoryReportDownload(w http.ResponseWriter, r *http.Request) { ctx := context.Background() @@ -34,6 +42,14 @@ func (h *Handler) InventoryReportDownload(w http.ResponseWriter, r *http.Request w.Write(reportData) } +// UpdateReportDownload returns the updates report as an XLSX download. +// @Summary Download updates report +// @Description Generates an updates XLSX report and returns it as a file download. +// @Tags reports +// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet +// @Success 200 {file} file "Updates XLSX report" +// @Failure 500 {object} map[string]string "Report generation failed" +// @Router /api/report/updates [get] func (h *Handler) UpdateReportDownload(w http.ResponseWriter, r *http.Request) { ctx := context.Background() diff --git a/server/handler/updateCleanup.go b/server/handler/updateCleanup.go index a122760..ae9343f 100644 --- a/server/handler/updateCleanup.go +++ b/server/handler/updateCleanup.go @@ -6,7 +6,14 @@ import ( "net/http" ) -// VmUpdate receives the CloudEvent for a VM modification or move +// UpdateCleanup removes orphaned update records. +// @Summary Cleanup updates +// @Description Removes update records that are no longer associated with a VM. +// @Tags maintenance +// @Produce text/plain +// @Success 200 {string} string "Cleanup completed" +// @Failure 500 {string} string "Server error" +// @Router /api/cleanup/updates [delete] func (h *Handler) UpdateCleanup(w http.ResponseWriter, r *http.Request) { /* // Get the current time diff --git a/server/handler/vcCleanup.go b/server/handler/vcCleanup.go index e5b3bb3..309dac2 100644 --- a/server/handler/vcCleanup.go +++ b/server/handler/vcCleanup.go @@ -9,7 +9,15 @@ import ( "net/http" ) -// Remove a specified VM from the inventory +// VcCleanup removes inventory entries for a vCenter instance. +// @Summary Cleanup vCenter inventory +// @Description Removes all inventory entries associated with a vCenter URL. +// @Tags maintenance +// @Produce json +// @Param vc_url query string true "vCenter URL" +// @Success 200 {object} map[string]string "Cleanup completed" +// @Failure 400 {object} map[string]string "Invalid request" +// @Router /api/cleanup/vcenter [delete] func (h *Handler) VcCleanup(w http.ResponseWriter, r *http.Request) { ctx := context.Background() diff --git a/server/handler/vmCleanup.go b/server/handler/vmCleanup.go index 09e4e76..1be2bea 100644 --- a/server/handler/vmCleanup.go +++ b/server/handler/vmCleanup.go @@ -10,7 +10,16 @@ import ( "vctp/db/queries" ) -// Remove a specified VM from the inventory +// VmCleanup removes a VM from inventory by ID and datacenter. +// @Summary Cleanup VM inventory entry +// @Description Removes a VM inventory entry by VM ID and datacenter name. +// @Tags inventory +// @Produce json +// @Param vm_id query string true "VM ID" +// @Param datacenter_name query string true "Datacenter name" +// @Success 200 {object} map[string]string "Cleanup completed" +// @Failure 400 {object} map[string]string "Invalid request" +// @Router /api/inventory/vm/delete [delete] func (h *Handler) VmCleanup(w http.ResponseWriter, r *http.Request) { ctx := context.Background() diff --git a/server/handler/vmCreateEvent.go b/server/handler/vmCreateEvent.go index 9ef6e6e..756a3e6 100644 --- a/server/handler/vmCreateEvent.go +++ b/server/handler/vmCreateEvent.go @@ -14,7 +14,17 @@ import ( models "vctp/server/models" ) -// VmCreateEvent receives the CloudEvent for a VM creation +// VmCreateEvent records a VM creation CloudEvent. +// @Summary Record VM create event +// @Description Parses a VM create CloudEvent and stores the event data. +// @Tags events +// @Accept json +// @Produce text/plain +// @Param event body models.CloudEventReceived true "CloudEvent payload" +// @Success 200 {string} string "Create event processed" +// @Failure 400 {string} string "Invalid request" +// @Failure 500 {string} string "Server error" +// @Router /api/event/vm/create [post] func (h *Handler) VmCreateEvent(w http.ResponseWriter, r *http.Request) { var ( unixTimestamp int64 diff --git a/server/handler/vmDeleteEvent.go b/server/handler/vmDeleteEvent.go index 8c9c555..e754582 100644 --- a/server/handler/vmDeleteEvent.go +++ b/server/handler/vmDeleteEvent.go @@ -12,7 +12,17 @@ import ( models "vctp/server/models" ) -// VmUpdate receives the CloudEvent for a VM modification or move +// VmDeleteEvent records a VM deletion CloudEvent in the inventory. +// @Summary Record VM delete event +// @Description Parses a VM delete CloudEvent and marks the VM as deleted in inventory. +// @Tags events +// @Accept json +// @Produce text/plain +// @Param event body models.CloudEventReceived true "CloudEvent payload" +// @Success 200 {string} string "Delete event processed" +// @Failure 400 {string} string "Invalid request" +// @Failure 500 {string} string "Server error" +// @Router /api/event/vm/delete [post] func (h *Handler) VmDeleteEvent(w http.ResponseWriter, r *http.Request) { var ( deletedTimestamp int64 diff --git a/server/handler/vmImport.go b/server/handler/vmImport.go index 4011da8..371bb65 100644 --- a/server/handler/vmImport.go +++ b/server/handler/vmImport.go @@ -13,7 +13,16 @@ import ( models "vctp/server/models" ) -// VmImport is used for bulk import of existing VMs +// VmImport ingests a bulk VM import payload. +// @Summary Import VMs +// @Description Imports existing VM inventory data in bulk. +// @Tags inventory +// @Accept json +// @Produce json +// @Param import body models.ImportReceived true "Bulk import payload" +// @Success 200 {object} map[string]string "Import processed" +// @Failure 500 {object} map[string]string "Server error" +// @Router /api/import/vm [post] func (h *Handler) VmImport(w http.ResponseWriter, r *http.Request) { // Read request body reqBody, err := io.ReadAll(r.Body) diff --git a/server/handler/vmModifyEvent.go b/server/handler/vmModifyEvent.go index 9248289..43eb063 100644 --- a/server/handler/vmModifyEvent.go +++ b/server/handler/vmModifyEvent.go @@ -19,7 +19,17 @@ import ( "github.com/vmware/govmomi/vim25/types" ) -// VmModifyEvent receives the CloudEvent for a VM modification or move +// VmModifyEvent records a VM modification CloudEvent. +// @Summary Record VM modify event +// @Description Parses a VM modify CloudEvent and creates an update record when relevant changes are detected. +// @Tags events +// @Accept json +// @Produce json +// @Param event body models.CloudEventReceived true "CloudEvent payload" +// @Success 200 {object} map[string]string "Modify event processed" +// @Success 202 {object} map[string]string "No relevant changes" +// @Failure 500 {object} map[string]string "Server error" +// @Router /api/event/vm/modify [post] func (h *Handler) VmModifyEvent(w http.ResponseWriter, r *http.Request) { var configChanges []map[string]string params := queries.CreateUpdateParams{} diff --git a/server/handler/vmMoveEvent.go b/server/handler/vmMoveEvent.go index b11f033..fe8bc1e 100644 --- a/server/handler/vmMoveEvent.go +++ b/server/handler/vmMoveEvent.go @@ -14,6 +14,17 @@ import ( models "vctp/server/models" ) +// VmMoveEvent records a VM move CloudEvent as an update. +// @Summary Record VM move event +// @Description Parses a VM move CloudEvent and creates an update record. +// @Tags events +// @Accept json +// @Produce json +// @Param event body models.CloudEventReceived true "CloudEvent payload" +// @Success 200 {object} map[string]string "Move event processed" +// @Failure 400 {object} map[string]string "Invalid request" +// @Failure 500 {object} map[string]string "Server error" +// @Router /api/event/vm/move [post] func (h *Handler) VmMoveEvent(w http.ResponseWriter, r *http.Request) { params := queries.CreateUpdateParams{} var unixTimestamp int64 diff --git a/server/handler/vmUpdateDetails.go b/server/handler/vmUpdateDetails.go index 93bdb51..70032eb 100644 --- a/server/handler/vmUpdateDetails.go +++ b/server/handler/vmUpdateDetails.go @@ -9,7 +9,14 @@ import ( "vctp/internal/vcenter" ) -// VmUpdate receives the CloudEvent for a VM modification or move +// VmUpdateDetails refreshes inventory metadata from vCenter. +// @Summary Refresh VM details +// @Description Queries vCenter and updates inventory records with missing details. +// @Tags inventory +// @Produce text/plain +// @Success 200 {string} string "Update completed" +// @Failure 500 {string} string "Server error" +// @Router /api/inventory/vm/update [post] func (h *Handler) VmUpdateDetails(w http.ResponseWriter, r *http.Request) { var matchFound bool var inventoryId int64