package main import ( "context" "flag" "fmt" "log" "net" "os" "os/signal" "syscall" "time" "github.com/jackc/pgx/v5/pgxpool" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "github.com/metadata-agregator/internal/config" "github.com/metadata-agregator/internal/provider/musicbrainz" "github.com/metadata-agregator/internal/repository/postgres" "github.com/metadata-agregator/internal/server" "github.com/metadata-agregator/internal/service" metadatav1 "github.com/metadata-agregator/pkg/gen/metadata/v1" ) func main() { configPath := flag.String("config", "", "path to config file") flag.Parse() cfg, err := config.Load(*configPath) if err != nil { log.Fatalf("failed to load config: %v", err) } ctx := context.Background() services, cleanup := buildServices(ctx, cfg) defer cleanup() addr := fmt.Sprintf(":%d", cfg.Server.Port) lis, err := net.Listen("tcp", addr) if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() metadatav1.RegisterMetadataServiceServer(grpcServer, server.NewMetadataServer(services)) reflection.Register(grpcServer) go gracefulShutdown(grpcServer) log.Printf("gRPC server listening on %s", addr) if err := grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } func buildServices(ctx context.Context, cfg *config.Config) (map[metadatav1.Provider]*service.MetadataService, func()) { mb := musicbrainz.New() services := make(map[metadatav1.Provider]*service.MetadataService) dbURL := cfg.Database.DSN() if dbURL == "" { dbURL = os.Getenv("DATABASE_URL") } if dbURL == "" { log.Println("no database configured, running in provider-only mode") services[metadatav1.Provider_PROVIDER_MUSICBRAINZ] = service.NewMetadataService( &noopArtistRepo{}, &noopAlbumRepo{}, &noopTrackRepo{}, mb, ) return services, func() {} } pool, err := connectDB(ctx, dbURL) if err != nil { log.Fatalf("database connection failed: %v", err) } artistRepo := postgres.NewArtistRepository(pool) albumRepo := postgres.NewAlbumRepository(pool) trackRepo := postgres.NewTrackRepository(pool) services[metadatav1.Provider_PROVIDER_MUSICBRAINZ] = service.NewMetadataService( artistRepo, albumRepo, trackRepo, mb, ) log.Println("database connected, caching enabled") return services, func() { pool.Close() } } func connectDB(ctx context.Context, dbURL string) (*pgxpool.Pool, error) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() config, err := pgxpool.ParseConfig(dbURL) if err != nil { return nil, err } config.MaxConns = 10 config.MinConns = 2 pool, err := pgxpool.NewWithConfig(ctx, config) if err != nil { return nil, err } if err := pool.Ping(ctx); err != nil { pool.Close() return nil, err } return pool, nil } func gracefulShutdown(server *grpc.Server) { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh log.Println("shutting down...") server.GracefulStop() }