package server import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" gonostr "github.com/nbd-wtf/go-nostr" "gitcitadel-online/internal/cache" "gitcitadel-online/internal/generator" "gitcitadel-online/internal/logger" "gitcitadel-online/internal/nostr" ) // Server represents the HTTP server type Server struct { httpServer *http.Server cache *cache.Cache feedCache *cache.FeedCache port int issueService IssueServiceInterface repoAnnouncement string htmlGenerator HTMLGeneratorInterface nostrClient *nostr.Client } // IssueServiceInterface defines the interface for issue service type IssueServiceInterface interface { FetchRepoAnnouncement(ctx context.Context, repoNaddr string) (*nostr.RepoAnnouncement, error) PublishIssue(ctx context.Context, repoAnnouncement *nostr.RepoAnnouncement, req *nostr.IssueRequest, privateKey string) (string, error) PublishSignedIssue(ctx context.Context, signedEvent *gonostr.Event) (string, error) } // HTMLGeneratorInterface defines the interface for HTML generator type HTMLGeneratorInterface interface { GenerateContactPage(success bool, errorMsg string, eventID string, formData map[string]string, repoAnnouncement *nostr.RepoAnnouncement, feedItems []generator.FeedItemInfo, profile *nostr.Profile) (string, error) GenerateErrorPage(statusCode int, feedItems []generator.FeedItemInfo) (string, error) } // NewServer creates a new HTTP server func NewServer(port int, pageCache *cache.Cache, feedCache *cache.FeedCache, issueService IssueServiceInterface, repoAnnouncement string, htmlGenerator HTMLGeneratorInterface, nostrClient *nostr.Client) *Server { s := &Server{ cache: pageCache, feedCache: feedCache, port: port, issueService: issueService, repoAnnouncement: repoAnnouncement, htmlGenerator: htmlGenerator, nostrClient: nostrClient, } mux := http.NewServeMux() // Setup routes s.setupRoutes(mux) s.httpServer = &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: s.middleware(mux), ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } return s } // Start starts the HTTP server func (s *Server) Start() error { logger.Infof("Starting server on port %d", s.port) return s.httpServer.ListenAndServe() } // Shutdown gracefully shuts down the server func (s *Server) Shutdown(ctx context.Context) error { return s.httpServer.Shutdown(ctx) } // WaitForShutdown waits for shutdown signals func (s *Server) WaitForShutdown() { quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit logger.Info("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := s.Shutdown(ctx); err != nil { logger.Fatal("Server forced to shutdown:", err) } logger.Info("Server exited") } // convertFeedItemsToInfo converts cache.FeedItem to generator.FeedItemInfo func (s *Server) convertFeedItemsToInfo(items []cache.FeedItem) []generator.FeedItemInfo { feedItems := make([]generator.FeedItemInfo, 0, len(items)) for _, item := range items { feedItems = append(feedItems, generator.FeedItemInfo{ Author: item.Author, Content: item.Content, Time: item.Time.Format("2006-01-02 15:04:05"), TimeISO: item.Time.Format(time.RFC3339), Link: item.Link, }) } return feedItems }