diff --git a/demo/bungalows_carousel2.avif b/demo/bungalows_carousel2.avif new file mode 100644 index 0000000..5954ed9 Binary files /dev/null and b/demo/bungalows_carousel2.avif differ diff --git a/demo/bungalows_carousel3.avif b/demo/bungalows_carousel3.avif new file mode 100644 index 0000000..644857c Binary files /dev/null and b/demo/bungalows_carousel3.avif differ diff --git a/demo/bungalows_carousel4.avif b/demo/bungalows_carousel4.avif new file mode 100644 index 0000000..d5c9f86 Binary files /dev/null and b/demo/bungalows_carousel4.avif differ diff --git a/demo/bungalows_carousel5.avif b/demo/bungalows_carousel5.avif new file mode 100644 index 0000000..ce2b7c1 Binary files /dev/null and b/demo/bungalows_carousel5.avif differ diff --git a/demo/bungalows_carousel6.avif b/demo/bungalows_carousel6.avif new file mode 100644 index 0000000..f49aed3 Binary files /dev/null and b/demo/bungalows_carousel6.avif differ diff --git a/demo/demo.sql b/demo/demo.sql index 9068e7c..5fbe04e 100644 --- a/demo/demo.sql +++ b/demo/demo.sql @@ -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_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, '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) @@ -370,4 +394,115 @@ values (102, 92, 795) , (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; diff --git a/demo/safari_tents_carousel1.avif b/demo/safari_tents_carousel1.avif new file mode 100644 index 0000000..0030d92 Binary files /dev/null and b/demo/safari_tents_carousel1.avif differ diff --git a/demo/safari_tents_carousel2.avif b/demo/safari_tents_carousel2.avif new file mode 100644 index 0000000..c1b1060 Binary files /dev/null and b/demo/safari_tents_carousel2.avif differ diff --git a/demo/safari_tents_carousel3.avif b/demo/safari_tents_carousel3.avif new file mode 100644 index 0000000..21f9bb4 Binary files /dev/null and b/demo/safari_tents_carousel3.avif differ diff --git a/demo/safari_tents_carousel4.avif b/demo/safari_tents_carousel4.avif new file mode 100644 index 0000000..1eb5579 Binary files /dev/null and b/demo/safari_tents_carousel4.avif differ diff --git a/demo/safari_tents_carousel5.avif b/demo/safari_tents_carousel5.avif new file mode 100644 index 0000000..9646220 Binary files /dev/null and b/demo/safari_tents_carousel5.avif differ diff --git a/demo/safari_tents_carousel6.avif b/demo/safari_tents_carousel6.avif new file mode 100644 index 0000000..ac847d3 Binary files /dev/null and b/demo/safari_tents_carousel6.avif differ diff --git a/demo/wooden_lodges_carousel0.avif b/demo/wooden_lodges_carousel0.avif new file mode 100644 index 0000000..1045b84 Binary files /dev/null and b/demo/wooden_lodges_carousel0.avif differ diff --git a/demo/wooden_lodges_carousel1.avif b/demo/wooden_lodges_carousel1.avif new file mode 100644 index 0000000..74321c7 Binary files /dev/null and b/demo/wooden_lodges_carousel1.avif differ diff --git a/demo/wooden_lodges_carousel2.avif b/demo/wooden_lodges_carousel2.avif new file mode 100644 index 0000000..b759942 Binary files /dev/null and b/demo/wooden_lodges_carousel2.avif differ diff --git a/demo/wooden_lodges_carousel3.avif b/demo/wooden_lodges_carousel3.avif new file mode 100644 index 0000000..ded2d71 Binary files /dev/null and b/demo/wooden_lodges_carousel3.avif differ diff --git a/demo/wooden_lodges_carousel4.avif b/demo/wooden_lodges_carousel4.avif new file mode 100644 index 0000000..7c0a618 Binary files /dev/null and b/demo/wooden_lodges_carousel4.avif differ diff --git a/demo/wooden_lodges_carousel5.avif b/demo/wooden_lodges_carousel5.avif new file mode 100644 index 0000000..5305e10 Binary files /dev/null and b/demo/wooden_lodges_carousel5.avif differ diff --git a/demo/wooden_lodges_carousel6.avif b/demo/wooden_lodges_carousel6.avif new file mode 100644 index 0000000..adb3128 Binary files /dev/null and b/demo/wooden_lodges_carousel6.avif differ diff --git a/demo/wooden_lodges_carousel7.avif b/demo/wooden_lodges_carousel7.avif new file mode 100644 index 0000000..e0ad6f2 Binary files /dev/null and b/demo/wooden_lodges_carousel7.avif differ diff --git a/demo/wooden_lodges_carousel8.avif b/demo/wooden_lodges_carousel8.avif new file mode 100644 index 0000000..193d817 Binary files /dev/null and b/demo/wooden_lodges_carousel8.avif differ diff --git a/demo/wooden_lodges_carousel9.avif b/demo/wooden_lodges_carousel9.avif new file mode 100644 index 0000000..f744ccd Binary files /dev/null and b/demo/wooden_lodges_carousel9.avif differ diff --git a/demo/wooden_lodges_carouselA.avif b/demo/wooden_lodges_carouselA.avif new file mode 100644 index 0000000..512f2c9 Binary files /dev/null and b/demo/wooden_lodges_carouselA.avif differ diff --git a/demo/wooden_lodges_carouselB.avif b/demo/wooden_lodges_carouselB.avif new file mode 100644 index 0000000..ab04646 Binary files /dev/null and b/demo/wooden_lodges_carouselB.avif differ diff --git a/demo/wooden_lodges_carouselC.avif b/demo/wooden_lodges_carouselC.avif new file mode 100644 index 0000000..c2d49b0 Binary files /dev/null and b/demo/wooden_lodges_carouselC.avif differ diff --git a/deploy/add_campsite_type_carousel_slide.sql b/deploy/add_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..e4229e8 --- /dev/null +++ b/deploy/add_campsite_type_carousel_slide.sql @@ -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; diff --git a/deploy/campsite_type_carousel.sql b/deploy/campsite_type_carousel.sql new file mode 100644 index 0000000..4038db7 --- /dev/null +++ b/deploy/campsite_type_carousel.sql @@ -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; diff --git a/deploy/campsite_type_carousel_i18n.sql b/deploy/campsite_type_carousel_i18n.sql new file mode 100644 index 0000000..78e94dd --- /dev/null +++ b/deploy/campsite_type_carousel_i18n.sql @@ -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; diff --git a/deploy/remove_campsite_type_carousel_slide.sql b/deploy/remove_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..d944d2b --- /dev/null +++ b/deploy/remove_campsite_type_carousel_slide.sql @@ -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; diff --git a/deploy/translate_campsite_type_carousel_slide.sql b/deploy/translate_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..f39e57e --- /dev/null +++ b/deploy/translate_campsite_type_carousel_slide.sql @@ -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; diff --git a/pkg/campsite/types/admin.go b/pkg/campsite/types/admin.go index a3fb09e..f8864ba 100644 --- a/pkg/campsite/types/admin.go +++ b/pkg/campsite/types/admin.go @@ -93,6 +93,8 @@ func (h *AdminHandler) typeHandler(user *auth.User, company *auth.Company, conn } case "options": h.optionsHandler(user, company, conn, f.Slug).ServeHTTP(w, r) + case "slides": + h.carouselHandler(user, company, conn, f.Slug).ServeHTTP(w, r) default: loc, ok := h.locales.Get(head) if !ok { diff --git a/pkg/campsite/types/carousel.go b/pkg/campsite/types/carousel.go new file mode 100644 index 0000000..401d9d1 --- /dev/null +++ b/pkg/campsite/types/carousel.go @@ -0,0 +1,297 @@ +/* + * SPDX-FileCopyrightText: 2023 jordi fita mas + * 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) +} diff --git a/pkg/campsite/types/l10n.go b/pkg/campsite/types/l10n.go index 2456a9b..5202cb6 100644 --- a/pkg/campsite/types/l10n.go +++ b/pkg/campsite/types/l10n.go @@ -128,3 +128,70 @@ func (l10n *optionL10nForm) Valid(l *locale.Locale) bool { v.CheckRequired(&l10n.Name.Input, l.GettextNoop("Name can not be empty.")) 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 +} diff --git a/pkg/campsite/types/public.go b/pkg/campsite/types/public.go index 6ca8301..e947a78 100644 --- a/pkg/campsite/types/public.go +++ b/pkg/campsite/types/public.go @@ -13,6 +13,7 @@ import ( "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" httplib "dev.tandem.ws/tandem/camper/pkg/http" "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 { *template.PublicPage Name string + Carousel []*carousel.Slide Prices []*typePrice 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) { page := &publicPage{ PublicPage: template.NewPublicPage(), + Carousel: mustCollectSlides(ctx, conn, loc, slug), } row := conn.QueryRow(ctx, ` select coalesce(i18n.name, campsite_type.name) as l10n_name diff --git a/pkg/carousel/admin.go b/pkg/carousel/admin.go index 4c01036..b49616c 100644 --- a/pkg/carousel/admin.go +++ b/pkg/carousel/admin.go @@ -144,10 +144,10 @@ func MustCollectSlides(ctx context.Context, company *auth.Company, conn *databas type SlideEntry struct { Slide ID int - Translations []*translation + Translations []*Translation } -type translation struct { +type Translation struct { Language string Endonym string Missing bool @@ -184,7 +184,7 @@ func CollectSlideEntries(ctx context.Context, company *auth.Company, conn *datab return nil, err } for _, el := range translations.Elements { - slide.Translations = append(slide.Translations, &translation{ + slide.Translations = append(slide.Translations, &Translation{ el.Fields[0].Get().(string), el.Fields[1].Get().(string), el.Fields[2].Get().(bool), diff --git a/po/ca.po b/po/ca.po index cbe1aeb..01e67b9 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\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" "Last-Translator: jordi fita mas \n" "Language-Team: Catalan \n" @@ -77,22 +77,22 @@ msgstr "Descobreix l’entorn" msgid "Come and enjoy!" 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/type/form.gohtml:73 msgctxt "title" msgid "Prices" msgstr "Preus" -#: web/templates/public/campsite/type.gohtml:26 +#: web/templates/public/campsite/type.gohtml:50 msgid "%s €/night" msgstr "%s €/nit" -#: web/templates/public/campsite/type.gohtml:28 +#: web/templates/public/campsite/type.gohtml:52 msgid "%s: %s €/night" msgstr "%s: %s €/nit" -#: web/templates/public/campsite/type.gohtml:31 +#: web/templates/public/campsite/type.gohtml:55 msgid "*Minimum %d nights per stay" 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/l10n.gohtml:20 +#: web/templates/admin/campsite/carousel/form.gohtml:37 +#: web/templates/admin/campsite/carousel/l10n.gohtml:20 msgctxt "input" msgid "Caption" msgstr "Llegenda" #: 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/option/form.gohtml:78 #: web/templates/admin/campsite/type/form.gohtml:108 @@ -219,6 +222,7 @@ msgid "Update" msgstr "Actualitza" #: 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/option/form.gohtml:80 #: 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" #: 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/type/l10n.gohtml:21 #: web/templates/admin/campsite/type/l10n.gohtml:33 @@ -245,6 +250,7 @@ msgid "Source:" msgstr "Origen:" #: 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/type/l10n.gohtml:23 #: web/templates/admin/campsite/type/l10n.gohtml:36 @@ -256,6 +262,7 @@ msgid "Translation:" msgstr "Traducció:" #: 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/type/l10n.gohtml:45 #: web/templates/admin/season/l10n.gohtml:32 @@ -264,6 +271,90 @@ msgctxt "action" msgid "Translate" 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 d’allotjament" + +#: 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 d’allotjament" + +#: 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 d’allotjament" + +#: 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 s’ha 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 d’allotjament a %s" + #: web/templates/admin/campsite/form.gohtml:8 #: web/templates/admin/campsite/form.gohtml:25 msgctxt "title" @@ -355,16 +446,6 @@ msgctxt "header" msgid "Name" 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 msgid "No campsite type options added yet." msgstr "No s’ha afegit cap opció al tipus d’allotjament encara." @@ -391,13 +472,13 @@ msgid "Type" msgstr "Tipus" #: 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 msgid "Yes" msgstr "Sí" #: 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 msgid "No" msgstr "No" @@ -419,7 +500,7 @@ msgid "New Campsite Type" msgstr "Nou tipus d’allotjament" #: 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" msgid "Active" msgstr "Actiu" @@ -464,12 +545,22 @@ msgctxt "header" msgid "Options" 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" msgid "Edit Options" 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." msgstr "No s’ha afegit cap tipus d’allotjament encara." @@ -631,48 +722,6 @@ msgctxt "title" msgid "Carousel" 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 s’ha afegit cap diapositiva encara." - #: web/templates/admin/services/index.gohtml:54 msgctxt "action" msgid "Add service" @@ -893,21 +942,21 @@ msgctxt "title" msgid "Upload Media" msgstr "Pujada de mèdia" -#: pkg/carousel/admin.go:233 +#: pkg/carousel/admin.go:233 pkg/campsite/types/carousel.go:232 msgctxt "input" msgid "Slide image" msgstr "Imatge de la diapositiva" -#: pkg/carousel/admin.go:234 +#: pkg/carousel/admin.go:234 pkg/campsite/types/carousel.go:233 msgctxt "action" msgid "Set slide image" 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." 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." 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/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 msgid "Name can not be empty." 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" 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." 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." 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." 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." 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." 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" msgid "Cover image" msgstr "Imatge de portada" -#: pkg/campsite/types/admin.go:281 +#: pkg/campsite/types/admin.go:283 msgctxt "action" msgid "Set campsite type cover" msgstr "Estableix la portada del tipus d’allotjament" -#: pkg/campsite/types/admin.go:392 +#: pkg/campsite/types/admin.go:394 msgid "Cover image can not be empty." 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." 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." 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." 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." 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." 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." 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." msgstr "El número mínim de nits no pot ser zero." diff --git a/po/es.po b/po/es.po index fcad996..4678dae 100644 --- a/po/es.po +++ b/po/es.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\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" "Last-Translator: jordi fita mas \n" "Language-Team: Spanish \n" @@ -77,22 +77,22 @@ msgstr "Descubre el entorno" msgid "Come and enjoy!" 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/type/form.gohtml:73 msgctxt "title" msgid "Prices" msgstr "Precios" -#: web/templates/public/campsite/type.gohtml:26 +#: web/templates/public/campsite/type.gohtml:50 msgid "%s €/night" msgstr "%s €/noche" -#: web/templates/public/campsite/type.gohtml:28 +#: web/templates/public/campsite/type.gohtml:52 msgid "%s: %s €/night" msgstr ":%s: %s €/noche" -#: web/templates/public/campsite/type.gohtml:31 +#: web/templates/public/campsite/type.gohtml:55 msgid "*Minimum %d nights per stay" 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/l10n.gohtml:20 +#: web/templates/admin/campsite/carousel/form.gohtml:37 +#: web/templates/admin/campsite/carousel/l10n.gohtml:20 msgctxt "input" msgid "Caption" msgstr "Leyenda" #: 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/option/form.gohtml:78 #: web/templates/admin/campsite/type/form.gohtml:108 @@ -219,6 +222,7 @@ msgid "Update" msgstr "Actualizar" #: 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/option/form.gohtml:80 #: 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" #: 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/type/l10n.gohtml:21 #: web/templates/admin/campsite/type/l10n.gohtml:33 @@ -245,6 +250,7 @@ msgid "Source:" msgstr "Origen:" #: 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/type/l10n.gohtml:23 #: web/templates/admin/campsite/type/l10n.gohtml:36 @@ -256,6 +262,7 @@ msgid "Translation:" msgstr "Traducción" #: 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/type/l10n.gohtml:45 #: web/templates/admin/season/l10n.gohtml:32 @@ -264,6 +271,90 @@ msgctxt "action" msgid "Translate" 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:25 msgctxt "title" @@ -355,16 +446,6 @@ msgctxt "header" msgid "Name" 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 msgid "No campsite type options added yet." msgstr "No se ha añadido ninguna opció al tipo de alojamiento todavía." @@ -391,13 +472,13 @@ msgid "Type" msgstr "Tipo" #: 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 msgid "Yes" msgstr "Sí" #: 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 msgid "No" msgstr "No" @@ -419,7 +500,7 @@ msgid "New Campsite Type" msgstr "Nuevo tipo de alojamiento" #: 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" msgid "Active" msgstr "Activo" @@ -464,12 +545,22 @@ msgctxt "header" msgid "Options" 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" msgid "Edit Options" 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." msgstr "No se ha añadido ningún tipo de alojamiento todavía." @@ -631,48 +722,6 @@ msgctxt "title" msgid "Carousel" 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 msgctxt "action" msgid "Add service" @@ -893,21 +942,21 @@ msgctxt "title" msgid "Upload Media" msgstr "Subida de medio" -#: pkg/carousel/admin.go:233 +#: pkg/carousel/admin.go:233 pkg/campsite/types/carousel.go:232 msgctxt "input" msgid "Slide image" msgstr "Imagen de la diapositiva" -#: pkg/carousel/admin.go:234 +#: pkg/carousel/admin.go:234 pkg/campsite/types/carousel.go:233 msgctxt "action" msgid "Set slide image" 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." 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." 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/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 msgid "Name can not be empty." 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" 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." 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." 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." 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." 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." 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" msgid "Cover image" msgstr "Imagen de portada" -#: pkg/campsite/types/admin.go:281 +#: pkg/campsite/types/admin.go:283 msgctxt "action" msgid "Set campsite type cover" 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." 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." 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." 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." 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." 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." 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." 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." msgstr "El número mínimo de noches no puede ser cero." diff --git a/revert/add_campsite_type_carousel_slide.sql b/revert/add_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..92bab23 --- /dev/null +++ b/revert/add_campsite_type_carousel_slide.sql @@ -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; diff --git a/revert/campsite_type_carousel.sql b/revert/campsite_type_carousel.sql new file mode 100644 index 0000000..acd3175 --- /dev/null +++ b/revert/campsite_type_carousel.sql @@ -0,0 +1,7 @@ +-- Revert camper:campsite_type_carousel from pg + +begin; + +drop table if exists camper.campsite_type_carousel; + +commit; diff --git a/revert/campsite_type_carousel_i18n.sql b/revert/campsite_type_carousel_i18n.sql new file mode 100644 index 0000000..bda21db --- /dev/null +++ b/revert/campsite_type_carousel_i18n.sql @@ -0,0 +1,7 @@ +-- Revert camper:campsite_type_carousel_i18n from pg + +begin; + +drop table if exists camper.campsite_type_carousel_i18n; + +commit; diff --git a/revert/remove_campsite_type_carousel_slide.sql b/revert/remove_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..f941db8 --- /dev/null +++ b/revert/remove_campsite_type_carousel_slide.sql @@ -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; diff --git a/revert/translate_campsite_type_carousel_slide.sql b/revert/translate_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..33b21ac --- /dev/null +++ b/revert/translate_campsite_type_carousel_slide.sql @@ -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; diff --git a/sqitch.plan b/sqitch.plan index 7d3fce4..1dea491 100644 --- a/sqitch.plan +++ b/sqitch.plan @@ -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 # Add function to edit campsite type options extension_postgis [schema_public] 2023-10-06T17:18:52Z jordi fita mas # Add PostGIS extension company_geography [roles schema_camper company user_profile extension_postgis] 2023-10-06T17:53:34Z jordi fita mas # 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 # 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 # 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 # 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 # 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 # Add function to remove campsite type slides diff --git a/test/add_campsite_type_carousel_slide.sql b/test/add_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..3286c11 --- /dev/null +++ b/test/add_campsite_type_carousel_slide.sql @@ -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', '') +; + +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('')) +; + +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', '

