This is more or less the same as the campsites, as public information goes, but for buildings and other amenities that the camping provides that are not campsites.
290 lines
8.1 KiB
Go
290 lines
8.1 KiB
Go
/*
|
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
|
* 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)
|
|
}
|