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 { type adminHandler struct {
campsite *campsite.Handler campsite *campsite.AdminHandler
} }
func newAdminHandler() *adminHandler { func newAdminHandler() *adminHandler {
return &adminHandler{ return &adminHandler{
campsite: campsite.NewHandler(), campsite: campsite.NewAdminHandler(),
} }
} }

View File

@ -6,19 +6,23 @@
package app package app
import ( import (
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/campsite"
"dev.tandem.ws/tandem/camper/pkg/database" "dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http" httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/template" "dev.tandem.ws/tandem/camper/pkg/template"
"fmt"
"net/http"
"sort"
) )
type publicHandler struct{} type publicHandler struct {
campsite *campsite.PublicHandler
}
func newPublicHandler() *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 { 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 { switch head {
case "": case "":
page := newHomePage() 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: default:
http.NotFound(w, r) http.NotFound(w, r)
} }
@ -36,41 +42,14 @@ func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *da
} }
type homePage struct { type homePage struct {
*PublicPage *template.PublicPage
} }
func newHomePage() *homePage { func newHomePage() *homePage {
return &homePage{newPublicPage("home.gohtml")} return &homePage{template.NewPublicPage()}
} }
type PublicPage struct { func (p *homePage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
template string p.Setup(r, user, company, conn)
LocalizedAlternates []*LocalizedAlternate template.MustRenderPublic(w, r, user, company, "home.gohtml", p)
}
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
} }

View File

@ -9,21 +9,22 @@ import (
"net/http" "net/http"
"dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/campsite/types"
"dev.tandem.ws/tandem/camper/pkg/database" "dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http" httplib "dev.tandem.ws/tandem/camper/pkg/http"
) )
type Handler struct { type AdminHandler struct {
types *typeHandler types *types.AdminHandler
} }
func NewHandler() *Handler { func NewAdminHandler() *AdminHandler {
return &Handler{ return &AdminHandler{
types: &typeHandler{}, 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) { return func(w http.ResponseWriter, r *http.Request) {
var head string var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path) 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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package campsite package types
import ( import (
"context" "context"
@ -15,12 +15,13 @@ import (
httplib "dev.tandem.ws/tandem/camper/pkg/http" httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale" "dev.tandem.ws/tandem/camper/pkg/locale"
"dev.tandem.ws/tandem/camper/pkg/template" "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) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var head string var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path) 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) httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
} }
default: 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 return
} }
conn.MustExec(r.Context(), "select add_campsite_type($1, $2, $3)", company.ID, f.Name, f.Description) 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 { type typeForm struct {
Slug string
Name *form.Input Name *form.Input
Description *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 { func (f *typeForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
return err 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 "" msgstr ""
"Project-Id-Version: camper\n" "Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\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" "PO-Revision-Date: 2023-07-22 23:45+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n" "Language-Team: Catalan <ca@dodds.net>\n"
@ -23,32 +23,47 @@ msgctxt "title"
msgid "Home" msgid "Home"
msgstr "Inici" 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" msgid "Campsite Montagut"
msgstr "Càmping 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" msgid "Skip to main content"
msgstr "Salta al contingut principal" msgstr "Salta al contingut principal"
#: web/templates/admin/campsite/type/new.gohtml:14 #: web/templates/public/layout.gohtml:38
#: web/templates/admin/campsite/type/new.gohtml:20 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" msgctxt "title"
msgid "New Campsite Type" msgid "New Campsite Type"
msgstr "Nou tipus dallotjament" 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 #: web/templates/admin/profile.gohtml:26
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: web/templates/admin/campsite/type/new.gohtml:33 #: web/templates/admin/campsite/type/new.gohtml:50
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripció" 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" msgctxt "action"
msgid "Add" msgid "Add"
msgstr "Afegeix" msgstr "Afegeix"
@ -140,11 +155,11 @@ msgctxt "action"
msgid "Logout" msgid "Logout"
msgstr "Surt" 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." msgid "Email can not be empty."
msgstr "No podeu deixar el correu en blanc." 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." 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." 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." msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes." msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/app/user.go:196 #: pkg/app/user.go:197
msgctxt "language option" msgctxt "language option"
msgid "Automatic" msgid "Automatic"
msgstr "Automàtic" 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." msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc." msgstr "No podeu deixar el nom en blanc."
#: pkg/app/user.go:249 #: pkg/app/user.go:250
msgid "Confirmation does not match password." msgid "Confirmation does not match password."
msgstr "La confirmació no es correspon amb la contrasenya." 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." msgid "Selected language is not valid."
msgstr "Lidioma escollit no és vàlid." 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." msgid "File must be a valid PNG or JPEG image."
msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida." msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: camper\n" "Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\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" "PO-Revision-Date: 2023-07-22 23:46+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n" "Language-Team: Spanish <es@tp.org.es>\n"
@ -23,32 +23,47 @@ msgctxt "title"
msgid "Home" msgid "Home"
msgstr "Inicio" 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" msgid "Campsite Montagut"
msgstr "Camping 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" msgid "Skip to main content"
msgstr "Saltar al contenido principal" msgstr "Saltar al contenido principal"
#: web/templates/admin/campsite/type/new.gohtml:14 #: web/templates/public/layout.gohtml:38
#: web/templates/admin/campsite/type/new.gohtml:20 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" msgctxt "title"
msgid "New Campsite Type" msgid "New Campsite Type"
msgstr "Nuevo tipo de alojamiento" 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 #: web/templates/admin/profile.gohtml:26
msgctxt "input" msgctxt "input"
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: web/templates/admin/campsite/type/new.gohtml:33 #: web/templates/admin/campsite/type/new.gohtml:50
msgctxt "input" msgctxt "input"
msgid "Description" msgid "Description"
msgstr "Descripción" 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" msgctxt "action"
msgid "Add" msgid "Add"
msgstr "Añadir" msgstr "Añadir"
@ -140,11 +155,11 @@ msgctxt "action"
msgid "Logout" msgid "Logout"
msgstr "Salir" 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." msgid "Email can not be empty."
msgstr "No podéis dejar el correo-e en blanco." 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." 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." 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." msgid "Invalid user or password."
msgstr "Usuario o contraseña incorrectos." msgstr "Usuario o contraseña incorrectos."
#: pkg/app/user.go:196 #: pkg/app/user.go:197
msgctxt "language option" msgctxt "language option"
msgid "Automatic" msgid "Automatic"
msgstr "Automático" 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." msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco." 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." msgid "Confirmation does not match password."
msgstr "La confirmación no se corresponde con la contraseña." 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." msgid "Selected language is not valid."
msgstr "El idioma escogido no es válido." 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." msgid "File must be a valid PNG or JPEG image."
msgstr "El archivo tiene que ser una imagen PNG o JPEG válida." 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 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 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 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 }} {{- end }}
{{ define "content" -}} {{ 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> <a href="/admin/campsites/types/new">{{( pgettext "Add Type" "action" )}}</a>
<h2>{{( pgettext "Campsite Types" "title" )}}</h2> <h2>{{( pgettext "Campsite Types" "title" )}}</h2>
{{ if .Types -}} {{ if .Types -}}
@ -20,7 +20,7 @@
<tbody> <tbody>
{{ range .Types -}} {{ range .Types -}}
<tr> <tr>
<td><a href="/admin/campsites/type/{{ .Slug }}">{{ .Name }}</a></td> <td><a href="/admin/campsites/types/{{ .Slug }}">{{ .Name }}</a></td>
</tr> </tr>
{{- end }} {{- end }}
</tbody> </tbody>

View File

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