camper/pkg/amenity/admin.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)
}