Compare commits

...

22 Commits

Author SHA1 Message Date
Rogee
8bcc2cbdad feat: 更新错误处理,提供更清晰的路径错误信息 2025-09-10 14:41:58 +08:00
Rogee
7187205143 feat: 增加命令行工具的参数支持,包括路径、干运行模式和输出目录选项 2025-09-10 14:38:53 +08:00
Rogee
3b804b83da feat: 增加命令行工具的干运行模式和输出目录选项 2025-09-10 14:30:16 +08:00
Rogee
a714d4a3a9 feat: 优化项目创建命令,增加模板渲染支持 2025-09-10 14:17:11 +08:00
Rogee
1fac55115d feat: update .gitignore to exclude 'atomctl' binary and AGENTS.md 2025-09-10 14:04:42 +08:00
Rogee
3759295afa feat: update tools 2025-05-28 19:39:32 +08:00
Rogee
099bdfc7d9 feat: update tools 2025-05-28 19:38:03 +08:00
Rogee
3b902509f7 feat: update 2025-05-27 09:47:24 +08:00
Rogee
ec1fa93033 feat: update 2025-05-26 12:17:52 +08:00
Rogee
2b0ee0e61c feat: add table vars to model provider 2025-05-26 10:13:30 +08:00
Rogee
906858dbd6 feat: update 2025-05-23 23:42:18 +08:00
Rogee
b4cc2347e5 feat: update 2025-05-23 22:59:18 +08:00
yanghao05
1147ca4733 feat: update 2025-05-23 22:47:15 +08:00
yanghao05
cd7c13e49d feat: update 2025-05-23 22:46:39 +08:00
yanghao05
e5000bcc73 feat: update 2025-05-23 22:45:36 +08:00
yanghao05
9ddea39084 feat: add table functions 2025-05-23 22:27:15 +08:00
yanghao05
e83332ea6a feat: support softdelete in table functions 2025-05-23 22:24:53 +08:00
yanghao05
ab36ea0e5d feat: update 2025-05-23 22:21:28 +08:00
yanghao05
c6b1a1664c feat: update 2025-05-23 22:07:26 +08:00
yanghao05
f9a32a9ecb feat: update 2025-05-23 22:06:22 +08:00
yanghao05
56ec95e43a feat: update 2025-05-23 22:03:33 +08:00
yanghao05
3617c68a91 feat: update 2025-05-23 22:02:36 +08:00
19 changed files with 721 additions and 320 deletions

4
.gitignore vendored
View File

