Add the carousel for campsite types

I can not reuse the carousel package because these carousels need the
campsite site’s slug as a first parameters: i can not have a relation
per campsite type, as i do in home and services pages, because the
campsite types are added by administration types; even if i had a
single relation for slides of home and services pages, these would go
in a different relation due to the foreign key to campsite type.

What i could reuse, however, is the Slide and SlideEntry types from
that package, although i had to export carousel.Translation to be usable
from the types package.  I should change that to use locale.Translation,
but this was the easier option, or i would need to change the queries
and templates for carousel package too.

Besides that, they work exactly like the slides in home and services
pages.
This commit is contained in:
jordi fita mas 2023-10-12 16:33:45 +02:00
parent ad7126cc92
commit 471ed9e870
59 changed files with 1765 additions and 167 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -46,6 +46,30 @@ select add_media(52, 'services_carousel0.avif', 'image/avif', decode('m4_esyscmd
select add_media(52, 'services_carousel1.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel1.avif]])', 'base64')); select add_media(52, 'services_carousel1.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel1.avif]])', 'base64'));
select add_media(52, 'services_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel2.avif]])', 'base64')); select add_media(52, 'services_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel2.avif]])', 'base64'));
select add_media(52, 'services_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel3.avif]])', 'base64')); select add_media(52, 'services_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/services_carousel3.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel1.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel1.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel2.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel3.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel4.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel4.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel5.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel5.avif]])', 'base64'));
select add_media(52, 'safari_tents_carousel6.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/safari_tents_carousel6.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel2.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel3.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel4.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel4.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel5.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel5.avif]])', 'base64'));
select add_media(52, 'bungalows_carousel6.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/bungalows_carousel6.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel0.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel0.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel1.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel1.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel2.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel2.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel3.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel3.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel4.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel4.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel5.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel5.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel6.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel6.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel7.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel7.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel8.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel8.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carousel9.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carousel9.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carouselA.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carouselA.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carouselB.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carouselB.avif]])', 'base64'));
select add_media(52, 'wooden_lodges_carouselC.avif', 'image/avif', decode('m4_esyscmd([[base64 -w0 demo/wooden_lodges_carouselC.avif]])', 'base64'));
; ;
insert into home_carousel (media_id, caption) insert into home_carousel (media_id, caption)
@ -370,4 +394,115 @@ values (102, 92, 795)
, (111, 94, 590) , (111, 94, 590)
; ;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (72, 77, 'Llegenda')
, (72, 78, 'Llegenda')
, (72, 79, 'Llegenda')
, (73, 63, 'Llegenda')
, (73, 80, 'Llegenda')
, (73, 81, 'Llegenda')
, (73, 82, 'Llegenda')
, (73, 83, 'Llegenda')
, (73, 84, 'Llegenda')
, (73, 85, 'Llegenda')
, (74, 64, 'Llegenda')
, (74, 65, 'Llegenda')
, (74, 86, 'Llegenda')
, (74, 87, 'Llegenda')
, (74, 88, 'Llegenda')
, (74, 89, 'Llegenda')
, (74, 90, 'Llegenda')
, (75, 65, 'Llegenda')
, (75, 64, 'Llegenda')
, (75, 88, 'Llegenda')
, (75, 86, 'Llegenda')
, (75, 87, 'Llegenda')
, (76, 91, 'Llegenda')
, (76, 92, 'Llegenda')
, (76, 93, 'Llegenda')
, (76, 94, 'Llegenda')
, (76, 95, 'Llegenda')
, (76, 96, 'Llegenda')
, (76, 97, 'Llegenda')
, (76, 98, 'Llegenda')
, (76, 99, 'Llegenda')
, (76, 100, 'Llegenda')
, (76, 101, 'Llegenda')
, (76, 102, 'Llegenda')
, (76, 103, 'Llegenda')
;
insert into campsite_type_carousel_i18n (campsite_type_id, media_id, lang_tag, caption)
values (72, 77, 'en', 'Legend')
, (72, 77, 'es', 'Leyenda')
, (72, 78, 'en', 'Legend')
, (72, 78, 'es', 'Leyenda')
, (72, 79, 'en', 'Legend')
, (72, 79, 'es', 'Leyenda')
, (73, 63, 'en', 'Legend')
, (73, 63, 'es', 'Leyenda')
, (73, 80, 'en', 'Legend')
, (73, 80, 'es', 'Leyenda')
, (73, 81, 'en', 'Legend')
, (73, 81, 'es', 'Leyenda')
, (73, 82, 'en', 'Legend')
, (73, 82, 'es', 'Leyenda')
, (73, 83, 'en', 'Legend')
, (73, 83, 'es', 'Leyenda')
, (73, 84, 'en', 'Legend')
, (73, 84, 'es', 'Leyenda')
, (73, 85, 'en', 'Legend')
, (73, 85, 'es', 'Leyenda')
, (74, 64, 'en', 'Legend')
, (74, 64, 'es', 'Leyenda')
, (74, 65, 'en', 'Legend')
, (74, 65, 'es', 'Leyenda')
, (74, 86, 'en', 'Legend')
, (74, 86, 'es', 'Leyenda')
, (74, 87, 'en', 'Legend')
, (74, 87, 'es', 'Leyenda')
, (74, 88, 'en', 'Legend')
, (74, 88, 'es', 'Leyenda')
, (74, 89, 'en', 'Legend')
, (74, 89, 'es', 'Leyenda')
, (74, 90, 'en', 'Legend')
, (74, 90, 'es', 'Leyenda')
, (75, 65, 'en', 'Legend')
, (75, 65, 'es', 'Leyenda')
, (75, 64, 'en', 'Legend')
, (75, 64, 'es', 'Leyenda')
, (75, 88, 'en', 'Legend')
, (75, 88, 'es', 'Leyenda')
, (75, 86, 'en', 'Legend')
, (75, 86, 'es', 'Leyenda')
, (75, 87, 'en', 'Legend')
, (75, 87, 'es', 'Leyenda')
, (76, 91, 'en', 'Legend')
, (76, 91, 'es', 'Leyenda')
, (76, 92, 'en', 'Legend')
, (76, 92, 'es', 'Leyenda')
, (76, 93, 'en', 'Legend')
, (76, 93, 'es', 'Leyenda')
, (76, 94, 'en', 'Legend')
, (76, 94, 'es', 'Leyenda')
, (76, 95, 'en', 'Legend')
, (76, 95, 'es', 'Leyenda')
, (76, 96, 'en', 'Legend')
, (76, 96, 'es', 'Leyenda')
, (76, 97, 'en', 'Legend')
, (76, 97, 'es', 'Leyenda')
, (76, 98, 'en', 'Legend')
, (76, 98, 'es', 'Leyenda')
, (76, 99, 'en', 'Legend')
, (76, 99, 'es', 'Leyenda')
, (76, 100, 'en', 'Legend')
, (76, 100, 'es', 'Leyenda')
, (76, 101, 'en', 'Legend')
, (76, 101, 'es', 'Leyenda')
, (76, 102, 'en', 'Legend')
, (76, 102, 'es', 'Leyenda')
, (76, 103, 'en', 'Legend')
, (76, 103, 'es', 'Leyenda')
;
commit; commit;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,28 @@
-- Deploy camper:add_campsite_type_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type_carousel
-- requires: campsite_type
begin;
set search_path to camper, public;
create or replace function add_campsite_type_carousel_slide(slug uuid, media_id integer, caption text) returns integer as
$$
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
select campsite_type_id, add_campsite_type_carousel_slide.media_id, coalesce(caption, '')
from campsite_type
where slug = add_campsite_type_carousel_slide.slug
on conflict (campsite_type_id, media_id) do update
set caption = excluded.caption
returning campsite_type_id
;
$$
language sql
;
revoke execute on function add_campsite_type_carousel_slide(uuid, integer, text) from public;
grant execute on function add_campsite_type_carousel_slide(uuid, integer, text) to admin;
commit;

View File

@ -0,0 +1,55 @@
-- Deploy camper:campsite_type_carousel to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type
-- requires: media
-- requires: user_profile
begin;
set search_path to camper, public;
create table campsite_type_carousel (
campsite_type_id integer not null references campsite_type,
media_id integer not null references media,
caption text not null,
primary key (campsite_type_id, media_id)
);
grant select on table campsite_type_carousel to guest;
grant select on table campsite_type_carousel to employee;
grant select, insert, update, delete on table campsite_type_carousel to admin;
alter table campsite_type_carousel enable row level security;
create policy guest_ok
on campsite_type_carousel
for select
using (true)
;
create policy insert_to_company
on campsite_type_carousel
for insert
with check (
exists (select 1 from campsite_type join media using (company_id) join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_carousel.campsite_type_id and media.media_id = campsite_type_carousel.media_id)
)
;
create policy update_company
on campsite_type_carousel
for update
using (
exists (select 1 from campsite_type join media using (company_id) join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_carousel.campsite_type_id and media.media_id = campsite_type_carousel.media_id)
)
;
create policy delete_from_company
on campsite_type_carousel
for delete
using (
exists (select 1 from campsite_type join media using (company_id) join user_profile using (company_id) where campsite_type.campsite_type_id = campsite_type_carousel.campsite_type_id and media.media_id = campsite_type_carousel.media_id)
)
;
commit;

View File

@ -0,0 +1,24 @@
-- Deploy camper:campsite_type_carousel_i18n to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type_carousel
-- requires: language
begin;
set search_path to camper, public;
create table campsite_type_carousel_i18n (
campsite_type_id integer not null,
media_id integer not null,
lang_tag text not null references language,
caption text not null,
primary key (campsite_type_id, media_id, lang_tag),
foreign key (campsite_type_id, media_id) references campsite_type_carousel
);
grant select on table campsite_type_carousel_i18n to guest;
grant select on table campsite_type_carousel_i18n to employee;
grant select, insert, update, delete on table campsite_type_carousel_i18n to admin;
commit;

View File

@ -0,0 +1,37 @@
-- Deploy camper:remove_campsite_type_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type_carousel
-- requires: campsite_type_carousel_i18n
begin;
set search_path to camper, public;
create or replace function remove_campsite_type_carousel_slide(slug uuid, media_id integer) returns void as
$$
declare
csid integer;
begin
select campsite_type_id
into csid
from campsite_type
where campsite_type.slug = $1
;
delete from campsite_type_carousel_i18n
where campsite_type_id = csid
and campsite_type_carousel_i18n.media_id = $2
;
delete from campsite_type_carousel
where campsite_type_id = csid
and campsite_type_carousel.media_id = $2
;
end
$$
language plpgsql
;
revoke execute on function remove_campsite_type_carousel_slide(uuid, integer) from public;
grant execute on function remove_campsite_type_carousel_slide(uuid, integer) to admin;
commit;

View File

@ -0,0 +1,27 @@
-- Deploy camper:translate_campsite_type_carousel_slide to pg
-- requires: roles
-- requires: schema_camper
-- requires: campsite_type
-- requires: campsite_type_carousel_i18n
begin;
set search_path to camper, public;
create or replace function translate_campsite_type_carousel_slide(slug uuid, media_id integer, lang_tag text, caption text) returns void as
$$
insert into campsite_type_carousel_i18n (campsite_type_id, media_id, lang_tag, caption)
select campsite_type_id, translate_campsite_type_carousel_slide.media_id, lang_tag, coalesce(caption, '')
from campsite_type
where slug = translate_campsite_type_carousel_slide.slug
on conflict (campsite_type_id, media_id, lang_tag) do update
set caption = excluded.caption
;
$$
language sql
;
revoke execute on function translate_campsite_type_carousel_slide(uuid, integer, text, text) from public;
grant execute on function translate_campsite_type_carousel_slide(uuid, integer, text, text) to admin;
commit;

