Add public page for campsite type, and function to edit them

Had to export and move PublicPage struct to template because i can not
import app from campsites/types: app already imports campsite for the
http handler, and it, in turn, imports the types package for its own
http handler; an import loop.

Also had to replace PublicPage.MustRender with a Setup function because
the page passed down to html/template was the PublicPage struct, not
whatever struct embeds it.  I was thinking more of Java inheritance here
rather than struct embedding.
This commit is contained in:
jordi fita mas 2023-08-08 02:29:14 +02:00
parent 866af09b50
commit d117ce5027
18 changed files with 490 additions and 88 deletions

View File

@ -0,0 +1,25 @@
-- Deploy camper:edit_campsite_type to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type
-- requires: company
begin;
set search_path to camper, public;
create or replace function edit_campsite_type(slug uuid, name text, description text) returns uuid as
$$
update campsite_type
set name = edit_campsite_type.name
, description = xmlparse(content edit_campsite_type.description)
where slug = edit_campsite_type.slug
returning slug;
$$
language sql
;
revoke execute on function edit_campsite_type(uuid, text, text) from public;
grant execute on function edit_campsite_type(uuid, text, text) to admin;
commit;

View File

@ -16,12 +16,12 @@ import (
)
type adminHandler struct {
campsite *campsite.Handler
campsite *campsite.AdminHandler
}
func newAdminHandler() *adminHandler {
return &adminHandler{
campsite: campsite.NewHandler(),
campsite: campsite.NewAdminHandler(),
}
}

View File