@@ -21,4 +21,6 @@
# Go workspace file
go.work
test/*
tests/*
atomctl
AGENTS.md

19
.vscode/settings.json vendored
View File

@@ -1,3 +1,20 @@
{
"editor.fontSize": 10
"editor.fontSize": 10,
"dbcode.connections": [
{
"connectionId": "l6fEOogu2kQt5G3BashDt",
"name": "10.1.1.2",
"driver": "postgres",
"connectionType": "host",
"host": "10.1.1.2",
"port": 5432,
"ssl": false,
"username": "postgres",
"password": "",
"savePassword": "secretStorage",
"database": "quyun",
"readOnly": false,
"connectionTimeout": 30
}
]
}

View File

@@ -2,23 +2,30 @@ package cmd
import (
"fmt"
"os"
"os/exec"
"path/filepath"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func CommandBuf(root *cobra.Command) {
cmd := &cobra.Command{
Use: "buf",
Short: "run buf commands",
RunE: commandBufE,
}
cmd := &cobra.Command{
Use: "buf",
Short: "run buf commands",
RunE: commandBufE,
}
cmd.Flags().String("dir", ".", "Directory to run buf from")
cmd.Flags().Bool("dry-run", false, "Preview buf command without executing")
root.AddCommand(cmd)
}
func commandBufE(cmd *cobra.Command, args []string) error {
dir := cmd.Flag("dir").Value.String()
dryRun, _ := cmd.Flags().GetBool("dry-run")
if _, err := exec.LookPath("buf"); err != nil {
log.Warn("buf 命令不存在,正在安装 buf...")
log.Info("go install github.com/bufbuild/buf/cmd/buf@v1.48.0")
@@ -33,9 +40,19 @@ func commandBufE(cmd *cobra.Command, args []string) error {
}
}
log.Info("buf 命令已存在,正在运行 buf generate...")
log.Info("PROTOBUF GUIDE: https://buf.build/docs/best-practices/style-guide/")
generateCmd := exec.Command("buf", "generate")
// preflight: ensure buf.yaml exists
if _, err := os.Stat(filepath.Join(dir, "buf.yaml")); err != nil {
log.Warnf("未找到 %sbuf generate 可能失败", filepath.Join(dir, "buf.yaml"))
}
log.Info("buf 命令已存在,正在运行 buf generate...")
log.Info("PROTOBUF GUIDE: https://buf.build/docs/best-practices/style-guide/")
if dryRun {
log.Infof("[dry-run] (cd %s && buf generate)", dir)
return nil
}
generateCmd := exec.Command("buf", "generate")
generateCmd.Dir = dir
if err := generateCmd.Run(); err != nil {
return fmt.Errorf("运行 buf generate 失败: %v", err)
}

View File

@@ -10,17 +10,20 @@ import (
)
func CommandFmt(root *cobra.Command) {
cmd := &cobra.Command{
Use: "fmt",
Short: "fmt codes",
RunE: commandFmtE,
}
cmd := &cobra.Command{
Use: "fmt",
Short: "fmt codes",
RunE: commandFmtE,
}
cmd.Flags().Bool("check", false, "Check formatting without writing changes")
cmd.Flags().String("path", ".", "Path to format (default .)")
root.AddCommand(cmd)
}
func commandFmtE(cmd *cobra.Command, args []string) error {
log.Info("开始格式化代码")
log.Info("开始格式化代码")
if _, err := exec.LookPath("gofumpt"); err != nil {
log.Info("gofumpt 不存在,正在安装...")
installCmd := exec.Command("go", "install", "mvdan.cc/gofumpt@latest")
@@ -34,14 +37,31 @@ func commandFmtE(cmd *cobra.Command, args []string) error {
}
}
log.Info("运行 gofumpt...")
gofumptCmd := exec.Command("gofumpt", "-l", "-extra", "-w", ".")
gofumptCmd.Stdout = os.Stdout
gofumptCmd.Stderr = os.Stderr
if err := gofumptCmd.Run(); err != nil {
return fmt.Errorf("运行 gofumpt 失败: %v", err)
}
check, _ := cmd.Flags().GetBool("check")
path, _ := cmd.Flags().GetString("path")
log.Info("格式化代码完成")
return nil
if check {
log.Info("运行 gofumpt 检查模式...")
out, err := exec.Command("gofumpt", "-l", "-extra", path).CombinedOutput()
if err != nil {
return fmt.Errorf("运行 gofumpt 失败: %v", err)
}
if len(out) > 0 {
fmt.Fprintln(os.Stdout, string(out))
return fmt.Errorf("发现未格式化文件,请运行: gofumpt -l -extra -w %s", path)
}
log.Info("代码格式良好")
return nil
}
log.Info("运行 gofumpt...")
gofumptCmd := exec.Command("gofumpt", "-l", "-extra", "-w", path)
gofumptCmd.Stdout = os.Stdout
gofumptCmd.Stderr = os.Stderr
if err := gofumptCmd.Run(); err != nil {
return fmt.Errorf("运行 gofumpt 失败: %v", err)
}
log.Info("格式化代码完成")
return nil
}

View File

@@ -25,12 +25,16 @@ import (
)
func CommandGenModel(root *cobra.Command) {
cmd := &cobra.Command{
Use: "model",
Aliases: []string{"m"},
Short: "Generate jet models",
RunE: commandGenModelE,
}
cmd := &cobra.Command{
Use: "model",
Aliases: []string{"m"},
Short: "Generate jet models",
RunE: commandGenModelE,
}
cmd.Flags().String("schema", "", "Override database schema")
cmd.Flags().Bool("rename-schemas", true, "Rename generated database/<db> to database/schemas")
cmd.Flags().String("schemas-out", "database/schemas", "Schemas output directory when renaming")
root.AddCommand(cmd)
}
@@ -40,10 +44,15 @@ func commandGenModelE(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "parse go.mod")
}
_, dbConf, err := pgDatabase.GetDB(cmd.Flag("config").Value.String())
if err != nil {
return errors.Wrap(err, "get db")
}
_, dbConf, err := pgDatabase.GetDB(cmd.Flag("config").Value.String())
if err != nil {
return errors.Wrap(err, "get db")
}
// optional schema override
if s := cmd.Flag("schema").Value.String(); s != "" {
dbConf.Schema = s
}
v := viper.New()
v.SetConfigType("yaml")
@@ -166,14 +175,16 @@ func commandGenModelE(cmd *cobra.Command, args []string) error {
return err
}
if err := os.RemoveAll("database/schemas"); err != nil {
return err
}
dataPath := fmt.Sprintf("database/%s", cfg.Database)
if err := os.Rename(dataPath, "database/schemas"); err != nil {
return err
}
if rename, _ := cmd.Flags().GetBool("rename-schemas"); rename {
out := cmd.Flag("schemas-out").Value.String()
if err := os.RemoveAll(out); err != nil {
return err
}
dataPath := fmt.Sprintf("database/%s", cfg.Database)
if err := os.Rename(dataPath, out); err != nil {
return err
}
}
if err := astModel.Generate(generatedTables, transformer); err != nil {
return err

View File

@@ -1,6 +1,7 @@
package cmd
import (
"fmt"
"io/fs"
"os"
"path/filepath"
@@ -14,28 +15,35 @@ import (
)
func CommandGenRoute(root *cobra.Command) {
cmd := &cobra.Command{
Use: "route",
Short: "generate routes",
RunE: commandGenRouteE,
PostRunE: commandGenProviderE,
}
cmd := &cobra.Command{
Use: "route",
Short: "generate routes",
RunE: commandGenRouteE,
PostRunE: commandGenProviderE,
}
root.AddCommand(cmd)
cmd.Flags().String("path", ".", "Base path to scan (defaults to CWD)")
root.AddCommand(cmd)
}
// https://go.ipao.vip/atomctl/pkg/swag?tab=readme-ov-file#api-operation
func commandGenRouteE(cmd *cobra.Command, args []string) error {
var err error
var path string
if len(args) > 0 {
path = args[0]
} else {
path, err = os.Getwd()
if err != nil {
return err
}
}
var err error
var path string
if len(args) > 0 {
path = args[0]
} else {
path, err = os.Getwd()
if err != nil {
return err
}
}
// allow overriding via --path flag
if f := cmd.Flag("path"); f != nil && f.Value.String() != "." && f.Value.String() != "" {
path = f.Value.String()
}
path, _ = filepath.Abs(path)
@@ -48,7 +56,7 @@ func commandGenRouteE(cmd *cobra.Command, args []string) error {
modulePath := filepath.Join(path, "app/http")
if _, err := os.Stat(modulePath); os.IsNotExist(err) {
log.Fatal("modules dir not exist, ", modulePath)
return fmt.Errorf("routes directory not found: %s (set --path to your project root)", modulePath)
}
// controllerPattern := regexp.MustCompile(`controller(_?\w+)?\.go`)

View File

@@ -13,12 +13,14 @@ import (
// migrate
func CommandMigrate(root *cobra.Command) {
cmd := &cobra.Command{
Use: "migrate [up|up-by-one|up-to|create|down|down-to|fix|redo|reset|status|version]",
Aliases: []string{"m"},
RunE: commandMigrate,
}
cmd.Flags().StringP("config", "c", "config.toml", "database config file")
cmd := &cobra.Command{
Use: "migrate [up|up-by-one|up-to|create|down|down-to|fix|redo|reset|status|version]",
Aliases: []string{"m"},
RunE: commandMigrate,
}
cmd.Flags().StringP("config", "c", "config.toml", "database config file")
cmd.Flags().String("dir", "database/migrations", "migrations directory")
cmd.Flags().String("table", "migrations", "migrations table name")
root.AddCommand(cmd)
}
@@ -39,10 +41,13 @@ func commandMigrate(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "get db")
}
action, args := args[0], args[1:]
log.Infof("migration action: %s args: %+v", action, args)
action, args := args[0], args[1:]
log.Infof("migration action: %s args: %+v", action, args)
goose.SetTableName("migrations")
dir := cmd.Flag("dir").Value.String()
table := cmd.Flag("table").Value.String()
return goose.RunContext(context.Background(), action, db, "database/migrations", args...)
goose.SetTableName(table)
return goose.RunContext(context.Background(), action, db, dir, args...)
}

View File

@@ -5,20 +5,22 @@ import (
)
func CommandInit(root *cobra.Command) {
cmd := &cobra.Command{
Use: "new [project|module]",
Short: "new project/module",
}
cmd := &cobra.Command{
Use: "new [project|module]",
Short: "new project/module",
}
cmd.PersistentFlags().BoolP("force", "f", false, "Force init project if exists")
cmd.PersistentFlags().BoolP("force", "f", false, "Force overwrite existing files or directories")
cmd.PersistentFlags().Bool("dry-run", false, "Preview actions without writing files")
cmd.PersistentFlags().String("dir", ".", "Base directory for outputs")
cmds := []func(*cobra.Command){
CommandNewProject,
// deprecate CommandNewModule,
CommandNewProvider,
CommandNewEvent,
CommandNewJob,
}
cmds := []func(*cobra.Command){
CommandNewProject,
// deprecate CommandNewModule,
CommandNewProvider,
CommandNewEvent,
CommandNewJob,
}
for _, c := range cmds {
c(cmd)

View File

@@ -1,11 +1,12 @@
package cmd
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"text/template"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"text/template"
"go.ipao.vip/atomctl/pkg/utils/gomod"
"go.ipao.vip/atomctl/templates"
@@ -15,23 +16,30 @@ import (
// CommandNewProvider 注册 new_provider 命令
func CommandNewEvent(root *cobra.Command) {
cmd := &cobra.Command{
Use: "event",
Aliases: []string{"e"},
Short: "创建新的 event publish & subscriber",
Args: cobra.ExactArgs(1),
RunE: commandNewEventE,
}
cmd := &cobra.Command{
Use: "event",
Aliases: []string{"e"},
Short: "创建新的 event publish & subscriber",
Args: cobra.ExactArgs(1),
RunE: commandNewEventE,
}
root.AddCommand(cmd)
cmd.Flags().String("only", "", "仅生成: publisher 或 subscriber")
root.AddCommand(cmd)
}
func commandNewEventE(cmd *cobra.Command, args []string) error {
snakeName := lo.SnakeCase(args[0])
camelName := lo.PascalCase(args[0])
publisherPath := "app/events/publishers"
subscriberPath := "app/events/subscribers"
// shared flags
dryRun, _ := cmd.Flags().GetBool("dry-run")
baseDir, _ := cmd.Flags().GetString("dir")
only, _ := cmd.Flags().GetString("only")
publisherPath := filepath.Join(baseDir, "app/events/publishers")
subscriberPath := filepath.Join(baseDir, "app/events/subscribers")
path, err := os.Getwd()
if err != nil {
@@ -44,13 +52,17 @@ func commandNewEventE(cmd *cobra.Command, args []string) error {
return err
}
if err := os.MkdirAll(publisherPath, os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(subscriberPath, os.ModePerm); err != nil {
return err
}
if dryRun {
fmt.Printf("[dry-run] mkdir -p %s\n", publisherPath)
fmt.Printf("[dry-run] mkdir -p %s\n", subscriberPath)
} else {
if err := os.MkdirAll(publisherPath, os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(subscriberPath, os.ModePerm); err != nil {
return err
}
}
err = fs.WalkDir(templates.Events, "events", func(path string, d fs.DirEntry, err error) error {
if err != nil {
@@ -65,42 +77,59 @@ func commandNewEventE(cmd *cobra.Command, args []string) error {
return err
}
var destPath string
if relPath == "publisher.go.tpl" {
destPath = filepath.Join(publisherPath, snakeName+".go")
} else if relPath == "subscriber.go.tpl" {
destPath = filepath.Join(subscriberPath, snakeName+".go")
}
var destPath string
if relPath == "publisher.go.tpl" {
if only == "subscriber" { return nil }
destPath = filepath.Join(publisherPath, snakeName+".go")
} else if relPath == "subscriber.go.tpl" {
if only == "publisher" { return nil }
destPath = filepath.Join(subscriberPath, snakeName+".go")
} else { return nil }
tmpl, err := template.ParseFS(templates.Events, path)
if err != nil {
return err
}
destFile, err := os.Create(destPath)
if err != nil {
return err
}
defer destFile.Close()
if dryRun {
fmt.Printf("[dry-run] render > %s\n", destPath)
return nil
}
return tmpl.Execute(destFile, map[string]string{
"Name": camelName,
"ModuleName": gomod.GetModuleName(),
})
})
destFile, err := os.Create(destPath)
if err != nil {
return err
}
defer destFile.Close()
topicStr := fmt.Sprintf("const Topic%s = %q\n", camelName, snakeName)
// 写入到 app/events/topic.go
topicFile, err := os.OpenFile("app/events/topics.go", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer topicFile.Close()
return tmpl.Execute(destFile, map[string]string{
"Name": camelName,
"ModuleName": gomod.GetModuleName(),
})
})
_, err = topicFile.WriteString(topicStr)
if err != nil {
return err
}
// 写入或追加 topic 常量,避免重复。
topicsPath := filepath.Join(baseDir, "app/events/topics.go")
topicLine := fmt.Sprintf("const Topic%s = %q\n", camelName, snakeName)
if dryRun {
fmt.Printf("[dry-run] ensure topics file and add constant > %s\n", topicsPath)
} else {
// ensure file exists with basic header
if _, statErr := os.Stat(topicsPath); os.IsNotExist(statErr) {
if err := os.MkdirAll(filepath.Dir(topicsPath), os.ModePerm); err != nil { return err }
header := "package events\n\n// topics generated by atomctl\n\n"
if err := os.WriteFile(topicsPath, []byte(header), 0o644); err != nil { return err }
}
// check duplicate
content, _ := os.ReadFile(topicsPath)
if !strings.Contains(string(content), "Topic"+camelName+" ") && !strings.Contains(string(content), topicLine) {
f, err := os.OpenFile(topicsPath, os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil { return err }
defer f.Close()
if _, err := f.WriteString(topicLine); err != nil { return err }
}
}
fmt.Printf("event 已创建: %s\n", snakeName)
return nil

View File

@@ -26,10 +26,14 @@ func CommandNewJob(root *cobra.Command) {
}
func commandNewJobE(cmd *cobra.Command, args []string) error {
snakeName := lo.SnakeCase(args[0])
camelName := lo.PascalCase(args[0])
snakeName := lo.SnakeCase(args[0])
camelName := lo.PascalCase(args[0])
destPath := "app/jobs"
// shared flags
dryRun, _ := cmd.Flags().GetBool("dry-run")
baseDir, _ := cmd.Flags().GetString("dir")
basePath := filepath.Join(baseDir, "app/jobs")
path, err := os.Getwd()
if err != nil {
@@ -42,9 +46,13 @@ func commandNewJobE(cmd *cobra.Command, args []string) error {
return err
}
if err := os.MkdirAll(destPath, os.ModePerm); err != nil {
return err
}
if dryRun {
fmt.Printf("[dry-run] mkdir -p %s\n", basePath)
} else {
if err := os.MkdirAll(basePath, os.ModePerm); err != nil {
return err
}
}
err = fs.WalkDir(templates.Jobs, "jobs", func(path string, d fs.DirEntry, err error) error {
if err != nil {
@@ -55,22 +63,27 @@ func commandNewJobE(cmd *cobra.Command, args []string) error {
return nil
}
destPath := filepath.Join(destPath, snakeName+".go")
tmpl, err := template.ParseFS(templates.Jobs, path)
if err != nil {
return err
}
filePath := filepath.Join(basePath, snakeName+".go")
tmpl, err := template.ParseFS(templates.Jobs, path)
if err != nil {
return err
}
destFile, err := os.Create(destPath)
if err != nil {
return err
}
defer destFile.Close()
if dryRun {
fmt.Printf("[dry-run] render > %s\n", filePath)
return nil
}
return tmpl.Execute(destFile, map[string]string{
"Name": camelName,
"ModuleName": gomod.GetModuleName(),
})
destFile, err := os.Create(filePath)
if err != nil {
return err
}
defer destFile.Close()
return tmpl.Execute(destFile, map[string]string{
"Name": camelName,
"ModuleName": gomod.GetModuleName(),
})
})
if err != nil {
return err

View File

@@ -1,6 +1,7 @@
package cmd
import (
"bytes"
"fmt"
"io/fs"
"os"
@@ -9,10 +10,11 @@ import (
"strings"
"text/template"
"go.ipao.vip/atomctl/templates"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"go.ipao.vip/atomctl/pkg/utils/gomod"
"go.ipao.vip/atomctl/templates"
)
// 验证包名是否合法:支持域名、路径分隔符和常见字符
@@ -34,9 +36,31 @@ func CommandNewProject(root *cobra.Command) {
}
func commandNewProjectE(cmd *cobra.Command, args []string) error {
moduleName := args[0]
if !isValidGoPackageName(moduleName) {
return fmt.Errorf("invalid module name: %s, should be a valid go package name", moduleName)
var (
moduleName string
inPlace bool
)
// shared flags
dryRun, _ := cmd.Flags().GetBool("dry-run")
baseDir, _ := cmd.Flags().GetString("dir")
if len(args) == 0 {
if _, err := os.Stat("go.mod"); err == nil {
pwd, _ := os.Getwd()
if err := gomod.Parse(filepath.Join(pwd, "go.mod")); err != nil {
return fmt.Errorf("parse go.mod failed: %v", err)
}
moduleName = gomod.GetModuleName()
inPlace = true
} else {
return fmt.Errorf("module name required or run inside an existing module (go.mod)")
}
} else {
moduleName = args[0]
if !isValidGoPackageName(moduleName) {
return fmt.Errorf("invalid module name: %s, should be a valid go package name", moduleName)
}
}
log.Info("创建项目: ", moduleName)
@@ -50,90 +74,126 @@ func commandNewProjectE(cmd *cobra.Command, args []string) error {
moduleSplitInfo := strings.Split(projectInfo.ModuleName, "/")
projectInfo.ProjectName = moduleSplitInfo[len(moduleSplitInfo)-1]
// 检查目录是否存在
force, _ := cmd.Flags().GetBool("force")
if _, err := os.Stat(projectInfo.ProjectName); err == nil {
if !force {
return fmt.Errorf("project directory %s already exists", projectInfo.ProjectName)
}
log.Warnf("强制删除已存在的目录: %s", projectInfo.ProjectName)
if err := os.RemoveAll(projectInfo.ProjectName); err != nil {
return fmt.Errorf("failed to remove existing directory: %v", err)
}
}
// 创建项目根目录
if err := os.MkdirAll(projectInfo.ProjectName, 0o755); err != nil {
return fmt.Errorf("failed to create project directory: %v", err)
}
rootDir := "."
if !inPlace {
// honor base dir when creating a new project
rootDir = filepath.Join(baseDir, projectInfo.ProjectName)
if _, err := os.Stat(rootDir); err == nil {
if !force {
return fmt.Errorf("project directory %s already exists", rootDir)
}
log.Warnf("强制删除已存在的目录: %s", rootDir)
if dryRun {
log.Infof("[dry-run] 将删除目录: %s", rootDir)
} else if err := os.RemoveAll(rootDir); err != nil {
return fmt.Errorf("failed to remove existing directory: %v", err)
}
}
if dryRun {
log.Infof("[dry-run] 将创建目录: %s", rootDir)
} else if err := os.MkdirAll(rootDir, 0o755); err != nil {
return fmt.Errorf("failed to create project directory: %v", err)
}
}
// 遍历和处理模板文件
if err := fs.WalkDir(templates.Project, "project", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// 计算相对路径,并处理隐藏文件
relPath, err := filepath.Rel("project", path)
if err != nil {
return err
}
// 如果是隐藏文件模板,将文件名中的前缀 "-" 替换为 "."
fileName := filepath.Base(relPath)
if strings.HasPrefix(fileName, "-") {
fileName = "." + strings.TrimPrefix(fileName, "-")
relPath = filepath.Join(filepath.Dir(relPath), fileName)
}
targetPath := filepath.Join(projectInfo.ProjectName, relPath)
if d.IsDir() {
log.Infof("创建目录: %s", targetPath)
return os.MkdirAll(targetPath, 0o755)
}
targetPath := filepath.Join(rootDir, relPath)
if d.IsDir() {
log.Infof("创建目录: %s", targetPath)
if dryRun {
log.Infof("[dry-run] mkdir -p %s", targetPath)
return nil
}
return os.MkdirAll(targetPath, 0o755)
}
// 读取模板内容
content, err := templates.Project.ReadFile(path)
if err != nil {
return err
}
// 处理模板文件
// 渲染判定:优先(.tpl/.raw) > 行内指令 > 模板分隔符
isTpl := false
if strings.HasSuffix(path, ".tpl") {
isTpl = true
targetPath = strings.TrimSuffix(targetPath, ".tpl")
} else if strings.HasSuffix(path, ".raw") {
isTpl = false
targetPath = strings.TrimSuffix(targetPath, ".raw")
} else if bytes.Contains(content, []byte("atomctl:mode=tpl")) {
isTpl = true
} else if bytes.Contains(content, []byte("atomctl:mode=raw")) {
isTpl = false
} else if bytes.Contains(content, []byte("{{")) && bytes.Contains(content, []byte("}}")) {
isTpl = true
}
if inPlace && strings.HasSuffix(path, string(os.PathSeparator)+"go.mod.tpl") {
if _, err := os.Stat(filepath.Join(rootDir, "go.mod")); err == nil {
log.Infof("跳过已有文件: %s", filepath.Join(rootDir, "go.mod"))
return nil
}
}
if !force {
if _, err := os.Stat(targetPath); err == nil {
log.Warnf("文件已存在,跳过: %s", targetPath)
return nil
}
}
if isTpl {
tmpl, err := template.New(filepath.Base(path)).Parse(string(content))
if err != nil {
return err
}
log.Infof("[渲染] 文件: %s", targetPath)
if dryRun {
log.Infof("[dry-run] render > %s", targetPath)
return nil
}
f, err := os.Create(targetPath)
if err != nil {
return errors.Wrapf(err, "创建文件失败 %s", targetPath)
}
defer f.Close()
return tmpl.Execute(f, projectInfo)
}
// 创建目标文件(去除.tpl后缀
targetPath = strings.TrimSuffix(targetPath, ".tpl")
log.Infof("创建文件: %s", targetPath)
f, err := os.Create(targetPath)
if err != nil {
return errors.Wrapf(err, "创建文件失败 %s", targetPath)
}
defer f.Close()
log.Infof("[复制] 文件: %s", targetPath)
if dryRun {
log.Infof("[dry-run] write > %s", targetPath)
return nil
}
return os.WriteFile(targetPath, content, 0o644)
}); err != nil {
return err
}
return tmpl.Execute(f, projectInfo)
}
// 处理模板文件
if strings.HasSuffix(path, ".raw") {
// 创建目标文件(去除.tpl后缀
targetPath = strings.TrimSuffix(targetPath, ".raw")
log.Infof("创建文件: %s", targetPath)
}
// 复制非模板文件
return os.WriteFile(targetPath, content, 0o644)
}); err != nil {
return err
if inPlace {
log.Info("🎉 项目初始化成功 (当前目录)!")
} else {
log.Info("🎉 项目创建成功!")
log.Info("后续步骤:")
log.Infof(" cd %s", projectInfo.ProjectName)
}
// 添加成功提示
log.Info("🎉 项目创建成功!")
log.Info("后续步骤:")
log.Infof(" cd %s", projectInfo.ProjectName)
log.Info(" go mod tidy")
return nil

View File

@@ -27,16 +27,24 @@ func CommandNewProvider(root *cobra.Command) {
}
func commandNewProviderE(cmd *cobra.Command, args []string) error {
providerName := args[0]
targetPath := filepath.Join("providers", providerName)
providerName := args[0]
// shared flags
dryRun, _ := cmd.Flags().GetBool("dry-run")
baseDir, _ := cmd.Flags().GetString("dir")
if _, err := os.Stat(targetPath); err == nil {
return fmt.Errorf("目录 %s 已存在", targetPath)
}
targetPath := filepath.Join(baseDir, "providers", providerName)
if err := os.MkdirAll(targetPath, os.ModePerm); err != nil {
return err
}
if _, err := os.Stat(targetPath); err == nil {
return fmt.Errorf("目录 %s 已存在", targetPath)
}
if dryRun {
fmt.Printf("[dry-run] mkdir -p %s\n", targetPath)
} else {
if err := os.MkdirAll(targetPath, os.ModePerm); err != nil {
return err
}
}
err := fs.WalkDir(templates.Provider, "provider", func(path string, d fs.DirEntry, err error) error {
if err != nil {
@@ -51,26 +59,35 @@ func commandNewProviderE(cmd *cobra.Command, args []string) error {
return err
}
destPath := filepath.Join(targetPath, strings.TrimSuffix(relPath, ".tpl"))
if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
return err
}
destPath := filepath.Join(targetPath, strings.TrimSuffix(relPath, ".tpl"))
if dryRun {
fmt.Printf("[dry-run] mkdir -p %s\n", filepath.Dir(destPath))
} else {
if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
return err
}
}
tmpl, err := template.ParseFS(templates.Provider, path)
if err != nil {
return err
}
destFile, err := os.Create(destPath)
if err != nil {
return err
}
defer destFile.Close()
if dryRun {
fmt.Printf("[dry-run] render > %s\n", destPath)
return nil
}
return tmpl.Execute(destFile, map[string]string{
"Name": providerName,
"CamelName": strcase.ToCamel(providerName),
})
destFile, err := os.Create(destPath)
if err != nil {
return err
}
defer destFile.Close()
return tmpl.Execute(destFile, map[string]string{
"Name": providerName,
"CamelName": strcase.ToCamel(providerName),
})
})
if err != nil {
return errors.New("渲染 provider 模板失败")

View File

@@ -6,20 +6,25 @@ import (
)
func CommandSwagFmt(root *cobra.Command) {
cmd := &cobra.Command{
Use: "fmt",
Aliases: []string{"f"},
Short: "swag format",
RunE: commandSwagFmtE,
}
cmd := &cobra.Command{
Use: "fmt",
Aliases: []string{"f"},
Short: "swag format",
RunE: commandSwagFmtE,
}
cmd.Flags().String("dir", "./app/http", "SearchDir for swag format")
cmd.Flags().String("main", "main.go", "MainFile for swag format")
root.AddCommand(cmd)
}
func commandSwagFmtE(cmd *cobra.Command, args []string) error {
return format.New().Build(&format.Config{
SearchDir: "./app/http",
Excludes: "",
MainFile: "main.go",
})
dir := cmd.Flag("dir").Value.String()
main := cmd.Flag("main").Value.String()
return format.New().Build(&format.Config{
SearchDir: dir,
Excludes: "",
MainFile: main,
})
}

View File

@@ -11,39 +11,45 @@ import (
)
func CommandSwagInit(root *cobra.Command) {
cmd := &cobra.Command{
Use: "init",
Short: "swag init",
Aliases: []string{"i"},
RunE: commandSwagInitE,
}
cmd := &cobra.Command{
Use: "init",
Short: "swag init",
Aliases: []string{"i"},
RunE: commandSwagInitE,
}
cmd.Flags().String("dir", ".", "SearchDir (project root)")
cmd.Flags().String("out", "docs", "Output dir for generated docs")
cmd.Flags().String("main", "main.go", "Main API file path")
root.AddCommand(cmd)
}
func commandSwagInitE(cmd *cobra.Command, args []string) error {
pwd, err := os.Getwd()
if err != nil {
return err
}
if len(args) > 0 {
pwd = args[0]
}
root := cmd.Flag("dir").Value.String()
if root == "" {
var err error
root, err = os.Getwd()
if err != nil { return err }
}
leftDelim, rightDelim := "{{", "}}"
return gen.New().Build(&gen.Config{
SearchDir: pwd,
Excludes: "",
ParseExtension: "",
MainAPIFile: "main.go",
PropNamingStrategy: swag.CamelCase,
OutputDir: filepath.Join(pwd, "docs"),
OutputTypes: []string{"go", "json", "yaml"},
ParseVendor: false,
ParseDependency: 0,
MarkdownFilesDir: "",
ParseInternal: false,
outDir := cmd.Flag("out").Value.String()
mainFile := cmd.Flag("main").Value.String()
return gen.New().Build(&gen.Config{
SearchDir: root,
Excludes: "",
ParseExtension: "",
MainAPIFile: mainFile,
PropNamingStrategy: swag.CamelCase,
OutputDir: filepath.Join(root, outDir),
OutputTypes: []string{"go", "json", "yaml"},
ParseVendor: false,
ParseDependency: 0,
MarkdownFilesDir: "",
ParseInternal: false,
Strict: false,
GeneratedTime: false,
RequiredByDefault: false,

View File

@@ -26,9 +26,12 @@ var tableTestTpl string
var providerTplStr string
type TableModelParam struct {
PkgName string
CamelTable string // user
PascalTable string // User
PkgName string
CamelTable string // user
PascalTable string // User
SoftDelete bool
HasUpdatedAt bool
HasCreatedAt bool
}
func Generate(tables []string, transformer Transformer) error {
@@ -65,6 +68,7 @@ func Generate(tables []string, transformer Transformer) error {
return err
}
modelContent := make(map[string]string)
for _, file := range files {
// get filename without ext
name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
@@ -75,6 +79,13 @@ func Generate(tables []string, transformer Transformer) error {
if err := os.Rename(from, to); err != nil {
return err
}
// read file content
content, err := os.ReadFile(to)
if err != nil {
return err
}
modelContent[name] = string(content)
}
// remove database/schemas/public/model
@@ -95,12 +106,35 @@ func Generate(tables []string, transformer Transformer) error {
}
tableInfo := TableModelParam{
CamelTable: lo.CamelCase(table),
PascalTable: lo.PascalCase(table),
PkgName: gomod.GetModuleName(),
CamelTable: lo.CamelCase(table),
PascalTable: lo.PascalCase(table),
PkgName: gomod.GetModuleName(),
SoftDelete: strings.Contains(modelContent[table], "DeletedAt"),
HasUpdatedAt: strings.Contains(modelContent[table], "UpdatedAt"),
HasCreatedAt: strings.Contains(modelContent[table], "CreatedAt"),
}
items = append(items, tableInfo)
// tableFuncsFile
tableFuncsFile := fmt.Sprintf("%s/%s.funcs.gen.go", baseDir, table)
// 如果 modelFuncsFile 已存在,则跳过
if _, err := os.Stat(tableFuncsFile); err == nil {
fmt.Printf("Model funcs file %s already exists. Skipping...\n", tableFuncsFile)
continue
}
// 如果 modelFuncsFile 不存在,则创建
fd, err := os.Create(tableFuncsFile)
if err != nil {
return fmt.Errorf("failed to create model funcs file %s: %w", tableFuncsFile, err)
}
defer fd.Close()
if err := tableFuncsTpl.Execute(fd, tableInfo); err != nil {
return fmt.Errorf("failed to render model funcs template: %w", err)
}
modelFile := fmt.Sprintf("%s/%s.go", baseDir, table)
// 如果 modelFile 已存在,则跳过
if _, err := os.Stat(modelFile); err == nil {
@@ -109,7 +143,7 @@ func Generate(tables []string, transformer Transformer) error {
}
// 如果 modelFile 不存在,则创建
fd, err := os.Create(modelFile)
fd, err = os.Create(modelFile)
if err != nil {
return fmt.Errorf("failed to create model file %s: %w", modelFile, err)
}
@@ -137,24 +171,6 @@ func Generate(tables []string, transformer Transformer) error {
return fmt.Errorf("failed to render model test template: %w", err)
}
// tableFuncsFile
tableFuncsFile := fmt.Sprintf("%s/%s.funcs.gen.go", baseDir, table)
// 如果 modelFuncsFile 已存在,则跳过
if _, err := os.Stat(tableFuncsFile); err == nil {
fmt.Printf("Model funcs file %s already exists. Skipping...\n", tableFuncsFile)
continue
}
// 如果 modelFuncsFile 不存在,则创建
fd, err = os.Create(tableFuncsFile)
if err != nil {
return fmt.Errorf("failed to create model funcs file %s: %w", tableFuncsFile, err)
}
defer fd.Close()
if err := tableFuncsTpl.Execute(fd, tableInfo); err != nil {
return fmt.Errorf("failed to render model funcs template: %w", err)
}
}
// 渲染总的 provider 文件

View File

@@ -6,12 +6,75 @@ import (
"context"
"database/sql"
{{ if gt (len .) 0 }}
"{{ (index . 0).PkgName }}/database/table"
{{ end }}
"go.ipao.vip/atom"
"go.ipao.vip/atom/container"
"go.ipao.vip/atom/contracts"
"go.ipao.vip/atom/opt"
. "github.com/go-jet/jet/v2/postgres"
"github.com/samber/lo"
"golang.org/x/exp/constraints"
)
type Cond func(BoolExpression) BoolExpression
func ExprCond(expr BoolExpression) Cond {
return func(cond BoolExpression) BoolExpression {
return cond.AND(expr)
}
}
func CondTrue(conds ...Cond) BoolExpression {
cond:= BoolExp(Bool(true))
for _, c := range conds {
cond = c(cond)
}
return cond
}
func CondJoin(cond Cond, conds ...Cond) []Cond {
return append([]Cond{cond}, conds...)
}
// converts
func IntExprSlice[T constraints.Integer](slice []T) []Expression {
if len(slice) == 0 { return nil }
return lo.Map(slice, func(item T, _ int) Expression {
switch any(item).(type) {
case int8:
return Int8(int8(item))
case int16:
return Int16(int16(item))
case int32:
return Int32(int32(item))
case int64:
return Int64(int64(item))
case uint8:
return Uint8(uint8(item))
case uint16:
return Uint16(uint16(item))
case uint32:
return Uint32(uint32(item))
case uint64:
return Uint64(uint64(item))
default:
return nil
}
})
}
// tables
{{- range . }}
var tbl{{.PascalTable}} = table.{{.PascalTable}}
{{- end }}
// models
var db *sql.DB
{{- range . }}
func {{.PascalTable}}Model() *{{.PascalTable}} { return &{{.PascalTable}}{} }

View File

@@ -1,9 +1,11 @@
package model
import (
log "github.com/sirupsen/logrus"
)
var tbl{{.PascalTable}}UpdateMutableColumns = tbl{{.PascalTable}}.MutableColumns.Except(
{{- if .HasCreatedAt}}
tbl{{.PascalTable}}.CreatedAt,
{{- end}}
func (m *{{.PascalTable}}) log() *log.Entry {
return log.WithField("model", "{{.PascalTable}}Model")
}
{{- if .SoftDelete}}
tbl{{.PascalTable}}.DeletedAt,
{{- end}}
)

View File

@@ -1,18 +1,52 @@
// Code generated by the atomctl ; DO NOT EDIT.
// Code generated by the atomctl ; DO NOT EDIT.
// Code generated by the atomctl ; DO NOT EDIT.
package model
import (
"context"
"time"
"github.com/samber/lo"
. "github.com/go-jet/jet/v2/postgres"
log "github.com/sirupsen/logrus"
)
// conds
{{- if .SoftDelete }}
func (m *{{.PascalTable}}) CondNotDeleted() Cond {
return func(cond BoolExpression) BoolExpression {
return cond.AND(tbl{{.PascalTable}}.DeletedAt.IS_NULL())
}
}
func (m *{{.PascalTable}}) CondDeleted() Cond {
return func(cond BoolExpression) BoolExpression {
return cond.AND(tbl{{.PascalTable}}.DeletedAt.IS_NOT_NULL())
}
}
{{- end}}
func (m *{{.PascalTable}}) CondID(id int64) Cond {
return func(cond BoolExpression) BoolExpression {
return cond.AND(tbl{{.PascalTable}}.ID.EQ(Int(id)))
}
}
// funcs
func (m *{{.PascalTable}}) log() *log.Entry {
return log.WithField("model", "{{.PascalTable}}")
}
func (m *{{.PascalTable}}) Create(ctx context.Context) error {
{{- if .HasCreatedAt}}
m.CreatedAt = time.Now()
stmt := table.Medias.INSERT(table.{{.PascalTable}}.MutableColumns).MODEL(m).RETURNING(table.Medias.AllColumns)
{{- end}}
{{- if .HasUpdatedAt}}
m.UpdatedAt = time.Now()
{{- end}}
stmt := tbl{{.PascalTable}}.INSERT(tbl{{.PascalTable}}.MutableColumns).MODEL(m).RETURNING(tbl{{.PascalTable}}.AllColumns)
m.log().WithField("func","Create").Info( stmt.DebugSql())
if err := stmt.QueryContext(ctx, db, m); err != nil {
@@ -26,7 +60,7 @@ func (m *{{.PascalTable}}) Create(ctx context.Context) error {
func (m *{{.PascalTable}}) BatchCreate(ctx context.Context, models []*{{.PascalTable}}) error {
stmt := table.{{.PascalTable}}.INSERT(table.{{.PascalTable}}.MutableColumns).MODELS(models)
stmt := tbl{{.PascalTable}}.INSERT(tbl{{.PascalTable}}.MutableColumns).MODELS(models)
m.log().WithField("func", "BatchCreate").Info(stmt.DebugSql())
if _, err := stmt.ExecContext(ctx, db); err != nil {
@@ -38,9 +72,41 @@ func (m *{{.PascalTable}}) BatchCreate(ctx context.Context, models []*{{.PascalT
return nil
}
// Delete
{{- if .SoftDelete }}
func (m *{{.PascalTable}}) Delete(ctx context.Context) error {
stmt := table.{{.PascalTable}}.DELETE().WHERE(table.{{.PascalTable}}.ID.EQ(m.ID))
stmt := tbl{{.PascalTable}}.UPDATE().SET(tbl{{.PascalTable}}.DeletedAt.SET(TimestampT(time.Now()))).WHERE(tbl{{.PascalTable}}.ID.EQ(Int(m.ID)))
m.log().WithField("func", "SoftDelete").Info(stmt.DebugSql())
if err := stmt.QueryContext(ctx, db, m); err != nil {
m.log().WithField("func","SoftDelete").Errorf("error soft deleting {{.PascalTable}} item: %v", err)
return err
}
m.log().WithField("func", "SoftDelete").Infof("{{.PascalTable}} item soft deleted successfully")
return nil
}
// BatchDelete
func (m *{{.PascalTable}}) BatchDelete(ctx context.Context, ids []int64) error {
condIds := lo.Map(ids, func(id int64, _ int) Expression {
return Int64(id)
})
stmt := tbl{{.PascalTable}}.UPDATE().SET(tbl{{.PascalTable}}.DeletedAt.SET(TimestampT(time.Now()))).WHERE(tbl{{.PascalTable}}.ID.IN(condIds...))
m.log().WithField("func", "BatchSoftDelete").Info(stmt.DebugSql())
if err := stmt.QueryContext(ctx, db, m); err != nil {
m.log().WithField("func","BatchSoftDelete").Errorf("error soft deleting {{.PascalTable}} items: %v", err)
return err
}
m.log().WithField("func", "BatchSoftDelete").WithField("ids", ids).Infof("{{.PascalTable}} items soft deleted successfully")
return nil
}
func (m *{{.PascalTable}}) ForceDelete(ctx context.Context) error {
stmt := tbl{{.PascalTable}}.DELETE().WHERE(tbl{{.PascalTable}}.ID.EQ(Int(m.ID)))
m.log().WithField("func", "Delete").Info(stmt.DebugSql())
if _, err := stmt.ExecContext(ctx, db); err != nil {
@@ -52,21 +118,61 @@ func (m *{{.PascalTable}}) Delete(ctx context.Context) error {
return nil
}
func (m *{{.PascalTable}}) BatchDelete(ctx context.Context, ids []int64) error {
stmt := table.{{.PascalTable}}.DELETE().WHERE(table.{{.PascalTable}}.ID.IN(ids))
func (m *{{.PascalTable}}) BatchForceDelete(ctx context.Context, ids []int64) error {
condIds := lo.Map(ids, func(id int64, _ int) Expression {
return Int64(id)
})
stmt := tbl{{.PascalTable}}.DELETE().WHERE(tbl{{.PascalTable}}.ID.IN(condIds...))
m.log().WithField("func", "BatchDelete").Info(stmt.DebugSql())
if _, err := stmt.ExecContext(ctx, db); err != nil {
m.log().WithField("func","BatchForceDelete").Errorf("error deleting {{.PascalTable}} items: %v", err)
return err
}
m.log().WithField("func", "BatchForceDelete").WithField("ids", ids).Infof("{{.PascalTable}} items deleted successfully")
return nil
}
{{- else}}
func (m *{{.PascalTable}}) Delete(ctx context.Context) error {
stmt := tbl{{.PascalTable}}.DELETE().WHERE(tbl{{.PascalTable}}.ID.EQ(Int(m.ID)))
m.log().WithField("func", "Delete").Info(stmt.DebugSql())
if err := stmt.QueryContext(ctx, db, m); err != nil {
m.log().WithField("func","Delete").Errorf("error deleting {{.PascalTable}} item: %v", err)
return err
}
m.log().WithField("func", "Delete").Infof("{{.PascalTable}} item deleted successfully")
return nil
}
// BatchDelete
func (m *{{.PascalTable}}) BatchDelete(ctx context.Context, ids []int64) error {
condIds := lo.Map(ids, func(id int64, _ int) Expression {
return Int64(id)
})
stmt := tbl{{.PascalTable}}.DELETE().WHERE(tbl{{.PascalTable}}.ID.IN(condIds...))
m.log().WithField("func", "BatchDelete").Info(stmt.DebugSql())
if err := stmt.QueryContext(ctx, db, m); err != nil {
m.log().WithField("func","BatchDelete").Errorf("error deleting {{.PascalTable}} items: %v", err)
return err
}
m.log().WithField("func", "BatchDelete").Infof("{{.PascalTable}} items deleted successfully")
m.log().WithField("func", "BatchDelete").WithField("ids", ids).Infof("{{.PascalTable}} items deleted successfully")
return nil
}
{{- end}}
func (m *{{.PascalTable}}) Update(ctx context.Context) error {
stmt := table.{{.PascalTable}}.UPDATE(table.{{.PascalTable}}.MutableColumns).SET(m).WHERE(table.{{.PascalTable}}.ID.EQ(m.ID)).RETURNING(table.{{.PascalTable}}.AllColumns)
{{- if .HasUpdatedAt}}
m.UpdatedAt = time.Now()
{{- end}}
stmt := tbl{{.PascalTable}}.UPDATE(tbl{{.PascalTable}}UpdateMutableColumns).MODEL(m).WHERE(tbl{{.PascalTable}}.ID.EQ(Int(m.ID))).RETURNING(tbl{{.PascalTable}}.AllColumns)
m.log().WithField("func", "Update").Info(stmt.DebugSql())
if err := stmt.QueryContext(ctx, db, m); err != nil {
@@ -77,32 +183,34 @@ func (m *{{.PascalTable}}) Update(ctx context.Context) error {
m.log().WithField("func", "Update").Infof("{{.PascalTable}} item updated successfully")
return nil
}
// GetByID
func (m *{{.PascalTable}}) GetByID(ctx context.Context, id int64) (*{{.PascalTable}}, error) {
stmt := table.{{.PascalTable}}.SELECT(table.{{.PascalTable}}.AllColumns).WHERE(table.{{.PascalTable}}.ID.EQ(id))
m.log().WithField("func", "GetByID").Info(stmt.DebugSql())
// GetByCond
func (m *{{.PascalTable}}) GetByCond(ctx context.Context, conds ...Cond) (*{{.PascalTable}}, error) {
cond := CondTrue(conds...)
stmt := tbl{{.PascalTable}}.SELECT(tbl{{.PascalTable}}.AllColumns).WHERE(cond)
m.log().WithField("func", "GetByCond").Info(stmt.DebugSql())
if err := stmt.QueryContext(ctx, db, m); err != nil {
m.log().WithField("func","GetByID").Errorf("error getting {{.PascalTable}} item by ID: %v", err)
m.log().WithField("func","GetByCond").Errorf("error getting {{.PascalTable}} item by ID: %v", err)
return nil, err
}
m.log().WithField("func", "GetByID").Infof("{{.PascalTable}} item retrieved successfully")
m.log().WithField("func", "GetByCond").Infof("{{.PascalTable}} item retrieved successfully")
return m, nil
}
// GetByID
func (m *{{.PascalTable}}) GetByID(ctx context.Context, id int64, conds ...Cond) (*{{.PascalTable}}, error) {
return m.GetByCond(ctx, CondJoin(m.CondID(id), conds...)...)
}
// Count
func (m *{{.PascalTable}}) Count(ctx context.Context, conds ...BoolExpression) (int64, error) {
cond := Bool(true)
if len(conds) > 0 {
for _, c := range conds {
cond = cond.AND(c)
}
}
func (m *{{.PascalTable}}) Count(ctx context.Context, conds ...Cond) (int64, error) {
cond := CondTrue(conds...)
tbl := table.{{.PascalTable}}
stmt := tbl.SELECT(COUNT(tbl.ID).AS("count")).WHERE(cond)
stmt := tbl{{.PascalTable}}.SELECT(COUNT(tbl{{.PascalTable}}.ID).AS("count")).WHERE(cond)
m.log().Infof("sql: %s", stmt.DebugSql())
var count struct {

View File

@@ -6,7 +6,7 @@ import (
"{{ .PkgName }}/app/service/testx"
"{{ .PkgName }}/database"
"{{ .PkgName }}/database/schemas/public/table"
"{{ .PkgName }}/database/table"
. "github.com/smartystreets/goconvey/convey"
"go.ipao.vip/atom/contracts"
@@ -38,6 +38,6 @@ func Test_{{ .PascalTable }}(t *testing.T) {
func (s *{{ .PascalTable }}TestSuite) Test_Demo() {
Convey("Test_Demo", s.T(), func() {
database.Truncate(context.Background(), db, table.{{ .PascalTable }}.TableName())
database.Truncate(context.Background(), db, tbl{{ .PascalTable }}.TableName())
})
}