View File

@ -93,6 +93,8 @@ func (h *AdminHandler) typeHandler(user *auth.User, company *auth.Company, conn
} }
case "options": case "options":
h.optionsHandler(user, company, conn, f.Slug).ServeHTTP(w, r) h.optionsHandler(user, company, conn, f.Slug).ServeHTTP(w, r)
case "slides":
h.carouselHandler(user, company, conn, f.Slug).ServeHTTP(w, r)
default: default:
loc, ok := h.locales.Get(head) loc, ok := h.locales.Get(head)
if !ok { if !ok {

View File

@ -0,0 +1,297 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package types
import (
"context"
"net/http"
"strconv"
"github.com/jackc/pgx/v4"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/carousel"
"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"
)
func (h *AdminHandler) carouselHandler(user *auth.User, company *auth.Company, conn *database.Conn, typeSlug string) 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 "":
switch r.Method {
case http.MethodGet:
serveCarouselIndex(w, r, user, company, conn, typeSlug)
case http.MethodPost:
addSlide(w, r, user, company, conn, typeSlug)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodGet)
}
case "new":
switch r.Method {
case http.MethodGet:
f := newSlideForm(typeSlug)
f.MustRender(w, r, user, company)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
mediaID, err := strconv.Atoi(head)
if err != nil {
http.NotFound(w, r)
}
f := newSlideForm(typeSlug)
if err := f.FillFromDatabase(r.Context(), conn, mediaID); err != nil {
if database.ErrorIsNotFound(err) {
http.NotFound(w, r)
return
}
panic(err)
}
var langTag string
langTag, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch langTag {
case "":
switch r.Method {
case http.MethodGet:
f.MustRender(w, r, user, company)
case http.MethodPut:
editSlide(w, r, user, company, conn, f)
case http.MethodDelete:
h.deleteSlide(w, r, user, conn, typeSlug, mediaID)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut, http.MethodDelete)
}
default:
loc, ok := h.locales.Get(langTag)
if !ok {
http.NotFound(w, r)
return
}
l10n := newSlideL10nForm(f, loc)
if err := l10n.FillFromDatabase(r.Context(), conn); err != nil {
panic(err)
}
switch r.Method {
case http.MethodGet:
l10n.MustRender(w, r, user, company)
case http.MethodPut:
editSlideL10n(w, r, user, company, conn, l10n)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
}
}
}
})
}
func serveCarouselIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, typeSlug string) {
slides, err := collectSlideEntries(r.Context(), conn, typeSlug)
if err != nil {
panic(err)
}
page := &carouselIndex{
TypeSlug: typeSlug,
Slides: slides,
}
page.MustRender(w, r, user, company)
}
type carouselIndex struct {
TypeSlug string
Slides []*carousel.SlideEntry
}
func (page *carouselIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "campsite/carousel/index.gohtml", page)
}
func mustCollectSlides(ctx context.Context, conn *database.Conn, loc *locale.Locale, typeSlug string) []*carousel.Slide {
rows, err := conn.Query(ctx, `
select coalesce(i18n.caption, slide.caption) as l10_caption
, media.path
from campsite_type_carousel as slide
join campsite_type using (campsite_type_id)
join media on media.media_id = slide.media_id
left join campsite_type_carousel_i18n as i18n
on i18n.campsite_type_id = slide.campsite_type_id
and i18n.media_id = slide.media_id
and lang_tag = $1
where campsite_type.slug = $2
`, loc.Language, typeSlug)
if err != nil {
panic(err)
}
defer rows.Close()
var slides []*carousel.Slide
for rows.Next() {
slide := &carousel.Slide{}
err = rows.Scan(&slide.Caption, &slide.Media)
if err != nil {
panic(err)
}
slides = append(slides, slide)
}
if rows.Err() != nil {
panic(rows.Err())
}
return slides
}
func collectSlideEntries(ctx context.Context, conn *database.Conn, typeSlug string) ([]*carousel.SlideEntry, error) {
rows, err := conn.Query(ctx, `
select carousel.media_id
, media.path
, caption
, array_agg((lang_tag, endonym, not exists (select 1 from campsite_type_carousel_i18n as i18n where i18n.media_id = carousel.media_id and i18n.lang_tag = language.lang_tag)) order by endonym)
from campsite_type_carousel as carousel
join campsite_type using (campsite_type_id)
join media on media.media_id = carousel.media_id
join company on company.company_id = campsite_type.company_id
, language
where lang_tag <> default_lang_tag
and language.selectable
and campsite_type.slug = $1
group by carousel.media_id
, media.path
, caption
order by caption
`, pgx.QueryResultFormats{pgx.BinaryFormatCode}, typeSlug)
if err != nil {
return nil, err
}
defer rows.Close()
var slides []*carousel.SlideEntry
for rows.Next() {
slide := &carousel.SlideEntry{}
var translations database.RecordArray
if err = rows.Scan(&slide.ID, &slide.Media, &slide.Caption, &translations); err != nil {
return nil, err
}
for _, el := range translations.Elements {
slide.Translations = append(slide.Translations, &carousel.Translation{
Language: el.Fields[0].Get().(string),
Endonym: el.Fields[1].Get().(string),
Missing: el.Fields[2].Get().(bool),
})
}
slides = append(slides, slide)
}
return slides, nil
}
func addSlide(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, typeSlug string) {
f := newSlideForm(typeSlug)
editSlide(w, r, user, company, conn, f)
}
func editSlide(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *slideForm) {
f.process(w, r, user, company, conn, func(ctx context.Context) {
conn.MustExec(ctx, "select add_campsite_type_carousel_slide($1, $2, $3)", f.TypeSlug, f.Media, f.Caption)
})
}
func (h *AdminHandler) deleteSlide(w http.ResponseWriter, r *http.Request, user *auth.User, conn *database.Conn, typeSlug string, mediaId int) {
if err := user.VerifyCSRFToken(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
conn.MustExec(r.Context(), "select remove_campsite_type_carousel_slide($1, $2)", typeSlug, mediaId)
httplib.Redirect(w, r, "/admin/campsites/types/"+typeSlug+"/slides", http.StatusSeeOther)
}
type slideForm struct {
TypeSlug string
MediaID int
Media *form.Media
Caption *form.Input
}
func newSlideForm(typeSlug string) *slideForm {
return &slideForm{
TypeSlug: typeSlug,
Media: &form.Media{
Input: &form.Input{
Name: "media",
},
Label: locale.PgettextNoop("Slide image", "input"),
Prompt: locale.PgettextNoop("Set slide image", "action"),
},
Caption: &form.Input{
Name: "caption",
},
}
}
func (f *slideForm) FillFromDatabase(ctx context.Context, conn *database.Conn, mediaID int) error {
f.MediaID = mediaID
row := conn.QueryRow(ctx, `
select caption
, carousel.media_id::text
from campsite_type_carousel as carousel
join campsite_type using (campsite_type_id)
where campsite_type.slug = $1
and carousel.media_id = $2
`, f.TypeSlug, mediaID)
return row.Scan(&f.Caption.Val, &f.Media.Val)
}
func (f *slideForm) process(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, act func(ctx context.Context)) {
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 ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
panic(err)
} else if !ok {
if !httplib.IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
f.MustRender(w, r, user, company)
return
}
act(r.Context())
httplib.Redirect(w, r, "/admin/campsites/types/"+f.TypeSlug+"/slides", http.StatusSeeOther)
}
func (f *slideForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
f.Caption.FillValue(r)
f.Media.FillValue(r)
return nil
}
func (f *slideForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) {
v := form.NewValidator(l)
if v.CheckRequired(f.Media.Input, l.GettextNoop("Slide image can not be empty.")) {
if _, err := v.CheckImageMedia(ctx, conn, f.Media.Input, l.GettextNoop("Slide image must be an image media type.")); err != nil {
return false, err
}
}
return v.AllOK, nil
}
func (f *slideForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "campsite/carousel/form.gohtml", f)
}

View File

@ -128,3 +128,70 @@ func (l10n *optionL10nForm) Valid(l *locale.Locale) bool {
v.CheckRequired(&l10n.Name.Input, l.GettextNoop("Name can not be empty.")) v.CheckRequired(&l10n.Name.Input, l.GettextNoop("Name can not be empty."))
return v.AllOK return v.AllOK
} }
type slideL10nForm struct {
Locale *locale.Locale
TypeSlug string
MediaID int
Caption *form.L10nInput
}
func newSlideL10nForm(f *slideForm, loc *locale.Locale) *slideL10nForm {
return &slideL10nForm{
Locale: loc,
TypeSlug: f.TypeSlug,
MediaID: f.MediaID,
Caption: f.Caption.L10nInput(),
}
}
func (l10n *slideL10nForm) FillFromDatabase(ctx context.Context, conn *database.Conn) error {
row := conn.QueryRow(ctx, `
select coalesce(i18n.caption, '') as l10n_caption
from campsite_type_carousel as carousel
join campsite_type using (campsite_type_id)
left join campsite_type_carousel_i18n as i18n
on carousel.campsite_type_id = i18n.campsite_type_id
and carousel.media_id = i18n.media_id
and i18n.lang_tag = $1
where campsite_type.slug = $2 and carousel.media_id = $3
`, l10n.Locale.Language, l10n.TypeSlug, l10n.MediaID)
return row.Scan(&l10n.Caption.Val)
}
func (l10n *slideL10nForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "campsite/carousel/l10n.gohtml", l10n)
}
func editSlideL10n(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, l10n *slideL10nForm) {
if err := l10n.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 !l10n.Valid(user.Locale) {
if !httplib.IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
l10n.MustRender(w, r, user, company)
return
}
conn.MustExec(r.Context(), "select translate_campsite_type_carousel_slide($1, $2, $3, $4)", l10n.TypeSlug, l10n.MediaID, l10n.Locale.Language, l10n.Caption)
httplib.Redirect(w, r, "/admin/campsites/types/"+l10n.TypeSlug+"/slides", http.StatusSeeOther)
}
func (l10n *slideL10nForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
l10n.Caption.FillValue(r)
return nil
}
func (l10n *slideL10nForm) Valid(l *locale.Locale) bool {
v := form.NewValidator(l)
return v.AllOK
}

View File