@ -6,19 +6,23 @@
package app
import (
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/campsite"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/template"
"fmt"
"net/http"
"sort"
)
type publicHandler struct{}
type publicHandler struct {
campsite *campsite.PublicHandler
}
func newPublicHandler() *publicHandler {
return &publicHandler{}
return &publicHandler{
campsite: campsite.NewPublicHandler(),
}
}
func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
@ -28,7 +32,9 @@ func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *da
switch head {
case "":
page := newHomePage()
page.MustRender(w, r, user, company)
page.MustRender(w, r, user, company, conn)
case "campsites":
h.campsite.Handler(user, company, conn).ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
@ -36,41 +42,14 @@ func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *da
}
type homePage struct {
*PublicPage
*template.PublicPage
}
func newHomePage() *homePage {
return &homePage{newPublicPage("home.gohtml")}
return &homePage{template.NewPublicPage()}
}
type PublicPage struct {
template string
LocalizedAlternates []*LocalizedAlternate
}
func newPublicPage(template string) *PublicPage {
return &PublicPage{
template: template,
}
}
func (p *PublicPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
schema := httplib.Protocol(r)
authority := httplib.Host(r)
_, path := httplib.ShiftPath(r.RequestURI)
for _, l := range company.Locales {
p.LocalizedAlternates = append(p.LocalizedAlternates, &LocalizedAlternate{
Lang: l.Language.String(),
Endonym: l.Endonym,
HRef: fmt.Sprintf("%s://%s/%s%s", schema, authority, l.Language, path),
})
}
sort.Slice(p.LocalizedAlternates, func(i, j int) bool { return p.LocalizedAlternates[i].Lang < p.LocalizedAlternates[j].Lang })
template.MustRenderPublic(w, r, user, company, p.template, p)
}
type LocalizedAlternate struct {
Lang string
HRef string
Endonym string
func (p *homePage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublic(w, r, user, company, "home.gohtml", p)
}

View File

@ -9,21 +9,22 @@ import (
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/campsite/types"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
)
type Handler struct {
types *typeHandler
type AdminHandler struct {
types *types.AdminHandler
}
func NewHandler() *Handler {
return &Handler{
types: &typeHandler{},
func NewAdminHandler() *AdminHandler {
return &AdminHandler{
types: &types.AdminHandler{},
}
}
func (h *Handler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.HandlerFunc {
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)

39
pkg/campsite/public.go Normal file
View File

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package campsite
import (
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/campsite/types"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
)
type PublicHandler struct {
types *types.PublicHandler
}
func NewPublicHandler() *PublicHandler {
return &PublicHandler{
types: &types.PublicHandler{},
}
}
func (h *PublicHandler) 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 "types":
h.types.Handler(user, company, conn).ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package campsite
package types
import (
"context"
@ -15,12 +15,13 @@ import (
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
"dev.tandem.ws/tandem/camper/pkg/template"
"dev.tandem.ws/tandem/camper/pkg/uuid"
)
type typeHandler struct {
type AdminHandler struct {
}
func (h *typeHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
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)
@ -44,7 +45,26 @@ func (h *typeHandler) Handler(user *auth.User, company *auth.Company, conn *data
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
}
default:
http.NotFound(w, r)
if !uuid.Valid(head) {
http.NotFound(w, r)
return
}
f := newTypeForm()
if err := f.FillFromDatabase(r.Context(), conn, head); err != nil {
if database.ErrorIsNotFound(err) {
http.NotFound(w, r)
return
}
panic(err)
}
switch r.Method {
case http.MethodGet:
f.MustRender(w, r, user, company)
case http.MethodPut:
editType(w, r, user, company, conn, f)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
}
}
})
}
@ -110,10 +130,31 @@ func addType(w http.ResponseWriter, r *http.Request, user *auth.User, company *a
return
}
conn.MustExec(r.Context(), "select add_campsite_type($1, $2, $3)", company.ID, f.Name, f.Description)
httplib.Redirect(w, r, "/campsites/types", http.StatusSeeOther)
httplib.Redirect(w, r, "/admin/campsites/types", http.StatusSeeOther)
}
func editType(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *typeForm) {
if err := f.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := user.VerifyCSRFToken(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if !f.Valid(user.Locale) {
if !httplib.IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
f.MustRender(w, r, user, company)
return
}
conn.MustExec(r.Context(), "select edit_campsite_type($1, $2, $3)", f.Slug, f.Name, f.Description)
httplib.Redirect(w, r, "/admin/campsites/types", http.StatusSeeOther)
}
type typeForm struct {
Slug string
Name *form.Input
Description *form.Input
}
@ -129,6 +170,12 @@ func newTypeForm() *typeForm {
}
}
func (f *typeForm) FillFromDatabase(ctx context.Context, conn *database.Conn, slug string) error {
f.Slug = slug
row := conn.QueryRow(ctx, "select name, description from campsite_type where slug = $1", slug)
return row.Scan(&f.Name.Val, &f.Description.Val)
}
func (f *typeForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err

View File

@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package types
import (
"context"
gotemplate "html/template"
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/template"
"dev.tandem.ws/tandem/camper/pkg/uuid"
)
type PublicHandler struct {
}
func (h *PublicHandler) 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 r.Method {
case http.MethodGet:
if !uuid.Valid(head) {
http.NotFound(w, r)
return
}
page, err := newPublicPage(r.Context(), conn, head)
if database.ErrorIsNotFound(err) {
http.NotFound(w, r)
} else if err != nil {
panic(err)
}
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
})
}
type publicPage struct {
*template.PublicPage
Name string
Description gotemplate.HTML
}
func newPublicPage(ctx context.Context, conn *database.Conn, slug string) (*publicPage, error) {
page := &publicPage{
PublicPage: template.NewPublicPage(),
}
row := conn.QueryRow(ctx, "select name, description::text from campsite_type where slug = $1", slug)
if err := row.Scan(&page.Name, &page.Description); err != nil {
return nil, err
}
return page, nil
}
func (p *publicPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublic(w, r, user, company, "campsite/type.gohtml", p)
}

85
pkg/template/page.go Normal file
View File

@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package template
import (
"context"
"fmt"
"net/http"
"sort"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
)
type PublicPage struct {
LocalizedAlternates []*LocalizedAlternate
Menu *siteMenu
}
func NewPublicPage() *PublicPage {
return &PublicPage{}
}
func (p *PublicPage) Setup(r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
schema := httplib.Protocol(r)
authority := httplib.Host(r)
_, path := httplib.ShiftPath(r.RequestURI)
for _, l := range company.Locales {
p.LocalizedAlternates = append(p.LocalizedAlternates, &LocalizedAlternate{
Lang: l.Language.String(),
Endonym: l.Endonym,
HRef: fmt.Sprintf("%s://%s/%s%s", schema, authority, l.Language, path),
})
}
sort.Slice(p.LocalizedAlternates, func(i, j int) bool { return p.LocalizedAlternates[i].Lang < p.LocalizedAlternates[j].Lang })
p.Menu = &siteMenu{
CampsiteTypes: mustCollectMenuItems(r.Context(), conn, user.Locale, "select name, '/campsites/types/' || slug from campsite_type where company_id = $1", company.ID),
}
}
type LocalizedAlternate struct {
Lang string
HRef string
Endonym string
}
type siteMenu struct {
CampsiteTypes []*menuItem
}
type menuItem struct {
Label string
HRef string
}
func mustCollectMenuItems(ctx context.Context, conn *database.Conn, loc *locale.Locale, sql string, args ...interface{}) []*menuItem {
rows, err := conn.Query(ctx, sql, args...)
if err != nil {
panic(err)
}
defer rows.Close()
localePath := "/" + loc.Language.String()
var items []*menuItem
for rows.Next() {
item := &menuItem{}
err = rows.Scan(&item.Label, &item.HRef)
if err != nil {
panic(err)
}
item.HRef = localePath + item.HRef
items = append(items, item)
}
if rows.Err() != nil {
panic(rows.Err())
}
return items
}

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-08-05 03:23+0200\n"
"POT-Creation-Date: 2023-08-08 02:43+0200\n"
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -23,32 +23,47 @@ msgctxt "title"
msgid "Home"
msgstr "Inici"
#: web/templates/public/layout.gohtml:10 web/templates/public/layout.gohtml:17
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:21
msgid "Campsite Montagut"
msgstr "Càmping Montagut"
#: web/templates/public/layout.gohtml:16 web/templates/admin/layout.gohtml:18
#: web/templates/public/layout.gohtml:20 web/templates/admin/layout.gohtml:18
msgid "Skip to main content"
msgstr "Salta al contingut principal"
#: web/templates/admin/campsite/type/new.gohtml:14
#: web/templates/admin/campsite/type/new.gohtml:20
#: web/templates/public/layout.gohtml:38
msgid "Singular Lodges"
msgstr "Allotjaments singulars"
#: web/templates/admin/campsite/type/new.gohtml:16
#: web/templates/admin/campsite/type/new.gohtml:33
msgctxt "title"
msgid "Edit Campsite Type"
msgstr "Edició del tipus dallotjament"
#: web/templates/admin/campsite/type/new.gohtml:18
#: web/templates/admin/campsite/type/new.gohtml:35
msgctxt "title"
msgid "New Campsite Type"
msgstr "Nou tipus dallotjament"
#: web/templates/admin/campsite/type/new.gohtml:25
#: web/templates/admin/campsite/type/new.gohtml:42
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/campsite/type/new.gohtml:33
#: web/templates/admin/campsite/type/new.gohtml:50
msgctxt "input"
msgid "Description"
msgstr "Descripció"
#: web/templates/admin/campsite/type/new.gohtml:40
#: web/templates/admin/campsite/type/new.gohtml:59
msgctxt "action"
msgid "Update"
msgstr "Actualitza"
#: web/templates/admin/campsite/type/new.gohtml:61
msgctxt "action"
msgid "Add"
msgstr "Afegeix"
@ -140,11 +155,11 @@ msgctxt "action"
msgid "Logout"
msgstr "Surt"
#: pkg/app/login.go:56 pkg/app/user.go:245
#: pkg/app/login.go:56 pkg/app/user.go:246
msgid "Email can not be empty."
msgstr "No podeu deixar el correu en blanc."
#: pkg/app/login.go:57 pkg/app/user.go:246
#: pkg/app/login.go:57 pkg/app/user.go:247
msgid "This email is not valid. It should be like name@domain.com."
msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com."
@ -156,24 +171,24 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/app/user.go:196
#: pkg/app/user.go:197
msgctxt "language option"
msgid "Automatic"
msgstr "Automàtic"
#: pkg/app/user.go:248 pkg/campsite/type.go:143
#: pkg/app/user.go:249 pkg/campsite/types/admin.go:190
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
#: pkg/app/user.go:249
#: pkg/app/user.go:250
msgid "Confirmation does not match password."
msgstr "La confirmació no es correspon amb la contrasenya."
#: pkg/app/user.go:250
#: pkg/app/user.go:251
msgid "Selected language is not valid."
msgstr "Lidioma escollit no és vàlid."
#: pkg/app/user.go:252
#: pkg/app/user.go:253
msgid "File must be a valid PNG or JPEG image."
msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-08-05 03:23+0200\n"
"POT-Creation-Date: 2023-08-08 02:43+0200\n"
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -23,32 +23,47 @@ msgctxt "title"
msgid "Home"
msgstr "Inicio"
#: web/templates/public/layout.gohtml:10 web/templates/public/layout.gohtml:17
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:21
msgid "Campsite Montagut"
msgstr "Camping Montagut"
#: web/templates/public/layout.gohtml:16 web/templates/admin/layout.gohtml:18
#: web/templates/public/layout.gohtml:20 web/templates/admin/layout.gohtml:18
msgid "Skip to main content"
msgstr "Saltar al contenido principal"
#: web/templates/admin/campsite/type/new.gohtml:14
#: web/templates/admin/campsite/type/new.gohtml:20
#: web/templates/public/layout.gohtml:38
msgid "Singular Lodges"
msgstr "Alojamientos singulares"
#: web/templates/admin/campsite/type/new.gohtml:16
#: web/templates/admin/campsite/type/new.gohtml:33
msgctxt "title"
msgid "Edit Campsite Type"
msgstr "Edición del tipo de alojamientos"
#: web/templates/admin/campsite/type/new.gohtml:18
#: web/templates/admin/campsite/type/new.gohtml:35
msgctxt "title"
msgid "New Campsite Type"
msgstr "Nuevo tipo de alojamiento"
#: web/templates/admin/campsite/type/new.gohtml:25
#: web/templates/admin/campsite/type/new.gohtml:42
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nombre"
#: web/templates/admin/campsite/type/new.gohtml:33
#: web/templates/admin/campsite/type/new.gohtml:50
msgctxt "input"
msgid "Description"
msgstr "Descripción"
#: web/templates/admin/campsite/type/new.gohtml:40
#: web/templates/admin/campsite/type/new.gohtml:59
msgctxt "action"
msgid "Update"
msgstr "Actualitzar"
#: web/templates/admin/campsite/type/new.gohtml:61
msgctxt "action"
msgid "Add"
msgstr "Añadir"
@ -140,11 +155,11 @@ msgctxt "action"
msgid "Logout"
msgstr "Salir"
#: pkg/app/login.go:56 pkg/app/user.go:245
#: pkg/app/login.go:56 pkg/app/user.go:246
msgid "Email can not be empty."
msgstr "No podéis dejar el correo-e en blanco."
#: pkg/app/login.go:57 pkg/app/user.go:246
#: pkg/app/login.go:57 pkg/app/user.go:247
msgid "This email is not valid. It should be like name@domain.com."
msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com."
@ -156,24 +171,24 @@ msgstr "No podéis dejar la contraseña en blanco."
msgid "Invalid user or password."
msgstr "Usuario o contraseña incorrectos."
#: pkg/app/user.go:196
#: pkg/app/user.go:197
msgctxt "language option"
msgid "Automatic"
msgstr "Automático"
#: pkg/app/user.go:248 pkg/campsite/type.go:143
#: pkg/app/user.go:249 pkg/campsite/types/admin.go:190
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
#: pkg/app/user.go:249
#: pkg/app/user.go:250
msgid "Confirmation does not match password."
msgstr "La confirmación no se corresponde con la contraseña."
#: pkg/app/user.go:250
#: pkg/app/user.go:251
msgid "Selected language is not valid."
msgstr "El idioma escogido no es válido."
#: pkg/app/user.go:252
#: pkg/app/user.go:253
msgid "File must be a valid PNG or JPEG image."
msgstr "El archivo tiene que ser una imagen PNG o JPEG válida."

View File

@ -0,0 +1,7 @@
-- Revert camper:edit_campsite_type from pg
begin;
drop function if exists camper.edit_campsite_type(uuid, text, text);
commit;

View File

@ -41,3 +41,4 @@ policies_company [company user_profile] 2023-08-07T20:04:26Z jordi fita mas <jor
change_password [roles schema_auth schema_camper user] 2023-07-21T23:54:52Z jordi fita mas <jordi@tandem.blog> # Add function to change the current users password
campsite_type [roles schema_camper company user_profile] 2023-07-31T11:20:29Z jordi fita mas <jordi@tandem.blog> # Add relation of campsite type
add_campsite_type [roles schema_camper campsite_type company] 2023-08-04T16:14:48Z jordi fita mas <jordi@tandem.blog> # Add function to create campsite types
edit_campsite_type [roles schema_camper campsite_type company] 2023-08-07T22:21:34Z jordi fita mas <jordi@tandem.blog> # Add function to edit campsite types

View File

@ -0,0 +1,59 @@
-- Test edit_campsite_type
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
set search_path to camper, public;
select plan(12);
select has_function('camper', 'edit_campsite_type', array ['uuid', 'text', 'text']);
select function_lang_is('camper', 'edit_campsite_type', array ['uuid', 'text', 'text'], 'sql');
select function_returns('camper', 'edit_campsite_type', array ['uuid', 'text', 'text'], 'uuid');
select isnt_definer('camper', 'edit_campsite_type', array ['uuid', 'text', 'text']);
select volatility_is('camper', 'edit_campsite_type', array ['uuid', 'text', 'text'], 'volatile');
select function_privs_are('camper', 'edit_campsite_type', array ['uuid', 'text', 'text'], 'guest', array[]::text[]);
select function_privs_are('camper', 'edit_campsite_type', array ['uuid', 'text', 'text'], 'employee', array[]::text[]);
select function_privs_are('camper', 'edit_campsite_type', array ['uuid', 'text', 'text'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'edit_campsite_type', array ['uuid', 'text', 'text'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate campsite_type cascade;
truncate company cascade;
reset client_min_messages;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, country_code, currency_code, default_lang_tag)
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca')
;
insert into campsite_type (company_id, slug, name, description)
values (1, '87452b88-b48f-48d3-bb6c-0296de64164e', 'Type A', '<p>A</p>')
, (1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 'Type B', '<p>B</p>')
;
select lives_ok(
$$ select edit_campsite_type('87452b88-b48f-48d3-bb6c-0296de64164e', 'Type 1', '<p>1</p>') $$,
'Should be ablo to edit the first type'
);
select lives_ok(
$$ select edit_campsite_type('9b6370f7-f941-46f2-bc6e-de455675bd0a', 'Type 2', '<p>2</p>') $$,
'Should be ablo to edit the second type'
);
select bag_eq(
$$ select slug::text, name, description::text from campsite_type $$,
$$ values ('87452b88-b48f-48d3-bb6c-0296de64164e', 'Type 1', '<p>1</p>')
, ('9b6370f7-f941-46f2-bc6e-de455675bd0a', 'Type 2', '<p>2</p>')
$$,
'Should have updated all campsite types.'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:edit_campsite_type on pg
begin;
select has_function_privilege('camper.edit_campsite_type(uuid, text, text)', 'execute');
rollback;

View File

@ -7,7 +7,7 @@
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.typeIndex*/ -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.typeIndex*/ -}}
<a href="/admin/campsites/types/new">{{( pgettext "Add Type" "action" )}}</a>
<h2>{{( pgettext "Campsite Types" "title" )}}</h2>
{{ if .Types -}}
@ -20,7 +20,7 @@
<tbody>
{{ range .Types -}}
<tr>
<td><a href="/admin/campsites/type/{{ .Slug }}">{{ .Name }}</a></td>
<td><a href="/admin/campsites/types/{{ .Slug }}">{{ .Name }}</a></td>
</tr>
{{- end }}
</tbody>

View File

@ -11,13 +11,30 @@
{{- end }}
{{ define "title" -}}
{{( pgettext "New Campsite Type" "title" )}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.typeForm*/ -}}
{{ if .Slug}}
{{( pgettext "Edit Campsite Type" "title" )}}
{{ else }}
{{( pgettext "New Campsite Type" "title" )}}
{{ end }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.typeForm*/ -}}
<form action="/admin/campsites/types" method="post">
<h2>{{( pgettext "New Campsite Type" "title" )}}</h2>
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.typeForm*/ -}}
<form
{{ if .Slug }}
data-hx-put="/admin/campsites/types/{{ .Slug }}"
{{ else }}
action="/admin/campsites/types" method="post"
{{ end }}
>
<h2>
{{ if .Slug }}
{{( pgettext "Edit Campsite Type" "title" )}}
{{ else }}
{{( pgettext "New Campsite Type" "title" )}}
{{ end }}
</h2>
{{ CSRFInput }}
<fieldset>
{{ with .Name -}}
@ -37,7 +54,13 @@
{{- end }}
</fieldset>
<footer>
<button type="submit">{{( pgettext "Add" "action" )}}</button>
<button type="submit">
{{ if .Slug }}
{{( pgettext "Update" "action" )}}
{{ else }}
{{( pgettext "Add" "action" )}}
{{ end }}
</button>
</footer>
</form>
{{- end }}

View File

@ -0,0 +1,14 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.publicPage*/ -}}
{{ .Name }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.publicPage*/ -}}
<h2>{{ .Name }}</h2>
{{ .Description }}
{{- end }}

View File

@ -29,6 +29,23 @@
</ul>
</nav>
{{- end }}
{{ with .Menu -}}
<nav>
<ul>
{{ if .CampsiteTypes -}}
<li>
<button type="button" aria-expanded="false" aria-controls="campsite-types-menu"
>{{( gettext "Singular Lodges" )}}</button>
<ul id="campsite-types-menu">
{{ range .CampsiteTypes -}}
<li><a href="{{ .HRef }}">{{ .Label }}</a></li>
{{ end }}
</ul>
</li>
{{- end }}
</ul>
</nav>
{{- end }}
</header>
<main id="content">
{{- template "content" . }}