init go project
This commit is contained in:
commit
5dde43e409
85 changed files with 16720 additions and 0 deletions
48
internal/db/db.go
Normal file
48
internal/db/db.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func Init(driver, connection string) (*sqlx.DB, error) {
|
||||
if driver == "sqlite" {
|
||||
dir := filepath.Dir(connection)
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create data directory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
db, err := sqlx.Connect(driver, connection)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect: %w", err)
|
||||
}
|
||||
|
||||
db.SetMaxOpenConns(25)
|
||||
db.SetMaxIdleConns(5)
|
||||
db.SetConnMaxLifetime(5 * time.Minute)
|
||||
|
||||
slog.Info("database connected", "driver", driver)
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func Close(db *sqlx.DB) error {
|
||||
if db != nil {
|
||||
return db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
6
internal/db/embed.go
Normal file
6
internal/db/embed.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package db
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsFS embed.FS
|
||||
55
internal/db/migrate.go
Normal file
55
internal/db/migrate.go
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
var dialectMap = map[string]string{
|
||||
"sqlite": "sqlite3",
|
||||
"pgx": "postgres",
|
||||
}
|
||||
|
||||
func getDialect(driver string) string {
|
||||
dialect, ok := dialectMap[driver]
|
||||
if ok {
|
||||
return dialect
|
||||
}
|
||||
return driver
|
||||
}
|
||||
|
||||
func setupGoose(driver string) error {
|
||||
err := goose.SetDialect(getDialect(driver))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set dialect: %w", err)
|
||||
}
|
||||
|
||||
migrationsDir, err := fs.Sub(migrationsFS, "migrations")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sub-filesystem migrations directory: %w", err)
|
||||
}
|
||||
|
||||
goose.SetBaseFS(migrationsDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunMigrations(db *sql.DB, driver string) error {
|
||||
err := setupGoose(driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = goose.Up(db, ".")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run migrations: %w", err)
|
||||
}
|
||||
|
||||
slog.Info("migrations completed successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
21
internal/db/migrations/00001_create_users_table.sql
Normal file
21
internal/db/migrations/00001_create_users_table.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NULL, -- Allow null for passwordless login
|
||||
pending_email TEXT NULL, -- Store new email when changing email
|
||||
email_verified_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_passwordless ON users(id) WHERE password_hash IS NULL;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS idx_users_passwordless;
|
||||
DROP INDEX IF EXISTS idx_users_email;
|
||||
DROP TABLE IF EXISTS users;
|
||||
-- +goose StatementEnd
|
||||
Loading…
Add table
Add a link
Reference in a new issue