Compare commits
5 commits
45904d54ea
...
4d927a88e0
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d927a88e0 | |||
| 99a9f9d7d1 | |||
|
|
b47dbcce70 | ||
| 06778a5357 | |||
| 807875da43 |
7 changed files with 546 additions and 16 deletions
|
|
@ -14,7 +14,7 @@ var cfgFile string
|
|||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "goffee",
|
||||
Version: "v1.7.2",
|
||||
Version: "v1.7.5",
|
||||
Short: "Goffee is the cli tool",
|
||||
Long: `Goffee is the cli tool for creating new projects and performing other tasks`,
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ goffee run:dev
|
|||
w.SetMaxEvents(1)
|
||||
w.IgnoreHiddenFiles(true)
|
||||
w.Ignore(
|
||||
pwd+"/logs/app.log",
|
||||
pwd+"/logs",
|
||||
pwd+"/tmp",
|
||||
pwd+"/.git",
|
||||
pwd+"/storage/sqlite",
|
||||
|
|
|
|||
308
cmd/scheduler.go
Normal file
308
cmd/scheduler.go
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
// Copyright (c) 2026 Jose Cely <me@jacs.guru>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/cobra"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ─── Helper ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// table names (must match core/scheduler/scheduler.go)
|
||||
const (
|
||||
queueTable = "queue_items"
|
||||
processedTable = "processed_items"
|
||||
schedulerMetaTable = "scheduler_meta"
|
||||
semaphoreKey = "semaphore"
|
||||
semaphoreDefault = "green"
|
||||
)
|
||||
|
||||
// loadEnvAndDB loads the env file for the given mode and returns a connected *gorm.DB.
|
||||
func loadEnvAndDB(envMode string) (*gorm.DB, error) {
|
||||
var envFile string
|
||||
switch envMode {
|
||||
case "dev":
|
||||
envFile = ".env-dev"
|
||||
case "prod":
|
||||
envFile = ".env"
|
||||
default:
|
||||
return nil, fmt.Errorf("environment must be 'dev' or 'prod', got '%s'", envMode)
|
||||
}
|
||||
|
||||
envPath := filepath.Join(".", envFile)
|
||||
if _, err := os.Stat(envPath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("environment file '%s' not found in current directory", envFile)
|
||||
}
|
||||
|
||||
if err := godotenv.Load(envPath); err != nil {
|
||||
return nil, fmt.Errorf("error loading '%s': %v", envFile, err)
|
||||
}
|
||||
|
||||
return connectDatabase()
|
||||
}
|
||||
|
||||
// ─── scheduler:queue ─────────────────────────────────────────────────────────
|
||||
|
||||
var SchedulerQueueCmd = &cobra.Command{
|
||||
Use: "scheduler:queue [dev|prod]",
|
||||
Short: "List queued scheduler tasks",
|
||||
Long: `Lists all pending and processing tasks in the scheduler queue.
|
||||
|
||||
Example:
|
||||
goffee scheduler:queue dev
|
||||
goffee scheduler:queue prod
|
||||
`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
db, err := loadEnvAndDB(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
type QueueItem struct {
|
||||
ID uint `gorm:"column:id"`
|
||||
TaskType string `gorm:"column:task_type"`
|
||||
Payload string `gorm:"column:payload"`
|
||||
Status string `gorm:"column:status"`
|
||||
Priority int `gorm:"column:priority"`
|
||||
MaxRuns int `gorm:"column:max_runs"`
|
||||
Thread int `gorm:"column:thread"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
var items []QueueItem
|
||||
result := db.Table(queueTable).
|
||||
Where("status IN ?", []string{"pending", "processing"}).
|
||||
Order("priority DESC, id ASC").
|
||||
Find(&items)
|
||||
if result.Error != nil {
|
||||
fmt.Printf("Error querying queue: %v\n", result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
fmt.Println("Queue is empty.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%-6s %-18s %-12s %-8s %-8s %-7s %-6s %s\n",
|
||||
"ID", "TYPE", "STATUS", "PRIORITY", "MAXRUNS", "THREAD", "CREATED", "PAYLOAD")
|
||||
fmt.Println("------ ------------------ ------------ -------- -------- ------- ------ ------------------------------")
|
||||
for _, it := range items {
|
||||
trunc := it.Payload
|
||||
if len(trunc) > 40 {
|
||||
trunc = trunc[:37] + "..."
|
||||
}
|
||||
fmt.Printf("%-6d %-18s %-12s %-8d %-8d %-7d %-6s %s\n",
|
||||
it.ID, it.TaskType, it.Status, it.Priority, it.MaxRuns, it.Thread,
|
||||
it.CreatedAt.Format("15:04:05"), trunc)
|
||||
}
|
||||
fmt.Printf("\nTotal: %d queued task(s)\n", len(items))
|
||||
},
|
||||
}
|
||||
|
||||
// ─── scheduler:processed ─────────────────────────────────────────────────────
|
||||
|
||||
var SchedulerProcessedCmd = &cobra.Command{
|
||||
Use: "scheduler:processed [dev|prod] [limit]",
|
||||
Short: "List processed task executions",
|
||||
Long: `Lists the most recent processed task executions.
|
||||
|
||||
The limit defaults to 10. Pass a number to change it.
|
||||
|
||||
Example:
|
||||
goffee scheduler:processed dev # last 10
|
||||
goffee scheduler:processed prod # last 10
|
||||
goffee scheduler:processed dev 100 # last 100
|
||||
goffee scheduler:processed prod 50 # last 50
|
||||
`,
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
db, err := loadEnvAndDB(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
limit := 10
|
||||
if len(args) == 2 {
|
||||
n, err := fmt.Sscanf(args[1], "%d", &limit)
|
||||
if err != nil || n != 1 || limit < 1 {
|
||||
fmt.Printf("Error: invalid limit '%s' — must be a positive integer\n", args[1])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type ProcessedItem struct {
|
||||
ID uint `gorm:"column:id"`
|
||||
QueueItemID uint `gorm:"column:queue_item_id"`
|
||||
TaskType string `gorm:"column:task_type"`
|
||||
Status string `gorm:"column:status"`
|
||||
ErrorMsg string `gorm:"column:error_msg"`
|
||||
DurationMs int64 `gorm:"column:duration_ms"`
|
||||
StartedAt time.Time `gorm:"column:started_at"`
|
||||
FinishedAt time.Time `gorm:"column:finished_at"`
|
||||
}
|
||||
|
||||
var items []ProcessedItem
|
||||
result := db.Table(processedTable).
|
||||
Order("id DESC").
|
||||
Limit(limit).
|
||||
Find(&items)
|
||||
if result.Error != nil {
|
||||
fmt.Printf("Error querying processed items: %v\n", result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
fmt.Println("No processed executions found.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%-6s %-14s %-18s %-8s %-10s %-8s %s\n",
|
||||
"ID", "QUEUE_ITEM_ID", "TYPE", "STATUS", "DURATION", "STARTED", "ERROR")
|
||||
fmt.Println("------ -------------- ------------------ -------- ---------- -------- ------------------------------")
|
||||
for _, it := range items {
|
||||
errMsg := it.ErrorMsg
|
||||
if len(errMsg) > 35 {
|
||||
errMsg = errMsg[:32] + "..."
|
||||
}
|
||||
fmt.Printf("%-6d %-14d %-18s %-8s %-10s %-8s %s\n",
|
||||
it.ID, it.QueueItemID, it.TaskType, it.Status,
|
||||
fmt.Sprintf("%dms", it.DurationMs),
|
||||
it.StartedAt.Format("15:04:05"), errMsg)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// ─── scheduler:semaphore ─────────────────────────────────────────────────────
|
||||
|
||||
var SchedulerSemaphoreCmd = &cobra.Command{
|
||||
Use: "scheduler:semaphore [dev|prod] [green|red]",
|
||||
Short: "Get or set the scheduler semaphore state",
|
||||
Long: `View or change the scheduler semaphore state.
|
||||
|
||||
Without a state argument, prints the current state.
|
||||
With "green" or "red", sets the semaphore accordingly.
|
||||
|
||||
⚠ WARNING: changing the state only updates the database. The running
|
||||
application keeps the semaphore in memory and will NOT reflect the
|
||||
change until you restart it.
|
||||
|
||||
Example:
|
||||
goffee scheduler:semaphore dev # show current state
|
||||
goffee scheduler:semaphore dev red # block after restart
|
||||
goffee scheduler:semaphore prod green # allow after restart
|
||||
`,
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
db, err := loadEnvAndDB(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the meta table exists.
|
||||
_ = db.Table(schedulerMetaTable).AutoMigrate(&struct {
|
||||
Key string `gorm:"primaryKey;size:64"`
|
||||
Value string `gorm:"size:16;not null"`
|
||||
}{})
|
||||
|
||||
if len(args) == 2 {
|
||||
state := args[1]
|
||||
if state != "green" && state != "red" {
|
||||
fmt.Printf("Error: state must be 'green' or 'red', got '%s'\n", state)
|
||||
return
|
||||
}
|
||||
var count int64
|
||||
db.Table(schedulerMetaTable).Where("key = ?", semaphoreKey).Count(&count)
|
||||
if count == 0 {
|
||||
_ = db.Table(schedulerMetaTable).Create(map[string]interface{}{
|
||||
"key": semaphoreKey, "value": state,
|
||||
})
|
||||
} else {
|
||||
_ = db.Table(schedulerMetaTable).
|
||||
Where("key = ?", semaphoreKey).
|
||||
Update("value", state)
|
||||
}
|
||||
fmt.Printf("Semaphore set to %s (restart the application for it to take effect)\n", state)
|
||||
} else {
|
||||
var value string
|
||||
err := db.Table(schedulerMetaTable).
|
||||
Where("key = ?", semaphoreKey).
|
||||
Select("value").
|
||||
Take(&value).Error
|
||||
if err != nil {
|
||||
fmt.Printf("Semaphore: %s (default)\n", semaphoreDefault)
|
||||
} else {
|
||||
fmt.Printf("Semaphore: %s\n", value)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// ─── scheduler:truncate ──────────────────────────────────────────────────────
|
||||
|
||||
var SchedulerTruncateCmd = &cobra.Command{
|
||||
Use: "scheduler:truncate [dev|prod]",
|
||||
Short: "Empty the scheduler queue and processed logs",
|
||||
Long: `Deletes all rows from queue_items and processed_items tables.
|
||||
Use with caution — this action cannot be undone.
|
||||
|
||||
Example:
|
||||
goffee scheduler:truncate dev
|
||||
goffee scheduler:truncate prod
|
||||
`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
db, err := loadEnvAndDB(args[0])
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete all processed items first (child table), then queue items.
|
||||
procResult := db.Exec(fmt.Sprintf("DELETE FROM %s", processedTable))
|
||||
if procResult.Error != nil {
|
||||
fmt.Printf("Error truncating processed_items: %v\n", procResult.Error)
|
||||
return
|
||||
}
|
||||
|
||||
queueResult := db.Exec(fmt.Sprintf("DELETE FROM %s", queueTable))
|
||||
if queueResult.Error != nil {
|
||||
fmt.Printf("Error truncating queue_items: %v\n", queueResult.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// Reset semaphore to green (default).
|
||||
var count int64
|
||||
db.Table(schedulerMetaTable).Where("key = ?", semaphoreKey).Count(&count)
|
||||
if count > 0 {
|
||||
db.Table(schedulerMetaTable).
|
||||
Where("key = ?", semaphoreKey).
|
||||
Update("value", semaphoreDefault)
|
||||
}
|
||||
|
||||
fmt.Printf("Truncated %d processed item(s) and %d queue item(s). Semaphore reset to %s.\n",
|
||||
procResult.RowsAffected, queueResult.RowsAffected, semaphoreDefault)
|
||||
},
|
||||
}
|
||||
|
||||
// ─── Registration ────────────────────────────────────────────────────────────
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(SchedulerQueueCmd)
|
||||
rootCmd.AddCommand(SchedulerProcessedCmd)
|
||||
rootCmd.AddCommand(SchedulerSemaphoreCmd)
|
||||
rootCmd.AddCommand(SchedulerTruncateCmd)
|
||||
}
|
||||
163
cmd/user-password.go
Normal file
163
cmd/user-password.go
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright (c) 2026 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var UserPasswordCmd = &cobra.Command{
|
||||
Use: "user:password [username] [newpassword] [dev|prod]",
|
||||
Short: "Change a user password",
|
||||
Long: `Set the password for the user account with the specified name.
|
||||
|
||||
Requires three arguments:
|
||||
username - the name of the user to update
|
||||
password - the new password to set
|
||||
env - "dev" to use .env-dev, "prod" to use .env
|
||||
|
||||
Example:
|
||||
goffee user:password someuser 'strongpassword' dev
|
||||
goffee user:password admin 'newpass123' prod
|
||||
|
||||
`,
|
||||
Args: cobra.ExactArgs(3),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
username := args[0]
|
||||
newPassword := args[1]
|
||||
envMode := args[2]
|
||||
|
||||
// Determine which env file to load
|
||||
var envFile string
|
||||
switch envMode {
|
||||
case "dev":
|
||||
envFile = ".env-dev"
|
||||
case "prod":
|
||||
envFile = ".env"
|
||||
default:
|
||||
fmt.Printf("Error: environment must be 'dev' or 'prod', got '%s'\n", envMode)
|
||||
return
|
||||
}
|
||||
|
||||
// Build full path to env file
|
||||
envPath := filepath.Join(".", envFile)
|
||||
if _, err := os.Stat(envPath); os.IsNotExist(err) {
|
||||
fmt.Printf("Error: environment file '%s' not found in current directory\n", envFile)
|
||||
return
|
||||
}
|
||||
|
||||
// Load env file
|
||||
err := godotenv.Load(envPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading environment file '%s': %v\n", envFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Connect to database
|
||||
db, err := connectDatabase()
|
||||
if err != nil {
|
||||
fmt.Printf("Error connecting to database: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Find user by name
|
||||
type User struct {
|
||||
ID uint
|
||||
Name string
|
||||
Password string
|
||||
Email string
|
||||
}
|
||||
var user User
|
||||
result := db.Where("name = ?", username).First(&user)
|
||||
if result.Error != nil {
|
||||
if strings.Contains(result.Error.Error(), "record not found") {
|
||||
fmt.Printf("Error: user '%s' not found\n", username)
|
||||
} else {
|
||||
fmt.Printf("Error finding user: %v\n", result.Error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Hash the new password
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
fmt.Printf("Error hashing password: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update password in database
|
||||
result = db.Model(&user).Update("password", string(hashedPassword))
|
||||
if result.Error != nil {
|
||||
fmt.Printf("Error updating password: %v\n", result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Password updated successfully for user '%s'\n", username)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(UserPasswordCmd)
|
||||
}
|
||||
|
||||
// connectDatabase establishes a database connection based on the loaded environment variables.
|
||||
func connectDatabase() (*gorm.DB, error) {
|
||||
dbDriver := os.Getenv("DB_DRIVER")
|
||||
|
||||
var dialector gorm.Dialector
|
||||
switch dbDriver {
|
||||
case "sqlite":
|
||||
sqlitePath := os.Getenv("SQLITE_DB_PATH")
|
||||
if sqlitePath == "" {
|
||||
return nil, fmt.Errorf("SQLITE_DB_PATH environment variable not set")
|
||||
}
|
||||
dialector = sqlite.Open(sqlitePath)
|
||||
case "mysql":
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local",
|
||||
os.Getenv("MYSQL_USERNAME"),
|
||||
os.Getenv("MYSQL_PASSWORD"),
|
||||
os.Getenv("MYSQL_HOST"),
|
||||
os.Getenv("MYSQL_PORT"),
|
||||
os.Getenv("MYSQL_DB_NAME"),
|
||||
os.Getenv("MYSQL_CHARSET"),
|
||||
)
|
||||
dialector = mysql.Open(dsn)
|
||||
case "postgres":
|
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=%s",
|
||||
os.Getenv("POSTGRES_HOST"),
|
||||
os.Getenv("POSTGRES_USER"),
|
||||
os.Getenv("POSTGRES_PASSWORD"),
|
||||
os.Getenv("POSTGRES_DB_NAME"),
|
||||
os.Getenv("POSTGRES_PORT"),
|
||||
os.Getenv("POSTGRES_SSL_MODE"),
|
||||
os.Getenv("POSTGRES_TIMEZONE"),
|
||||
)
|
||||
dialector = postgres.Open(dsn)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported database driver: '%s'", dbDriver)
|
||||
}
|
||||
|
||||
db, err := gorm.Open(dialector, &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"cliReleasedVersion": "v1.7.2",
|
||||
"cliReleasedVersion": "v1.7.5",
|
||||
"releaseUrl": "https://git.smarteching.com/goffee/cup/archive/{name}.tar.gz",
|
||||
"paths": [
|
||||
"git.smarteching.com/goffee/cup"
|
||||
|
|
|
|||
26
go.mod
26
go.mod
|
|
@ -15,16 +15,26 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/klauspost/compress v1.18.6 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
|
|
@ -35,9 +45,15 @@ require (
|
|||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/term v0.42.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
golang.org/x/crypto v0.52.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.6.0 // indirect
|
||||
gorm.io/driver/postgres v1.6.0 // indirect
|
||||
gorm.io/driver/sqlite v1.6.0 // indirect
|
||||
gorm.io/gorm v1.31.1 // indirect
|
||||
)
|
||||
|
|
|
|||
59
go.sum
59
go.sum
|
|
@ -1,3 +1,5 @@
|
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
||||
github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=
|
||||
|
|
@ -5,6 +7,7 @@ github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXO
|
|||
github.com/c4milo/unpackit v1.0.0 h1:Umce1lwtFvEHNFQev+xENObYiiYxdSmKhvGlkcufUGE=
|
||||
github.com/c4milo/unpackit v1.0.0/go.mod h1:0cXRaRz5pMcJm7o9jYQmPAeBl6y1na9BKy3K+og0UJY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
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/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
|
|
@ -14,8 +17,10 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
|||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.10.0 h1:Xx/5Ydg9CeBDX/wi4VJqStNtohYjitZhhlHt4h3St1M=
|
||||
github.com/fsnotify/fsnotify v1.10.0/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
|
|
@ -24,6 +29,20 @@ github.com/hooklift/assert v0.1.0 h1:UZzFxx5dSb9aBtvMHTtnPuvFnBvcEhHTPb9+0+jpEjs
|
|||
github.com/hooklift/assert v0.1.0/go.mod h1:pfexfvIHnKCdjh6CkkIZv5ic6dQ6aU2jhKghBlXuwwY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI=
|
||||
github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
|
|
@ -32,14 +51,19 @@ github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl
|
|||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/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.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
|
||||
|
|
@ -66,6 +90,9 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
|||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
|
|
@ -77,14 +104,30 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
|||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
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.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue