mirror of
https://github.com/lus/pasty.git
synced 2023-08-10 21:13:09 +03:00
restructure storage driver
This commit is contained in:
@ -3,29 +3,11 @@ package storage
|
||||
import (
|
||||
"context"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"strings"
|
||||
|
||||
"github.com/lus/pasty/internal/paste"
|
||||
"github.com/lus/pasty/internal/storage/postgres"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
)
|
||||
|
||||
// Driver represents a storage driver
|
||||
type Driver interface {
|
||||
Initialize(ctx context.Context, cfg *config.Config) error
|
||||
Close() error
|
||||
ListIDs() ([]string, error)
|
||||
Get(id string) (*paste.Paste, error)
|
||||
Save(paste *paste.Paste) error
|
||||
Delete(id string) error
|
||||
Cleanup() (int, error)
|
||||
}
|
||||
|
||||
// ResolveDriver returns the driver with the given name if it exists
|
||||
func ResolveDriver(name string) (Driver, bool) {
|
||||
switch strings.TrimSpace(strings.ToLower(name)) {
|
||||
case "postgres":
|
||||
return new(postgres.Driver), true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
Pastes() pastes.Repository
|
||||
}
|
||||
|
68
internal/storage/postgres/driver.go
Normal file
68
internal/storage/postgres/driver.go
Normal file
@ -0,0 +1,68 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
"github.com/lus/pasty/internal/storage"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrations embed.FS
|
||||
|
||||
type Driver struct {
|
||||
connPool *pgxpool.Pool
|
||||
pastes *pasteRepository
|
||||
}
|
||||
|
||||
var _ storage.Driver = (*Driver)(nil)
|
||||
|
||||
func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error {
|
||||
pool, err := pgxpool.New(ctx, cfg.Postgres.DSN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msg("Performing PostgreSQL database migrations...")
|
||||
source, err := iofs.New(migrations, "migrations")
|
||||
if err != nil {
|
||||
pool.Close()
|
||||
return err
|
||||
}
|
||||
migrator, err := migrate.NewWithSourceInstance("iofs", source, cfg.Postgres.DSN)
|
||||
if err != nil {
|
||||
pool.Close()
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = migrator.Close()
|
||||
}()
|
||||
if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
pool.Close()
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("Successfully performed PostgreSQL database migrations.")
|
||||
|
||||
driver.connPool = pool
|
||||
driver.pastes = &pasteRepository{
|
||||
connPool: pool,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *Driver) Close() error {
|
||||
driver.pastes = nil
|
||||
driver.connPool.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *Driver) Pastes() pastes.Repository {
|
||||
return driver.pastes
|
||||
}
|
63
internal/storage/postgres/paste_repository.go
Normal file
63
internal/storage/postgres/paste_repository.go
Normal file
@ -0,0 +1,63 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/lus/pasty/internal/pastes"
|
||||
"time"
|
||||
)
|
||||
|
||||
type pasteRepository struct {
|
||||
connPool *pgxpool.Pool
|
||||
}
|
||||
|
||||
var _ pastes.Repository = (*pasteRepository)(nil)
|
||||
|
||||
func (repo *pasteRepository) ListIDs(ctx context.Context) ([]string, error) {
|
||||
rows, _ := repo.connPool.Query(ctx, "SELECT id FROM pastes")
|
||||
result, err := pgx.CollectRows(rows, pgx.RowTo[string])
|
||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *pasteRepository) FindByID(ctx context.Context, id string) (*pastes.Paste, error) {
|
||||
rows, _ := repo.connPool.Query(ctx, "SELECT * FROM pastes WHERE id = $1", id)
|
||||
result, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByPos[pastes.Paste])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *pasteRepository) Upsert(ctx context.Context, paste *pastes.Paste) error {
|
||||
const query = `
|
||||
INSERT INTO pastes
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (id) DO UPDATE
|
||||
SET content = excluded.content,
|
||||
"modificationToken" = excluded."modificationToken",
|
||||
metadata = excluded.metadata
|
||||
`
|
||||
_, err := repo.connPool.Exec(ctx, query, paste.ID, paste.Content, paste.ModificationToken, paste.Created, paste.Metadata)
|
||||
return err
|
||||
}
|
||||
|
||||
func (repo *pasteRepository) DeleteByID(ctx context.Context, id string) error {
|
||||
_, err := repo.connPool.Exec(ctx, "DELETE FROM pastes WHERE id = $1", id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (repo *pasteRepository) DeleteOlderThan(ctx context.Context, age time.Duration) (int, error) {
|
||||
tag, err := repo.connPool.Exec(ctx, "DELETE FROM pastes WHERE created < $1", time.Now().Add(-age).Unix())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(tag.RowsAffected()), nil
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/johejo/golang-migrate-extra/source/iofs"
|
||||
"github.com/lus/pasty/internal/config"
|
||||
"github.com/lus/pasty/internal/paste"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrations embed.FS
|
||||
|
||||
// Driver represents the Postgres storage driver
|
||||
type Driver struct {
|
||||
pool *pgxpool.Pool
|
||||
autoDeleteLifetime time.Duration
|
||||
}
|
||||
|
||||
// Initialize initializes the Postgres storage driver
|
||||
func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error {
|
||||
pool, err := pgxpool.Connect(ctx, cfg.Postgres.DSN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := iofs.New(migrations, "migrations")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrator, err := migrate.NewWithSourceInstance("iofs", source, cfg.Postgres.DSN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
|
||||
driver.pool = pool
|
||||
driver.autoDeleteLifetime = cfg.AutoDelete.Lifetime
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close terminates the Postgres storage driver
|
||||
func (driver *Driver) Close() error {
|
||||
driver.pool.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListIDs returns a list of all existing paste IDs
|
||||
func (driver *Driver) ListIDs() ([]string, error) {
|
||||
query := "SELECT id FROM pastes"
|
||||
|
||||
rows, err := driver.pool.Query(context.Background(), query)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Get loads a paste
|
||||
func (driver *Driver) Get(id string) (*paste.Paste, error) {
|
||||
query := "SELECT * FROM pastes WHERE id = $1"
|
||||
|
||||
row := driver.pool.QueryRow(context.Background(), query, id)
|
||||
|
||||
paste := new(paste.Paste)
|
||||
if err := row.Scan(&paste.ID, &paste.Content, &paste.ModificationToken, &paste.Created, &paste.Metadata); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return paste, nil
|
||||
}
|
||||
|
||||
// Save saves a paste
|
||||
func (driver *Driver) Save(paste *paste.Paste) error {
|
||||
query := `
|
||||
INSERT INTO pastes (id, content, "modificationToken", created, metadata)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (id) DO UPDATE
|
||||
SET content = excluded.content,
|
||||
"modificationToken" = excluded."modificationToken",
|
||||
created = excluded.created,
|
||||
metadata = excluded.metadata
|
||||
`
|
||||
|
||||
_, err := driver.pool.Exec(context.Background(), query, paste.ID, paste.Content, paste.ModificationToken, paste.Created, paste.Metadata)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete deletes a paste
|
||||
func (driver *Driver) Delete(id string) error {
|
||||
query := "DELETE FROM pastes WHERE id = $1"
|
||||
|
||||
_, err := driver.pool.Exec(context.Background(), query, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Cleanup cleans up the expired pastes
|
||||
func (driver *Driver) Cleanup() (int, error) {
|
||||
query := "DELETE FROM pastes WHERE created < $1"
|
||||
|
||||
tag, err := driver.pool.Exec(context.Background(), query, time.Now().Add(-driver.autoDeleteLifetime).Unix())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(tag.RowsAffected()), nil
|
||||
}
|
Reference in New Issue
Block a user