refactor(API): ✨ Relspect integration
This commit is contained in:
246
pkg/storage/db.go
Normal file
246
pkg/storage/db.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/config"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/models"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
"github.com/uptrace/bun/driver/sqliteshim"
|
||||
"github.com/uptrace/bun/extra/bundebug"
|
||||
)
|
||||
|
||||
// DB is the global database instance
|
||||
var DB *bun.DB
|
||||
var dbType string // Store the database type for later use
|
||||
|
||||
// Initialize sets up the database connection based on configuration
|
||||
func Initialize(cfg *config.DatabaseConfig) error {
|
||||
var sqldb *sql.DB
|
||||
var err error
|
||||
|
||||
dbType = cfg.Type
|
||||
switch cfg.Type {
|
||||
case "postgres", "postgresql":
|
||||
dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable",
|
||||
cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.Database)
|
||||
sqldb = sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
DB = bun.NewDB(sqldb, pgdialect.New())
|
||||
|
||||
case "sqlite":
|
||||
sqldb, err = sql.Open(sqliteshim.ShimName, cfg.SQLitePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open sqlite database: %w", err)
|
||||
}
|
||||
DB = bun.NewDB(sqldb, sqlitedialect.New())
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported database type: %s", cfg.Type)
|
||||
}
|
||||
|
||||
// Add query hook for debugging (optional, can be removed in production)
|
||||
DB.AddQueryHook(bundebug.NewQueryHook(
|
||||
bundebug.WithVerbose(true),
|
||||
bundebug.FromEnv("BUNDEBUG"),
|
||||
))
|
||||
|
||||
// Set connection pool settings
|
||||
sqldb.SetMaxIdleConns(10)
|
||||
sqldb.SetMaxOpenConns(100)
|
||||
sqldb.SetConnMaxLifetime(time.Hour)
|
||||
|
||||
// Test the connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := sqldb.PingContext(ctx); err != nil {
|
||||
return fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTables creates database tables based on BUN models
|
||||
func CreateTables(ctx context.Context) error {
|
||||
if DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
// For SQLite, use raw SQL with compatible syntax
|
||||
if dbType == "sqlite" {
|
||||
return createTablesSQLite(ctx)
|
||||
}
|
||||
|
||||
// For PostgreSQL, use BUN's auto-generation
|
||||
models := []interface{}{
|
||||
(*models.ModelPublicUser)(nil),
|
||||
(*models.ModelPublicAPIKey)(nil),
|
||||
(*models.ModelPublicHook)(nil),
|
||||
(*models.ModelPublicWhatsappAccount)(nil),
|
||||
(*models.ModelPublicEventLog)(nil),
|
||||
(*models.ModelPublicSession)(nil),
|
||||
(*models.ModelPublicMessageCache)(nil),
|
||||
}
|
||||
|
||||
for _, model := range models {
|
||||
_, err := DB.NewCreateTable().Model(model).IfNotExists().Exec(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create table: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createTablesSQLite creates tables using SQLite-compatible SQL
|
||||
func createTablesSQLite(ctx context.Context) error {
|
||||
tables := []string{
|
||||
// Users table
|
||||
`CREATE TABLE IF NOT EXISTS users (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(255),
|
||||
role VARCHAR(50) NOT NULL DEFAULT 'user',
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
)`,
|
||||
|
||||
// API Keys table
|
||||
`CREATE TABLE IF NOT EXISTS api_keys (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
key VARCHAR(255) NOT NULL UNIQUE,
|
||||
key_prefix VARCHAR(20),
|
||||
permissions TEXT,
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
last_used_at TIMESTAMP,
|
||||
expires_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// Hooks table
|
||||
`CREATE TABLE IF NOT EXISTS hooks (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
method VARCHAR(10) NOT NULL DEFAULT 'POST',
|
||||
headers TEXT,
|
||||
events TEXT,
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
retry_count INTEGER NOT NULL DEFAULT 3,
|
||||
timeout_seconds INTEGER NOT NULL DEFAULT 30,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// WhatsApp Accounts table
|
||||
`CREATE TABLE IF NOT EXISTS whatsapp_accounts (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
phone_number VARCHAR(20) NOT NULL UNIQUE,
|
||||
account_type VARCHAR(50) NOT NULL DEFAULT 'whatsmeow',
|
||||
business_api_config TEXT,
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
connected BOOLEAN NOT NULL DEFAULT 0,
|
||||
last_connected_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// Event Logs table
|
||||
`CREATE TABLE IF NOT EXISTS event_logs (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36),
|
||||
account_id VARCHAR(36),
|
||||
event_type VARCHAR(100) NOT NULL,
|
||||
event_data TEXT,
|
||||
from_number VARCHAR(20),
|
||||
to_number VARCHAR(20),
|
||||
message_id VARCHAR(255),
|
||||
status VARCHAR(50),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (account_id) REFERENCES whatsapp_accounts(id) ON DELETE SET NULL
|
||||
)`,
|
||||
|
||||
// Sessions table
|
||||
`CREATE TABLE IF NOT EXISTS sessions (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
token VARCHAR(500) NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// Message Cache table
|
||||
`CREATE TABLE IF NOT EXISTS message_cache (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
account_id VARCHAR(36),
|
||||
event_type VARCHAR(100) NOT NULL,
|
||||
event_data TEXT NOT NULL,
|
||||
message_id VARCHAR(255),
|
||||
from_number VARCHAR(20),
|
||||
to_number VARCHAR(20),
|
||||
processed BOOLEAN NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
processed_at TIMESTAMP,
|
||||
FOREIGN KEY (account_id) REFERENCES whatsapp_accounts(id) ON DELETE SET NULL
|
||||
)`,
|
||||
}
|
||||
|
||||
for _, sql := range tables {
|
||||
if _, err := DB.ExecContext(ctx, sql); err != nil {
|
||||
return fmt.Errorf("failed to create table: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func Close() error {
|
||||
if DB == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return DB.Close()
|
||||
}
|
||||
|
||||
// GetDB returns the database instance
|
||||
func GetDB() *bun.DB {
|
||||
return DB
|
||||
}
|
||||
|
||||
// HealthCheck checks if the database connection is healthy
|
||||
func HealthCheck() error {
|
||||
if DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return DB.PingContext(ctx)
|
||||
}
|
||||
299
pkg/storage/repository.go
Normal file
299
pkg/storage/repository.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/models"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
// Repository provides common CRUD operations
|
||||
type Repository[T any] struct {
|
||||
db *bun.DB
|
||||
}
|
||||
|
||||
// NewRepository creates a new repository instance
|
||||
func NewRepository[T any](db *bun.DB) *Repository[T] {
|
||||
return &Repository[T]{db: db}
|
||||
}
|
||||
|
||||
// Create inserts a new record
|
||||
func (r *Repository[T]) Create(ctx context.Context, entity *T) error {
|
||||
_, err := r.db.NewInsert().Model(entity).Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetByID retrieves a record by ID
|
||||
func (r *Repository[T]) GetByID(ctx context.Context, id string) (*T, error) {
|
||||
var entity T
|
||||
err := r.db.NewSelect().Model(&entity).Where("id = ?", id).Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &entity, nil
|
||||
}
|
||||
|
||||
// Update updates an existing record
|
||||
func (r *Repository[T]) Update(ctx context.Context, entity *T) error {
|
||||
_, err := r.db.NewUpdate().Model(entity).WherePK().Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete soft deletes a record by ID (if model has DeletedAt field)
|
||||
func (r *Repository[T]) Delete(ctx context.Context, id string) error {
|
||||
var entity T
|
||||
_, err := r.db.NewDelete().Model(&entity).Where("id = ?", id).Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// List retrieves all records with optional filtering
|
||||
func (r *Repository[T]) List(ctx context.Context, filter map[string]interface{}) ([]T, error) {
|
||||
var entities []T
|
||||
query := r.db.NewSelect().Model(&entities)
|
||||
|
||||
for key, value := range filter {
|
||||
query = query.Where("? = ?", bun.Ident(key), value)
|
||||
}
|
||||
|
||||
err := query.Scan(ctx)
|
||||
return entities, err
|
||||
}
|
||||
|
||||
// Count returns the total number of records matching the filter
|
||||
func (r *Repository[T]) Count(ctx context.Context, filter map[string]interface{}) (int, error) {
|
||||
query := r.db.NewSelect().Model((*T)(nil))
|
||||
|
||||
for key, value := range filter {
|
||||
query = query.Where("? = ?", bun.Ident(key), value)
|
||||
}
|
||||
|
||||
count, err := query.Count(ctx)
|
||||
return count, err
|
||||
}
|
||||
|
||||
// UserRepository provides user-specific operations
|
||||
type UserRepository struct {
|
||||
*Repository[models.ModelPublicUser]
|
||||
}
|
||||
|
||||
// NewUserRepository creates a new user repository
|
||||
func NewUserRepository(db *bun.DB) *UserRepository {
|
||||
return &UserRepository{
|
||||
Repository: NewRepository[models.ModelPublicUser](db),
|
||||
}
|
||||
}
|
||||
|
||||
// GetByUsername retrieves a user by username
|
||||
func (r *UserRepository) GetByUsername(ctx context.Context, username string) (*models.ModelPublicUser, error) {
|
||||
var user models.ModelPublicUser
|
||||
err := r.db.NewSelect().Model(&user).Where("username = ?", username).Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByEmail retrieves a user by email
|
||||
func (r *UserRepository) GetByEmail(ctx context.Context, email string) (*models.ModelPublicUser, error) {
|
||||
var user models.ModelPublicUser
|
||||
err := r.db.NewSelect().Model(&user).Where("email = ?", email).Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// APIKeyRepository provides API key-specific operations
|
||||
type APIKeyRepository struct {
|
||||
*Repository[models.ModelPublicAPIKey]
|
||||
}
|
||||
|
||||
// NewAPIKeyRepository creates a new API key repository
|
||||
func NewAPIKeyRepository(db *bun.DB) *APIKeyRepository {
|
||||
return &APIKeyRepository{
|
||||
Repository: NewRepository[models.ModelPublicAPIKey](db),
|
||||
}
|
||||
}
|
||||
|
||||
// GetByKey retrieves an API key by its key value
|
||||
func (r *APIKeyRepository) GetByKey(ctx context.Context, key string) (*models.ModelPublicAPIKey, error) {
|
||||
var apiKey models.ModelPublicAPIKey
|
||||
err := r.db.NewSelect().Model(&apiKey).
|
||||
Where("key = ? AND active = ?", key, true).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiKey, nil
|
||||
}
|
||||
|
||||
// GetByUserID retrieves all API keys for a user
|
||||
func (r *APIKeyRepository) GetByUserID(ctx context.Context, userID string) ([]models.ModelPublicAPIKey, error) {
|
||||
var apiKeys []models.ModelPublicAPIKey
|
||||
err := r.db.NewSelect().Model(&apiKeys).Where("user_id = ?", userID).Scan(ctx)
|
||||
return apiKeys, err
|
||||
}
|
||||
|
||||
// UpdateLastUsed updates the last used timestamp for an API key
|
||||
func (r *APIKeyRepository) UpdateLastUsed(ctx context.Context, id string) error {
|
||||
now := time.Now()
|
||||
_, err := r.db.NewUpdate().Model((*models.ModelPublicAPIKey)(nil)).
|
||||
Set("last_used_at = ?", now).
|
||||
Where("id = ?", id).
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// HookRepository provides hook-specific operations
|
||||
type HookRepository struct {
|
||||
*Repository[models.ModelPublicHook]
|
||||
}
|
||||
|
||||
// NewHookRepository creates a new hook repository
|
||||
func NewHookRepository(db *bun.DB) *HookRepository {
|
||||
return &HookRepository{
|
||||
Repository: NewRepository[models.ModelPublicHook](db),
|
||||
}
|
||||
}
|
||||
|
||||
// GetByUserID retrieves all hooks for a user
|
||||
func (r *HookRepository) GetByUserID(ctx context.Context, userID string) ([]models.ModelPublicHook, error) {
|
||||
var hooks []models.ModelPublicHook
|
||||
err := r.db.NewSelect().Model(&hooks).Where("user_id = ?", userID).Scan(ctx)
|
||||
return hooks, err
|
||||
}
|
||||
|
||||
// GetActiveHooks retrieves all active hooks
|
||||
func (r *HookRepository) GetActiveHooks(ctx context.Context) ([]models.ModelPublicHook, error) {
|
||||
var hooks []models.ModelPublicHook
|
||||
err := r.db.NewSelect().Model(&hooks).Where("active = ?", true).Scan(ctx)
|
||||
return hooks, err
|
||||
}
|
||||
|
||||
// WhatsAppAccountRepository provides WhatsApp account-specific operations
|
||||
type WhatsAppAccountRepository struct {
|
||||
*Repository[models.ModelPublicWhatsappAccount]
|
||||
}
|
||||
|
||||
// NewWhatsAppAccountRepository creates a new WhatsApp account repository
|
||||
func NewWhatsAppAccountRepository(db *bun.DB) *WhatsAppAccountRepository {
|
||||
return &WhatsAppAccountRepository{
|
||||
Repository: NewRepository[models.ModelPublicWhatsappAccount](db),
|
||||
}
|
||||
}
|
||||
|
||||
// GetByUserID retrieves all WhatsApp accounts for a user
|
||||
func (r *WhatsAppAccountRepository) GetByUserID(ctx context.Context, userID string) ([]models.ModelPublicWhatsappAccount, error) {
|
||||
var accounts []models.ModelPublicWhatsappAccount
|
||||
err := r.db.NewSelect().Model(&accounts).Where("user_id = ?", userID).Scan(ctx)
|
||||
return accounts, err
|
||||
}
|
||||
|
||||
// GetByPhoneNumber retrieves an account by phone number
|
||||
func (r *WhatsAppAccountRepository) GetByPhoneNumber(ctx context.Context, phoneNumber string) (*models.ModelPublicWhatsappAccount, error) {
|
||||
var account models.ModelPublicWhatsappAccount
|
||||
err := r.db.NewSelect().Model(&account).Where("phone_number = ?", phoneNumber).Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates the status of a WhatsApp account
|
||||
func (r *WhatsAppAccountRepository) UpdateStatus(ctx context.Context, id string, status string) error {
|
||||
query := r.db.NewUpdate().Model((*models.ModelPublicWhatsappAccount)(nil)).
|
||||
Set("status = ?", status).
|
||||
Where("id = ?", id)
|
||||
|
||||
if status == "connected" {
|
||||
now := time.Now()
|
||||
query = query.Set("last_connected_at = ?", now)
|
||||
}
|
||||
|
||||
_, err := query.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// SessionRepository provides session-specific operations
|
||||
type SessionRepository struct {
|
||||
*Repository[models.ModelPublicSession]
|
||||
}
|
||||
|
||||
// NewSessionRepository creates a new session repository
|
||||
func NewSessionRepository(db *bun.DB) *SessionRepository {
|
||||
return &SessionRepository{
|
||||
Repository: NewRepository[models.ModelPublicSession](db),
|
||||
}
|
||||
}
|
||||
|
||||
// GetByToken retrieves a session by token
|
||||
func (r *SessionRepository) GetByToken(ctx context.Context, token string) (*models.ModelPublicSession, error) {
|
||||
var session models.ModelPublicSession
|
||||
err := r.db.NewSelect().Model(&session).
|
||||
Where("token = ? AND expires_at > ?", token, time.Now()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
// DeleteExpired removes all expired sessions
|
||||
func (r *SessionRepository) DeleteExpired(ctx context.Context) error {
|
||||
_, err := r.db.NewDelete().Model((*models.ModelPublicSession)(nil)).
|
||||
Where("expires_at <= ?", time.Now()).
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteByUserID removes all sessions for a user
|
||||
func (r *SessionRepository) DeleteByUserID(ctx context.Context, userID string) error {
|
||||
_, err := r.db.NewDelete().Model((*models.ModelPublicSession)(nil)).
|
||||
Where("user_id = ?", userID).
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// EventLogRepository provides event log-specific operations
|
||||
type EventLogRepository struct {
|
||||
*Repository[models.ModelPublicEventLog]
|
||||
}
|
||||
|
||||
// NewEventLogRepository creates a new event log repository
|
||||
func NewEventLogRepository(db *bun.DB) *EventLogRepository {
|
||||
return &EventLogRepository{
|
||||
Repository: NewRepository[models.ModelPublicEventLog](db),
|
||||
}
|
||||
}
|
||||
|
||||
// GetByUserID retrieves event logs for a user
|
||||
func (r *EventLogRepository) GetByUserID(ctx context.Context, userID string, limit int) ([]models.ModelPublicEventLog, error) {
|
||||
var logs []models.ModelPublicEventLog
|
||||
err := r.db.NewSelect().Model(&logs).
|
||||
Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Scan(ctx)
|
||||
return logs, err
|
||||
}
|
||||
|
||||
// GetByEntityID retrieves event logs for a specific entity
|
||||
func (r *EventLogRepository) GetByEntityID(ctx context.Context, entityType, entityID string, limit int) ([]models.ModelPublicEventLog, error) {
|
||||
var logs []models.ModelPublicEventLog
|
||||
err := r.db.NewSelect().Model(&logs).
|
||||
Where("entity_type = ? AND entity_id = ?", entityType, entityID).
|
||||
Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Scan(ctx)
|
||||
return logs, err
|
||||
}
|
||||
|
||||
// DeleteOlderThan removes event logs older than the specified duration
|
||||
func (r *EventLogRepository) DeleteOlderThan(ctx context.Context, duration time.Duration) error {
|
||||
cutoff := time.Now().Add(-duration)
|
||||
_, err := r.db.NewDelete().Model((*models.ModelPublicEventLog)(nil)).
|
||||
Where("created_at < ?", cutoff).
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
55
pkg/storage/seed.go
Normal file
55
pkg/storage/seed.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/models"
|
||||
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// SeedData creates initial data for the application
|
||||
func SeedData(ctx context.Context) error {
|
||||
if DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
// Check if admin user already exists
|
||||
userRepo := NewUserRepository(DB)
|
||||
_, err := userRepo.GetByUsername(ctx, "admin")
|
||||
if err == nil {
|
||||
// Admin user already exists
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create default admin user
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
adminUser := &models.ModelPublicUser{
|
||||
ID: resolvespec_common.NewSqlString(uuid.New().String()),
|
||||
Username: resolvespec_common.NewSqlString("admin"),
|
||||
Email: resolvespec_common.NewSqlString("admin@whatshooked.local"),
|
||||
Password: resolvespec_common.NewSqlString(string(hashedPassword)),
|
||||
FullName: resolvespec_common.NewSqlString("System Administrator"),
|
||||
Role: resolvespec_common.NewSqlString("admin"),
|
||||
Active: true,
|
||||
CreatedAt: resolvespec_common.NewSqlTime(now),
|
||||
UpdatedAt: resolvespec_common.NewSqlTime(now),
|
||||
}
|
||||
|
||||
if err := userRepo.Create(ctx, adminUser); err != nil {
|
||||
return fmt.Errorf("failed to create admin user: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ Created default admin user (username: admin, password: admin123)")
|
||||
fmt.Println("⚠ Please change the default password after first login!")
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user