mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
This commit standardizes the naming of identifier and API key fields across the codebase to use consistent camel case (e.g., `ID`, `APIKey`, `DatabaseURL`). This includes updates to struct fields, method names, function parameters, and environment variable references. The changes improve code clarity and maintainability by reducing ambiguity and aligning with Go naming conventions. No functional behavior is changed.
406 lines
12 KiB
Go
406 lines
12 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/a-h/templ"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/labstack/gommon/log"
|
|
u "github.com/rjNemo/underscore"
|
|
|
|
"github.com/rjNemo/rentease/internal/config"
|
|
"github.com/rjNemo/rentease/internal/constant"
|
|
"github.com/rjNemo/rentease/internal/service/booking"
|
|
"github.com/rjNemo/rentease/internal/view"
|
|
myTime "github.com/rjNemo/rentease/pkg/time"
|
|
)
|
|
|
|
func handleBookingListPage(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
search := c.FormValue("search")
|
|
|
|
var bookings []*booking.Line
|
|
if search != "" {
|
|
bookings = bs.Search(search)
|
|
} else {
|
|
bookings = bs.All()
|
|
}
|
|
|
|
slog.Info("serving bookings", slog.Int("bookings_length", len(bookings)))
|
|
|
|
bvm := u.Map(bookings, func(b *booking.Line) *view.ListBookingsViewModel {
|
|
return &view.ListBookingsViewModel{
|
|
Id: b.InvoiceNumber(hc),
|
|
Url: templ.SafeURL(fmt.Sprintf("%s/%d", constant.RouteBooking, b.ID)),
|
|
From: b.From.Format(time.DateOnly),
|
|
To: b.To.Format(time.DateOnly),
|
|
Platform: b.Platform,
|
|
Name: b.CustomerName,
|
|
Total: strconv.FormatFloat(b.Total, 'f', 2, 64),
|
|
Canceled: b.Canceled,
|
|
}
|
|
})
|
|
|
|
if hxRequest(c) && !hxBoosted(c) {
|
|
return renderTempl(c, http.StatusOK, view.BookingLines(bvm))
|
|
} else {
|
|
return renderTempl(c, http.StatusOK, view.ListBookings(bvm))
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleBookingList(bs *booking.Service) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
search := c.FormValue("search")
|
|
|
|
var bookings []*booking.Line
|
|
if search != "" {
|
|
bookings = bs.Search(search)
|
|
} else {
|
|
bookings = bs.All()
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, bookings)
|
|
}
|
|
}
|
|
|
|
func handleBookingCreatePage(hc *config.Host) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
return renderTempl(c, http.StatusOK, view.NewBooking(u.Map(hc.Platforms, func(p config.Platform) string {
|
|
return string(p)
|
|
})))
|
|
}
|
|
}
|
|
|
|
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"`
|
|
CustomerNumber int `form:"customer_number"`
|
|
PlatformFees float64 `form:"platform_fees"`
|
|
}
|
|
nb := new(NewBooking)
|
|
err := c.Bind(nb)
|
|
if err != nil {
|
|
log.Warn(err)
|
|
return err
|
|
}
|
|
|
|
ts, _ := myTime.ParseFromForm(c.FormValue("from"))
|
|
nb.From = ts
|
|
ts, _ = myTime.ParseFromForm(c.FormValue("to"))
|
|
nb.To = ts
|
|
|
|
if *nb.ExternalId == "" {
|
|
nb.ExternalId = nil
|
|
}
|
|
b := bs.Create(nb.From, nb.To, nb.Name, nb.PhoneNumber, nb.Email, nb.Platform, nb.CustomerNumber, nb.PlatformFees, nb.ExternalId)
|
|
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s/%d", constant.RouteBooking, b.ID))
|
|
}
|
|
}
|
|
|
|
func handleBookingPage(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b := bs.One(id)
|
|
|
|
var eid string
|
|
if b.ExternalID == nil {
|
|
eid = ""
|
|
} else {
|
|
eid = *b.ExternalID
|
|
}
|
|
bvm := &view.BookingViewModel{
|
|
Id: b.InvoiceNumber(hc),
|
|
Name: b.Name,
|
|
PhoneNumber: b.PhoneNumber,
|
|
CustomerNumber: strconv.Itoa(b.CustomerNumber),
|
|
Email: b.Email,
|
|
From: b.From.Format(time.DateOnly),
|
|
To: b.To.Format(time.DateOnly),
|
|
Platform: string(b.Platform),
|
|
ExternalId: eid,
|
|
Canceled: b.Canceled,
|
|
PlatformFees: strconv.FormatFloat(b.PlatformFees, 'f', 2, 64),
|
|
Url: templ.EscapeString(fmt.Sprintf("%s/%d", constant.RouteBooking, b.ID)),
|
|
PdfUrl: templ.SafeURL(fmt.Sprintf("%s/pdf/%d", constant.RouteBooking, b.ID)),
|
|
CancelUrl: fmt.Sprintf("%s/%d/cancel", constant.RouteBooking, b.ID),
|
|
PaymentUrl: fmt.Sprintf("%s/%d", constant.RoutePayment, b.ID),
|
|
Items: view.ItemListViewModel{
|
|
Items: u.Map(b.Items, func(i booking.Item) view.ItemViewModel {
|
|
return view.ItemViewModel{
|
|
Id: strconv.Itoa(i.ID),
|
|
Item: i.Item,
|
|
Quantity: strconv.Itoa(i.Quantity),
|
|
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
|
SubTotal: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
|
PaymentStatus: i.PaymentStatus,
|
|
ItemUrl: fmt.Sprintf("%s/%d", constant.RouteItem, i.ID),
|
|
}
|
|
}),
|
|
Payments: u.Map(b.Payments, func(p booking.Payment) view.PaymentViewModel {
|
|
return view.PaymentViewModel{
|
|
Amount: strconv.FormatFloat(p.Amount, 'f', 2, 64),
|
|
PaymentMethod: string(p.PaymentMethod),
|
|
PaymentUrl: fmt.Sprintf("%s/%d", constant.RoutePayment, p.ID),
|
|
}
|
|
}),
|
|
},
|
|
Total: strconv.FormatFloat(u.Reduce(b.Items, func(i booking.Item, sum float64) float64 {
|
|
return sum + i.Price*float64(i.Quantity)
|
|
}, 0.0), 'f', 2, 64),
|
|
Platforms: u.Map(hc.Platforms, func(p config.Platform) string { return string(p) }),
|
|
ItemList: u.OrderBy(func(items map[string]config.HostItem) (out []string) { // TODO: return the full item to prefill the form
|
|
for _, item := range items {
|
|
out = append(out, item.Name)
|
|
}
|
|
return out
|
|
}(hc.Items),
|
|
func(l, r string) bool { return l > r },
|
|
),
|
|
PaymentMethods: hc.PaymentMethods,
|
|
}
|
|
return renderTempl(c, http.StatusOK, view.BookingById(bvm))
|
|
}
|
|
}
|
|
|
|
func handleBookingUpdate(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
type UpdateBooking 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"`
|
|
Id int `param:"id"`
|
|
CustomerNumber int `form:"customer_number"`
|
|
PlatformFees float64 `form:"platform_fees"`
|
|
}
|
|
nb := new(UpdateBooking)
|
|
err := c.Bind(nb)
|
|
if err != nil {
|
|
log.Warn(err)
|
|
return err
|
|
}
|
|
|
|
ts, _ := myTime.ParseFromForm(c.FormValue("from"))
|
|
nb.From = ts
|
|
ts, _ = myTime.ParseFromForm(c.FormValue("to"))
|
|
nb.To = ts
|
|
|
|
if *nb.ExternalId == "" {
|
|
nb.ExternalId = nil
|
|
}
|
|
|
|
b := bs.Update(nb.Id, nb.From, nb.To, nb.Name, nb.PhoneNumber, nb.Email, nb.Platform, nb.CustomerNumber, nb.PlatformFees, nb.ExternalId)
|
|
|
|
form := view.BookingForm(view.BookingViewModel{
|
|
Id: b.InvoiceNumber(hc),
|
|
Name: b.Name,
|
|
PhoneNumber: b.PhoneNumber,
|
|
CustomerNumber: strconv.Itoa(b.CustomerNumber),
|
|
Email: b.Email,
|
|
From: b.From.Format(time.DateOnly),
|
|
To: b.To.Format(time.DateOnly),
|
|
Canceled: b.Canceled,
|
|
Platform: string(b.Platform),
|
|
ExternalId: *b.ExternalID,
|
|
Platforms: u.Map(hc.Platforms, func(p config.Platform) string { return string(p) }),
|
|
PlatformFees: strconv.FormatFloat(b.PlatformFees, 'f', 2, 64),
|
|
PaymentMethods: hc.PaymentMethods,
|
|
Url: templ.EscapeString(fmt.Sprintf("%s/%d", constant.RouteBooking, b.ID)),
|
|
PdfUrl: templ.SafeURL(fmt.Sprintf("%s/pdf/%d", constant.RouteBooking, b.ID)),
|
|
})
|
|
|
|
return renderTempl(c, http.StatusOK, form)
|
|
}
|
|
}
|
|
|
|
func handleLineItemForm(bs *booking.Service) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
i := bs.OneItem(id)
|
|
form := view.LineItemForm(&view.ItemViewModel{
|
|
Id: strconv.Itoa(i.ID),
|
|
Item: i.Item,
|
|
Quantity: strconv.Itoa(i.Quantity),
|
|
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
|
PaymentStatus: i.PaymentStatus,
|
|
SubTotal: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
|
ItemUrl: fmt.Sprintf("%s/%d", constant.RouteItem, i.ID),
|
|
})
|
|
return renderTempl(c, http.StatusOK, form)
|
|
}
|
|
}
|
|
|
|
func handleCreateItem(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
|
|
type NewItem struct {
|
|
Item string `form:"item"`
|
|
PaymentMethod string `form:"method"`
|
|
Quantity int `form:"quantity"`
|
|
Price float64 `form:"price"`
|
|
}
|
|
|
|
return func(c echo.Context) error {
|
|
bookingIdStr := c.Param("id")
|
|
bid, err := strconv.Atoi(bookingIdStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b := bs.One(bid)
|
|
|
|
ni := new(NewItem)
|
|
if err := c.Bind(ni); err != nil {
|
|
log.Warn(err)
|
|
return err
|
|
}
|
|
|
|
itm, ok := hc.Items[ni.Item]
|
|
if !ok {
|
|
return fmt.Errorf("invalid item name %q", ni.Item)
|
|
}
|
|
|
|
newItems := bs.CreateItem(b.ID, itm, ni.Quantity, ni.Price, ni.PaymentMethod, b.CustomerNumber, string(b.Platform))
|
|
|
|
// TODO: fix the calendar integration
|
|
// if err = cs.Create(
|
|
// itm.CalendarId,
|
|
// b.Name,
|
|
// fmt.Sprintf("Reservation: %s\n %d voyageur(s)\n", b.Name, b.CustomerNumber),
|
|
// b.From, b.To,
|
|
// ); err != nil {
|
|
// log.Warnf("could not create event: %s", err)
|
|
// captureError(c, err)
|
|
// }
|
|
|
|
for _, i := range newItems {
|
|
_ = renderTempl(c, http.StatusCreated, view.LineItem(&view.ItemViewModel{
|
|
Id: strconv.Itoa(i.ID),
|
|
Item: i.Item,
|
|
Quantity: strconv.Itoa(i.Quantity),
|
|
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
|
PaymentStatus: i.PaymentStatus,
|
|
SubTotal: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
|
ItemUrl: fmt.Sprintf("%s/%d", constant.RouteItem, i.ID),
|
|
}))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func handleItemPay(bs *booking.Service) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
itemIdStr := c.Param("id")
|
|
itemId, err := strconv.Atoi(itemIdStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
i := bs.PayItem(itemId)
|
|
return renderTempl(c, http.StatusOK, view.LineItem(&view.ItemViewModel{
|
|
Id: itemIdStr,
|
|
Item: i.Item,
|
|
Quantity: strconv.Itoa(i.Quantity),
|
|
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
|
PaymentStatus: i.PaymentStatus,
|
|
SubTotal: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
|
ItemUrl: fmt.Sprintf("%s/%d", constant.RouteItem, i.ID),
|
|
}))
|
|
}
|
|
}
|
|
|
|
func handleItemUpdate(bs *booking.Service) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
type updateItem struct {
|
|
Item string `form:"item"`
|
|
PaymentMethod string `form:"paymentMethod"`
|
|
PaymentStatus string `form:"paymentStatus"`
|
|
Id int `param:"id"`
|
|
Quantity int `form:"quantity"`
|
|
Price float64 `form:"price"`
|
|
}
|
|
ui := new(updateItem)
|
|
|
|
if err := c.Bind(ui); err != nil {
|
|
log.Warn(err)
|
|
return err
|
|
}
|
|
|
|
i := bs.UpdateItem(ui.Id, ui.Item, ui.Quantity, ui.Price, ui.PaymentMethod, ui.PaymentStatus)
|
|
|
|
return renderTempl(c, http.StatusCreated, view.LineItem(&view.ItemViewModel{
|
|
Id: strconv.Itoa(ui.Id),
|
|
Item: i.Item,
|
|
Quantity: strconv.Itoa(i.Quantity),
|
|
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
|
PaymentStatus: i.PaymentStatus,
|
|
SubTotal: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
|
ItemUrl: fmt.Sprintf("%s/%d", constant.RouteItem, i.ID),
|
|
}))
|
|
}
|
|
}
|
|
|
|
func handlePaymentUpdate(bs *booking.Service) echo.HandlerFunc {
|
|
type updatePayment struct {
|
|
Id int `param:"id"`
|
|
Amount float64 `form:"amount"`
|
|
PaymentMethod string `form:"paymentMethod"`
|
|
}
|
|
return func(c echo.Context) error {
|
|
up := new(updatePayment)
|
|
|
|
if err := c.Bind(up); err != nil {
|
|
return fmt.Errorf("could not parse update payment request body: %w", err)
|
|
}
|
|
|
|
p := bs.UpdatePayment(up.Id, up.Amount, up.PaymentMethod)
|
|
|
|
return renderTempl(c, http.StatusOK, view.PaymentLine(&view.PaymentViewModel{
|
|
Amount: strconv.FormatFloat(p.Amount, 'f', 2, 64),
|
|
PaymentMethod: string(p.PaymentMethod),
|
|
PaymentUrl: fmt.Sprintf("%s/%d", constant.RoutePayment, p.ID),
|
|
}))
|
|
}
|
|
}
|
|
|
|
func handleBookingCancel(bs *booking.Service) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bs.Cancel(id)
|
|
|
|
return renderTempl(c, http.StatusOK, templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
|
|
_, err := io.WriteString(w, " <span>Canceled</span>")
|
|
return err
|
|
}))
|
|
}
|
|
}
|