diff --git a/.claude/plans/main-plan.md b/.claude/plans/main-plan.md index 5a568fa..eac5a50 100644 --- a/.claude/plans/main-plan.md +++ b/.claude/plans/main-plan.md @@ -275,18 +275,16 @@ type Strategy interface { ## 分阶段实施计划 ### Phase 1: 项目骨架 & 基础设施(第 1-2 天) +> 📋 **详细计划**: [phase-1-infrastructure.md](phase-1-infrastructure.md) + **学习目标**: Go 项目组织、package 管理、Gin 基础 -具体任务: -- [-] 初始化 `go.mod`(go mod init github.com/synoth/janus) -- [ ] 安装核心依赖:gin, gorm, viper, pgx 驱动 -- [ ] 实现 `internal/config/config.go`:Viper 加载 YAML 配置 -- [ ] 编写 `config.yaml`:数据库连接、服务器端口等 -- [ ] 实现 `cmd/server/main.go`:组装依赖,启动 Gin -- [ ] 实现 `internal/router/router.go`:基础路由 -- [ ] 实现 `internal/handler/health_handler.go`:GET /health -- [ ] 实现 GORM 数据库连接 & AutoMigrate -- [ ] 添加 `.env` 支持敏感配置 +**核心任务**: +- 初始化 `go.mod`,安装 gin / gorm / viper / pgx 依赖 +- 实现配置加载(Viper + YAML + 环境变量) +- 实现 GORM 数据库连接和连接池配置 +- 实现 `/health` 端点,组装 Gin 启动流程 +- 添加 `.env` 支持和 `.gitignore` **交付物**: 可运行的服务,响应 `/health`,连接数据库 diff --git a/.claude/plans/phase-1-infrastructure.md b/.claude/plans/phase-1-infrastructure.md new file mode 100644 index 0000000..2f0bc44 --- /dev/null +++ b/.claude/plans/phase-1-infrastructure.md @@ -0,0 +1,366 @@ +# Phase 1: 项目骨架 & 基础设施 — 详细计划 + +> **所属主计划**: [main-plan.md](main-plan.md#phase-1-项目骨架--基础设施第-1-2-天) +> **预计时间**: 第 1-2 天 +> **核心学习**: Go 项目组织、package 管理、Gin 基础 + +--- + +## 学习目标 + +| Go 概念 | 说明 | Java 对照 | +|----------|------|-----------| +| `go mod init` / `go mod tidy` | 依赖管理 | Maven `pom.xml` / Gradle `build.gradle`,但更轻量 | +| package 组织 | 按功能分目录,每个目录一个 package | Java package 按目录组织 | +| `func main()` | 程序入口 | `public static void main(String[])` | +| `init()` | 包初始化函数,main 之前自动执行 | Spring `@PostConstruct` / static initializer | +| pointer `*` | 指针类型,传递引用而非拷贝 | Java 中除了基本类型,对象默认是引用 | +| value `&` | 取地址 | Java 无直接对应 | +| `defer` | 函数返回前执行,常用于清理资源 | `try-finally` 的 `finally` 块 | +| error 返回值 | 函数返回 error,调用方检查 | Java checked exception,但更显式 | +| `:=` vs `var` | 短声明 vs 显式声明 | `var` 类型推断 | +| struct tag | 结构体字段上的元数据注解 | Java annotation(`@JsonProperty` 等) | + +--- + +## 任务分解 + +### Step 1.0: 初始化 Go Module + +**任务**: 在项目根目录执行 `go mod init`,建立 Go 模块。 + +**关键点**: +- 模块路径已经在 CLAUDE.md 中定义为 `synoth.com/janus` +- 不要用 `github.com/xxx` —— 这是学习项目,不需要托管到 GitHub +- `go.mod` 相当于 Java Maven 的 `pom.xml`,记录模块名、Go 版本、依赖 + +**验证**: `cat go.mod` 确认模块声明正确 + +--- + +### Step 1.1: 安装核心依赖 + +**任务**: 通过 `go get` 安装以下依赖包: + +| 包 | 用途 | Java 类比 | +|----|------|-----------| +| `github.com/gin-gonic/gin` | Web 框架,路由 + 中间件 | Spring MVC | +| `gorm.io/gorm` | ORM 核心库 | JPA / Hibernate | +| `gorm.io/driver/postgres` | GORM PostgreSQL 驱动 | PostgreSQL JDBC Driver | +| `github.com/spf13/viper` | 配置管理(YAML + 环境变量) | Spring `@ConfigurationProperties` | +| `github.com/joho/godotenv` | 加载 `.env` 文件到环境变量 | dotenv-java | + +**关键点**: +- `go get` 会自动更新 `go.mod` 和生成 `go.sum`(类似 `pom.xml` + 依赖锁) +- `go.sum` 记录每个依赖的校验和,确保可复现构建 +- 间接依赖(indirect)会被自动标记 + +**验证**: `go.mod` 中出现所有依赖,`go.sum` 非空 + +--- + +### Step 1.2: 实现配置加载 — `internal/config/config.go` + +**任务**: 用 Viper 加载 `config.yaml` + 环境变量覆盖。 + +**需要定义的结构体**: + +```go +// Config —— 顶层配置结构体 +type Config struct { + Server ServerConfig `mapstructure:"server"` + Database DatabaseConfig `mapstructure:"database"` +} + +// ServerConfig —— HTTP 服务器配置 +type ServerConfig struct { + Port int `mapstructure:"port"` // 默认 8080 + Mode string `mapstructure:"mode"` // debug / release / test +} + +// DatabaseConfig —— PostgreSQL 连接配置 +type DatabaseConfig struct { + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + User string `mapstructure:"user"` + Password string `mapstructure:"password"` + DBName string `mapstructure:"dbname"` + SSLMode string `mapstructure:"sslmode"` +} +``` + +**需要实现的函数**: +- `Load(path string) (*Config, error)` — 加载配置文件 +- 支持环境变量覆盖(如 `DATABASE_PASSWORD` 覆盖 YAML 中的明文密码) + +**Go 学习点**: +- `mapstructure` tag 是 Viper 专用的 struct tag,类似 Java `@ConfigurationProperties(prefix="...")` +- `*Config` 返回指针而非值 — Go 中大型 struct 传指针避免拷贝 +- error 作为返回值,调用方必须处理 + +**Viper 配置方式**: +```go +v := viper.New() +v.SetConfigFile(path) // 指定配置文件路径 +v.AutomaticEnv() // 自动读取环境变量 +v.SetEnvKeyReplacer(...) // 将 "." 替换为 "_"(database.host → DATABASE_HOST) +``` + +**验证**: 写一个简单的测试(可选),或者直接在 main 中调用 Load 打印结果 + +--- + +### Step 1.3: 编写配置文件 — `config.yaml` + +**任务**: 在项目根目录创建默认配置文件。 + +**内容要点**: +```yaml +server: + port: 8080 + mode: debug # Gin 模式:debug(带日志)/ release(生产)/ test + +database: + host: localhost + port: 5432 + user: janus + password: "" # 留空,通过环境变量或 .env 传入 + dbname: janus + sslmode: disable # 本地开发关 SSL,生产用 require +``` + +**Go 学习点**: +- YAML 用缩进表示层级,没有 XML/JSON 的括号 +- Viper 默认支持 YAML、JSON、TOML 等格式 +- 敏感信息(密码)不写死在 YAML 中,通过环境变量注入 + +**验证**: 文件语法正确(可以用在线 YAML 验证器或 `yamllint`) + +--- + +### Step 1.4: 添加 `.env` 支持 + +**任务**: 创建 `.env.example` 模板文件,并在 `main.go` 中用 `godotenv.Load()` 加载。 + +**`.env.example` 内容**: +``` +DATABASE_PASSWORD=your_password_here +``` + +**关键点**: +- `.env` 加入 `.gitignore`,避免敏感信息提交 +- `.env.example` 提交到仓库,作为模板 +- `godotenv.Load()` 在 `main()` 最开始调用,失败不阻断启动(`.env` 可能不存在) + +**Go 学习点**: +- `godotenv.Load()` 读取 `.env` 并设置到进程环境变量 +- Viper 的 `AutomaticEnv()` 自动读取环境变量,两者配合工作 + +**验证**: 创建 `.env` 文件后,`os.Getenv("DATABASE_PASSWORD")` 能读到值 + +--- + +### Step 1.5: 实现 GORM 数据库连接 + +**任务**: 在 `internal/config/` 或单独的 `internal/database/` 中实现 DB 连接初始化。 + +**需要实现的函数**: +```go +func NewDatabase(cfg *DatabaseConfig) (*gorm.DB, error) +``` + +**实现要点**: +1. 构造 PostgreSQL DSN(Data Source Name): + ``` + host=localhost user=janus password=xxx dbname=janus port=5432 sslmode=disable + ``` +2. `gorm.Open(postgres.Open(dsn), &gorm.Config{})` 打开连接 +3. 获取底层 `*sql.DB`,配置连接池参数: + - `SetMaxOpenConns(25)` — 最大打开连接数 + - `SetMaxIdleConns(10)` — 最大空闲连接数 + - `SetConnMaxLifetime(5 * time.Minute)` — 连接最大存活时间 +4. `db.Ping()` 验证连接是否可用 + +**Go 学习点**: +- GORM 的 `gorm.Open()` 返回 `(*gorm.DB, error)`,惯例返回 error +- `*sql.DB` 是 Go 标准库的数据库句柄,GORM 在其上构建 +- 连接池配置对标 Java HikariCP 的 `maximumPoolSize`、`minimumIdle`、`maxLifetime` +- `defer` 不在这里用(连接需要在整个应用生命周期保持),而是在 `main.go` 中管理 + +**验证**: 启动程序,GORM 打印连接日志无报错 + +--- + +### Step 1.6: 实现健康检查 Handler — `internal/handler/health_handler.go` + +**任务**: 实现一个简单的健康检查端点。 + +**代码结构**: +```go +package handler + +import "github.com/gin-gonic/gin" + +type HealthHandler struct { + db *gorm.DB // 用于检查数据库连接 +} + +func NewHealthHandler(db *gorm.DB) *HealthHandler { ... } + +func (h *HealthHandler) Check(c *gin.Context) { + // 1. ping 数据库 + // 2. 返回 JSON: { "status": "ok", "db": "connected" } +} +``` + +**返回格式**: +```json +{ + "status": "ok", + "timestamp": "2026-07-08T12:00:00Z", + "db": "connected" +} +``` + +**Go 学习点**: +- `(h *HealthHandler)` 是指针接收者(pointer receiver)— 方法可以修改 h 的状态 +- `gin.Context` 封装了 HTTP 请求和响应,类似 Spring 的 `HttpServletRequest` + `HttpServletResponse` +- `c.JSON(200, obj)` 自动设置 Content-Type 并序列化 JSON +- Gin 的 handler 签名是 `func(c *gin.Context)`,不使用返回值 — 通过 `c` 写响应 + +**验证**: `curl http://localhost:8080/health` 返回 JSON + +--- + +### Step 1.7: 实现路由注册 — `internal/router/router.go` + +**任务**: 集中管理所有路由注册。 + +**代码结构**: +```go +package router + +func Setup(r *gin.Engine, handler *handler.HealthHandler) { + r.GET("/health", handler.Check) +} +``` + +**Go 学习点**: +- 函数接收 `*gin.Engine`(Gin 的路由引擎),在其上注册路由 +- 这是"依赖注入"的最简形式 —— 手动传参,不用框架的 DI 容器 +- 路由分离到单独文件,避免 `main.go` 膨胀 + +**验证**: 路由文件编译通过,程序运行后 `/health` 可访问 + +--- + +### Step 1.8: 实现程序入口 — `cmd/server/main.go` + +**任务**: 组装所有依赖,启动 HTTP 服务器。 + +**流程**: +``` +main() + ├── godotenv.Load(".env") // 加载环境变量 + ├── config.Load("config.yaml") // 加载配置 + ├── database.NewDatabase(&cfg.Database) // 初始化 DB + ├── handler.NewHealthHandler(db) // 创建 handler + ├── gin.New() / gin.Default() // 创建 Gin 引擎 + ├── router.Setup(r, healthHandler) // 注册路由 + └── r.Run(":8080") // 启动服务器 +``` + +**Gin 模式选择**: +- `gin.Default()` — 带 Logger 和 Recovery 中间件(开发推荐) +- `gin.New()` — 空白引擎,手动添加中间件 + +**Go 学习点**: +- `main` 函数必须在 `package main` 中,否则无法编译成可执行文件 +- `main()` 无参数、无返回值 — 退出用 `os.Exit(code)` +- `defer` 用于在 main 返回前关闭数据库连接等资源 +- 包的 `init()` 函数在 main 之前自动执行(GORM 驱动注册就是通过 init) + +**验证**: `go run ./cmd/server/main.go` 启动成功,访问 `http://localhost:8080/health` + +--- + +### Step 1.9: 创建 `.gitignore` + +**任务**: 确保敏感文件和构建产物不提交。 + +**必须忽略的内容**: +``` +# 环境变量 +.env + +# 构建产物 +/server +/janus +*.exe + +# IDE +.idea/ +.vscode/ +*.swp + +# 依赖 +vendor/ + +# 临时文件 +tmp/ +temp/ +``` + +--- + +## 文件清单(Phase 1 产出) + +``` +janus/ +├── cmd/server/main.go ← Step 1.8 实现 +├── internal/ +│ ├── config/ +│ │ └── config.go ← Step 1.2 实现 +│ ├── handler/ +│ │ └── health_handler.go ← Step 1.6 实现 +│ └── router/ +│ └── router.go ← Step 1.7 实现 +├── config.yaml ← Step 1.3 编写 +├── .env.example ← Step 1.4 编写 +├── .gitignore ← Step 1.9 编写 +├── go.mod ← Step 1.0 生成 +└── go.sum ← Step 1.1 生成 +``` + +--- + +## Phase 1 完成标准 + +- [ ] `go build ./...` 编译通过,无错误 +- [ ] `go vet ./...` 静态分析无警告 +- [ ] 程序启动后 `curl http://localhost:8080/health` 返回 `{"status":"ok",...}` +- [ ] GORM 成功连接 PostgreSQL(或优雅报错"数据库未启动"而不 panic) +- [ ] 配置文件中的值被正确读取(改端口后服务在新端口启动) +- [ ] `.env` 中的密码能覆盖 `config.yaml` 中的空值 + +--- + +## 常见问题 & 排错 + +| 问题 | 可能原因 | 解决 | +|------|----------|------| +| `go get` 失败/慢 | 网络问题 | 设置 `GOPROXY=https://goproxy.cn,direct` | +| `cannot find package` | 模块路径不对 | 检查 `go.mod` 的 module 名和 import 路径一致 | +| GORM 连接报错 | PostgreSQL 未启动 | `pg_isready` 检查,或用 Docker 启动 PostgreSQL | +| `import cycle not allowed` | 循环依赖 | Go 禁止包之间循环引用,检查 import 关系 | +| Viper 读不到配置 | 路径问题 | 用绝对路径或相对于执行目录的路径 | +| `:=` vs `=` 报错 | 短声明只能用于新变量 | 已声明的变量用 `=` 赋值 | + +--- + +## 关键设计决策 + +1. **手动依赖注入** — 不使用 wire/di 框架。Phase 1 依赖少,手动传参最清晰,也是 Go 社区的常见做法 +2. **Config 用指针返回** — `func Load() (*Config, error)` 而非 `(Config, error)`,避免大 struct 拷贝 +3. **health check 注入 `*gorm.DB`** — 真实检查数据库连通性,而非返回假 OK +4. **`gin.Default()` 而非 `gin.New()`** — 开发阶段带 Logger 和 Recovery 中间件更方便 diff --git a/cmd/server/main.go b/cmd/server/main.go index e69de29..cfe5840 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -0,0 +1,10 @@ +package main + +import "synoth.com/janus/internal/config" + +func main() { + cfg, err := config.Load("config.yaml") + if err != nil { + panic(err) + } +} diff --git a/config.yaml b/config.yaml index e69de29..d27c4cc 100644 --- a/config.yaml +++ b/config.yaml @@ -0,0 +1,14 @@ +server: + port: 8080 + mode: debug + +database: + host: 175.178.9.76 + port: 25432 + dbname: postgres + username: synoth + password: uKWjf4ns5ahx + sslmode: disable + +log: + level: debug \ No newline at end of file diff --git a/go.mod b/go.mod index 74d6fcf..af0ed41 100644 --- a/go.mod +++ b/go.mod @@ -20,8 +20,10 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.10.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/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -45,8 +47,10 @@ require ( golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/protobuf v1.36.10 // indirect + gorm.io/driver/postgres v1.6.0 // indirect gorm.io/gorm v1.31.2 // indirect ) diff --git a/go.sum b/go.sum index 5d7061b..02179f2 100644 --- a/go.sum +++ b/go.sum @@ -35,10 +35,14 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -97,6 +101,8 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= @@ -107,5 +113,7 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..1c71e69 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,64 @@ +package config + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/spf13/viper" +) + +// Config 全局配置 +type Config struct { + Server ServerConfig `mapstructure:"server"` + Database DatabaseConfig `mapstructure:"database"` + Log LogConfig `mapstructure:"log"` +} + +// ServerConfig 服务基础配置 +type ServerConfig struct { + Port int `mapstructure:"port"` + Mode string `mapstructure:"mode"` +} + +// DatabaseConfig 数据库配置 +type DatabaseConfig struct { + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + User string `mapstructure:"user"` + Password string `mapstructure:"password"` + DBName string `mapstructure:"dbname"` + SSLMode string `mapstructure:"sslmode"` +} + +// LogConfig 日志配置 +type LogConfig struct { + Level string `mapstructure:"level"` +} + +func Load(path string) (*Config, error) { + v := viper.New() + v.SetConfigFile(path) + v.AutomaticEnv() + + if err := v.ReadInConfig(); err != nil { + return nil, fmt.Errorf("read config: %w", err) + } + + var cfg Config + if err := v.Unmarshal(&cfg); err != nil { + return nil, fmt.Errorf("unmarshal config: %w", err) + } + + return &cfg, nil +} + +func (c *Config) InitGinMode() { + switch c.Server.Mode { + case "release": + gin.SetMode(gin.ReleaseMode) + case "test": + gin.SetMode(gin.TestMode) + default: + gin.SetMode(gin.DebugMode) + } +} diff --git a/internal/database/database.go b/internal/database/database.go new file mode 100644 index 0000000..2e945a5 --- /dev/null +++ b/internal/database/database.go @@ -0,0 +1,10 @@ +package database + +import "gorm.io/gorm" + +type DatabaseConfig struct { +} + +func Connect(cfg DatabaseConfig) (*gorm.DB, error) { + return nil, nil +}