Initial import with modified Dockerfile for env-based config generation
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/vincentc-afk/gitea-notification-hub/internal/storage"
|
||||
)
|
||||
|
||||
// Repository implements storage.Repository using SQLite
|
||||
type Repository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// New creates a new SQLite repository
|
||||
func New(dsn string) (*Repository, error) {
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(dsn)
|
||||
if dir != "" && dir != "." {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("creating data directory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", dsn+"?_foreign_keys=on&_journal_mode=WAL")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening database: %w", err)
|
||||
}
|
||||
|
||||
// Test connection
|
||||
if err := db.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("pinging database: %w", err)
|
||||
}
|
||||
|
||||
return &Repository{db: db}, nil
|
||||
}
|
||||
|
||||
// Migrate runs database migrations
|
||||
func (r *Repository) Migrate(ctx context.Context) error {
|
||||
schema := `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gitea_username TEXT UNIQUE,
|
||||
gitea_id INTEGER,
|
||||
email TEXT UNIQUE,
|
||||
full_name TEXT,
|
||||
slack_id TEXT,
|
||||
slack_name TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_gitea_username ON users(gitea_username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_slack_id ON users(slack_id);
|
||||
`
|
||||
|
||||
_, err := r.db.ExecContext(ctx, schema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("running migrations: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByEmail retrieves a user by email
|
||||
func (r *Repository) GetUserByEmail(ctx context.Context, email string) (*storage.User, error) {
|
||||
query := `
|
||||
SELECT id, gitea_username, gitea_id, email, full_name, slack_id, slack_name, created_at, updated_at
|
||||
FROM users WHERE email = ?
|
||||
`
|
||||
|
||||
var user storage.User
|
||||
err := r.db.QueryRowContext(ctx, query, email).Scan(
|
||||
&user.ID,
|
||||
&user.GiteaUsername,
|
||||
&user.GiteaID,
|
||||
&user.Email,
|
||||
&user.FullName,
|
||||
&user.SlackID,
|
||||
&user.SlackName,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, &storage.ErrNotFound{Message: fmt.Sprintf("user with email %s not found", email)}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying user by email: %w", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserByGiteaUsername retrieves a user by Gitea username
|
||||
func (r *Repository) GetUserByGiteaUsername(ctx context.Context, username string) (*storage.User, error) {
|
||||
query := `
|
||||
SELECT id, gitea_username, gitea_id, email, full_name, slack_id, slack_name, created_at, updated_at
|
||||
FROM users WHERE gitea_username = ?
|
||||
`
|
||||
|
||||
var user storage.User
|
||||
err := r.db.QueryRowContext(ctx, query, username).Scan(
|
||||
&user.ID,
|
||||
&user.GiteaUsername,
|
||||
&user.GiteaID,
|
||||
&user.Email,
|
||||
&user.FullName,
|
||||
&user.SlackID,
|
||||
&user.SlackName,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, &storage.ErrNotFound{Message: fmt.Sprintf("user with username %s not found", username)}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying user by username: %w", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserBySlackID retrieves a user by Slack ID
|
||||
func (r *Repository) GetUserBySlackID(ctx context.Context, slackID string) (*storage.User, error) {
|
||||
query := `
|
||||
SELECT id, gitea_username, gitea_id, email, full_name, slack_id, slack_name, created_at, updated_at
|
||||
FROM users WHERE slack_id = ?
|
||||
`
|
||||
|
||||
var user storage.User
|
||||
err := r.db.QueryRowContext(ctx, query, slackID).Scan(
|
||||
&user.ID,
|
||||
&user.GiteaUsername,
|
||||
&user.GiteaID,
|
||||
&user.Email,
|
||||
&user.FullName,
|
||||
&user.SlackID,
|
||||
&user.SlackName,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, &storage.ErrNotFound{Message: fmt.Sprintf("user with slack_id %s not found", slackID)}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying user by slack_id: %w", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UpsertUser inserts or updates a user
|
||||
func (r *Repository) UpsertUser(ctx context.Context, user *storage.User) error {
|
||||
query := `
|
||||
INSERT INTO users (gitea_username, gitea_id, email, full_name, slack_id, slack_name, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(email) DO UPDATE SET
|
||||
gitea_username = COALESCE(excluded.gitea_username, users.gitea_username),
|
||||
gitea_id = COALESCE(excluded.gitea_id, users.gitea_id),
|
||||
full_name = COALESCE(excluded.full_name, users.full_name),
|
||||
slack_id = COALESCE(excluded.slack_id, users.slack_id),
|
||||
slack_name = COALESCE(excluded.slack_name, users.slack_name),
|
||||
updated_at = excluded.updated_at
|
||||
`
|
||||
|
||||
_, err := r.db.ExecContext(ctx, query,
|
||||
user.GiteaUsername,
|
||||
user.GiteaID,
|
||||
user.Email,
|
||||
user.FullName,
|
||||
user.SlackID,
|
||||
user.SlackName,
|
||||
time.Now(),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upserting user: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUsers returns all users
|
||||
func (r *Repository) ListUsers(ctx context.Context) ([]*storage.User, error) {
|
||||
query := `
|
||||
SELECT id, gitea_username, gitea_id, email, full_name, slack_id, slack_name, created_at, updated_at
|
||||
FROM users ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying users: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []*storage.User
|
||||
for rows.Next() {
|
||||
var user storage.User
|
||||
if err := rows.Scan(
|
||||
&user.ID,
|
||||
&user.GiteaUsername,
|
||||
&user.GiteaID,
|
||||
&user.Email,
|
||||
&user.FullName,
|
||||
&user.SlackID,
|
||||
&user.SlackName,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("scanning user: %w", err)
|
||||
}
|
||||
users = append(users, &user)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func (r *Repository) Close() error {
|
||||
return r.db.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user