Add the season_calendar relation and table on the admin section

This calendar is supposed to be edited by admin users, but do not yet
have the complete JavaScript code to do so, thus for now i have made it
read-only.
This commit is contained in:
jordi fita mas 2023-09-27 02:23:09 +02:00
parent 3768dd5082
commit ea2fe8848b
23 changed files with 939 additions and 12 deletions

View File

@ -243,5 +243,28 @@ select add_season(52, 'Temporada alta', '#ff926c');
select add_season(52, 'Temporada mitjana', '#ffe37f');
select add_season(52, 'Temporada baixa', '#00aa7d');
select set_season_range(92, '[2023-04-06, 2023-04-10]');
select set_season_range(94, '[2023-04-11, 2023-04-27]');
select set_season_range(93, '[2023-04-28, 2023-04-30]');
select set_season_range(94, '[2023-05-01, 2023-06-01]');
select set_season_range(93, '[2023-06-02, 2023-06-03]');
select set_season_range(94, '[2023-06-04, 2023-06-08]');
select set_season_range(93, '[2023-06-09, 2023-06-10]');
select set_season_range(94, '[2023-06-11, 2023-06-15]');
select set_season_range(93, '[2023-06-16, 2023-06-22]');
select set_season_range(92, '[2023-06-23, 2023-06-25]');
select set_season_range(93, '[2023-06-26, 2023-06-30]');
select set_season_range(92, '[2023-07-01, 2023-08-27]');
select set_season_range(93, '[2023-08-28, 2023-09-02]');
select set_season_range(94, '[2023-09-03, 2023-09-07]');
select set_season_range(93, '[2023-09-08, 2023-09-10]');
select set_season_range(94, '[2023-09-11, 2023-09-14]');
select set_season_range(93, '[2023-09-15, 2023-09-16]');
select set_season_range(94, '[2023-09-17, 2023-09-21]');
select set_season_range(93, '[2023-09-22, 2023-09-23]');
select set_season_range(94, '[2023-09-24, 2023-09-28]');
select set_season_range(93, '[2023-09-29, 2023-09-30]');
select set_season_range(94, '[2023-10-01, 2023-10-12]');
commit;

View File

@ -0,0 +1,8 @@
-- Deploy camper:extension_btree_gist to pg
-- requires: schema_public
begin;
create extension if not exists btree_gist;
commit;

View File

@ -0,0 +1,55 @@
-- Deploy camper:season_calendar to pg
-- requires: roles
-- requires: schema_camper
-- requires: season
-- requires: extension_btree_gist
-- requires: user_profile
begin;
set search_path to camper, public;
create table season_calendar (
season_id integer not null,
season_range daterange not null,
primary key (season_id, season_range),
constraint disallow_overlap exclude using gist (season_id with =, season_range with &&)
);
grant select on table season_calendar to guest;
grant select on table season_calendar to employee;
grant select, insert, update, delete on table season_calendar to admin;
alter table season_calendar enable row level security;
create policy guest_ok
on season_calendar
for select
using (true)
;
create policy insert_to_company
on season_calendar
for insert
with check (
exists (select 1 from season join user_profile using (company_id) where season.season_id = season_calendar.season_id)
)
;
create policy update_company
on season_calendar
for update
using (
exists (select 1 from season join user_profile using (company_id) where season.season_id = season_calendar.season_id)
)
;
create policy delete_from_company
on season_calendar
for delete
using (
exists (select 1 from season join user_profile using (company_id) where season.season_id = season_calendar.season_id)
)
;
commit;

View File

@ -0,0 +1,38 @@
-- Deploy camper:set_season_range to pg
-- requires: roles
-- requires: schema_camper
-- requires: season_calendar
-- requires: unset_season_range
begin;
set search_path to camper, public;
create or replace function set_season_range(szn_id integer, range daterange) returns void as
$$
declare
tmp_range daterange;
begin
select daterange(min(lower(season_range)), max(upper(season_range)))
into tmp_range
from season_calendar
where season_id = szn_id
and (season_range && range or season_range -|- range)
;
if not lower_inf(tmp_range) and not upper_inf(tmp_range) then
range := range + tmp_range;
end if;
perform unset_season_range(range);
insert into season_calendar (season_id, season_range)
values (szn_id, range);
end
$$
language plpgsql
;
revoke execute on function set_season_range(integer, daterange) from public;
grant execute on function set_season_range(integer, daterange) to admin;
commit;