@ -13,6 +13,7 @@ import (
"github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4"
"dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/carousel"
"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/locale" "dev.tandem.ws/tandem/camper/pkg/locale"
@ -51,6 +52,7 @@ func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *da
type publicPage struct { type publicPage struct {
*template.PublicPage *template.PublicPage
Name string Name string
Carousel []*carousel.Slide
Prices []*typePrice Prices []*typePrice
Description gotemplate.HTML Description gotemplate.HTML
} }
@ -71,6 +73,7 @@ type optionPrice struct {
func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, slug string) (*publicPage, error) { func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, slug string) (*publicPage, error) {
page := &publicPage{ page := &publicPage{
PublicPage: template.NewPublicPage(), PublicPage: template.NewPublicPage(),
Carousel: mustCollectSlides(ctx, conn, loc, slug),
} }
row := conn.QueryRow(ctx, ` row := conn.QueryRow(ctx, `
select coalesce(i18n.name, campsite_type.name) as l10n_name select coalesce(i18n.name, campsite_type.name) as l10n_name

View File

@ -144,10 +144,10 @@ func MustCollectSlides(ctx context.Context, company *auth.Company, conn *databas
type SlideEntry struct { type SlideEntry struct {
Slide Slide
ID int ID int
Translations []*translation Translations []*Translation
} }
type translation struct { type Translation struct {
Language string Language string
Endonym string Endonym string
Missing bool Missing bool
@ -184,7 +184,7 @@ func CollectSlideEntries(ctx context.Context, company *auth.Company, conn *datab
return nil, err return nil, err
} }
for _, el := range translations.Elements { for _, el := range translations.Elements {
slide.Translations = append(slide.Translations, &translation{ slide.Translations = append(slide.Translations, &Translation{
el.Fields[0].Get().(string), el.Fields[0].Get().(string),
el.Fields[1].Get().(string), el.Fields[1].Get().(string),
el.Fields[2].Get().(bool), el.Fields[2].Get().(bool),

211
po/ca.po
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-10-06 22:10+0200\n" "POT-Creation-Date: 2023-10-12 16:26+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"
@ -77,22 +77,22 @@ msgstr "Descobreix lentorn"
msgid "Come and enjoy!" msgid "Come and enjoy!"
msgstr "Vine a gaudir!" msgstr "Vine a gaudir!"
#: web/templates/public/campsite/type.gohtml:17 #: web/templates/public/campsite/type.gohtml:40
#: web/templates/admin/campsite/option/form.gohtml:58 #: web/templates/admin/campsite/option/form.gohtml:58
#: web/templates/admin/campsite/type/form.gohtml:73 #: web/templates/admin/campsite/type/form.gohtml:73
msgctxt "title" msgctxt "title"
msgid "Prices" msgid "Prices"
msgstr "Preus" msgstr "Preus"
#: web/templates/public/campsite/type.gohtml:26 #: web/templates/public/campsite/type.gohtml:50
msgid "%s €/night" msgid "%s €/night"
msgstr "%s €/nit" msgstr "%s €/nit"
#: web/templates/public/campsite/type.gohtml:28 #: web/templates/public/campsite/type.gohtml:52
msgid "%s: %s €/night" msgid "%s: %s €/night"
msgstr "%s: %s €/nit" msgstr "%s: %s €/nit"
#: web/templates/public/campsite/type.gohtml:31 #: web/templates/public/campsite/type.gohtml:55
msgid "*Minimum %d nights per stay" msgid "*Minimum %d nights per stay"
msgstr "*Mínim %d nits per estada" msgstr "*Mínim %d nits per estada"
@ -203,11 +203,14 @@ msgstr "Nova diapositiva del carrusel"
#: web/templates/admin/carousel/form.gohtml:37 #: web/templates/admin/carousel/form.gohtml:37
#: web/templates/admin/carousel/l10n.gohtml:20 #: web/templates/admin/carousel/l10n.gohtml:20
#: web/templates/admin/campsite/carousel/form.gohtml:37
#: web/templates/admin/campsite/carousel/l10n.gohtml:20
msgctxt "input" msgctxt "input"
msgid "Caption" msgid "Caption"
msgstr "Llegenda" msgstr "Llegenda"
#: web/templates/admin/carousel/form.gohtml:47 #: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70 #: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78 #: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:108 #: web/templates/admin/campsite/type/form.gohtml:108
@ -219,6 +222,7 @@ msgid "Update"
msgstr "Actualitza" msgstr "Actualitza"
#: web/templates/admin/carousel/form.gohtml:49 #: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72 #: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80 #: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:110 #: web/templates/admin/campsite/type/form.gohtml:110
@ -235,6 +239,7 @@ msgid "Translate Carousel Slide to %s"
msgstr "Traducció de la diapositiva del carrusel a %s" msgstr "Traducció de la diapositiva del carrusel a %s"
#: web/templates/admin/carousel/l10n.gohtml:21 #: web/templates/admin/carousel/l10n.gohtml:21
#: web/templates/admin/campsite/carousel/l10n.gohtml:21
#: web/templates/admin/campsite/option/l10n.gohtml:21 #: web/templates/admin/campsite/option/l10n.gohtml:21
#: web/templates/admin/campsite/type/l10n.gohtml:21 #: web/templates/admin/campsite/type/l10n.gohtml:21
#: web/templates/admin/campsite/type/l10n.gohtml:33 #: web/templates/admin/campsite/type/l10n.gohtml:33
@ -245,6 +250,7 @@ msgid "Source:"
msgstr "Origen:" msgstr "Origen:"
#: web/templates/admin/carousel/l10n.gohtml:23 #: web/templates/admin/carousel/l10n.gohtml:23
#: web/templates/admin/campsite/carousel/l10n.gohtml:23
#: web/templates/admin/campsite/option/l10n.gohtml:23 #: web/templates/admin/campsite/option/l10n.gohtml:23
#: web/templates/admin/campsite/type/l10n.gohtml:23 #: web/templates/admin/campsite/type/l10n.gohtml:23
#: web/templates/admin/campsite/type/l10n.gohtml:36 #: web/templates/admin/campsite/type/l10n.gohtml:36
@ -256,6 +262,7 @@ msgid "Translation:"
msgstr "Traducció:" msgstr "Traducció:"
#: web/templates/admin/carousel/l10n.gohtml:32 #: web/templates/admin/carousel/l10n.gohtml:32
#: web/templates/admin/campsite/carousel/l10n.gohtml:32
#: web/templates/admin/campsite/option/l10n.gohtml:32 #: web/templates/admin/campsite/option/l10n.gohtml:32
#: web/templates/admin/campsite/type/l10n.gohtml:45 #: web/templates/admin/campsite/type/l10n.gohtml:45
#: web/templates/admin/season/l10n.gohtml:32 #: web/templates/admin/season/l10n.gohtml:32
@ -264,6 +271,90 @@ msgctxt "action"
msgid "Translate" msgid "Translate"
msgstr "Tradueix" msgstr "Tradueix"
#: web/templates/admin/campsite/carousel/form.gohtml:8
#: web/templates/admin/campsite/carousel/form.gohtml:25
msgctxt "title"
msgid "Edit Campsite Type Carousel Slide"
msgstr "Edició de la diapositiva del carrusel del tipus dallotjament"
#: web/templates/admin/campsite/carousel/form.gohtml:10
#: web/templates/admin/campsite/carousel/form.gohtml:27
msgctxt "title"
msgid "New Campsite Type Carousel Slide"
msgstr "Nova diapositiva del carrusel del tipus dallotjament"
#: web/templates/admin/campsite/carousel/index.gohtml:6
#: web/templates/admin/campsite/carousel/index.gohtml:11
msgctxt "title"
msgid "Campsite Type Carousel"
msgstr "Carrusel del tipus dallotjament"
#: web/templates/admin/campsite/carousel/index.gohtml:12
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add slide"
msgstr "Afegeix diapositiva"
#: web/templates/admin/campsite/carousel/index.gohtml:17
#: web/templates/admin/services/index.gohtml:17
#: web/templates/admin/home/index.gohtml:17
msgctxt "header"
msgid "Image"
msgstr "Imatge"
#: web/templates/admin/campsite/carousel/index.gohtml:18
#: web/templates/admin/services/index.gohtml:18
#: web/templates/admin/home/index.gohtml:18
msgctxt "header"
msgid "Caption"
msgstr "Llegenda"
#: web/templates/admin/campsite/carousel/index.gohtml:19
#: web/templates/admin/campsite/option/index.gohtml:18
#: web/templates/admin/campsite/type/index.gohtml:18
#: web/templates/admin/season/index.gohtml:19
#: web/templates/admin/services/index.gohtml:19
#: web/templates/admin/services/index.gohtml:60
#: web/templates/admin/home/index.gohtml:19
msgctxt "header"
msgid "Translations"
msgstr "Traduccions"
#: web/templates/admin/campsite/carousel/index.gohtml:20
#: web/templates/admin/services/index.gohtml:20
#: web/templates/admin/services/index.gohtml:61
#: web/templates/admin/home/index.gohtml:20
msgctxt "header"
msgid "Actions"
msgstr "Accions"
#: web/templates/admin/campsite/carousel/index.gohtml:24
#: web/templates/admin/services/index.gohtml:24
#: web/templates/admin/home/index.gohtml:24
msgid "Are you sure you wish to delete this slide?"
msgstr "Esteu segur de voler esborrar aquesta diapositiva?"
#: web/templates/admin/campsite/carousel/index.gohtml:42
#: web/templates/admin/services/index.gohtml:42
#: web/templates/admin/services/index.gohtml:80
#: web/templates/admin/home/index.gohtml:42
msgctxt "action"
msgid "Delete"
msgstr "Esborra"
#: web/templates/admin/campsite/carousel/index.gohtml:50
#: web/templates/admin/services/index.gohtml:50
#: web/templates/admin/home/index.gohtml:50
msgid "No slides added yet."
msgstr "No sha afegit cap diapositiva encara."
#: web/templates/admin/campsite/carousel/l10n.gohtml:7
#: web/templates/admin/campsite/carousel/l10n.gohtml:14
msgctxt "title"
msgid "Translate Campsite Type Carousel Slide to %s"
msgstr "Traducció de la diapositiva del carrusel del tipus dallotjament a %s"
#: web/templates/admin/campsite/form.gohtml:8 #: web/templates/admin/campsite/form.gohtml:8
#: web/templates/admin/campsite/form.gohtml:25 #: web/templates/admin/campsite/form.gohtml:25
msgctxt "title" msgctxt "title"
@ -355,16 +446,6 @@ msgctxt "header"
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: web/templates/admin/campsite/option/index.gohtml:18
#: web/templates/admin/campsite/type/index.gohtml:18
#: web/templates/admin/season/index.gohtml:19
#: web/templates/admin/services/index.gohtml:19
#: web/templates/admin/services/index.gohtml:60
#: web/templates/admin/home/index.gohtml:19
msgctxt "header"
msgid "Translations"
msgstr "Traduccions"
#: web/templates/admin/campsite/option/index.gohtml:39 #: web/templates/admin/campsite/option/index.gohtml:39
msgid "No campsite type options added yet." msgid "No campsite type options added yet."
msgstr "No sha afegit cap opció al tipus dallotjament encara." msgstr "No sha afegit cap opció al tipus dallotjament encara."
@ -391,13 +472,13 @@ msgid "Type"
msgstr "Tipus" msgstr "Tipus"
#: web/templates/admin/campsite/index.gohtml:28 #: web/templates/admin/campsite/index.gohtml:28
#: web/templates/admin/campsite/type/index.gohtml:37 #: web/templates/admin/campsite/type/index.gohtml:39
#: web/templates/admin/season/index.gohtml:39 #: web/templates/admin/season/index.gohtml:39
msgid "Yes" msgid "Yes"
msgstr "Sí" msgstr "Sí"
#: web/templates/admin/campsite/index.gohtml:28 #: web/templates/admin/campsite/index.gohtml:28
#: web/templates/admin/campsite/type/index.gohtml:37 #: web/templates/admin/campsite/type/index.gohtml:39
#: web/templates/admin/season/index.gohtml:39 #: web/templates/admin/season/index.gohtml:39
msgid "No" msgid "No"
msgstr "No" msgstr "No"
@ -419,7 +500,7 @@ msgid "New Campsite Type"
msgstr "Nou tipus dallotjament" msgstr "Nou tipus dallotjament"
#: web/templates/admin/campsite/type/form.gohtml:37 #: web/templates/admin/campsite/type/form.gohtml:37
#: web/templates/admin/campsite/type/index.gohtml:20 #: web/templates/admin/campsite/type/index.gohtml:21
msgctxt "campsite type" msgctxt "campsite type"
msgid "Active" msgid "Active"
msgstr "Actiu" msgstr "Actiu"
@ -464,12 +545,22 @@ msgctxt "header"
msgid "Options" msgid "Options"
msgstr "Opcions" msgstr "Opcions"
#: web/templates/admin/campsite/type/index.gohtml:36 #: web/templates/admin/campsite/type/index.gohtml:20
msgctxt "header"
msgid "Carousel"
msgstr "Carrusel"
#: web/templates/admin/campsite/type/index.gohtml:37
msgctxt "action" msgctxt "action"
msgid "Edit Options" msgid "Edit Options"
msgstr "Edita les opcions" msgstr "Edita les opcions"
#: web/templates/admin/campsite/type/index.gohtml:43 #: web/templates/admin/campsite/type/index.gohtml:38
msgctxt "action"
msgid "Edit Carousel"
msgstr "Edita el carrusel"
#: web/templates/admin/campsite/type/index.gohtml:45
msgid "No campsite types added yet." msgid "No campsite types added yet."
msgstr "No sha afegit cap tipus dallotjament encara." msgstr "No sha afegit cap tipus dallotjament encara."
@ -631,48 +722,6 @@ msgctxt "title"
msgid "Carousel" msgid "Carousel"
msgstr "Carrusel" msgstr "Carrusel"
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add slide"
msgstr "Afegeix diapositiva"
#: web/templates/admin/services/index.gohtml:17
#: web/templates/admin/home/index.gohtml:17
msgctxt "header"
msgid "Image"
msgstr "Imatge"
#: web/templates/admin/services/index.gohtml:18
#: web/templates/admin/home/index.gohtml:18
msgctxt "header"
msgid "Caption"
msgstr "Llegenda"
#: web/templates/admin/services/index.gohtml:20
#: web/templates/admin/services/index.gohtml:61
#: web/templates/admin/home/index.gohtml:20
msgctxt "header"
msgid "Actions"
msgstr "Accions"
#: web/templates/admin/services/index.gohtml:24
#: web/templates/admin/home/index.gohtml:24
msgid "Are you sure you wish to delete this slide?"
msgstr "Esteu segur de voler esborrar aquesta diapositiva?"
#: web/templates/admin/services/index.gohtml:42
#: web/templates/admin/services/index.gohtml:80
#: web/templates/admin/home/index.gohtml:42
msgctxt "action"
msgid "Delete"
msgstr "Esborra"
#: web/templates/admin/services/index.gohtml:50
#: web/templates/admin/home/index.gohtml:50
msgid "No slides added yet."
msgstr "No sha afegit cap diapositiva encara."
#: web/templates/admin/services/index.gohtml:54 #: web/templates/admin/services/index.gohtml:54
msgctxt "action" msgctxt "action"
msgid "Add service" msgid "Add service"
@ -893,21 +942,21 @@ msgctxt "title"
msgid "Upload Media" msgid "Upload Media"
msgstr "Pujada de mèdia" msgstr "Pujada de mèdia"
#: pkg/carousel/admin.go:233 #: pkg/carousel/admin.go:233 pkg/campsite/types/carousel.go:232
msgctxt "input" msgctxt "input"
msgid "Slide image" msgid "Slide image"
msgstr "Imatge de la diapositiva" msgstr "Imatge de la diapositiva"
#: pkg/carousel/admin.go:234 #: pkg/carousel/admin.go:234 pkg/campsite/types/carousel.go:233
msgctxt "action" msgctxt "action"
msgid "Set slide image" msgid "Set slide image"
msgstr "Estableix la imatge de la diapositiva" msgstr "Estableix la imatge de la diapositiva"
#: pkg/carousel/admin.go:286 #: pkg/carousel/admin.go:286 pkg/campsite/types/carousel.go:287
msgid "Slide image can not be empty." msgid "Slide image can not be empty."
msgstr "No podeu deixar la imatge de la diapositiva en blanc." msgstr "No podeu deixar la imatge de la diapositiva en blanc."
#: pkg/carousel/admin.go:287 #: pkg/carousel/admin.go:287 pkg/campsite/types/carousel.go:288
msgid "Slide image must be an image media type." msgid "Slide image must be an image media type."
msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge." msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge."
@ -934,7 +983,7 @@ msgstr "Automàtic"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:73 #: pkg/app/user.go:249 pkg/campsite/types/l10n.go:73
#: pkg/campsite/types/l10n.go:128 pkg/campsite/types/option.go:336 #: pkg/campsite/types/l10n.go:128 pkg/campsite/types/option.go:336
#: pkg/campsite/types/admin.go:389 pkg/season/l10n.go:69 #: pkg/campsite/types/admin.go:391 pkg/season/l10n.go:69
#: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266 #: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266
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."
@ -955,7 +1004,7 @@ msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida."
msgid "Access forbidden" msgid "Access forbidden"
msgstr "Accés prohibit" msgstr "Accés prohibit"
#: pkg/campsite/types/option.go:337 pkg/campsite/types/admin.go:390 #: pkg/campsite/types/option.go:337 pkg/campsite/types/admin.go:392
msgid "Name must have at least one letter." msgid "Name must have at least one letter."
msgstr "El nom ha de tenir com a mínim una lletra." msgstr "El nom ha de tenir com a mínim una lletra."
@ -983,57 +1032,57 @@ msgstr "El valor del màxim ha de ser un número enter."
msgid "Maximum must be equal or greater than minimum." msgid "Maximum must be equal or greater than minimum."
msgstr "El valor del màxim ha de ser igual o superir al del mínim." msgstr "El valor del màxim ha de ser igual o superir al del mínim."
#: pkg/campsite/types/option.go:353 pkg/campsite/types/admin.go:403 #: pkg/campsite/types/option.go:353 pkg/campsite/types/admin.go:405
msgid "Price per night can not be empty." msgid "Price per night can not be empty."
msgstr "No podeu deixar el preu per nit en blanc." msgstr "No podeu deixar el preu per nit en blanc."
#: pkg/campsite/types/option.go:354 pkg/campsite/types/admin.go:404 #: pkg/campsite/types/option.go:354 pkg/campsite/types/admin.go:406
msgid "Price per night must be a decimal number." msgid "Price per night must be a decimal number."
msgstr "El preu per nit ha de ser un número decimal." msgstr "El preu per nit ha de ser un número decimal."
#: pkg/campsite/types/option.go:355 pkg/campsite/types/admin.go:405 #: pkg/campsite/types/option.go:355 pkg/campsite/types/admin.go:407
msgid "Price per night must be zero or greater." msgid "Price per night must be zero or greater."
msgstr "El preu per nit ha de ser com a mínim zero." msgstr "El preu per nit ha de ser com a mínim zero."
#: pkg/campsite/types/admin.go:280 #: pkg/campsite/types/admin.go:282
msgctxt "input" msgctxt "input"
msgid "Cover image" msgid "Cover image"
msgstr "Imatge de portada" msgstr "Imatge de portada"
#: pkg/campsite/types/admin.go:281 #: pkg/campsite/types/admin.go:283
msgctxt "action" msgctxt "action"
msgid "Set campsite type cover" msgid "Set campsite type cover"
msgstr "Estableix la portada del tipus dallotjament" msgstr "Estableix la portada del tipus dallotjament"
#: pkg/campsite/types/admin.go:392 #: pkg/campsite/types/admin.go:394
msgid "Cover image can not be empty." msgid "Cover image can not be empty."
msgstr "No podeu deixar la imatge de portada en blanc." msgstr "No podeu deixar la imatge de portada en blanc."
#: pkg/campsite/types/admin.go:393 #: pkg/campsite/types/admin.go:395
msgid "Cover image must be an image media type." msgid "Cover image must be an image media type."
msgstr "La imatge de portada ha de ser un mèdia de tipus imatge." msgstr "La imatge de portada ha de ser un mèdia de tipus imatge."
#: pkg/campsite/types/admin.go:397 #: pkg/campsite/types/admin.go:399
msgid "Maximum number of campers can not be empty." msgid "Maximum number of campers can not be empty."
msgstr "No podeu deixar el número màxim de persones en blanc." msgstr "No podeu deixar el número màxim de persones en blanc."
#: pkg/campsite/types/admin.go:398 #: pkg/campsite/types/admin.go:400
msgid "Maximum number of campers must be an integer number." msgid "Maximum number of campers must be an integer number."
msgstr "El número màxim de persones ha de ser enter." msgstr "El número màxim de persones ha de ser enter."
#: pkg/campsite/types/admin.go:399 #: pkg/campsite/types/admin.go:401
msgid "Maximum number of campers must be one or greater." msgid "Maximum number of campers must be one or greater."
msgstr "El número màxim de persones no pot ser zero." msgstr "El número màxim de persones no pot ser zero."
#: pkg/campsite/types/admin.go:408 #: pkg/campsite/types/admin.go:410
msgid "Minimum number of nights can not be empty." msgid "Minimum number of nights can not be empty."
msgstr "No podeu deixar el número mínim de nits en blanc." msgstr "No podeu deixar el número mínim de nits en blanc."
#: pkg/campsite/types/admin.go:409 #: pkg/campsite/types/admin.go:411
msgid "Minimum number of nights must be an integer." msgid "Minimum number of nights must be an integer."
msgstr "El número mínim de nits ha de ser enter." msgstr "El número mínim de nits ha de ser enter."
#: pkg/campsite/types/admin.go:410 #: pkg/campsite/types/admin.go:412
msgid "Minimum number of nights must be one or greater." msgid "Minimum number of nights must be one or greater."
msgstr "El número mínim de nits no pot ser zero." msgstr "El número mínim de nits no pot ser zero."

211
po/es.po
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-10-06 22:10+0200\n" "POT-Creation-Date: 2023-10-12 16:26+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"
@ -77,22 +77,22 @@ msgstr "Descubre el entorno"
msgid "Come and enjoy!" msgid "Come and enjoy!"
msgstr "¡Ven a disfrutar!" msgstr "¡Ven a disfrutar!"
#: web/templates/public/campsite/type.gohtml:17 #: web/templates/public/campsite/type.gohtml:40
#: web/templates/admin/campsite/option/form.gohtml:58 #: web/templates/admin/campsite/option/form.gohtml:58
#: web/templates/admin/campsite/type/form.gohtml:73 #: web/templates/admin/campsite/type/form.gohtml:73
msgctxt "title" msgctxt "title"
msgid "Prices" msgid "Prices"
msgstr "Precios" msgstr "Precios"
#: web/templates/public/campsite/type.gohtml:26 #: web/templates/public/campsite/type.gohtml:50
msgid "%s €/night" msgid "%s €/night"
msgstr "%s €/noche" msgstr "%s €/noche"
#: web/templates/public/campsite/type.gohtml:28 #: web/templates/public/campsite/type.gohtml:52
msgid "%s: %s €/night" msgid "%s: %s €/night"
msgstr ":%s: %s €/noche" msgstr ":%s: %s €/noche"
#: web/templates/public/campsite/type.gohtml:31 #: web/templates/public/campsite/type.gohtml:55
msgid "*Minimum %d nights per stay" msgid "*Minimum %d nights per stay"
msgstr "*Mínimo %d noches por estancia" msgstr "*Mínimo %d noches por estancia"
@ -203,11 +203,14 @@ msgstr "Nueva diapositiva del carrusel"
#: web/templates/admin/carousel/form.gohtml:37 #: web/templates/admin/carousel/form.gohtml:37
#: web/templates/admin/carousel/l10n.gohtml:20 #: web/templates/admin/carousel/l10n.gohtml:20
#: web/templates/admin/campsite/carousel/form.gohtml:37
#: web/templates/admin/campsite/carousel/l10n.gohtml:20
msgctxt "input" msgctxt "input"
msgid "Caption" msgid "Caption"
msgstr "Leyenda" msgstr "Leyenda"
#: web/templates/admin/carousel/form.gohtml:47 #: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70 #: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78 #: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:108 #: web/templates/admin/campsite/type/form.gohtml:108
@ -219,6 +222,7 @@ msgid "Update"
msgstr "Actualizar" msgstr "Actualizar"
#: web/templates/admin/carousel/form.gohtml:49 #: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72 #: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80 #: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:110 #: web/templates/admin/campsite/type/form.gohtml:110
@ -235,6 +239,7 @@ msgid "Translate Carousel Slide to %s"
msgstr "Traducción de la diapositiva de carrusel a %s" msgstr "Traducción de la diapositiva de carrusel a %s"
#: web/templates/admin/carousel/l10n.gohtml:21 #: web/templates/admin/carousel/l10n.gohtml:21
#: web/templates/admin/campsite/carousel/l10n.gohtml:21
#: web/templates/admin/campsite/option/l10n.gohtml:21 #: web/templates/admin/campsite/option/l10n.gohtml:21
#: web/templates/admin/campsite/type/l10n.gohtml:21 #: web/templates/admin/campsite/type/l10n.gohtml:21
#: web/templates/admin/campsite/type/l10n.gohtml:33 #: web/templates/admin/campsite/type/l10n.gohtml:33
@ -245,6 +250,7 @@ msgid "Source:"
msgstr "Origen:" msgstr "Origen:"
#: web/templates/admin/carousel/l10n.gohtml:23 #: web/templates/admin/carousel/l10n.gohtml:23
#: web/templates/admin/campsite/carousel/l10n.gohtml:23
#: web/templates/admin/campsite/option/l10n.gohtml:23 #: web/templates/admin/campsite/option/l10n.gohtml:23
#: web/templates/admin/campsite/type/l10n.gohtml:23 #: web/templates/admin/campsite/type/l10n.gohtml:23
#: web/templates/admin/campsite/type/l10n.gohtml:36 #: web/templates/admin/campsite/type/l10n.gohtml:36
@ -256,6 +262,7 @@ msgid "Translation:"
msgstr "Traducción" msgstr "Traducción"
#: web/templates/admin/carousel/l10n.gohtml:32 #: web/templates/admin/carousel/l10n.gohtml:32
#: web/templates/admin/campsite/carousel/l10n.gohtml:32
#: web/templates/admin/campsite/option/l10n.gohtml:32 #: web/templates/admin/campsite/option/l10n.gohtml:32
#: web/templates/admin/campsite/type/l10n.gohtml:45 #: web/templates/admin/campsite/type/l10n.gohtml:45
#: web/templates/admin/season/l10n.gohtml:32 #: web/templates/admin/season/l10n.gohtml:32
@ -264,6 +271,90 @@ msgctxt "action"
msgid "Translate" msgid "Translate"
msgstr "Traducir" msgstr "Traducir"
#: web/templates/admin/campsite/carousel/form.gohtml:8
#: web/templates/admin/campsite/carousel/form.gohtml:25
msgctxt "title"
msgid "Edit Campsite Type Carousel Slide"
msgstr "Edición de la diapositiva del carrusel del tipo de alojamiento"
#: web/templates/admin/campsite/carousel/form.gohtml:10
#: web/templates/admin/campsite/carousel/form.gohtml:27
msgctxt "title"
msgid "New Campsite Type Carousel Slide"
msgstr "Nueva diapositiva del carrusel del tipo de alojamiento"
#: web/templates/admin/campsite/carousel/index.gohtml:6
#: web/templates/admin/campsite/carousel/index.gohtml:11
msgctxt "title"
msgid "Campsite Type Carousel"
msgstr "Carrusel del tipo de alojamiento"
#: web/templates/admin/campsite/carousel/index.gohtml:12
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add slide"
msgstr "Añadir diapositiva"
#: web/templates/admin/campsite/carousel/index.gohtml:17
#: web/templates/admin/services/index.gohtml:17
#: web/templates/admin/home/index.gohtml:17
msgctxt "header"
msgid "Image"
msgstr "Imagen"
#: web/templates/admin/campsite/carousel/index.gohtml:18
#: web/templates/admin/services/index.gohtml:18
#: web/templates/admin/home/index.gohtml:18
msgctxt "header"
msgid "Caption"
msgstr "Leyenda"
#: web/templates/admin/campsite/carousel/index.gohtml:19
#: web/templates/admin/campsite/option/index.gohtml:18
#: web/templates/admin/campsite/type/index.gohtml:18
#: web/templates/admin/season/index.gohtml:19
#: web/templates/admin/services/index.gohtml:19
#: web/templates/admin/services/index.gohtml:60
#: web/templates/admin/home/index.gohtml:19
msgctxt "header"
msgid "Translations"
msgstr "Traducciones"
#: web/templates/admin/campsite/carousel/index.gohtml:20
#: web/templates/admin/services/index.gohtml:20
#: web/templates/admin/services/index.gohtml:61
#: web/templates/admin/home/index.gohtml:20
msgctxt "header"
msgid "Actions"
msgstr "Acciones"
#: web/templates/admin/campsite/carousel/index.gohtml:24
#: web/templates/admin/services/index.gohtml:24
#: web/templates/admin/home/index.gohtml:24
msgid "Are you sure you wish to delete this slide?"
msgstr "¿Estáis seguro de querer borrar esta diapositiva?"
#: web/templates/admin/campsite/carousel/index.gohtml:42
#: web/templates/admin/services/index.gohtml:42
#: web/templates/admin/services/index.gohtml:80
#: web/templates/admin/home/index.gohtml:42
msgctxt "action"
msgid "Delete"
msgstr "Borrar"
#: web/templates/admin/campsite/carousel/index.gohtml:50
#: web/templates/admin/services/index.gohtml:50
#: web/templates/admin/home/index.gohtml:50
msgid "No slides added yet."
msgstr "No se ha añadido ninguna diapositiva todavía."
#: web/templates/admin/campsite/carousel/l10n.gohtml:7
#: web/templates/admin/campsite/carousel/l10n.gohtml:14
msgctxt "title"
msgid "Translate Campsite Type Carousel Slide to %s"
msgstr "Traducción de la diapositiva de carrusel del tipo de alojamiento a %s"
#: web/templates/admin/campsite/form.gohtml:8 #: web/templates/admin/campsite/form.gohtml:8
#: web/templates/admin/campsite/form.gohtml:25 #: web/templates/admin/campsite/form.gohtml:25
msgctxt "title" msgctxt "title"
@ -355,16 +446,6 @@ msgctxt "header"
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: web/templates/admin/campsite/option/index.gohtml:18
#: web/templates/admin/campsite/type/index.gohtml:18
#: web/templates/admin/season/index.gohtml:19
#: web/templates/admin/services/index.gohtml:19
#: web/templates/admin/services/index.gohtml:60
#: web/templates/admin/home/index.gohtml:19
msgctxt "header"
msgid "Translations"
msgstr "Traducciones"
#: web/templates/admin/campsite/option/index.gohtml:39 #: web/templates/admin/campsite/option/index.gohtml:39
msgid "No campsite type options added yet." msgid "No campsite type options added yet."
msgstr "No se ha añadido ninguna opció al tipo de alojamiento todavía." msgstr "No se ha añadido ninguna opció al tipo de alojamiento todavía."
@ -391,13 +472,13 @@ msgid "Type"
msgstr "Tipo" msgstr "Tipo"
#: web/templates/admin/campsite/index.gohtml:28 #: web/templates/admin/campsite/index.gohtml:28
#: web/templates/admin/campsite/type/index.gohtml:37 #: web/templates/admin/campsite/type/index.gohtml:39
#: web/templates/admin/season/index.gohtml:39 #: web/templates/admin/season/index.gohtml:39
msgid "Yes" msgid "Yes"
msgstr "Sí" msgstr "Sí"
#: web/templates/admin/campsite/index.gohtml:28 #: web/templates/admin/campsite/index.gohtml:28
#: web/templates/admin/campsite/type/index.gohtml:37 #: web/templates/admin/campsite/type/index.gohtml:39
#: web/templates/admin/season/index.gohtml:39 #: web/templates/admin/season/index.gohtml:39
msgid "No" msgid "No"
msgstr "No" msgstr "No"
@ -419,7 +500,7 @@ msgid "New Campsite Type"
msgstr "Nuevo tipo de alojamiento" msgstr "Nuevo tipo de alojamiento"
#: web/templates/admin/campsite/type/form.gohtml:37 #: web/templates/admin/campsite/type/form.gohtml:37
#: web/templates/admin/campsite/type/index.gohtml:20 #: web/templates/admin/campsite/type/index.gohtml:21
msgctxt "campsite type" msgctxt "campsite type"
msgid "Active" msgid "Active"
msgstr "Activo" msgstr "Activo"
@ -464,12 +545,22 @@ msgctxt "header"
msgid "Options" msgid "Options"
msgstr "Opciones" msgstr "Opciones"
#: web/templates/admin/campsite/type/index.gohtml:36 #: web/templates/admin/campsite/type/index.gohtml:20
msgctxt "header"
msgid "Carousel"
msgstr "Carrusel"
#: web/templates/admin/campsite/type/index.gohtml:37
msgctxt "action" msgctxt "action"
msgid "Edit Options" msgid "Edit Options"
msgstr "Editar opciones" msgstr "Editar opciones"
#: web/templates/admin/campsite/type/index.gohtml:43 #: web/templates/admin/campsite/type/index.gohtml:38
msgctxt "action"
msgid "Edit Carousel"
msgstr "Editar el carrusel"
#: web/templates/admin/campsite/type/index.gohtml:45
msgid "No campsite types added yet." msgid "No campsite types added yet."
msgstr "No se ha añadido ningún tipo de alojamiento todavía." msgstr "No se ha añadido ningún tipo de alojamiento todavía."
@ -631,48 +722,6 @@ msgctxt "title"
msgid "Carousel" msgid "Carousel"
msgstr "Carrusel" msgstr "Carrusel"
#: web/templates/admin/services/index.gohtml:12
#: web/templates/admin/home/index.gohtml:12
msgctxt "action"
msgid "Add slide"
msgstr "Añadir diapositiva"
#: web/templates/admin/services/index.gohtml:17
#: web/templates/admin/home/index.gohtml:17
msgctxt "header"
msgid "Image"
msgstr "Imagen"
#: web/templates/admin/services/index.gohtml:18
#: web/templates/admin/home/index.gohtml:18
msgctxt "header"
msgid "Caption"
msgstr "Leyenda"
#: web/templates/admin/services/index.gohtml:20
#: web/templates/admin/services/index.gohtml:61
#: web/templates/admin/home/index.gohtml:20
msgctxt "header"
msgid "Actions"
msgstr "Acciones"
#: web/templates/admin/services/index.gohtml:24
#: web/templates/admin/home/index.gohtml:24
msgid "Are you sure you wish to delete this slide?"
msgstr "¿Estáis seguro de querer borrar esta diapositiva?"
#: web/templates/admin/services/index.gohtml:42
#: web/templates/admin/services/index.gohtml:80
#: web/templates/admin/home/index.gohtml:42
msgctxt "action"
msgid "Delete"
msgstr "Borrar"
#: web/templates/admin/services/index.gohtml:50
#: web/templates/admin/home/index.gohtml:50
msgid "No slides added yet."
msgstr "No se ha añadido ninguna diapositiva todavía."
#: web/templates/admin/services/index.gohtml:54 #: web/templates/admin/services/index.gohtml:54
msgctxt "action" msgctxt "action"
msgid "Add service" msgid "Add service"
@ -893,21 +942,21 @@ msgctxt "title"
msgid "Upload Media" msgid "Upload Media"
msgstr "Subida de medio" msgstr "Subida de medio"
#: pkg/carousel/admin.go:233 #: pkg/carousel/admin.go:233 pkg/campsite/types/carousel.go:232
msgctxt "input" msgctxt "input"
msgid "Slide image" msgid "Slide image"
msgstr "Imagen de la diapositiva" msgstr "Imagen de la diapositiva"
#: pkg/carousel/admin.go:234 #: pkg/carousel/admin.go:234 pkg/campsite/types/carousel.go:233
msgctxt "action" msgctxt "action"
msgid "Set slide image" msgid "Set slide image"
msgstr "Establecer la imagen de la diapositiva" msgstr "Establecer la imagen de la diapositiva"
#: pkg/carousel/admin.go:286 #: pkg/carousel/admin.go:286 pkg/campsite/types/carousel.go:287
msgid "Slide image can not be empty." msgid "Slide image can not be empty."
msgstr "No podéis dejar la imagen de la diapositiva en blanco." msgstr "No podéis dejar la imagen de la diapositiva en blanco."
#: pkg/carousel/admin.go:287 #: pkg/carousel/admin.go:287 pkg/campsite/types/carousel.go:288
msgid "Slide image must be an image media type." msgid "Slide image must be an image media type."
msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen." msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen."
@ -934,7 +983,7 @@ msgstr "Automático"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:73 #: pkg/app/user.go:249 pkg/campsite/types/l10n.go:73
#: pkg/campsite/types/l10n.go:128 pkg/campsite/types/option.go:336 #: pkg/campsite/types/l10n.go:128 pkg/campsite/types/option.go:336
#: pkg/campsite/types/admin.go:389 pkg/season/l10n.go:69 #: pkg/campsite/types/admin.go:391 pkg/season/l10n.go:69
#: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266 #: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266
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."
@ -955,7 +1004,7 @@ msgstr "El archivo tiene que ser una imagen PNG o JPEG válida."
msgid "Access forbidden" msgid "Access forbidden"
msgstr "Acceso prohibido" msgstr "Acceso prohibido"
#: pkg/campsite/types/option.go:337 pkg/campsite/types/admin.go:390 #: pkg/campsite/types/option.go:337 pkg/campsite/types/admin.go:392
msgid "Name must have at least one letter." msgid "Name must have at least one letter."
msgstr "El nombre tiene que tener como mínimo una letra." msgstr "El nombre tiene que tener como mínimo una letra."
@ -983,57 +1032,57 @@ msgstr "El valor del máximo tiene que ser un número entero."
msgid "Maximum must be equal or greater than minimum." msgid "Maximum must be equal or greater than minimum."
msgstr "El valor del máximo tiene que ser igual o mayor al del mínimo." msgstr "El valor del máximo tiene que ser igual o mayor al del mínimo."
#: pkg/campsite/types/option.go:353 pkg/campsite/types/admin.go:403 #: pkg/campsite/types/option.go:353 pkg/campsite/types/admin.go:405
msgid "Price per night can not be empty." msgid "Price per night can not be empty."
msgstr "No podéis dejar el precio por noche en blanco." msgstr "No podéis dejar el precio por noche en blanco."
#: pkg/campsite/types/option.go:354 pkg/campsite/types/admin.go:404 #: pkg/campsite/types/option.go:354 pkg/campsite/types/admin.go:406
msgid "Price per night must be a decimal number." msgid "Price per night must be a decimal number."
msgstr "El precio por noche tien que ser un número decimal." msgstr "El precio por noche tien que ser un número decimal."
#: pkg/campsite/types/option.go:355 pkg/campsite/types/admin.go:405 #: pkg/campsite/types/option.go:355 pkg/campsite/types/admin.go:407
msgid "Price per night must be zero or greater." msgid "Price per night must be zero or greater."
msgstr "El precio por noche tiene que ser como mínimo cero." msgstr "El precio por noche tiene que ser como mínimo cero."
#: pkg/campsite/types/admin.go:280 #: pkg/campsite/types/admin.go:282
msgctxt "input" msgctxt "input"
msgid "Cover image" msgid "Cover image"
msgstr "Imagen de portada" msgstr "Imagen de portada"
#: pkg/campsite/types/admin.go:281 #: pkg/campsite/types/admin.go:283
msgctxt "action" msgctxt "action"
msgid "Set campsite type cover" msgid "Set campsite type cover"
msgstr "Establecer la portada del tipo de alojamiento" msgstr "Establecer la portada del tipo de alojamiento"
#: pkg/campsite/types/admin.go:392 #: pkg/campsite/types/admin.go:394
msgid "Cover image can not be empty." msgid "Cover image can not be empty."
msgstr "No podéis dejar la imagen de portada en blanco." msgstr "No podéis dejar la imagen de portada en blanco."
#: pkg/campsite/types/admin.go:393 #: pkg/campsite/types/admin.go:395
msgid "Cover image must be an image media type." msgid "Cover image must be an image media type."
msgstr "La imagen de portada tiene que ser un medio de tipo imagen." msgstr "La imagen de portada tiene que ser un medio de tipo imagen."
#: pkg/campsite/types/admin.go:397 #: pkg/campsite/types/admin.go:399
msgid "Maximum number of campers can not be empty." msgid "Maximum number of campers can not be empty."
msgstr "No podéis dejar el número máximo de personas en blanco." msgstr "No podéis dejar el número máximo de personas en blanco."
#: pkg/campsite/types/admin.go:398 #: pkg/campsite/types/admin.go:400
msgid "Maximum number of campers must be an integer number." msgid "Maximum number of campers must be an integer number."
msgstr "El número máximo de personas tiene que ser entero." msgstr "El número máximo de personas tiene que ser entero."
#: pkg/campsite/types/admin.go:399 #: pkg/campsite/types/admin.go:401
msgid "Maximum number of campers must be one or greater." msgid "Maximum number of campers must be one or greater."
msgstr "El número máximo de personas no puede ser cero." msgstr "El número máximo de personas no puede ser cero."
#: pkg/campsite/types/admin.go:408 #: pkg/campsite/types/admin.go:410
msgid "Minimum number of nights can not be empty." msgid "Minimum number of nights can not be empty."
msgstr "No podéis dejar el número mínimo de noches en blanco." msgstr "No podéis dejar el número mínimo de noches en blanco."
#: pkg/campsite/types/admin.go:409 #: pkg/campsite/types/admin.go:411
msgid "Minimum number of nights must be an integer." msgid "Minimum number of nights must be an integer."
msgstr "El número mínimo de noches tiene que ser entero." msgstr "El número mínimo de noches tiene que ser entero."
#: pkg/campsite/types/admin.go:410 #: pkg/campsite/types/admin.go:412
msgid "Minimum number of nights must be one or greater." msgid "Minimum number of nights must be one or greater."
msgstr "El número mínimo de noches no puede ser cero." msgstr "El número mínimo de noches no puede ser cero."

View File

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

View File

@ -0,0 +1,7 @@
-- Revert camper:campsite_type_carousel from pg
begin;
drop table if exists camper.campsite_type_carousel;
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:campsite_type_carousel_i18n from pg
begin;
drop table if exists camper.campsite_type_carousel_i18n;
commit;

View File

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

View File

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

View File

@ -99,3 +99,8 @@ add_campsite_type_option [roles schema_camper campsite_type_option campsite_type
edit_campsite_type_option [roles schema_camper campsite_type_option] 2023-10-06T09:51:02Z jordi fita mas <jordi@tandem.blog> # Add function to edit campsite type options edit_campsite_type_option [roles schema_camper campsite_type_option] 2023-10-06T09:51:02Z jordi fita mas <jordi@tandem.blog> # Add function to edit campsite type options
extension_postgis [schema_public] 2023-10-06T17:18:52Z jordi fita mas <jordi@tandem.blog> # Add PostGIS extension extension_postgis [schema_public] 2023-10-06T17:18:52Z jordi fita mas <jordi@tandem.blog> # Add PostGIS extension
company_geography [roles schema_camper company user_profile extension_postgis] 2023-10-06T17:53:34Z jordi fita mas <jordi@tandem.blog> # Add the relation for the GPS coordinates of companies company_geography [roles schema_camper company user_profile extension_postgis] 2023-10-06T17:53:34Z jordi fita mas <jordi@tandem.blog> # Add the relation for the GPS coordinates of companies
campsite_type_carousel [roles schema_camper campsite_type media user_profile] 2023-10-09T17:18:38Z jordi fita mas <jordi@tandem.blog> # Add the carousel for campsite types
campsite_type_carousel_i18n [roles schema_camper campsite_type_carousel language] 2023-10-09T17:45:09Z jordi fita mas <jordi@tandem.blog> # Add relation for campsite type carousel translations
add_campsite_type_carousel_slide [roles schema_camper campsite_type_carousel campsite_type] 2023-10-09T17:59:49Z jordi fita mas <jordi@tandem.blog> # Add function to create slides for the campsite type carousel
translate_campsite_type_carousel_slide [roles schema_camper campsite_type campsite_type_carousel_i18n] 2023-10-09T18:17:13Z jordi fita mas <jordi@tandem.blog> # Add function to translate a campsite type slide
remove_campsite_type_carousel_slide [roles schema_camper campsite_type_carousel campsite_type_carousel_i18n] 2023-10-09T18:26:49Z jordi fita mas <jordi@tandem.blog> # Add function to remove campsite type slides

View File

@ -0,0 +1,94 @@
-- Test add_campsite_type_carousel_slide
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(14);
set search_path to camper, public;
select has_function('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text']);
select function_lang_is('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'sql');
select function_returns('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'integer');
select isnt_definer('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text']);
select volatility_is('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'volatile');
select function_privs_are('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'guest', array[]::text[]);
select function_privs_are('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'employee', array[]::text[]);
select function_privs_are('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'add_campsite_type_carousel_slide', array['uuid', 'integer', 'text'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate campsite_type_carousel_i18n cascade;
truncate campsite_type_carousel cascade;
truncate campsite_type cascade;
truncate media cascade;
truncate media_content 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 media_content (media_type, bytes)
values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ff00ff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffff00","a"};')
, ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
;
insert into media (media_id, company_id, original_filename, content_hash)
values (2, 1, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
, (3, 1, 'cover3.xpm', sha256('static char *s[]={"1 1 1 1","a c #ff00ff","a"};'))
, (4, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffff00","a"};'))
, (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
;
insert into campsite_type (campsite_type_id, company_id, slug, media_id, name, description, max_campers, dogs_allowed, active)
values (10, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 2, 'Type A', '<p>A</p>', 5, false, true)
, (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '<p>B</p>', 4, true, false)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (10, 4, 'A caption')
;
select lives_ok(
$$ select add_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 5, 'A caption') $$,
'Should be able to add a carousel slide with a caption'
);
select lives_ok(
$$ select add_campsite_type_carousel_slide('9b6370f7-f941-46f2-bc6e-de455675bd0a', 6, null) $$,
'Should be able to add a carousel slide without caption'
);
select lives_ok(
$$ select add_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 4, 'New caption') $$,
'Should be able to overwrite a slide with a new caption'
);
select bag_eq(
$$ select campsite_type_id, media_id, caption from campsite_type_carousel $$,
$$ values (10, 4, 'New caption')
, (10, 5, 'A caption')
, (11, 6, '')
$$,
'Should have all three slides'
);
select is_empty(
$$ select * from campsite_type_carousel_i18n $$,
'Should not have added any translation'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,223 @@
-- Test campsite_type_carousel
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(40);
set search_path to camper, public;
select has_table('campsite_type_carousel');
select has_pk('campsite_type_carousel');
select col_is_pk('campsite_type_carousel', array['campsite_type_id', 'media_id']);
select table_privs_are('campsite_type_carousel', 'guest', array['SELECT']);
select table_privs_are('campsite_type_carousel', 'employee', array['SELECT']);
select table_privs_are('campsite_type_carousel', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('campsite_type_carousel', 'authenticator', array[]::text[]);
select has_column('campsite_type_carousel', 'campsite_type_id');
select col_is_fk('campsite_type_carousel', 'campsite_type_id');
select fk_ok('campsite_type_carousel', 'campsite_type_id', 'campsite_type', 'campsite_type_id');
select col_type_is('campsite_type_carousel', 'campsite_type_id', 'integer');
select col_not_null('campsite_type_carousel', 'campsite_type_id');
select col_hasnt_default('campsite_type_carousel', 'campsite_type_id');
select has_column('campsite_type_carousel', 'media_id');
select col_is_fk('campsite_type_carousel', 'media_id');
select fk_ok('campsite_type_carousel', 'media_id', 'media', 'media_id');
select col_type_is('campsite_type_carousel', 'media_id', 'integer');
select col_not_null('campsite_type_carousel', 'media_id');
select col_hasnt_default('campsite_type_carousel', 'media_id');
select has_column('campsite_type_carousel', 'caption');
select col_type_is('campsite_type_carousel', 'caption', 'text');
select col_not_null('campsite_type_carousel', 'caption');
select col_hasnt_default('campsite_type_carousel', 'caption');
set client_min_messages to warning;
truncate campsite_type_carousel cascade;
truncate campsite_type cascade;
truncate media cascade;
truncate media_content cascade;
truncate company_host cascade;
truncate company_user cascade;
truncate company cascade;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
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 (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca')
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', 'FR', 'USD', 'ca')
;
insert into company_user (company_id, user_id, role)
values (2, 1, 'admin')
, (4, 5, 'admin')
;
insert into company_host (company_id, host)
values (2, 'co2')
, (4, 'co4')
;
insert into media_content (media_type, bytes)
values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
, ('text/plain', 'content2')
, ('text/plain', 'content3')
, ('text/plain', 'content4')
, ('text/plain', 'content5')
;
insert into media (media_id, company_id, original_filename, content_hash)
values ( 6, 2, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
, ( 7, 2, 'text2.txt', sha256('content2'))
, ( 8, 2, 'text3.txt', sha256('content3'))
, ( 9, 4, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
, (10, 4, 'text4.txt', sha256('content4'))
, (11, 4, 'text5.txt', sha256('content5'))
;
insert into campsite_type (campsite_type_id, company_id, name, media_id, dogs_allowed, max_campers)
values (21, 2, 'Wooden lodge', 6, false, 7)
, (22, 4, 'Bungalow', 9, false, 6)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
;
prepare carousel_data as
select campsite_type_id, media_id, caption
from campsite_type_carousel
;
set role guest;
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
$$,
'Everyone should be able to list all campsite slides across all companies'
);
reset role;
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog', 'co2');
select lives_ok(
$$ insert into campsite_type_carousel(campsite_type_id, media_id, caption) values (21, 8, 'Caption 8') $$,
'Admin from company 2 should be able to insert a new slide to campsite types of that company.'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (21, 8, 'Caption 8')
, (22, 10, 'Caption 10')
$$,
'The new row should have been added'
);
select lives_ok(
$$ update campsite_type_carousel set caption = 'Caption 8.8' where media_id = 8 $$,
'Admin from company 2 should be able to update campsite type slides of that company.'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (21, 8, 'Caption 8.8')
, (22, 10, 'Caption 10')
$$,
'The row should have been updated.'
);
select lives_ok(
$$ delete from campsite_type_carousel where media_id = 8 $$,
'Admin from company 2 should be able to delete campsite type slides from that company.'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
$$,
'The row should have been deleted.'
);
select throws_ok(
$$ insert into campsite_type_carousel (campsite_type_id, media_id, caption) values (21, 10, 'Nope') $$,
'42501', 'new row violates row-level security policy for table "campsite_type_carousel"',
'Admin from company 2 should NOT be able to insert a new campsite types slide from a media of company 4.'
);
select throws_ok(
$$ insert into campsite_type_carousel (campsite_type_id, media_id, caption) values (22, 8, 'Nope') $$,
'42501', 'new row violates row-level security policy for table "campsite_type_carousel"',
'Admin from company 2 should NOT be able to insert a slide to a campsite type of company 4.'
);
select lives_ok(
$$ update campsite_type_carousel set caption = 'Nope' where campsite_type_id = 22 $$,
'Admin from company 2 should not be able to update slides of campsite types from company 4, but no error if campsite_type_id or media_id is not changed.'
);
select lives_ok(
$$ update campsite_type_carousel set caption = 'Nope' where media_id = 10 $$,
'Admin from company 2 should not be able to update slides of campsite types from company 4, but no error if campsite_type_id or media_id is not changed.'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
$$,
'No row should have been changed.'
);
select throws_ok(
$$ update campsite_type_carousel set campsite_type_id = 22 where campsite_type_id = 21 $$,
'42501', 'new row violates row-level security policy for table "campsite_type_carousel"',
'Admin from company 2 should NOT be able to move slides to campsite types of company 4'
);
select throws_ok(
$$ update campsite_type_carousel set media_id = 11 where media_id = 7 $$,
'42501', 'new row violates row-level security policy for table "campsite_type_carousel"',
'Admin from company 2 should NOT be able to use media from company 4'
);
select lives_ok(
$$ delete from campsite_type_carousel where campsite_type_id = 22 $$,
'Admin from company 2 should NOT be able to delete slides of campsite types from company 4, but not error is thrown'
);
select lives_ok(
$$ delete from campsite_type_carousel where media_id = 10 $$,
'Admin from company 2 should NOT be able to delete slides with media from company 4, but not error is thrown'
);
select bag_eq(
'carousel_data',
$$ values (21, 7, 'Caption 7')
, (22, 10, 'Caption 10')
$$,
'No row should have been changed'
);
reset role;
select *
from finish();
rollback;

View File

@ -0,0 +1,49 @@
-- Test campsite_type_carousel_i18n
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(27);
set search_path to camper, public;
select has_table('campsite_type_carousel_i18n');
select has_pk('campsite_type_carousel_i18n');
select col_is_pk('campsite_type_carousel_i18n', array['campsite_type_id', 'media_id', 'lang_tag']);
select col_is_fk('campsite_type_carousel_i18n', array['campsite_type_id', 'media_id']);
select fk_ok('campsite_type_carousel_i18n', array['campsite_type_id', 'media_id'], 'campsite_type_carousel', array['campsite_type_id', 'media_id']);
select table_privs_are('campsite_type_carousel_i18n', 'guest', array['SELECT']);
select table_privs_are('campsite_type_carousel_i18n', 'employee', array['SELECT']);
select table_privs_are('campsite_type_carousel_i18n', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('campsite_type_carousel_i18n', 'authenticator', array[]::text[]);
select has_column('campsite_type_carousel_i18n', 'campsite_type_id');
select col_type_is('campsite_type_carousel_i18n', 'campsite_type_id', 'integer');
select col_not_null('campsite_type_carousel_i18n', 'campsite_type_id');
select col_hasnt_default('campsite_type_carousel_i18n', 'campsite_type_id');
select has_column('campsite_type_carousel_i18n', 'media_id');
select col_type_is('campsite_type_carousel_i18n', 'media_id', 'integer');
select col_not_null('campsite_type_carousel_i18n', 'media_id');
select col_hasnt_default('campsite_type_carousel_i18n', 'media_id');
select has_column('campsite_type_carousel_i18n', 'lang_tag');
select col_is_fk('campsite_type_carousel_i18n', 'lang_tag');
select fk_ok('campsite_type_carousel_i18n', 'lang_tag', 'language', 'lang_tag');
select col_type_is('campsite_type_carousel_i18n', 'lang_tag', 'text');
select col_not_null('campsite_type_carousel_i18n', 'lang_tag');
select col_hasnt_default('campsite_type_carousel_i18n', 'lang_tag');
select has_column('campsite_type_carousel_i18n', 'caption');
select col_type_is('campsite_type_carousel_i18n', 'caption', 'text');
select col_not_null('campsite_type_carousel_i18n', 'caption');
select col_hasnt_default('campsite_type_carousel_i18n', 'caption');
select *
from finish();
rollback;

View File

@ -0,0 +1,106 @@
-- Test remove_campsite_type_carousel_slide
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(13);
set search_path to camper, public;
select has_function('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer']);
select function_lang_is('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'plpgsql');
select function_returns('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'void');
select isnt_definer('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer']);
select volatility_is('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'volatile');
select function_privs_are('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'guest', array[]::text[]);
select function_privs_are('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'employee', array[]::text[]);
select function_privs_are('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'remove_campsite_type_carousel_slide', array['uuid', 'integer'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate campsite_type_carousel_i18n cascade;
truncate campsite_type_carousel cascade;
truncate media cascade;
truncate media_content 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 media_content (media_type, bytes)
values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ff00ff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffff00","a"};')
, ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
;
insert into media (media_id, company_id, original_filename, content_hash)
values (2, 1, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
, (3, 1, 'cover3.xpm', sha256('static char *s[]={"1 1 1 1","a c #ff00ff","a"};'))
, (4, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffff00","a"};'))
, (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
;
insert into campsite_type (campsite_type_id, company_id, slug, media_id, name, description, max_campers, dogs_allowed, active)
values (10, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 2, 'Type A', '<p>A</p>', 5, false, true)
, (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '<p>B</p>', 4, true, false)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (10, 3, 'Source caption')
, (10, 4, 'Source caption')
, (11, 5, 'Another caption')
, (11, 6, 'N/A')
;
insert into campsite_type_carousel_i18n (campsite_type_id, media_id, lang_tag, caption)
values (10, 3, 'en', 'Target caption')
, (10, 3, 'es', 'Target caption (spanish)')
, (10, 4, 'en', 'Target caption')
, (10, 4, 'es', 'Target caption (spanish)')
, (11, 5, 'en', 'Target caption')
, (11, 5, 'es', 'Target caption (spanish)')
, (11, 6, 'en', 'Target caption')
, (11, 6, 'es', 'Target caption (spanish)')
;
select lives_ok(
$$ select remove_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 3) $$,
'Should be able to delete a slide from the first campsite_type'
);
select lives_ok(
$$ select remove_campsite_type_carousel_slide('9b6370f7-f941-46f2-bc6e-de455675bd0a', 6) $$,
'Should be able to delete a slide from the first campsite_type'
);
select bag_eq(
$$ select campsite_type_id, media_id, caption from campsite_type_carousel $$,
$$ values (10, 4, 'Source caption')
, (11, 5, 'Another caption')
$$,
'Should have removed the slides'
);
select bag_eq(
$$ select campsite_type_id, media_id, lang_tag, caption from campsite_type_carousel_i18n $$,
$$ values (10, 4, 'en', 'Target caption')
, (10, 4, 'es', 'Target caption (spanish)')
, (11, 5, 'en', 'Target caption')
, (11, 5, 'es', 'Target caption (spanish)')
$$,
'Should have removed the slides translations'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,94 @@
-- Test translate_campsite_type_carousel_slide
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(13);
set search_path to camper, public;
select has_function('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text']);
select function_lang_is('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'sql');
select function_returns('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'void');
select isnt_definer('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text']);
select volatility_is('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'volatile');
select function_privs_are('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'guest', array[]::text[]);
select function_privs_are('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'employee', array[]::text[]);
select function_privs_are('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'translate_campsite_type_carousel_slide', array['uuid', 'integer', 'text', 'text'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate campsite_type_carousel_i18n cascade;
truncate campsite_type_carousel cascade;
truncate media cascade;
truncate media_content 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 media_content (media_type, bytes)
values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ff00ff","a"};')
, ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffff00","a"};')
, ('text/plain', 'hello, world!')
, ('image/svg+xml', '<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>')
;
insert into media (media_id, company_id, original_filename, content_hash)
values (2, 1, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
, (3, 1, 'cover3.xpm', sha256('static char *s[]={"1 1 1 1","a c #ff00ff","a"};'))
, (4, 1, 'cover4.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffff00","a"};'))
, (5, 1, 'text.txt', sha256('hello, world!'))
, (6, 1, 'image.svg', sha256('<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>'))
;
insert into campsite_type (campsite_type_id, company_id, slug, media_id, name, description, max_campers, dogs_allowed, active)
values (10, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 2, 'Type A', '<p>A</p>', 5, false, true)
, (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '<p>B</p>', 4, true, false)
;
insert into campsite_type_carousel (campsite_type_id, media_id, caption)
values (10, 4, 'Source caption')
, (11, 5, 'Another caption')
, (11, 6, 'N/A')
;
insert into campsite_type_carousel_i18n (campsite_type_id, media_id, lang_tag, caption)
values (10, 4, 'en', 'Target caption')
;
select lives_ok(
$$ select translate_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 4, 'ca', 'Traducció') $$,
'Should be able to translate a carousel slide'
);
select lives_ok(
$$ select translate_campsite_type_carousel_slide('9b6370f7-f941-46f2-bc6e-de455675bd0a', 5, 'es', null) $$,
'Should be able to “translate” a carousel slide to the empty string'
);
select lives_ok(
$$ select translate_campsite_type_carousel_slide('87452b88-b48f-48d3-bb6c-0296de64164e', 4, 'en', 'Not anymore') $$,
'Should be able to overwrite a slides translation'
);
select bag_eq(
$$ select campsite_type_id, media_id, lang_tag, caption from campsite_type_carousel_i18n $$,
$$ values (10, 4, 'ca', 'Traducció')
, (10, 4, 'en', 'Not anymore')
, (11, 5, 'es', '')
$$,
'Should have all three slides'
);
select *
from finish();
rollback;

View File

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

View File

@ -0,0 +1,17 @@
-- Verify camper:campsite_type_carousel on pg
begin;
select campsite_type_id
, media_id
, caption
from camper.campsite_type_carousel
where false;
select 1 / count(*) from pg_class where oid = 'camper.campsite_type_carousel'::regclass and relrowsecurity;
select 1 / count(*) from pg_policy where polname = 'guest_ok' and polrelid = 'camper.campsite_type_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'insert_to_company' and polrelid = 'camper.campsite_type_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'update_company' and polrelid = 'camper.campsite_type_carousel'::regclass;
select 1 / count(*) from pg_policy where polname = 'delete_from_company' and polrelid = 'camper.campsite_type_carousel'::regclass;
rollback;

View File

@ -0,0 +1,12 @@
-- Verify camper:campsite_type_carousel_i18n on pg
begin;
select campsite_type_id
, media_id
, lang_tag
, caption
from camper.campsite_type_carousel_i18n
where false;
rollback;

View File

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

View File

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

View File

@ -584,7 +584,7 @@ dt {
margin-top: 2rem; margin-top: 2rem;
} }
.outside_activities h3, .campsite_services .spiel { .outside_activities h3, .campsite_services .spiel, .campsite_type .spiel {
font-size: calc(2.2rem + 4vw); font-size: calc(2.2rem + 4vw);
font-weight: 600; font-weight: 600;
line-height: .9em; line-height: .9em;
@ -662,7 +662,7 @@ dt {
margin-bottom: 5rem; margin-bottom: 5rem;
} }
.campsite_services.carousel .slick-track { .campsite_services.carousel .slick-track, .campsite_type.carousel .slick-track {
align-items: center; align-items: center;
} }

View File

@ -0,0 +1,54 @@
<!--
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.slideForm*/ -}}
{{ if .MediaID }}
{{( pgettext "Edit Campsite Type Carousel Slide" "title" )}}
{{ else }}
{{( pgettext "New Campsite Type Carousel Slide" "title" )}}
{{ end }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.slideForm*/ -}}
<form
{{ if .MediaID }}
data-hx-put="/admin/campsites/types/{{ .TypeSlug }}/slides/{{ .MediaID }}"
{{ else }}
action="/admin/campsites/types/{{ .TypeSlug }}/slides" method="post"
{{ end }}
>
<h2>
{{ if .MediaID }}
{{( pgettext "Edit Campsite Type Carousel Slide" "title" )}}
{{ else }}
{{( pgettext "New Campsite Type Carousel Slide" "title" )}}
{{ end }}
</h2>
{{ CSRFInput }}
<fieldset>
{{ with .Media -}}
{{ template "media-picker" . }}
{{- end }}
{{ with .Caption -}}
<label>
{{( pgettext "Caption" "input")}}<br>
<input type="text" name="{{ .Name }}" value="{{ .Val }}"
{{ template "error-attrs" . }}><br>
</label>
{{ template "error-message" . }}
{{- end }}
</fieldset>
<footer>
<button type="submit">
{{ if .MediaID }}
{{( pgettext "Update" "action" )}}
{{ else }}
{{( pgettext "Add" "action" )}}
{{ end }}
</button>
</footer>
</form>
{{- end }}

View File

@ -0,0 +1,52 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Campsite Type Carousel" "title" )}}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.carouselIndex*/ -}}
<h2>{{( pgettext "Campsite Type Carousel" "title" )}}</h2>
<a href="/admin/campsites/types/{{ .TypeSlug }}/slides/new">{{( pgettext "Add slide" "action" )}}</a>
{{ if .Slides -}}
<table>
<thead>
<tr>
<th scope="col">{{( pgettext "Image" "header" )}}</th>
<th scope="col">{{( pgettext "Caption" "header" )}}</th>
<th scope="col">{{( pgettext "Translations" "header" )}}</th>
<th scope="col">{{( pgettext "Actions" "header" )}}</th>
</tr>
</thead>
<tbody>
{{ $confirm := (gettext "Are you sure you wish to delete this slide?")}}
{{ range $slide := .Slides -}}
<tr>
<td><a href="/admin/campsites/types/{{ $.TypeSlug }}/slides/{{ .ID }}"><img src="{{ .Media }}" alt=""></a></td>
<td><a href="/admin/campsites/types/{{ $.TypeSlug }}/slides/{{ .ID }}">{{ .Caption }}</a></td>
<td>
{{ range .Translations }}
<a
{{ if .Missing }}
class="missing-translation"
{{ end }}
href="/admin/campsites/types/{{ $.TypeSlug }}/slides/{{ $slide.ID }}/{{ .Language }}">{{ .Endonym }}</a>
{{ end }}
</td>
<td>
<form data-hx-delete="/admin/campsites/types/{{ $.TypeSlug }}/slides/{{ .ID }}"
data-hx-confirm="{{ $confirm }}"
data-hx-headers='{ {{ CSRFHeader }} }'>
<button type="submit">{{( pgettext "Delete" "action" )}}</button>
</form>
</td>
</tr>
{{- end }}
</tbody>
</table>
{{ else -}}
<p>{{( gettext "No slides added yet." )}}</p>
{{- end }}
{{- end }}

View File

@ -0,0 +1,35 @@
<!--
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.slideL10nForm*/ -}}
{{printf (pgettext "Translate Campsite Type Carousel Slide to %s" "title") .Locale.Endonym }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.slideL10nForm*/ -}}
<form data-hx-put="/admin/campsites/types/{{ .TypeSlug }}/slides/{{ .MediaID }}/{{ .Locale.Language }}">
<h2>
{{printf (pgettext "Translate Campsite Type Carousel Slide to %s" "title") .Locale.Endonym }}
</h2>
{{ CSRFInput }}
<fieldset>
{{ with .Caption -}}
<fieldset>
<legend>{{( pgettext "Caption" "input")}}</legend>
{{( gettext "Source:" )}} {{ .Source }}<br>
<label>
{{( pgettext "Translation:" "input" )}}
<input type="text" name="{{ .Name }}" value="{{ .Val }}"
{{ template "error-attrs" . }}><br>
</label>
{{ template "error-message" . }}
</fieldset>
{{- end }}
</fieldset>
<footer>
<button type="submit">{{( pgettext "Translate" "action" )}}</button>
</footer>
</form>
{{- end }}

View File

@ -17,6 +17,7 @@
<th scope="col">{{( pgettext "Name" "header" )}}</th> <th scope="col">{{( pgettext "Name" "header" )}}</th>
<th scope="col">{{( pgettext "Translations" "header" )}}</th> <th scope="col">{{( pgettext "Translations" "header" )}}</th>
<th scope="col">{{( pgettext "Options" "header" )}}</th> <th scope="col">{{( pgettext "Options" "header" )}}</th>
<th scope="col">{{( pgettext "Carousel" "header" )}}</th>
<th scope="col">{{( pgettext "Active" "campsite type" )}}</th> <th scope="col">{{( pgettext "Active" "campsite type" )}}</th>
</tr> </tr>
</thead> </thead>
@ -34,6 +35,7 @@
{{ end }} {{ end }}
</td> </td>
<td><a href="/admin/campsites/types/{{ .Slug }}/options">{{( pgettext "Edit Options" "action" )}}</a></td> <td><a href="/admin/campsites/types/{{ .Slug }}/options">{{( pgettext "Edit Options" "action" )}}</a></td>
<td><a href="/admin/campsites/types/{{ .Slug }}/slides">{{( pgettext "Edit Carousel" "action" )}}</a></td>
<td>{{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td> <td>{{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
</tr> </tr>
{{- end }} {{- end }}

View File

@ -7,9 +7,32 @@
{{ .Name }} {{ .Name }}
{{- end }} {{- end }}
{{ define "head" -}}
{{ template "carouselStyle" }}
{{- end }}
{{ define "content" -}} {{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.publicPage*/ -}} {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.publicPage*/ -}}
<h2>{{ .Name }}</h2> <h2>{{ .Name }}</h2>
{{ with .Carousel -}}
<div class="campsite_type carousel">
<div class="spiel">
<p>TODO</p>
</div>
{{ range . -}}
{{ if .Caption -}}
<figure>
<img src="{{ .Media }}" alt=""/>
<figcaption>{{ .Caption }}</figcaption>
</figure>
{{- else -}}
<img src="{{ .Media }}" alt=""/>
{{- end }}
{{- end }}
</div>
{{- end }}
{{ .Description }} {{ .Description }}
{{ with .Prices -}} {{ with .Prices -}}
@ -34,4 +57,7 @@
</dl> </dl>
</article> </article>
{{- end }} {{- end }}
{{ template "carouselInit" }}
{{- end }} {{- end }}