diff --git a/internal/auth/service.go b/internal/auth/service.go index fa902a4..d01a0ba 100644 --- a/internal/auth/service.go +++ b/internal/auth/service.go @@ -6,6 +6,7 @@ type Service struct { secret string admin string adminSecret string + apiKey string } type ProviderIndex struct { @@ -13,17 +14,22 @@ type ProviderIndex struct { Providers []string } -func NewService(secret, admin, adminSecret string) (*Service, error) { - if secret == "" || admin == "" || adminSecret == "" { +func NewService(secret, admin, adminSecret, apiKey string) (*Service, error) { + if secret == "" || admin == "" || adminSecret == "" || apiKey == "" { return nil, errors.New("error building Auth service. Verify your env variables") } return &Service{ secret, admin, adminSecret, + apiKey, }, nil } func (as *Service) Authenticate(email, password string) bool { return email == as.admin && password == as.adminSecret } + +func (as *Service) ValidateApiKey(key string) bool { + return key == as.apiKey +} diff --git a/internal/calendar/service.go b/internal/calendar/service.go index 8dd1794..2b3f3c2 100644 --- a/internal/calendar/service.go +++ b/internal/calendar/service.go @@ -52,7 +52,6 @@ func NewService(ctx context.Context, credJson string, opts ...Option) (*Service, return &Service{Service: srv}, nil } -// TODO: implement create event, list events in a period, delete event func (s *Service) Create(from, to time.Time) (*calendar.Event, error) { l := s.CalendarList.List() r, e := l.Do() diff --git a/internal/server/handle_bookings.go b/internal/server/handle_bookings.go index 7478993..f0f9f7c 100644 --- a/internal/server/handle_bookings.go +++ b/internal/server/handle_bookings.go @@ -20,7 +20,7 @@ import ( myTime "github.com/rjNemo/rentease/pkg/time" ) -func handleListBookingPage(bs *booking.Service, hc *config.Host) echo.HandlerFunc { +func handleBookingListPage(bs *booking.Service, hc *config.Host) echo.HandlerFunc { return func(c echo.Context) error { bookings := bs.All() @@ -42,22 +42,22 @@ func handleListBookingPage(bs *booking.Service, hc *config.Host) echo.HandlerFun } } -func handleNewBookingPage(hc *config.Host) echo.HandlerFunc { +func handleBookingCreatePage(hc *config.Host) echo.HandlerFunc { return func(c echo.Context) error { return renderTempl(c, http.StatusOK, view.NewBooking(hc.Platforms)) } } -func handleCreateBooking(bs *booking.Service) echo.HandlerFunc { +func handleBookingCreate(bs *booking.Service) echo.HandlerFunc { return func(c echo.Context) error { type NewBooking struct { From time.Time `json:"from"` To time.Time `json:"to"` + ExternalId *string `form:"external_id"` Name string `form:"name"` PhoneNumber string `form:"phone_number"` Email string `form:"email"` Platform string `form:"platform"` - ExternalId *string `form:"external_id"` CustomerNumber int `form:"customer_number"` PlatformFees float64 `form:"platform_fees"` } @@ -77,6 +77,7 @@ func handleCreateBooking(bs *booking.Service) echo.HandlerFunc { nb.ExternalId = nil } b := bs.Create(nb.From, nb.To, nb.Name, nb.PhoneNumber, nb.Email, nb.Platform, nb.CustomerNumber, nb.PlatformFees, nb.ExternalId) + // sync the calendar return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s/%d", constant.RouteBooking, b.Id)) } } @@ -135,7 +136,7 @@ func handleBookingPage(bs *booking.Service, hc *config.Host) echo.HandlerFunc { } } -func handleUpdateBooking(bs *booking.Service, hc *config.Host) echo.HandlerFunc { +func handleBookingUpdate(bs *booking.Service, hc *config.Host) echo.HandlerFunc { return func(c echo.Context) error { type UpdateBooking struct { From time.Time `json:"from"` @@ -244,7 +245,7 @@ func handleCreateItem(bs *booking.Service) echo.HandlerFunc { } } -func handlePayItem(bs *booking.Service) echo.HandlerFunc { +func handleItemPay(bs *booking.Service) echo.HandlerFunc { return func(c echo.Context) error { itemIdStr := c.Param("id") itemId, err := strconv.Atoi(itemIdStr) @@ -266,7 +267,7 @@ func handlePayItem(bs *booking.Service) echo.HandlerFunc { } } -func handleUpdateItem(bs *booking.Service) echo.HandlerFunc { +func handleItemUpdate(bs *booking.Service) echo.HandlerFunc { return func(c echo.Context) error { type updateItem struct { Item string `form:"item"` @@ -298,7 +299,7 @@ func handleUpdateItem(bs *booking.Service) echo.HandlerFunc { } } -func handleCancelBooking(bs *booking.Service) echo.HandlerFunc { +func handleBookingCancel(bs *booking.Service) echo.HandlerFunc { return func(c echo.Context) error { idStr := c.Param("id") id, err := strconv.Atoi(idStr) diff --git a/internal/server/handle_health.go b/internal/server/handle_health.go new file mode 100644 index 0000000..31ce366 --- /dev/null +++ b/internal/server/handle_health.go @@ -0,0 +1,13 @@ +package server + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func handleHealthCheck() echo.HandlerFunc { + return func(c echo.Context) error { + return c.String(http.StatusOK, "healthy") + } +} diff --git a/internal/server/handle_pdf.go b/internal/server/handle_pdf.go index b72fcd5..8188b31 100644 --- a/internal/server/handle_pdf.go +++ b/internal/server/handle_pdf.go @@ -13,7 +13,7 @@ import ( "github.com/rjNemo/rentease/internal/pdf" ) -func handleCreateInvoicePdf(bs *booking.Service, ps *pdf.PdfService, hc *config.Host) echo.HandlerFunc { +func handlePdfCreateInvoice(bs *booking.Service, ps *pdf.PdfService, hc *config.Host) echo.HandlerFunc { return func(c echo.Context) error { idStr := c.Param("id") id, err := strconv.Atoi(idStr) @@ -31,7 +31,7 @@ func handleCreateInvoicePdf(bs *booking.Service, ps *pdf.PdfService, hc *config. } } -func handleCreateReportPdf(bs *booking.Service, ps *pdf.PdfService) echo.HandlerFunc { +func handlePdfCreateReport(bs *booking.Service, ps *pdf.PdfService) echo.HandlerFunc { return func(c echo.Context) error { period := c.QueryParam("period") if !u.Contains([]string{"month", "year"}, period) { diff --git a/internal/server/handle_reports.go b/internal/server/handle_reports.go index 72e75a2..b2cdfb6 100644 --- a/internal/server/handle_reports.go +++ b/internal/server/handle_reports.go @@ -33,7 +33,7 @@ func handleReportsPage() echo.HandlerFunc { } } -func handleComputeReport(bs *booking.Service, hc *config.Host) echo.HandlerFunc { +func handleReportCompute(bs *booking.Service, hc *config.Host) echo.HandlerFunc { return func(c echo.Context) error { period := c.FormValue("period") if !u.Contains([]string{"month", "year"}, period) { diff --git a/internal/server/routes.go b/internal/server/routes.go index a7b87f9..d1848da 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -10,6 +10,7 @@ import ( func (s Server) MountHandlers() { // public + s.Router.GET("/health", handleHealthCheck()) s.Router.GET("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux)) s.Router.GET("/", handleLoginPage()) s.Router.POST("/", handleLogin(s.as)) @@ -18,25 +19,25 @@ func (s Server) MountHandlers() { api.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{ KeyLookup: "header:api-key", Validator: func(key string, c echo.Context) (bool, error) { - return key == s.apiKey, nil + return s.as.ValidateApiKey(key), nil }, })) api.POST("/sync", handleSync(s.bs)) // admin g := s.Router.Group("") g.Use(MakeAuthMiddleware()) - g.GET("/bookings", handleListBookingPage(s.bs, s.hc)) - g.GET("/bookings/new", handleNewBookingPage(s.hc)) - g.POST("/bookings/new", handleCreateBooking(s.bs)) + g.GET("/bookings", handleBookingListPage(s.bs, s.hc)) + g.GET("/bookings/new", handleBookingCreatePage(s.hc)) + g.POST("/bookings/new", handleBookingCreate(s.bs)) g.GET("/bookings/:id", handleBookingPage(s.bs, s.hc)) - g.PUT("/bookings/:id", handleUpdateBooking(s.bs, s.hc)) - g.PATCH("/bookings/:id/cancel", handleCancelBooking(s.bs)) + g.PUT("/bookings/:id", handleBookingUpdate(s.bs, s.hc)) + g.PATCH("/bookings/:id/cancel", handleBookingCancel(s.bs)) g.POST("/bookings/:id/items", handleCreateItem(s.bs)) - g.POST("/items/:id", handlePayItem(s.bs)) - g.PUT("/items/:id", handleUpdateItem(s.bs)) + g.POST("/items/:id", handleItemPay(s.bs)) + g.PUT("/items/:id", handleItemUpdate(s.bs)) g.GET("/items/:id", handleLineItemForm(s.bs)) - g.GET("/bookings/pdf/:id", handleCreateInvoicePdf(s.bs, s.ps, s.hc)) + g.GET("/bookings/pdf/:id", handlePdfCreateInvoice(s.bs, s.ps, s.hc)) g.GET("/reports", handleReportsPage()) - g.GET("/reports/do", handleComputeReport(s.bs, s.hc)) - g.GET("/reports/pdf", handleCreateReportPdf(s.bs, s.ps)) + g.GET("/reports/do", handleReportCompute(s.bs, s.hc)) + g.GET("/reports/pdf", handlePdfCreateReport(s.bs, s.ps)) } diff --git a/internal/server/server.go b/internal/server/server.go index a883933..9de24cd 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -25,15 +25,13 @@ import ( ) type Server struct { - Router *echo.Echo - bs *booking.Service - as *auth.Service - ps *pdf.PdfService - cs *calendar.Service - hc *config.Host - addr string - secretKey string - apiKey string + Router *echo.Echo + bs *booking.Service + as *auth.Service + ps *pdf.PdfService + cs *calendar.Service + hc *config.Host + addr string } type options struct { @@ -41,7 +39,6 @@ type options struct { fs *embed.FS debug *bool secretKey *string - apiKey *string origins []string } @@ -85,13 +82,6 @@ func WithOrigins(origins []string) Option { } } -func WithApiKey(apiKey string) Option { - return func(o *options) error { - o.apiKey = &apiKey - return nil - } -} - func New(bs *booking.Service, as *auth.Service, ps *pdf.PdfService, cs *calendar.Service, hc *config.Host, opts ...Option) (*Server, error) { option := new(options) for _, opt := range opts { @@ -102,15 +92,13 @@ func New(bs *booking.Service, as *auth.Service, ps *pdf.PdfService, cs *calendar } s := &Server{ - Router: NewRouter(*option.fs, *option.debug, *option.secretKey, option.origins), - bs: bs, - as: as, - ps: ps, - cs: cs, - hc: hc, - addr: fmt.Sprintf("0.0.0.0:%d", *option.port), - secretKey: *option.secretKey, - apiKey: *option.apiKey, + Router: NewRouter(*option.fs, *option.debug, *option.secretKey, option.origins), + bs: bs, + as: as, + ps: ps, + cs: cs, + hc: hc, + addr: fmt.Sprintf("0.0.0.0:%d", *option.port), } s.MountHandlers() diff --git a/main.go b/main.go index 3204cf6..1261e85 100644 --- a/main.go +++ b/main.go @@ -40,25 +40,29 @@ func run(c context.Context, getEnv func(string) string) error { ctx, cancel := signal.NotifyContext(c, os.Interrupt) defer cancel() + // init sentry if err := sentry.Init(sentry.ClientOptions{ Dsn: getEnv("SENTRY_DSN"), EnableTracing: true, TracesSampleRate: 1.0, ProfilesSampleRate: 1.0, }); err != nil { - return fmt.Errorf("error initializing sentry: %s", err) + return fmt.Errorf("error initializing sentry %s", err) } + // init database db, err := gorm.Open(postgres.Open(getEnv("DATABASE_URL")), &gorm.Config{}) if err != nil { return fmt.Errorf("error connecting to the database %s", err) } + // build booking service err = db.AutoMigrate(&booking.Booking{}, &booking.BookingRequest{}, &booking.Item{}) if err != nil { return fmt.Errorf("error migrating the database %s", err) } + // build pdf service ps, err := pdf.NewPdfService( getEnv("HTMLDOCS_PROJECT_ID"), getEnv("HTMLDOCS_REPORT_PROJECT_ID"), @@ -69,23 +73,29 @@ func run(c context.Context, getEnv func(string) string) error { return fmt.Errorf("error starting pdf service %s", err) } - as, err := auth.NewService(getEnv("SESSION_SECRET"), getEnv("ADMIN"), getEnv("ADMIN_SECRET")) + // build authentication service + as, err := auth.NewService( + getEnv("SESSION_SECRET"), + getEnv("ADMIN"), + getEnv("ADMIN_SECRET"), + getEnv("API_KEY"), + ) if err != nil { return fmt.Errorf("error starting auth service %s", err) } - creds := os.Getenv("CALENDAR_CREDENTIALS") - t2Id := os.Getenv("CALENDAR_ID_T2") - t3Id := os.Getenv("CALENDAR_ID_T3") - - cs, err := calendar.NewService(ctx, creds, - calendar.WithCalendar("T2", t2Id), - calendar.WithCalendar("T3", t3Id), + // build calendar service + cs, err := calendar.NewService( + ctx, + getEnv("CALENDAR_CREDENTIALS"), + calendar.WithCalendar("T2", getEnv("CALENDAR_ID_T2")), + calendar.WithCalendar("T3", getEnv("CALENDAR_ID_T3")), ) if err != nil { - log.Fatalf("cannot build calendar service: %s", err) + log.Fatalf("error starting calendar service %s", err) } + // starting server p := getEnv("PORT") port, err := strconv.Atoi(p) if err != nil { @@ -96,16 +106,15 @@ func run(c context.Context, getEnv func(string) string) error { origins := strings.Split(ogs, ",") srv, err := server.New( - booking.NewService(db), + booking.NewService(db), // TODO: should validate the booking service building as, ps, cs, - config.NewHost(), + config.NewHost(), // TODO: move to the database at some point server.WithPort(port), server.WithFileSystem(static), server.WithDebug(strings.ToLower(getEnv("DEBUG")) == "true"), server.WithSecretKey(getEnv("SECRET_KEY")), - server.WithApiKey(getEnv("API_KEY")), server.WithOrigins(origins), ) if err != nil {