/* * SPDX-FileCopyrightText: 2023 jordi fita mas * SPDX-License-Identifier: AGPL-3.0-only */ package amenity import ( "context" "net/http" "github.com/jackc/pgx/v4" "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.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var head string head, r.URL.Path = httplib.ShiftPath(r.URL.Path) switch head { case "new": switch r.Method { case http.MethodGet: f := newAmenityForm(company) f.MustRender(w, r, user, company) default: httplib.MethodNotAllowed(w, r, http.MethodGet) } case "": switch r.Method { case http.MethodGet: serveAmenityIndex(w, r, user, company, conn) case http.MethodPost: addAmenity(w, r, user, company, conn) default: httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost) } default: f := newAmenityForm(company) if err := f.FillFromDatabase(r.Context(), conn, company, head); err != nil { if database.ErrorIsNotFound(err) { http.NotFound(w, r) return } panic(err) } head, r.URL.Path = httplib.ShiftPath(r.URL.Path) switch head { case "": switch r.Method { case http.MethodGet: f.MustRender(w, r, user, company) case http.MethodPut: editAmenity(w, r, user, company, conn, f) case http.MethodDelete: deleteAmenity(w, r, user, conn, f.ID) default: httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut, http.MethodDelete) } case "slides": h.carouselHandler(user, company, conn, f.Label.Val).ServeHTTP(w, r) case "features": h.featuresHandler(user, company, conn, f.Label.Val).ServeHTTP(w, r) default: http.NotFound(w, r) } } } } func serveAmenityIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { amenities, err := collectAmenityEntries(r.Context(), company, conn) if err != nil { panic(err) } page := &amenityIndex{ Amenities: amenities, } page.MustRender(w, r, user, company) } func collectAmenityEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*amenityEntry, error) { rows, err := conn.Query(ctx, ` select label , name , active from amenity where company_id = $1 order by label`, company.ID) if err != nil { return nil, err } defer rows.Close() var amenities []*amenityEntry for rows.Next() { entry := &amenityEntry{} if err = rows.Scan(&entry.Label, &entry.Name, &entry.Active); err != nil { return nil, err } amenities = append(amenities, entry) } return amenities, nil } type amenityEntry struct { Label string Name string Active bool } type amenityIndex struct { Amenities []*amenityEntry } func (page *amenityIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { template.MustRenderAdmin(w, r, user, company, "amenity/index.gohtml", page) } func addAmenity(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { f := newAmenityForm(company) processAmenityForm(w, r, user, company, conn, f, func(ctx context.Context, tx *database.Tx) error { var err error f.ID, err = tx.AddAmenity(ctx, company.ID, f.Label.Val, f.Name[f.DefaultLang].Val, f.Info1[f.DefaultLang].Val, f.Info2[f.DefaultLang].Val) if err != nil { return err } return translateAmenity(ctx, tx, company, f) }) httplib.Redirect(w, r, "/admin/amenities", http.StatusSeeOther) } func translateAmenity(ctx context.Context, tx *database.Tx, company *auth.Company, f *amenityForm) error { for lang := range company.Locales { l := lang.String() if l == f.DefaultLang { continue } if err := tx.TranslateAmenity(ctx, f.ID, lang, f.Name[l].Val, f.Info1[l].Val, f.Info2[l].Val); err != nil { return err } } return nil } func editAmenity(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *amenityForm) { processAmenityForm(w, r, user, company, conn, f, func(ctx context.Context, tx *database.Tx) error { if err := tx.EditAmenity(ctx, f.ID, f.Label.Val, f.Name[f.DefaultLang].Val, f.Info1[f.DefaultLang].Val, f.Info2[f.DefaultLang].Val, f.Active.Checked); err != nil { return err } return translateAmenity(ctx, tx, company, f) }) httplib.Redirect(w, r, "/admin/amenities", http.StatusSeeOther) } func processAmenityForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *amenityForm, act func(ctx context.Context, tx *database.Tx) error) { if ok, err := form.Handle(f, w, r, user); err != nil { return } else if !ok { f.MustRender(w, r, user, company) return } tx := conn.MustBegin(r.Context()) defer tx.Rollback(r.Context()) if err := act(r.Context(), tx); err != nil { panic(err) } tx.MustCommit(r.Context()) httplib.Redirect(w, r, "/admin/amenities", http.StatusSeeOther) } func deleteAmenity(w http.ResponseWriter, r *http.Request, user *auth.User, conn *database.Conn, id int) { if err := user.VerifyCSRFToken(r); err != nil { http.Error(w, err.Error(), http.StatusForbidden) return } if err := conn.RemoveAmenity(r.Context(), id); err != nil { panic(err) } httplib.Redirect(w, r, "/admin/amenities/", http.StatusSeeOther) } type amenityForm struct { DefaultLang string ID int CurrentLabel string Active *form.Checkbox Label *form.Input Name form.I18nInput Info1 form.I18nInput Info2 form.I18nInput } func newAmenityForm(company *auth.Company) *amenityForm { return &amenityForm{ DefaultLang: company.DefaultLanguage.String(), Active: &form.Checkbox{ Name: "active", Checked: true, }, Label: &form.Input{ Name: "label", }, Name: form.NewI18nInput(company.Locales, "name"), Info1: form.NewI18nInput(company.Locales, "info1"), Info2: form.NewI18nInput(company.Locales, "info2"), } } func (f *amenityForm) FillFromDatabase(ctx context.Context, conn *database.Conn, company *auth.Company, label string) error { f.CurrentLabel = label var name database.RecordArray var info1 database.RecordArray var info2 database.RecordArray row := conn.QueryRow(ctx, ` select amenity_id , label , amenity.name , amenity.info1::text , amenity.info2::text , active , array_agg((lang_tag, i18n.name)) , array_agg((lang_tag, i18n.info1::text)) , array_agg((lang_tag, i18n.info2::text)) from amenity left join amenity_i18n as i18n using (amenity_id) where company_id = $1 and label = $2 group by amenity_id , label , amenity.name , amenity.info1::text , amenity.info2::text , active `, pgx.QueryResultFormats{pgx.BinaryFormatCode}, company.ID, label) if err := row.Scan(&f.ID, &f.Label.Val, &f.Name[f.DefaultLang].Val, &f.Info1[f.DefaultLang].Val, &f.Info2[f.DefaultLang].Val, &f.Active.Checked, &name, &info1, &info2); err != nil { return err } if err := f.Name.FillArray(name); err != nil { return err } if err := f.Info1.FillArray(info1); err != nil { return err } if err := f.Info2.FillArray(info2); err != nil { return err } return nil } func (f *amenityForm) Parse(r *http.Request) error { if err := r.ParseForm(); err != nil { return err } f.Active.FillValue(r) f.Label.FillValue(r) f.Name.FillValue(r) f.Info1.FillValue(r) f.Info2.FillValue(r) return nil } func (f *amenityForm) Valid(l *locale.Locale) bool { v := form.NewValidator(l) v.CheckRequired(f.Label, l.GettextNoop("Label can not be empty.")) v.CheckRequired(f.Name[f.DefaultLang], l.GettextNoop("Name can not be empty.")) return v.AllOK } func (f *amenityForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { template.MustRenderAdmin(w, r, user, company, "amenity/form.gohtml", f) }