A

', 5, false, true) + , (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '

B

', 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; diff --git a/test/campsite_type_carousel.sql b/test/campsite_type_carousel.sql new file mode 100644 index 0000000..c6b272e --- /dev/null +++ b/test/campsite_type_carousel.sql @@ -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; + diff --git a/test/campsite_type_carousel_i18n.sql b/test/campsite_type_carousel_i18n.sql new file mode 100644 index 0000000..2d72e32 --- /dev/null +++ b/test/campsite_type_carousel_i18n.sql @@ -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; + diff --git a/test/remove_campsite_type_carousel_slide.sql b/test/remove_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..4c969e6 --- /dev/null +++ b/test/remove_campsite_type_carousel_slide.sql @@ -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', '') +; + +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('')) +; + +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', '

A

', 5, false, true) + , (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '

B

', 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; diff --git a/test/translate_campsite_type_carousel_slide.sql b/test/translate_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..059d39b --- /dev/null +++ b/test/translate_campsite_type_carousel_slide.sql @@ -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', '') +; + +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('')) +; + +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', '

A

', 5, false, true) + , (11, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 3, 'Type B', '

B

', 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 slide’s 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; diff --git a/verify/add_campsite_type_carousel_slide.sql b/verify/add_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..6e56321 --- /dev/null +++ b/verify/add_campsite_type_carousel_slide.sql @@ -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; diff --git a/verify/campsite_type_carousel.sql b/verify/campsite_type_carousel.sql new file mode 100644 index 0000000..e99c574 --- /dev/null +++ b/verify/campsite_type_carousel.sql @@ -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; diff --git a/verify/campsite_type_carousel_i18n.sql b/verify/campsite_type_carousel_i18n.sql new file mode 100644 index 0000000..26c021d --- /dev/null +++ b/verify/campsite_type_carousel_i18n.sql @@ -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; diff --git a/verify/remove_campsite_type_carousel_slide.sql b/verify/remove_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..c8bd095 --- /dev/null +++ b/verify/remove_campsite_type_carousel_slide.sql @@ -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; diff --git a/verify/translate_campsite_type_carousel_slide.sql b/verify/translate_campsite_type_carousel_slide.sql new file mode 100644 index 0000000..98edeb6 --- /dev/null +++ b/verify/translate_campsite_type_carousel_slide.sql @@ -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; diff --git a/web/static/public.css b/web/static/public.css index e7c832a..6855949 100644 --- a/web/static/public.css +++ b/web/static/public.css @@ -584,7 +584,7 @@ dt { 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-weight: 600; line-height: .9em; @@ -662,7 +662,7 @@ dt { margin-bottom: 5rem; } -.campsite_services.carousel .slick-track { +.campsite_services.carousel .slick-track, .campsite_type.carousel .slick-track { align-items: center; } diff --git a/web/templates/admin/campsite/carousel/form.gohtml b/web/templates/admin/campsite/carousel/form.gohtml new file mode 100644 index 0000000..4faf41e --- /dev/null +++ b/web/templates/admin/campsite/carousel/form.gohtml @@ -0,0 +1,54 @@ + +{{ 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*/ -}} +
+

