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:
87
internal/storage/sqlite/driver.go
Normal file
87
internal/storage/sqlite/driver.go
Normal 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
|
||||
}
|
@ -0,0 +1 @@
|
||||
DROP TABLE "pastes";
|
@ -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")
|
||||
);
|
97
internal/storage/sqlite/paste_repository.go
Normal file
97
internal/storage/sqlite/paste_repository.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user