1
0
mirror of https://github.com/lus/pasty.git synced 2023-08-10 21:13:09 +03:00

implement SQLite storage driver

This commit is contained in:
Lukas Schulte Pelkum
2023-06-13 01:22:05 +02:00
parent 6260f20fc4
commit b9a6a81821
17 changed files with 265 additions and 8 deletions

View File

@ -0,0 +1,87 @@
package sqlite
import (
"context"
"database/sql"
"embed"
"errors"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/sqlite"
"github.com/golang-migrate/migrate/v4/source/iofs"
"github.com/lus/pasty/internal/pastes"
"github.com/lus/pasty/internal/storage"
"github.com/rs/zerolog/log"
_ "modernc.org/sqlite"
)
//go:embed migrations/*.sql
var migrations embed.FS
type Driver struct {
filePath string
connPool *sql.DB
pastes *pasteRepository
}
var _ storage.Driver = (*Driver)(nil)
func New(filePath string) *Driver {
return &Driver{
filePath: filePath,
}
}
func (driver *Driver) Initialize(ctx context.Context) error {
db, err := sql.Open("sqlite", driver.filePath)
if err != nil {
return err
}
if err := db.PingContext(ctx); err != nil {
return err
}
log.Info().Msg("Performing SQLite database migrations...")
source, err := iofs.New(migrations, "migrations")
if err != nil {
_ = db.Close()
return err
}
defer func() {
_ = source.Close()
}()
migrateDriver, err := sqlite.WithInstance(db, &sqlite.Config{
MigrationsTable: sqlite.DefaultMigrationsTable,
DatabaseName: driver.filePath,
NoTxWrap: false,
})
if err != nil {
_ = db.Close()
return err
}
migrator, err := migrate.NewWithInstance("iofs", source, "sqlite", migrateDriver)
if err != nil {
_ = db.Close()
return err
}
if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
_ = db.Close()
return err
}
driver.connPool = db
driver.pastes = &pasteRepository{
connPool: db,
}
return nil
}
func (driver *Driver) Close() error {
driver.pastes = nil
_ = driver.connPool.Close()
driver.connPool = nil
return nil
}
func (driver *Driver) Pastes() pastes.Repository {
return driver.pastes
}

View File

@ -0,0 +1 @@
DROP TABLE "pastes";

View File

@ -0,0 +1,8 @@
CREATE TABLE "pastes" (
"id" TEXT NOT NULL,
"content" TEXT NOT NULL,
"modification_token" TEXT NOT NULL,
"created" BIGINT NOT NULL,
"metadata" TEXT NOT NULL,
PRIMARY KEY ("id")
);

View File

@ -0,0 +1,97 @@
package sqlite
import (
"context"
"database/sql"
"encoding/json"
"errors"
"github.com/lus/pasty/internal/pastes"
"time"
)
type pasteRepository struct {
connPool *sql.DB
}
var _ pastes.Repository = (*pasteRepository)(nil)
func (repo *pasteRepository) ListIDs(ctx context.Context) ([]string, error) {
rows, err := repo.connPool.QueryContext(ctx, "SELECT id FROM pastes")
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
ids := make([]string, 0)
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return nil, err
}
ids = append(ids, id)
}
return ids, nil
}
func (repo *pasteRepository) FindByID(ctx context.Context, id string) (*pastes.Paste, error) {
row := repo.connPool.QueryRowContext(ctx, "SELECT * FROM pastes WHERE id = ?", id)
obj := new(pastes.Paste)
var rawMetadata string
if err := row.Scan(&obj.ID, &obj.Content, &obj.ModificationToken, &obj.Created, &rawMetadata); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}
var metadata map[string]any
if err := json.Unmarshal([]byte(rawMetadata), &metadata); err != nil {
return nil, err
}
obj.Metadata = metadata
return obj, nil
}
func (repo *pasteRepository) Upsert(ctx context.Context, paste *pastes.Paste) error {
const query = `
INSERT INTO pastes
VALUES (?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE
SET content = excluded.content,
modification_token = excluded.modification_token,
metadata = excluded.metadata
`
rawMetadata, err := json.Marshal(paste.Metadata)
if err != nil {
return err
}
_, err = repo.connPool.ExecContext(ctx, query, paste.ID, paste.Content, paste.ModificationToken, paste.Created, rawMetadata)
return err
}
func (repo *pasteRepository) DeleteByID(ctx context.Context, id string) error {
_, err := repo.connPool.ExecContext(ctx, "DELETE FROM pastes WHERE id = ?", id)
return err
}
func (repo *pasteRepository) DeleteOlderThan(ctx context.Context, age time.Duration) (int, error) {
result, err := repo.connPool.ExecContext(ctx, "DELETE FROM pastes WHERE created < ?", time.Now().Add(-age).Unix())
if err != nil {
return 0, err
}
affected, err := result.RowsAffected()
if err != nil {
return -1, nil
}
return int(affected), nil
}