+ {{ if .MediaID }} + {{( pgettext "Edit Campsite Type Carousel Slide" "title" )}} + {{ else }} + {{( pgettext "New Campsite Type Carousel Slide" "title" )}} + {{ end }} +

+ {{ CSRFInput }} +
+ {{ with .Media -}} + {{ template "media-picker" . }} + {{- end }} + {{ with .Caption -}} + + {{ template "error-message" . }} + {{- end }} +
+
+ +
+
+{{- end }} diff --git a/web/templates/admin/campsite/carousel/index.gohtml b/web/templates/admin/campsite/carousel/index.gohtml new file mode 100644 index 0000000..be3047b --- /dev/null +++ b/web/templates/admin/campsite/carousel/index.gohtml @@ -0,0 +1,52 @@ + +{{ define "title" -}} + {{( pgettext "Campsite Type Carousel" "title" )}} +{{- end }} + +{{ define "content" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.carouselIndex*/ -}} +

{{( pgettext "Campsite Type Carousel" "title" )}}

+ {{( pgettext "Add slide" "action" )}} + {{ if .Slides -}} + + + + + + + + + + + {{ $confirm := (gettext "Are you sure you wish to delete this slide?")}} + {{ range $slide := .Slides -}} + + + + + + + {{- end }} + +
{{( pgettext "Image" "header" )}}{{( pgettext "Caption" "header" )}}{{( pgettext "Translations" "header" )}}{{( pgettext "Actions" "header" )}}
{{ .Caption }} + {{ range .Translations }} + {{ .Endonym }} + {{ end }} + +
+ +
+
+ {{ else -}} +

