Refactor service (#14)

* add comments to main file

* create health check handler

health check

* use naming convention for booking handler

* use naming convention for hamndlers

naming convention

* clean up
This commit is contained in:
Ruidy 2024-08-07 09:12:50 +02:00 committed by GitHub
parent e9fcb8dd49
commit ac10e65097
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 81 additions and 64 deletions

View file

@ -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
}

View file

@ -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()

View file

@ -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)

View file

@ -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")
}
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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))
}

View file

@ -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()

35
main.go
View file

@ -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 {