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:
iu
2026-10-11 10:08:31 +03:00
parent ec8d907565
commit 176a8d8ced
69 changed files with 4174 additions and 95 deletions
+18 -24
View File
@@ -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