Initial import with modified Dockerfile for env-based config generation

This commit is contained in:
2026-06-22 11:56:10 +03:00
parent 28fa7537ac
commit 6bf27aa40e
25 changed files with 3228 additions and 0 deletions
+230
View File
@@ -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()
}