// cmd/server/main.go package main import ( "flag" "fmt" "os" "time" "git.sovbit.dev/Enki/nostr-poster/internal/api" "git.sovbit.dev/Enki/nostr-poster/internal/auth" "git.sovbit.dev/Enki/nostr-poster/internal/config" "git.sovbit.dev/Enki/nostr-poster/internal/crypto" "git.sovbit.dev/Enki/nostr-poster/internal/db" "git.sovbit.dev/Enki/nostr-poster/internal/media/prepare" "git.sovbit.dev/Enki/nostr-poster/internal/media/upload/blossom" "git.sovbit.dev/Enki/nostr-poster/internal/media/upload/nip94" "git.sovbit.dev/Enki/nostr-poster/internal/nostr/events" "git.sovbit.dev/Enki/nostr-poster/internal/nostr/poster" "git.sovbit.dev/Enki/nostr-poster/internal/nostr/relay" "git.sovbit.dev/Enki/nostr-poster/internal/scheduler" "git.sovbit.dev/Enki/nostr-poster/internal/utils" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func main() { // Parse command line flags configPath := flag.String("config", "", "Path to config file") dbPath := flag.String("db", "", "Path to database file") port := flag.Int("port", 0, "Port to listen on") password := flag.String("password", "", "Password for encrypting private keys") flag.Parse() // Setup logger logConfig := zap.NewProductionConfig() logConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder logger, err := logConfig.Build() if err != nil { fmt.Fprintf(os.Stderr, "Failed to create logger: %v\n", err) os.Exit(1) } defer logger.Sync() // Load config cfg, err := config.LoadConfig(*configPath) if err != nil { logger.Fatal("Failed to load config", zap.Error(err)) } // Override config with command line flags if provided if *dbPath != "" { cfg.DB.Path = *dbPath } if *port != 0 { cfg.ServerPort = *port } // Ensure directories exist if err := utils.EnsureDir(cfg.Bot.ContentDir); err != nil { logger.Fatal("Failed to create content directory", zap.Error(err)) } if err := utils.EnsureDir(cfg.Bot.ArchiveDir); err != nil { logger.Fatal("Failed to create archive directory", zap.Error(err)) } // Initialize database database, err := db.New(cfg.DB.Path) if err != nil { logger.Fatal("Failed to connect to database", zap.Error(err)) } if err := database.Initialize(); err != nil { logger.Fatal("Failed to initialize database", zap.Error(err)) } // Initialize key store keyPassword := *password if keyPassword == "" { // Use a default password or prompt for one // In a production environment, you'd want to handle this more securely keyPassword = "nostr-poster-default-password" } keyStore, err := crypto.NewKeyStore(cfg.Bot.KeysFile, keyPassword) if err != nil { logger.Fatal("Failed to initialize key store", zap.Error(err)) } // Initialize event manager eventManager := events.NewEventManager(func(pubkey string) (string, error) { return keyStore.GetPrivateKey(pubkey) }) // Initialize relay manager relayManager := relay.NewManager(logger) // Initialize media preparation manager mediaPrep := prepare.NewManager(logger) // Initialize uploaders // NIP-94 uploader nip94Uploader := nip94.NewUploader( cfg.Media.NIP94.ServerURL, "", // Download URL will be discovered nil, // Supported types will be discovered logger, func(url, method string, payload []byte) (string, error) { // Replace with a valid bot's public key. botPubkey := "your_valid_bot_pubkey_here" privkey, err := keyStore.GetPrivateKey(botPubkey) if err != nil { return "", err } return nip94.CreateNIP98AuthHeader(url, method, payload, privkey) }, ) // Blossom uploader blossomUploader := blossom.NewUploader( cfg.Media.Blossom.ServerURL, logger, func(url, method string) (string, error) { // Replace with the appropriate bot's public key botPubkey := "your_valid_bot_pubkey_here" privkey, err := keyStore.GetPrivateKey(botPubkey) if err != nil { return "", err } return blossom.CreateBlossomAuthHeader(url, method, privkey) }, ) // Initialize authentication service authService := auth.NewService( database, logger, keyPassword, // Use the same password for simplicity 24*time.Hour, // Token duration ) // Initialize bot service botService := api.NewBotService( database, keyStore, eventManager, relayManager, logger, ) // Create post content function postContentFunc := poster.CreatePostContentFunc( eventManager, relayManager, mediaPrep, logger, ) // Initialize scheduler posterScheduler := scheduler.NewScheduler( database, logger, cfg.Bot.ContentDir, cfg.Bot.ArchiveDir, nip94Uploader, blossomUploader, postContentFunc, ) // Initialize API apiServer := api.NewAPI( logger, botService, authService, posterScheduler, ) // Start the scheduler if err := posterScheduler.Start(); err != nil { logger.Error("Failed to start scheduler", zap.Error(err)) } // Start the server addr := fmt.Sprintf(":%d", cfg.ServerPort) logger.Info("Starting server", zap.String("address", addr)) if err := apiServer.Run(addr); err != nil { logger.Fatal("Failed to start server", zap.Error(err)) } }