{{( gettext "No slides added yet." )}}

+ {{- end }} +{{- end }} diff --git a/web/templates/admin/campsite/carousel/l10n.gohtml b/web/templates/admin/campsite/carousel/l10n.gohtml new file mode 100644 index 0000000..0fecb6a --- /dev/null +++ b/web/templates/admin/campsite/carousel/l10n.gohtml @@ -0,0 +1,35 @@ + +{{ 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*/ -}} +
+

+ {{printf (pgettext "Translate Campsite Type Carousel Slide to %s" "title") .Locale.Endonym }} +

+ {{ CSRFInput }} +
+ {{ with .Caption -}} +
+ {{( pgettext "Caption" "input")}} + {{( gettext "Source:" )}} {{ .Source }}
+ + {{ template "error-message" . }} +
+ {{- end }} +
+
+ +
+
+{{- end }} diff --git a/web/templates/admin/campsite/type/index.gohtml b/web/templates/admin/campsite/type/index.gohtml index 459c99f..55c388a 100644 --- a/web/templates/admin/campsite/type/index.gohtml +++ b/web/templates/admin/campsite/type/index.gohtml @@ -17,6 +17,7 @@ {{( pgettext "Name" "header" )}} {{( pgettext "Translations" "header" )}} {{( pgettext "Options" "header" )}} + {{( pgettext "Carousel" "header" )}} {{( pgettext "Active" "campsite type" )}} @@ -34,6 +35,7 @@ {{ end }} {{( pgettext "Edit Options" "action" )}} + {{( pgettext "Edit Carousel" "action" )}} {{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }} {{- end }} diff --git a/web/templates/public/campsite/type.gohtml b/web/templates/public/campsite/type.gohtml index 9d4aa70..da93c05 100644 --- a/web/templates/public/campsite/type.gohtml +++ b/web/templates/public/campsite/type.gohtml @@ -7,9 +7,32 @@ {{ .Name }} {{- end }} +{{ define "head" -}} + {{ template "carouselStyle" }} +{{- end }} + {{ define "content" -}} {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.publicPage*/ -}}

{{ .Name }}

+ + {{ with .Carousel -}} + + {{- end }} + {{ .Description }} {{ with .Prices -}} @@ -34,4 +57,7 @@ {{- end }} + + {{ template "carouselInit" }} + {{- end }}