Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: impl database sync constants cmd #1395

Merged
merged 8 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ migrate.create:
migrate.up:
make build
.bin/app-cli schema-migration database up
.bin/app-cli schema-migration database sync-constants
.bin/app-cli schema-migration database extract-schema

.PHONY: migrate.spanner.up
Expand Down
2 changes: 2 additions & 0 deletions db/main/constants/asset_types.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ids:
- private/user_images
6 changes: 6 additions & 0 deletions db/main/constants/embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package constant_files

import "embed"

//go:embed *.yaml
var EmbedConstants embed.FS
3 changes: 3 additions & 0 deletions db/main/constants/staff_roles.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ids:
- normal
- admin
54 changes: 21 additions & 33 deletions db/main/migrations/1_initialize.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
-- +goose Up
CREATE TABLE `asset_types` (
`id` VARCHAR(256) NOT NULL COMMENT "id",
CONSTRAINT `asset_types_pkey` PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT "asset_type";

CREATE TABLE `assets` (
`id` VARCHAR(64) NOT NULL COMMENT "id",
`content_type` VARCHAR(1024) NOT NULL COMMENT "content_type",
`type` VARCHAR(256) NOT NULL COMMENT "type",
`path` TEXT NOT NULL COMMENT "path",
`expires_at` DATETIME NOT NULL COMMENT "expires_at",
`created_at` DATETIME NOT NULL COMMENT "created date",
`updated_at` DATETIME NOT NULL COMMENT "update date",
CONSTRAINT `assets_pkey` PRIMARY KEY (`id`),
CONSTRAINT `assets_fkey_type` FOREIGN KEY (`type`) REFERENCES `asset_types` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT "asset";

CREATE TABLE `tenants` (
`id` VARCHAR(64) NOT NULL COMMENT "id",
`name` VARCHAR(256) NOT NULL COMMENT "name",
Expand All @@ -14,13 +33,6 @@ CREATE TABLE `staff_roles` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT "staff_role";

INSERT INTO `staff_roles`
(`id`)
VALUES
('normal'),
('admin');


CREATE TABLE `staffs` (
`id` VARCHAR(64) NOT NULL COMMENT "id",
`tenant_id` VARCHAR(64) NOT NULL COMMENT "tenant_id",
Expand All @@ -39,33 +51,9 @@ CREATE TABLE `staffs` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT "staff";

CREATE TABLE `asset_types` (
`id` VARCHAR(256) NOT NULL COMMENT "id",
CONSTRAINT `asset_types_pkey` PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT "asset_type";

INSERT INTO `asset_types`
(`id`)
VALUES
('private/user_images');

CREATE TABLE `assets` (
`id` VARCHAR(64) NOT NULL COMMENT "id",
`content_type` VARCHAR(1024) NOT NULL COMMENT "content_type",
`type` VARCHAR(256) NOT NULL COMMENT "type",
`path` TEXT NOT NULL COMMENT "path",
`expires_at` DATETIME NOT NULL COMMENT "expires_at",
`created_at` DATETIME NOT NULL COMMENT "created date",
`updated_at` DATETIME NOT NULL COMMENT "update date",
CONSTRAINT `assets_pkey` PRIMARY KEY (`id`),
CONSTRAINT `assets_fkey_type` FOREIGN KEY (`type`) REFERENCES `asset_types` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT "asset";

-- +goose Down
DROP TABLE assets;
DROP TABLE asset_types;
DROP TABLE staffs;
DROP TABLE staff_roles;
DROP TABLE tenants;
DROP TABLE assets;
DROP TABLE asset_types;
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,12 @@ func NewDatabaseCmd() *cobra.Command {
migration.RunExtractSchema()
},
})
cmd.AddCommand(&cobra.Command{
Use: "sync-constants",
Short: "sync constants",
Run: func(cmd *cobra.Command, args []string) {
migration.RunSyncConstants()
},
})
return cmd
}
100 changes: 100 additions & 0 deletions internal/infrastructure/database/migration/constant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package migration

import (
"context"
"database/sql"
"fmt"
"io/fs"
"strings"

constant_files "github.com/abyssparanoia/rapid-go/db/main/constants"
"github.com/abyssparanoia/rapid-go/internal/domain/errors"
"github.com/abyssparanoia/rapid-go/internal/pkg/logger"
_ "github.com/go-sql-driver/mysql"
"gopkg.in/yaml.v2"
)

// constantData represents the structure of a YAML file for a constants table.
type constantData struct {
IDs []string `yaml:"ids"`
}

func syncConstantByYaml( //nolint:gocognit
ctx context.Context,
tx *sql.Tx,
yamlFileEntry fs.DirEntry,
) error {
tableName := strings.TrimSuffix(yamlFileEntry.Name(), ".yaml")
tableName = strings.TrimSuffix(tableName, ".yml")

fileContent, err := constant_files.EmbedConstants.ReadFile(yamlFileEntry.Name())
if err != nil {
return errors.InternalErr.Wrap(err)
}

var data constantData
if err = yaml.Unmarshal(fileContent, &data); err != nil {
return errors.InternalErr.Wrap(err)
}

// Fetch current IDs from the database
query := fmt.Sprintf("SELECT id FROM %s", tableName) //nolint:gosec
rows, err := tx.Query(query) //nolint:rowserrcheck
if err != nil {
return errors.InternalErr.Wrap(err)
}
defer rows.Close()

currentIDs := make(map[string]struct{})
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return errors.InternalErr.Wrap(err)
}
currentIDs[id] = struct{}{}
}

// Determine IDs to insert and delete
newIDs := make(map[string]struct{})
for _, id := range data.IDs {
newIDs[id] = struct{}{}
}

var toInsert []string
var toDelete []string

for id := range newIDs {
if _, exists := currentIDs[id]; !exists {
toInsert = append(toInsert, id)
}
}
for id := range currentIDs {
if _, exists := newIDs[id]; !exists {
toDelete = append(toDelete, id)
}
}

// Perform insertions
if len(toInsert) > 0 {
insertQuery := fmt.Sprintf("INSERT INTO %s (id) VALUES (?)", tableName) //nolint:gosec
for _, id := range toInsert {
if _, err := tx.Exec(insertQuery, id); err != nil {
return errors.InternalErr.Wrap(err)
}
}
}

// Perform deletions
if len(toDelete) > 0 {
deleteQuery := fmt.Sprintf("DELETE FROM %s WHERE id = ?", tableName) //nolint:gosec
for _, id := range toDelete {
if _, err := tx.Exec(deleteQuery, id); err != nil {
return errors.InternalErr.Wrap(err)
}
}
}

logger.L(ctx).Info(fmt.Sprintf("Synced table %s: %d inserted, %d deleted", tableName, len(toInsert), len(toDelete)))

return nil
}
53 changes: 52 additions & 1 deletion internal/infrastructure/database/migration/run.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package migration

import (
"context"
"os"
"path/filepath"
"strings"

constant_files "github.com/abyssparanoia/rapid-go/db/main/constants"
migration_files "github.com/abyssparanoia/rapid-go/db/main/migrations"
"github.com/abyssparanoia/rapid-go/internal/infrastructure/database"
"github.com/abyssparanoia/rapid-go/internal/infrastructure/environment"
"github.com/abyssparanoia/rapid-go/internal/pkg/logger"
"github.com/abyssparanoia/rapid-go/internal/pkg/logger/logger_field"
"github.com/caarlos0/env/v11"
"github.com/pressly/goose/v3"
)
Expand Down Expand Up @@ -60,7 +64,7 @@ func RunExtractSchema() {
databaseCli := database.NewClient(e.DBHost, e.DBUser, e.DBPassword, e.DBDatabase, true)

//nolint:execinquery
tables, err := databaseCli.DB.Query("SHOW TABLES")
tables, err := databaseCli.DB.Query("SHOW TABLES") //nolint:rowserrcheck
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -88,3 +92,50 @@ func RunExtractSchema() {
}
}
}

