feat(metadata): SQLite backend, storage reconciliation and local defaults
- pluggable per-domain Repository interfaces; PostgreSQL and SQLite impls - SQLite on modernc.org/sqlite (WAL, busy_timeout, foreign_keys, immediate tx) - separate migrations/sqlite and db.MigrateSQLite; store.NewPostgres/NewSQLite - cmd/reconcile compares object storage with the journal (report, then --delete) - storage.Backend.List and photos.Repository.AllStorageKeys - DB_BACKEND/SQLITE_PATH and STORAGE_BACKEND/FS_ROOT selectable; local defaults (sqlite ./galo.db, fs ./data) - Makefile reconcile/reconcile-delete; docs and .env.example updated
This commit is contained in:
+18
-24
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
assets "shared-album"
|
||||
"shared-album/internal/albums"
|
||||
@@ -23,16 +22,17 @@ import (
|
||||
"shared-album/internal/social"
|
||||
"shared-album/internal/sse"
|
||||
"shared-album/internal/storage"
|
||||
"shared-album/internal/store"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
cfg *config.Config
|
||||
pool *pgxpool.Pool
|
||||
repos store.Repos
|
||||
ping func(ctx context.Context) error
|
||||
storage storage.Backend
|
||||
hub *sse.Hub
|
||||
|
||||
albumsStore *albums.Store
|
||||
albums *albums.Service
|
||||
albums *albums.Service
|
||||
|
||||
auth *auth.Service
|
||||
|
||||
@@ -48,36 +48,30 @@ type Server struct {
|
||||
share *sharelinks.Service
|
||||
}
|
||||
|
||||
func New(cfg *config.Config, pool *pgxpool.Pool, st storage.Backend, hub *sse.Hub) *Server {
|
||||
authStore := auth.NewStore(pool)
|
||||
// New wires the domain services over the selected metadata repositories. ping
|
||||
// reports the health of the metadata backend so health stays backend-agnostic.
|
||||
func New(cfg *config.Config, repos store.Repos, ping func(ctx context.Context) error, st storage.Backend, hub *sse.Hub) *Server {
|
||||
tm := auth.NewTokenManager(cfg.Auth.JWTSecret, cfg.Auth.AccessTTL, cfg.Auth.RefreshTTL)
|
||||
authSvc := auth.NewService(authStore, tm)
|
||||
authSvc := auth.NewService(repos.Auth, tm)
|
||||
|
||||
albumsStore := albums.NewStore(pool)
|
||||
membersStore := members.NewStore(pool)
|
||||
notifSvc := notifications.NewService(repos.Notifications, hub)
|
||||
|
||||
notifStore := notifications.NewStore(pool)
|
||||
notifSvc := notifications.NewService(notifStore, hub)
|
||||
albumsSvc := albums.NewService(repos.Albums, repos.Members, hub, st)
|
||||
membersSvc := members.NewService(repos.Members, repos.Albums, hub, notifSvc)
|
||||
|
||||
albumsSvc := albums.NewService(albumsStore, membersStore, hub, st)
|
||||
membersSvc := members.NewService(membersStore, albumsStore, hub, notifSvc)
|
||||
|
||||
photosStore := photos.NewStore(pool)
|
||||
photosSvc := photos.NewService(photosStore, albumsSvc, st, hub)
|
||||
photosSvc := photos.NewService(repos.Photos, albumsSvc, st, hub)
|
||||
photosWorker := photos.NewWorker(photosSvc, cfg.Worker.Interval, cfg.Worker.Batch, cfg.Worker.PendingTTL)
|
||||
|
||||
socialStore := social.NewStore(pool)
|
||||
socialSvc := social.NewService(socialStore, albumsSvc, photosStore, hub, notifSvc)
|
||||
socialSvc := social.NewService(repos.Social, albumsSvc, repos.Photos, hub, notifSvc)
|
||||
|
||||
shareStore := sharelinks.NewStore(pool)
|
||||
shareSvc := sharelinks.NewService(shareStore, albumsSvc)
|
||||
shareSvc := sharelinks.NewService(repos.ShareLinks, albumsSvc)
|
||||
|
||||
return &Server{
|
||||
cfg: cfg,
|
||||
pool: pool,
|
||||
repos: repos,
|
||||
ping: ping,
|
||||
storage: st,
|
||||
hub: hub,
|
||||
albumsStore: albumsStore,
|
||||
albums: albumsSvc,
|
||||
auth: authSvc,
|
||||
membersSvc: membersSvc,
|
||||
@@ -183,7 +177,7 @@ func (s *Server) Router() *gin.Engine {
|
||||
func (s *Server) health(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
dbStatus := "ok"
|
||||
if err := s.pool.Ping(ctx); err != nil {
|
||||
if err := s.ping(ctx); err != nil {
|
||||
dbStatus = "error"
|
||||
}
|
||||
storageLabel := s.cfg.Storage.Backend
|
||||
@@ -243,7 +237,7 @@ func (s *Server) shareAlbum(c *gin.Context) {
|
||||
httpx.RespondError(c, err)
|
||||
return
|
||||
}
|
||||
album, err := s.albumsStore.GetByID(c.Request.Context(), link.AlbumID)
|
||||
album, err := s.repos.Albums.GetByID(c.Request.Context(), link.AlbumID)
|
||||
if err != nil {
|
||||
httpx.RespondError(c, httpx.NotFound("album_not_found", "album not found"))
|
||||
return
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"shared-album/internal/config"
|
||||
"shared-album/internal/storage"
|
||||
@@ -24,23 +23,14 @@ func mustSigner(t *testing.T) *storage.Signer {
|
||||
return signer
|
||||
}
|
||||
|
||||
func lazyPool(t *testing.T) *pgxpool.Pool {
|
||||
t.Helper()
|
||||
pool, err := pgxpool.New(context.Background(), "postgres://u:p@127.0.0.1:1/x?sslmode=disable")
|
||||
if err != nil {
|
||||
t.Fatalf("pool: %v", err)
|
||||
}
|
||||
t.Cleanup(pool.Close)
|
||||
return pool
|
||||
}
|
||||
|
||||
func TestHealthStorageLabel(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
fs, err := storage.NewFS(t.TempDir(), mustSigner(t))
|
||||
if err != nil {
|
||||
t.Fatalf("fs: %v", err)
|
||||
}
|
||||
srv := &Server{cfg: &config.Config{}, pool: lazyPool(t), storage: fs}
|
||||
ok := func(context.Context) error { return nil }
|
||||
srv := &Server{cfg: &config.Config{}, ping: ok, storage: fs}
|
||||
|
||||
call := func() map[string]any {
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"shared-album/internal/config"
|
||||
"shared-album/internal/sse"
|
||||
"shared-album/internal/storage"
|
||||
"shared-album/internal/store"
|
||||
)
|
||||
|
||||
func newTestServerWithFS(t *testing.T) (*storage.FS, *gin.Engine) {
|
||||
@@ -28,7 +29,8 @@ func newTestServerWithFS(t *testing.T) (*storage.FS, *gin.Engine) {
|
||||
if err != nil {
|
||||
t.Fatalf("new fs: %v", err)
|
||||
}
|
||||
srv := New(&config.Config{}, nil, fs, sse.NewHub(1))
|
||||
noop := func(context.Context) error { return nil }
|
||||
srv := New(&config.Config{}, store.Repos{}, noop, fs, sse.NewHub(1))
|
||||
return fs, srv.Router()
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"shared-album/internal/sharelinks"
|
||||
"shared-album/internal/sse"
|
||||
"shared-album/internal/storage"
|
||||
"shared-album/internal/store"
|
||||
)
|
||||
|
||||
func integrationPool(t *testing.T) *pgxpool.Pool {
|
||||
@@ -70,7 +71,7 @@ func runShareFlow(t *testing.T, backend storage.Backend, verify func(t *testing.
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
hub := sse.NewHub(1)
|
||||
srv := New(&config.Config{}, pool, backend, hub)
|
||||
srv := New(&config.Config{}, store.NewPostgres(pool), pool.Ping, backend, hub)
|
||||
router := srv.Router()
|
||||
|
||||
authStore := auth.NewStore(pool)
|
||||
|
||||
Reference in New Issue
Block a user