View File

@ -0,0 +1,49 @@
-- Deploy camper:unset_season_range to pg
-- requires: roles
-- requires: schema_camper
-- requires: season_calendar
begin;
set search_path to camper, public;
create or replace function unset_season_range(range daterange) returns void as
$$
declare
tmp_range daterange;
tmp_id integer;
begin
for tmp_id, tmp_range in
select season_id, season_range
from season_calendar
where season_range @> range
loop
delete from season_calendar where season_id = tmp_id and season_range = tmp_range;
insert into season_calendar (season_id, season_range)
values (tmp_id, daterange(lower(tmp_range), lower(range)))
, (tmp_id, daterange(upper(range), upper(tmp_range)))
;
end loop;
delete from season_calendar where range @> season_range;
update season_calendar
set season_range = season_range - range
where season_range && range
and season_range < range
;
update season_calendar
set season_range = season_range - (season_range * range)
where season_range && range
and season_range > range
;
end
$$
language plpgsql
;
revoke execute on function unset_season_range(daterange) from public;
grant execute on function unset_season_range(daterange) to admin;
commit;

View File

@ -8,6 +8,7 @@ package season
import (
"context"
"net/http"
"time"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
@ -74,12 +75,17 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
}
func serveSeasonIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
campsites, err := collectSeasonEntries(r.Context(), company, conn)
seasons, err := collectSeasonEntries(r.Context(), company, conn)
if err != nil {
panic(err)
}
calendar, err := collectSeasonCalendar(r.Context(), company, conn, 2023)
if err != nil {
panic(err)
}
page := &seasonIndex{
Seasons: campsites,
Seasons: seasons,
Calendar: calendar,
}
page.MustRender(w, r, user, company)
}
@ -117,8 +123,100 @@ type seasonEntry struct {
Active bool
}
var longMonthNames = []string{
locale.PgettextNoop("January", "month"),
locale.PgettextNoop("February", "month"),
locale.PgettextNoop("March", "month"),
locale.PgettextNoop("April", "month"),
locale.PgettextNoop("May", "month"),
locale.PgettextNoop("June", "month"),
locale.PgettextNoop("July", "month"),
locale.PgettextNoop("August", "month"),
locale.PgettextNoop("September", "month"),
locale.PgettextNoop("October", "month"),
locale.PgettextNoop("November", "month"),
locale.PgettextNoop("December", "month"),
}
func collectSeasonCalendar(ctx context.Context, company *auth.Company, conn *database.Conn, year int) (seasonCalendar, error) {
firstDay := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
lastDay := time.Date(year, time.December, 31, 23, 59, 59, 0, time.UTC)
rows, err := conn.Query(ctx, `
select t.day::date
, to_color(coalesce(color, 13750495)) as color
from generate_series($1, $2, interval '1 day') as t(day)
left join season_calendar on season_range @> t.day::date
left join season on season.season_id = season_calendar.season_id and company_id = $3
`, firstDay, lastDay, company.ID)
if err != nil {
return nil, err
}
var month *seasonMonth
var week seasonWeek
var calendar seasonCalendar
weekday := int(time.Monday)
for rows.Next() {
day := &seasonDay{}
if err = rows.Scan(&day.Date, &day.Color); err != nil {
return nil, err
}
dayMonth := day.Date.Month()
if month == nil || month.Month != dayMonth {
if month != nil {
for ; weekday != int(time.Sunday); weekday = (weekday + 1) % 7 {
week = append(week, &seasonDay{})
}
month.Weeks = append(month.Weeks, week)
calendar = append(calendar, month)
}
month = &seasonMonth{
Month: dayMonth,
Name: longMonthNames[dayMonth-1],
}
week = seasonWeek{}
weekday = int(time.Monday)
dayWeekday := int(day.Date.Weekday())
for ; weekday != dayWeekday; weekday = (weekday + 1) % 7 {
week = append(week, &seasonDay{})
}
}
week = append(week, day)
weekday = (weekday + 1) % 7
if weekday == int(time.Monday) {
month.Weeks = append(month.Weeks, week)
week = seasonWeek{}
}
}
if month != nil {
for ; weekday != int(time.Sunday); weekday = (weekday + 1) % 7 {
week = append(week, &seasonDay{})
}
month.Weeks = append(month.Weeks, week)
calendar = append(calendar, month)
}
return calendar, nil
}
type seasonCalendar []*seasonMonth
type seasonMonth struct {
Month time.Month
Name string
Weeks []seasonWeek
}
type seasonWeek []*seasonDay
type seasonDay struct {
Date time.Time
Color string
}
type seasonIndex struct {
Seasons []*seasonEntry
Seasons []*seasonEntry
Calendar seasonCalendar
}
func (page *seasonIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {

108
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-09-26 17:28+0200\n"
"POT-Creation-Date: 2023-09-27 02:15+0200\n"
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -398,6 +398,46 @@ msgstr "Color"
msgid "No seasons added yet."
msgstr "No sha afegit cap temporada encara."
#: web/templates/admin/season/index.gohtml:40
msgctxt "title"
msgid "Calendar"
msgstr "Calendari"
#: web/templates/admin/season/index.gohtml:47
msgctxt "day"
msgid "Mon"
msgstr "dl"
#: web/templates/admin/season/index.gohtml:48
msgctxt "day"
msgid "Tue"
msgstr "dt"
#: web/templates/admin/season/index.gohtml:49
msgctxt "day"
msgid "Wed"
msgstr "dc"
#: web/templates/admin/season/index.gohtml:50
msgctxt "day"
msgid "Thu"
msgstr "dj"
#: web/templates/admin/season/index.gohtml:51
msgctxt "day"
msgid "Fri"
msgstr "dv"
#: web/templates/admin/season/index.gohtml:52
msgctxt "day"
msgid "Sat"
msgstr "ds"
#: web/templates/admin/season/index.gohtml:53
msgctxt "day"
msgid "Sun"
msgstr "dg"
#: web/templates/admin/dashboard.gohtml:6
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:68
msgctxt "title"
@ -761,7 +801,7 @@ msgid "Automatic"
msgstr "Automàtic"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:82
#: pkg/campsite/types/admin.go:274 pkg/season/admin.go:203
#: pkg/campsite/types/admin.go:274 pkg/season/admin.go:301
#: pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
@ -808,11 +848,71 @@ msgstr "El tipus dallotjament escollit no és vàlid."
msgid "Label can not be empty."
msgstr "No podeu deixar letiqueta en blanc."
#: pkg/season/admin.go:204
#: pkg/season/admin.go:127
msgctxt "month"
msgid "January"
msgstr "gener"
#: pkg/season/admin.go:128
msgctxt "month"
msgid "February"
msgstr "febrer"
#: pkg/season/admin.go:129
msgctxt "month"
msgid "March"
msgstr "març"
#: pkg/season/admin.go:130
msgctxt "month"
msgid "April"
msgstr "abril"
#: pkg/season/admin.go:131
msgctxt "month"
msgid "May"
msgstr "maig"
#: pkg/season/admin.go:132
msgctxt "month"
msgid "June"
msgstr "juny"
#: pkg/season/admin.go:133
msgctxt "month"
msgid "July"
msgstr "juliol"
#: pkg/season/admin.go:134
msgctxt "month"
msgid "August"
msgstr "agost"
#: pkg/season/admin.go:135
msgctxt "month"
msgid "September"
msgstr "setembre"
#: pkg/season/admin.go:136
msgctxt "month"
msgid "October"
msgstr "octubre"
#: pkg/season/admin.go:137
msgctxt "month"
msgid "November"
msgstr "novembre"
#: pkg/season/admin.go:138
msgctxt "month"
msgid "December"
msgstr "desembre"
#: pkg/season/admin.go:302
msgid "Color can not be empty."
msgstr "No podeu deixar el color en blanc."
#: pkg/season/admin.go:205
#: pkg/season/admin.go:303
msgid "This color is not valid. It must be like #123abc."
msgstr "Aquest color no és vàlid. Hauria de ser similar a #123abc."

108
po/es.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-09-26 17:28+0200\n"
"POT-Creation-Date: 2023-09-27 02:15+0200\n"
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -398,6 +398,46 @@ msgstr "Color"
msgid "No seasons added yet."
msgstr "No se ha añadido ninguna temporada todavía."
#: web/templates/admin/season/index.gohtml:40
msgctxt "title"
msgid "Calendar"
msgstr "Calendario"
#: web/templates/admin/season/index.gohtml:47
msgctxt "day"
msgid "Mon"
msgstr "lu"
#: web/templates/admin/season/index.gohtml:48
msgctxt "day"
msgid "Tue"
msgstr "ma"
#: web/templates/admin/season/index.gohtml:49
msgctxt "day"
msgid "Wed"
msgstr "mi"
#: web/templates/admin/season/index.gohtml:50
msgctxt "day"
msgid "Thu"
msgstr "ju"
#: web/templates/admin/season/index.gohtml:51
msgctxt "day"
msgid "Fri"
msgstr "vi"
#: web/templates/admin/season/index.gohtml:52
msgctxt "day"
msgid "Sat"
msgstr "sá"
#: web/templates/admin/season/index.gohtml:53
msgctxt "day"
msgid "Sun"
msgstr "do"
#: web/templates/admin/dashboard.gohtml:6
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:68
msgctxt "title"
@ -761,7 +801,7 @@ msgid "Automatic"
msgstr "Automático"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:82
#: pkg/campsite/types/admin.go:274 pkg/season/admin.go:203
#: pkg/campsite/types/admin.go:274 pkg/season/admin.go:301
#: 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."
@ -808,11 +848,71 @@ msgstr "El tipo de alojamiento escogido no es válido."
msgid "Label can not be empty."
msgstr "No podéis dejar la etiqueta en blanco."
#: pkg/season/admin.go:204
#: pkg/season/admin.go:127
msgctxt "month"
msgid "January"
msgstr "enero"
#: pkg/season/admin.go:128
msgctxt "month"
msgid "February"
msgstr "febrero"
#: pkg/season/admin.go:129
msgctxt "month"
msgid "March"
msgstr "marzo"
#: pkg/season/admin.go:130
msgctxt "month"
msgid "April"
msgstr "abril"
#: pkg/season/admin.go:131
msgctxt "month"
msgid "May"
msgstr "mayo"
#: pkg/season/admin.go:132
msgctxt "month"
msgid "June"
msgstr "junio"
#: pkg/season/admin.go:133
msgctxt "month"
msgid "July"
msgstr "julio"
#: pkg/season/admin.go:134
msgctxt "month"
msgid "August"
msgstr "agosto"
#: pkg/season/admin.go:135
msgctxt "month"
msgid "September"
msgstr "septiembre"
#: pkg/season/admin.go:136
msgctxt "month"
msgid "October"
msgstr "octubre"
#: pkg/season/admin.go:137
msgctxt "month"
msgid "November"
msgstr "noviembre"
#: pkg/season/admin.go:138
msgctxt "month"
msgid "December"
msgstr "diciembre"
#: pkg/season/admin.go:302
msgid "Color can not be empty."
msgstr "No podéis dejar el color en blanco."
#: pkg/season/admin.go:205
#: pkg/season/admin.go:303
msgid "This color is not valid. It must be like #123abc."
msgstr "Este color no es válido. Tiene que ser parecido a #123abc."

View File

@ -0,0 +1,7 @@
-- Revert camper:extension_btree_gist from pg
begin;
drop extension if exists btree_gist;
commit;

View File

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

View File

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

View File

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

View File

@ -80,3 +80,7 @@ service_i18n [roles schema_camper service language] 2023-09-17T00:13:42Z jordi f
translate_service [roles schema_camper service_i18n] 2023-09-17T00:17:00Z jordi fita mas <jordi@tandem.blog> # Add function to translate a service
remove_service [roles schema_camper service service_i18n] 2023-09-26T15:21:00Z jordi fita mas <jordi@tandem.blog> # Add function to remove service
translation [schema_camper] 2023-09-25T16:27:50Z jordi fita mas <jordi@tandem.blog> # Add the type for a translation
extension_btree_gist [schema_public] 2023-09-26T17:49:30Z jordi fita mas <jordi@tandem.blog> # Add btree_gist extension
season_calendar [roles schema_camper season extension_btree_gist user_profile] 2023-09-26T18:07:21Z jordi fita mas <jordi@tandem.blog> # Add the relation of date ranges for seasons
unset_season_range [roles schema_camper season_calendar] 2023-09-26T21:56:38Z jordi fita mas <jordi@tandem.blog> # Add function to unset a date range from the seasons calendar
set_season_range [roles schema_camper season_calendar unset_season_range] 2023-09-26T18:37:29Z jordi fita mas <jordi@tandem.blog> # Add function to set a seasons date range

View File

@ -8,7 +8,8 @@ begin;
select plan(1);
select extensions_are(array [
'citext'
'btree_gist'
, 'citext'
, 'pgtap'
, 'pgcrypto'
, 'pg_libphonenumber'

179
test/season_calendar.sql Normal file
View File

@ -0,0 +1,179 @@
-- Test season_calendar
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(29);
set search_path to camper, public;
select has_table('season_calendar');
select has_pk('season_calendar');
select col_is_pk('season_calendar', array['season_id', 'season_range']);
select table_privs_are('season_calendar', 'guest', array['SELECT']);
select table_privs_are('season_calendar', 'employee', array['SELECT']);
select table_privs_are('season_calendar', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('season_calendar', 'authenticator', array[]::text[]);
select has_column('season_calendar', 'season_id');
select col_type_is('season_calendar', 'season_id', 'integer');
select col_not_null('season_calendar', 'season_id');
select col_hasnt_default('season_calendar', 'season_id');
select has_column('season_calendar', 'season_range');
select col_type_is('season_calendar', 'season_range', 'daterange');
select col_not_null('season_calendar', 'season_range');
select col_hasnt_default('season_calendar', 'season_range');
set client_min_messages to warning;
truncate season_calendar cascade;
truncate season 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 season (season_id, company_id, name)
values (7, 2, 'Peak')
, (8, 4, 'Low')
;
insert into season_calendar (season_id, season_range)
values (7, '[2023-01-01, 2023-02-01)')
, (8, '[2023-02-01, 2023-03-01)')
;
prepare season_data as
select season_id, lower(season_range)::text
from season_calendar
;
set role guest;
select bag_eq(
'season_data',
$$ values (7, '2023-01-01')
, (8, '2023-02-01')
$$,
'Everyone should be able to list all seasons across all companies'
);
reset role;
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog', 'co2');
select lives_ok(
$$ insert into season_calendar(season_id, season_range) values (7, '[2023-03-01, 2023-04-01)' ) $$,
'Admin from company 2 should be able to insert a new season to that company.'
);
select bag_eq(
'season_data',
$$ values (7, '2023-01-01')
, (7, '2023-03-01')
, (8, '2023-02-01')
$$,
'The new row should have been added'
);
select lives_ok(
$$ update season_calendar set season_range = '[2023-04-01, 2023-05-01)' where season_id = 7 and season_range = '[2023-03-01, 2023-04-01)' $$,
'Admin from company 2 should be able to update season of that company.'
);
select bag_eq(
'season_data',
$$ values (7, '2023-01-01')
, (7, '2023-04-01')
, (8, '2023-02-01')
$$,
'The row should have been updated.'
);
select lives_ok(
$$ delete from season_calendar where season_id = 7 and season_range = '[2023-04-01, 2023-05-01)' $$,
'Admin from company 2 should be able to delete season from that company.'
);
select bag_eq(
'season_data',
$$ values (7, '2023-01-01')
, (8, '2023-02-01')
$$,
'The row should have been deleted.'
);
select throws_ok(
$$ insert into season_calendar (season_id, season_range) values (8, '[2023-05-01, 2023-06-01)' ) $$,
'42501', 'new row violates row-level security policy for table "season_calendar"',
'Admin from company 2 should NOT be able to insert new seasons to company 4.'
);
select lives_ok(
$$ update season_calendar set season_range = '[2023-09-01, 2023-10-01)' where season_id = 8 $$,
'Admin from company 2 should not be able to update new seasons of company 4, but no error if season_id is not changed.'
);
select bag_eq(
'season_data',
$$ values (7, '2023-01-01')
, (8, '2023-02-01')
$$,
'No row should have been changed.'
);
select throws_ok(
$$ update season_calendar set season_id = 8 where season_id = 7 $$,
'42501', 'new row violates row-level security policy for table "season_calendar"',
'Admin from company 2 should NOT be able to move seasons to company 4'
);
select lives_ok(
$$ delete from season_calendar where season_id = 8 $$,
'Admin from company 2 should NOT be able to delete seasons from company 4, but not error is thrown'
);
select bag_eq(
'season_data',
$$ values (7, '2023-01-01')
, (8, '2023-02-01')
$$,
'No row should have been changed'
);
select throws_ok(
$$ insert into season_calendar (season_id, season_range) values (7, '[2023-01-30, 2023-02-02)' ) $$,
'23P01', 'conflicting key value violates exclusion constraint "disallow_overlap"',
'Should not be able to insert seasons with overlapping range.'
);
reset role;
select *
from finish();
rollback;

83
test/set_season_range.sql Normal file
View File

@ -0,0 +1,83 @@
-- Test set_season_range
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(19);
set search_path to camper, public;
select has_function('camper', 'set_season_range', array['integer', 'daterange']);
select function_lang_is('camper', 'set_season_range', array['integer', 'daterange'], 'plpgsql');
select function_returns('camper', 'set_season_range', array['integer', 'daterange'], 'void');
select isnt_definer('camper', 'set_season_range', array['integer', 'daterange']);
select volatility_is('camper', 'set_season_range', array['integer', 'daterange'], 'volatile');
select function_privs_are('camper', 'set_season_range', array ['integer', 'daterange'], 'guest', array[]::text[]);
select function_privs_are('camper', 'set_season_range', array ['integer', 'daterange'], 'employee', array[]::text[]);
select function_privs_are('camper', 'set_season_range', array ['integer', 'daterange'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'set_season_range', array ['integer', 'daterange'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate season_calendar cascade;
truncate season 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 (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca')
;
insert into season (season_id, company_id, name)
values ( 7, 2, 'Peak')
, ( 8, 2, 'Shoulder')
, ( 9, 2, 'Off-Season')
, (10, 2, 'Saint Johns Eve')
;
insert into season_calendar (season_id, season_range)
values (7, '[2023-04-07, 2023-04-09]')
, (9, '[2023-04-12, 2023-04-16]')
, (8, '[2023-04-18, 2023-04-22]')
, (9, '[2023-04-24, 2023-05-14]')
, (9, '[2023-05-22, 2023-05-28]')
, (9, '[2023-06-05, 2023-06-15]')
, (8, '[2023-06-16, 2023-06-30]')
;
select lives_ok($$ select set_season_range( 7, '[2023-04-06, 2023-04-07]') $$);
select lives_ok($$ select set_season_range( 7, '[2023-04-08, 2023-04-10]') $$);
select lives_ok($$ select set_season_range( 9, '[2023-04-11, 2023-06-15]') $$);
select lives_ok($$ select set_season_range( 8, '[2023-04-28, 2023-04-30]') $$);
select lives_ok($$ select set_season_range( 8, '[2023-06-02, 2023-06-03]') $$);
select lives_ok($$ select set_season_range( 8, '[2023-06-09, 2023-06-10]') $$);
select lives_ok($$ select set_season_range(10, '[2023-06-23, 2023-06-25]') $$);
select lives_ok($$ select set_season_range( 7, '[2023-07-01, 2023-08-27]') $$);
select lives_ok($$ select set_season_range( 8, '[2023-08-28, 2023-08-31]') $$);
select bag_eq(
$$ select season_id, season_range from season_calendar $$,
$$ values ( 7, '[2023-04-06, 2023-04-10]'::daterange)
, ( 9, '[2023-04-11, 2023-04-27]'::daterange)
, ( 8, '[2023-04-28, 2023-04-30]'::daterange)
, ( 9, '[2023-05-01, 2023-06-01]'::daterange)
, ( 8, '[2023-06-02, 2023-06-03]'::daterange)
, ( 9, '[2023-06-04, 2023-06-08]'::daterange)
, ( 8, '[2023-06-09, 2023-06-10]'::daterange)
, ( 9, '[2023-06-11, 2023-06-15]'::daterange)
, ( 8, '[2023-06-16, 2023-06-22]'::daterange)
, (10, '[2023-06-23, 2023-06-25]'::daterange)
, ( 8, '[2023-06-26, 2023-06-30]'::daterange)
, ( 7, '[2023-07-01, 2023-08-27]'::daterange)
, ( 8, '[2023-08-28, 2023-08-31]'::daterange)
$$,
'Should have updated the calendar'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,75 @@
-- Test unset_season_range
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', 'unset_season_range', array['daterange']);
select function_lang_is('camper', 'unset_season_range', array['daterange'], 'plpgsql');
select function_returns('camper', 'unset_season_range', array['daterange'], 'void');
select isnt_definer('camper', 'unset_season_range', array['daterange']);
select volatility_is('camper', 'unset_season_range', array['daterange'], 'volatile');
select function_privs_are('camper', 'unset_season_range', array ['daterange'], 'guest', array[]::text[]);
select function_privs_are('camper', 'unset_season_range', array ['daterange'], 'employee', array[]::text[]);
select function_privs_are('camper', 'unset_season_range', array ['daterange'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'unset_season_range', array ['daterange'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate season_calendar cascade;
truncate season 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 (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca')
;
insert into season (season_id, company_id, name)
values ( 7, 2, 'Peak')
, ( 8, 2, 'Shoulder')
, ( 9, 2, 'Off-Season')
, (10, 2, 'Saint Johns Eve')
;
insert into season_calendar (season_id, season_range)
values ( 9, '[2023-04-01, 2023-04-05]')
, ( 7, '[2023-04-06, 2023-04-10]')
, ( 9, '[2023-04-11, 2023-04-12]')
, ( 8, '[2023-04-13, 2023-04-16]')
, ( 9, '[2023-04-17, 2023-04-19]')
, (10, '[2023-04-20, 2023-04-22]')
, ( 9, '[2023-04-23, 2023-04-27]')
, ( 8, '[2023-04-28, 2023-04-30]')
;
select lives_ok($$ select unset_season_range('[2023-04-08, 2023-04-11]') $$);
select lives_ok($$ select unset_season_range('[2023-04-15, 2023-04-15]') $$);
select lives_ok($$ select unset_season_range('[2023-04-20, 2023-04-23]') $$);
select lives_ok($$ select unset_season_range('[2023-04-26, 2023-04-29]') $$);
select bag_eq(
$$ select season_id, season_range from season_calendar $$,
$$ values ( 9, '[2023-04-01, 2023-04-05]'::daterange)
, ( 7, '[2023-04-06, 2023-04-07]'::daterange)
, ( 9, '[2023-04-12, 2023-04-12]'::daterange)
, ( 8, '[2023-04-13, 2023-04-14]'::daterange)
, ( 8, '[2023-04-16, 2023-04-16]'::daterange)
, ( 9, '[2023-04-17, 2023-04-19]'::daterange)
, ( 9, '[2023-04-24, 2023-04-25]'::daterange)
, ( 8, '[2023-04-30, 2023-04-30]'::daterange)
$$,
'Should have updated the calendar'
);
select *
from finish();
rollback;

View File

@ -0,0 +1,10 @@
-- Verify camper:extension_btree_gist on pg
begin;
select 1 / count(*)
from pg_extension
where extname = 'btree_gist'
;
rollback;

View File

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

View File

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

View File

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

View File

@ -123,3 +123,14 @@ a.missing-translation {
.icon-input button[aria-pressed="true"] {
background-color: #ffeeaa;
}
.season-calendar {
display: grid;
grid-template-columns: repeat(3, auto);
justify-content: center;
gap: 2em;
}
.season-calendar svg {
max-width: 5rem;
}

View File

@ -36,4 +36,39 @@
{{ else -}}
<p>{{( gettext "No seasons added yet." )}}</p>
{{- end }}
<h2>{{( pgettext "Calendar" "title" )}}</h2>
<div class="season-calendar">
{{ range .Calendar -}}
<table>
<caption>{{ pgettext .Name "month" }}</caption>
<thead>
<tr>
<th scope="col">{{(pgettext "Mon" "day" )}}</th>
<th scope="col">{{(pgettext "Tue" "day" )}}</th>
<th scope="col">{{(pgettext "Wed" "day" )}}</th>
<th scope="col">{{(pgettext "Thu" "day" )}}</th>
<th scope="col">{{(pgettext "Fri" "day" )}}</th>
<th scope="col">{{(pgettext "Sat" "day" )}}</th>
<th scope="col">{{(pgettext "Sun" "day" )}}</th>
</tr>
</thead>
<tbody>
{{ range .Weeks }}
<tr>
{{ range . }}
<td>
{{- if .Color -}}
<svg viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" fill="{{ .Color }}"/>
</svg>
{{- end -}}
</td>
{{- end }}
</tr>
{{- end }}
</tbody>
</table>
{{- end }}
</div>
{{- end }}