func RunSyncConstants() {
e := &environment.DatabaseEnvironment{}
if err := env.Parse(e); err != nil {
panic(err)
}
ctx := context.Background()

l := logger.New()
ctx = logger.ToContext(ctx, l)

logger.L(ctx).Info("start sync constants")

databaseCli := database.NewClient(e.DBHost, e.DBUser, e.DBPassword, e.DBDatabase, true)

dirEntries, err := constant_files.EmbedConstants.ReadDir(".")
if err != nil {
panic(err)
}

tx, err := databaseCli.DB.BeginTx(ctx, nil)
if err != nil {
panic(err)
}
var syncErr error
defer func() {
// If there's an error, rollback the transaction, else commit it
if syncErr != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
logger.L(ctx).Error("Failed to rollback transaction", logger_field.Error(rollbackErr))
}
} else {
if commitErr := tx.Commit(); commitErr != nil {
logger.L(ctx).Error("Failed to commit transaction", logger_field.Error(commitErr))
}
}
}()

for _, entry := range dirEntries {
if strings.HasSuffix(entry.Name(), ".yaml") || strings.HasSuffix(entry.Name(), ".yml") {
syncErr = syncConstantByYaml(ctx, tx, entry)
if syncErr != nil {
return
}
}
}
}
Loading