package api import ( "context" "database/sql" "errors" "fmt" "net/http" "allaboutapps.dev/aw/go-starter/internal/config" "allaboutapps.dev/aw/go-starter/internal/data/dto" "allaboutapps.dev/aw/go-starter/internal/data/local" "allaboutapps.dev/aw/go-starter/internal/i18n" "allaboutapps.dev/aw/go-starter/internal/mailer" "allaboutapps.dev/aw/go-starter/internal/metrics" "allaboutapps.dev/aw/go-starter/internal/push" "allaboutapps.dev/aw/go-starter/internal/util" "github.com/dropbox/godropbox/time2" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" // Import postgres driver for database/sql package _ "github.com/lib/pq" ) type Router struct { Routes []*echo.Route Root *echo.Group Management *echo.Group APIV1Auth *echo.Group APIV1Push *echo.Group WellKnown *echo.Group } // Server is a central struct keeping all the dependencies. // It is initialized with wire, which handles making the new instances of the components // in the right order. To add a new component, 3 steps are required: // - declaring it in this struct // - adding a provider function in providers.go // - adding the provider's function name to the arguments of wire.Build() in wire.go // // Components labeled as `wire:"-"` will be skipped and have to be initialized after the InitNewServer* call. // For more information about wire refer to https://pkg.go.dev/github.com/google/wire type Server struct { // skip wire: // -> initialized with router.Init(s) function Echo *echo.Echo `wire:"-"` Router *Router `wire:"-"` Config config.Server DB *sql.DB Mailer *mailer.Mailer Push *push.Service I18n *i18n.Service Clock time2.Clock Auth AuthService Local *local.Service Metrics *metrics.Service } // newServerWithComponents is used by wire to initialize the server components. // Components not listed here won't be handled by wire and should be initialized separately. // Components which shouldn't be handled must be labeled `wire:"-"` in Server struct. func newServerWithComponents( cfg config.Server, db *sql.DB, mail *mailer.Mailer, pusher *push.Service, i18n *i18n.Service, clock time2.Clock, auth AuthService, local *local.Service, metrics *metrics.Service, ) *Server { return &Server{ Config: cfg, DB: db, Mailer: mail, Push: pusher, I18n: i18n, Clock: clock, Auth: auth, Local: local, Metrics: metrics, } } type AuthService interface { GetAppUserProfile(ctx context.Context, id string) (*dto.AppUserProfile, error) InitPasswordReset(ctx context.Context, request dto.InitPasswordResetRequest) (dto.InitPasswordResetResult, error) Login(ctx context.Context, request dto.LoginRequest) (dto.LoginResult, error) Logout(ctx context.Context, request dto.LogoutRequest) error Refresh(ctx context.Context, request dto.RefreshRequest) (dto.LoginResult, error) Register(ctx context.Context, request dto.RegisterRequest) (dto.RegisterResult, error) CompleteRegister(ctx context.Context, request dto.CompleteRegisterRequest) (dto.LoginResult, error) DeleteUserAccount(ctx context.Context, request dto.DeleteUserAccountRequest) error ResetPassword(ctx context.Context, request dto.ResetPasswordRequest) (dto.LoginResult, error) UpdatePassword(ctx context.Context, request dto.UpdatePasswordRequest) (dto.LoginResult, error) } func NewServer(config config.Server) *Server { s := &Server{ Config: config, } return s } func (s *Server) Ready() bool { if err := util.IsStructInitialized(s); err != nil { log.Debug().Err(err).Msg("Server is not fully initialized") return false } return true } func (s *Server) Start() error { if !s.Ready() { return errors.New("server is not ready") } if err := s.Echo.Start(s.Config.Echo.ListenAddress); err != nil { return fmt.Errorf("failed to start echo server: %w", err) } return nil } func (s *Server) Shutdown(ctx context.Context) []error { log.Warn().Msg("Shutting down server") var errs []error if s.DB != nil { log.Debug().Msg("Closing database connection") if err := s.DB.Close(); err != nil && !errors.Is(err, sql.ErrConnDone) { log.Error().Err(err).Msg("Failed to close database connection") errs = append(errs, err) } } if s.Echo != nil { log.Debug().Msg("Shutting down echo server") if err := s.Echo.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Error().Err(err).Msg("Failed to shutdown echo server") errs = append(errs, err) } } return errs }