package sqlite_repositories import ( "context" "database/sql" "errors" "fmt" "strings" "github.com/theorift/white-zone-bot/database" "github.com/theorift/white-zone-bot/models" "github.com/theorift/white-zone-bot/repositories" ) type SQLiteUserRepository struct { ctx context.Context db *sql.DB // Main database connection executor database.DBTX // Current executor (db or tx) } func NewUserRepository(ctx context.Context, db *sql.DB) *SQLiteUserRepository { return &SQLiteUserRepository{ ctx: ctx, db: db, executor: db, // Use db as default executor } } func (r *SQLiteUserRepository) queries() *database.Queries { return database.New(r.executor) } // Core operations func (r *SQLiteUserRepository) GetUser(id int64) (models.User, error) { dbUser, err := r.queries().GetUser(r.ctx, id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return models.User{ID: 0}, repositories.ErrRecordNotFound } return models.User{ID: 0}, fmt.Errorf("failed to get user: %w", err) } return models.User{ ID: dbUser.ID, Points: dbUser.Points, ReferrerID: dbUser.ReferrerID.Int64, }, nil } func (r *SQLiteUserRepository) CreateUser(id int64, referrerID int64) error { params := database.CreateUserParams{ ID: id, ReferrerID: sql.NullInt64{Int64: referrerID, Valid: referrerID != 0}, } err := r.queries().CreateUser(r.ctx, params) if err != nil { if strings.Contains(err.Error(), "UNIQUE constraint failed") { return repositories.ErrDuplicateEntry } if strings.Contains(err.Error(), "FOREIGN KEY constraint failed") { return repositories.ErrForeignKeyViolation } return fmt.Errorf("failed to create user: %w", err) } return nil } func (r *SQLiteUserRepository) UpsertReferrer(id int64, referrerID int64) error { params := database.UpsertReferrerParams{ ID: id, ReferrerID: sql.NullInt64{Int64: referrerID, Valid: referrerID != 0}, } err := r.queries().UpsertReferrer(r.ctx, params) if err != nil { return fmt.Errorf("failed to upsert referrer: %w", err) } return nil } // Points operations func (r *SQLiteUserRepository) AddPointsToUser(id int64, points int64) error { params := database.AddPointsToUserParams{ ID: id, Points: points, } if err := r.queries().AddPointsToUser(r.ctx, params); err != nil { return fmt.Errorf("failed to add points: %w", err) } return nil } func (r *SQLiteUserRepository) ReducePointsFromUser(id int64, points int64) (int64, error) { params := database.ReducePointsFromUserParams{ ID: id, Points: points, } newPoints, err := r.queries().ReducePointsFromUser(r.ctx, params) if err != nil { if strings.Contains(err.Error(), "CHECK constraint failed: points >= 0") { return 0, repositories.ErrInsufficientUserPoints } return 0, fmt.Errorf("failed to reduce points: %w", err) } return newPoints, nil } // Transaction management func (r *SQLiteUserRepository) BeginTx() (*sql.Tx, error) { tx, err := r.db.BeginTx(r.ctx, &sql.TxOptions{ Isolation: sql.LevelSerializable, }) if err != nil { return nil, fmt.Errorf("failed to begin transaction: %w", err) } return tx, nil } func (r *SQLiteUserRepository) WithTx(tx *sql.Tx) repositories.UserRepository { return &SQLiteUserRepository{ ctx: r.ctx, db: r.db, // Retain original DB connection executor: tx, // Use transaction for queries } } func (r *SQLiteUserRepository) Commit(tx *sql.Tx) error { if err := tx.Commit(); err != nil { return fmt.Errorf("failed to commit transaction: %w", err) } return nil } func (r *SQLiteUserRepository) Rollback(tx *sql.Tx) error { if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { return fmt.Errorf("failed to rollback transaction: %w", err) } return nil }