implemented storage
This commit is contained in:
123
internal/storage/sqlite.go
Normal file
123
internal/storage/sqlite.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3" // Драйвер для SQLite
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound возвращается, когда запись не найдена в хранилище.
|
||||
ErrNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
// SQLiteStorage реализует интерфейс interfaces.TrackStorage для SQLite.
|
||||
type SQLiteStorage struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewSQLiteStorage создает и инициализирует новое хранилище SQLite.
|
||||
// Он также проверяет и создает таблицу, если она не существует.
|
||||
func NewSQLiteStorage(ctx context.Context, dbPath string) (*SQLiteStorage, error) {
|
||||
// Убедимся, что директория для файла БД существует
|
||||
dir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create data directory: %w", err)
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Устанавливаем 1 соединение, т.к. SQLite плохо работает с конкурентной записью.
|
||||
// Для наших целей этого более чем достаточно.
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
storage := &SQLiteStorage{db: db}
|
||||
|
||||
if err := storage.initSchema(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize schema: %w", err)
|
||||
}
|
||||
|
||||
slog.Info("SQLite storage initialized successfully", "path", dbPath)
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
// initSchema создает таблицу для кэша, если она еще не существует.
|
||||
func (s *SQLiteStorage) initSchema(ctx context.Context) error {
|
||||
const ddl = `
|
||||
CREATE TABLE IF NOT EXISTS tracks_cache (
|
||||
yandex_track_id TEXT PRIMARY KEY,
|
||||
telegram_file_id TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
|
||||
_, err := s.db.ExecContext(ctx, ddl)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get получает telegram_file_id по yandex_track_id.
|
||||
// Возвращает ErrNotFound, если запись не найдена.
|
||||
func (s *SQLiteStorage) Get(ctx context.Context, yandexTrackID string) (string, error) {
|
||||
const op = "storage.sqlite.Get"
|
||||
stmt, err := s.db.PrepareContext(ctx, "SELECT telegram_file_id FROM tracks_cache WHERE yandex_track_id = ?")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var fileID string
|
||||
err = stmt.QueryRowContext(ctx, yandexTrackID).Scan(&fileID)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
return "", fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
|
||||
return fileID, nil
|
||||
}
|
||||
|
||||
// Set сохраняет новую запись в кэш.
|
||||
func (s *SQLiteStorage) Set(ctx context.Context, yandexTrackID, telegramFileID string) error {
|
||||
const op = "storage.sqlite.Set"
|
||||
stmt, err := s.db.PrepareContext(ctx, "INSERT INTO tracks_cache(yandex_track_id, telegram_file_id) VALUES(?, ?)")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.ExecContext(ctx, yandexTrackID, telegramFileID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count возвращает общее количество записей в кэше.
|
||||
func (s *SQLiteStorage) Count(ctx context.Context) (int, error) {
|
||||
const op = "storage.sqlite.Count"
|
||||
var count int
|
||||
err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM tracks_cache").Scan(&count)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Close закрывает соединение с базой данных.
|
||||
func (s *SQLiteStorage) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user