/* * SPDX-FileCopyrightText: 2023 jordi fita mas * SPDX-License-Identifier: AGPL-3.0-only */ package booking import ( "context" "golang.org/x/text/language" "net/http" "strings" "time" "dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/database" "dev.tandem.ws/tandem/camper/pkg/form" httplib "dev.tandem.ws/tandem/camper/pkg/http" "dev.tandem.ws/tandem/camper/pkg/locale" "dev.tandem.ws/tandem/camper/pkg/template" ) type AdminHandler struct { } func NewAdminHandler() *AdminHandler { return &AdminHandler{} } func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var head string head, r.URL.Path = httplib.ShiftPath(r.URL.Path) switch head { case "": switch r.Method { case http.MethodGet: serveBookingIndex(w, r, user, company, conn) default: httplib.MethodNotAllowed(w, r, http.MethodGet) } case "payment": head, r.URL.Path = httplib.ShiftPath(r.URL.Path) switch head { case "": switch r.Method { case http.MethodGet: f := newPaymentForm(user.Locale) if err := f.FillFromDatabase(r.Context(), company, conn); err != nil { if !database.ErrorIsNotFound(err) { panic(err) } } f.MustRender(w, r, user, company) case http.MethodPut: updatePaymentSettings(w, r, user, company, conn) default: httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut) } default: http.NotFound(w, r) } default: http.NotFound(w, r) } }) } func serveBookingIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { bookings, err := collectBookingEntries(r.Context(), conn, user.Locale.Language) if err != nil { panic(err) } page := bookingIndex(bookings) page.MustRender(w, r, user, company) } func collectBookingEntries(ctx context.Context, conn *database.Conn, lang language.Tag) ([]*bookingEntry, error) { rows, err := conn.Query(ctx, ` select left(slug::text, 10) , '/admin/booking/' || slug , arrival_date , departure_date , holder_name , booking.booking_status , coalesce(i18n.name, status.name) from booking join booking_status as status using (booking_status) left join booking_status_i18n as i18n on status.booking_status = i18n.booking_status and i18n.lang_tag = $1 order by arrival_date desc `, lang) if err != nil { return nil, err } defer rows.Close() var entries []*bookingEntry for rows.Next() { entry := &bookingEntry{} if err = rows.Scan(&entry.Reference, &entry.URL, &entry.ArrivalDate, &entry.DepartureDate, &entry.HolderName, &entry.Status, &entry.StatusLabel); err != nil { return nil, err } entries = append(entries, entry) } return entries, nil } type bookingEntry struct { Reference string URL string ArrivalDate time.Time DepartureDate time.Time HolderName string Status string StatusLabel string } type bookingIndex []*bookingEntry func (page bookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { switch r.URL.Query().Get("format") { case "ods": columns := []string{ "Reference", "Arrival Date", "Departure Date", "Holder Name", "Status", } ods, err := writeTableOds(page, columns, user.Locale, func(sb *strings.Builder, entry *bookingEntry) error { if err := writeCellString(sb, entry.Reference); err != nil { return err } writeCellDate(sb, entry.ArrivalDate) writeCellDate(sb, entry.DepartureDate) if err := writeCellString(sb, entry.HolderName); err != nil { return err } if err := writeCellString(sb, entry.StatusLabel); err != nil { return err } return nil }) if err != nil { panic(err) } mustWriteOdsResponse(w, ods, user.Locale.Pgettext("bookings.ods", "filename")) default: template.MustRenderAdmin(w, r, user, company, "booking/index.gohtml", page) } } type paymentForm struct { MerchantCode *form.Input TerminalNumber *form.Input Environment *form.Select Integration *form.Select EncryptKey *form.Input } func newPaymentForm(l *locale.Locale) *paymentForm { return &paymentForm{ MerchantCode: &form.Input{ Name: "merchant_code", }, TerminalNumber: &form.Input{ Name: "terminal_number", }, Environment: &form.Select{ Name: "environment", Options: []*form.Option{ { Value: "test", Label: l.Pgettext("Test", "redsys environment"), }, { Value: "live", Label: l.Pgettext("Live", "redsys environment"), }, }, }, Integration: &form.Select{ Name: "integration", Options: []*form.Option{ { Value: "insite", Label: l.Pgettext("InSite", "redsys integration"), }, { Value: "redirect", Label: l.Pgettext("Redirect", "redsys integration"), }, }, }, EncryptKey: &form.Input{ Name: "encrypt_key", }, } } func (f *paymentForm) FillFromDatabase(ctx context.Context, company *auth.Company, conn *database.Conn) error { return conn.QueryRow(ctx, ` select merchant_code , terminal_number::text , array[environment::text] , array[integration::text] from redsys where company_id = $1`, company.ID).Scan( &f.MerchantCode.Val, &f.TerminalNumber.Val, &f.Environment.Selected, &f.Integration.Selected, ) } func (f *paymentForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { template.MustRenderAdmin(w, r, user, company, "booking/payment.gohtml", f) } func (f *paymentForm) Parse(r *http.Request) error { if err := r.ParseForm(); err != nil { return err } f.MerchantCode.FillValue(r) f.TerminalNumber.FillValue(r) f.Environment.FillValue(r) f.Integration.FillValue(r) f.EncryptKey.FillValue(r) return nil } func (f *paymentForm) Valid(l *locale.Locale) bool { v := form.NewValidator(l) if v.CheckRequired(f.MerchantCode, l.GettextNoop("Merchant code can not be empty.")) { if v.CheckExactLength(f.MerchantCode, 9, l.GettextNoop("Merchant code must be exactly nine digits long.")) { v.CheckValidInteger(f.MerchantCode, l.GettextNoop("Merchant code must be a number.")) } } if v.CheckRequired(f.TerminalNumber, l.GettextNoop("Terminal number can not be empty.")) { message := l.GettextNoop("Terminal number must be a number between 1 and 999.") if v.CheckValidInteger(f.TerminalNumber, message) { if v.CheckMinInteger(f.TerminalNumber, 1, message) { v.CheckMaxInteger(f.TerminalNumber, 999, message) } } } v.CheckSelectedOptions(f.Environment, l.GettextNoop("Selected environment is not valid.")) v.CheckSelectedOptions(f.Integration, l.GettextNoop("Selected integration is not valid.")) if f.EncryptKey.Val != "" { v.CheckValidBase64(f.EncryptKey, l.GettextNoop("The merchant key is not valid.")) } return v.AllOK } func updatePaymentSettings(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { f := newPaymentForm(user.Locale) if ok, err := form.Handle(f, w, r, user); err != nil { return } else if !ok { f.MustRender(w, r, user, company) return } if err := conn.SetupRedsys(r.Context(), company.ID, f.MerchantCode.Val, f.TerminalNumber.Int(), f.Environment.Selected[0], f.Integration.Selected[0], f.EncryptKey.Val); err != nil { panic(err) } httplib.Redirect(w, r, "/admin/booking/payment", http.StatusSeeOther) }