@bygghammar/cms-platform 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migrations/20260728113741_f9744b19-078b-46e5-b00f-31c84aaf71ab.sql +111 -0
- package/migrations/20260728113757_4156a886-21fb-4a1f-9c12-acb7bb145102.sql +5 -0
- package/migrations/20260728113823_6e0237e1-99e3-4e1a-8a68-804b7bcdbbb8.sql +1 -0
- package/migrations/20260728115305_506d92b4-129f-4d2b-961f-cf76e592677e.sql +92 -0
- package/migrations/20260728115320_717d4447-f433-4a1d-89d2-fc8f028d4ac6.sql +1 -0
- package/migrations/20260728115347_c77f5a56-19db-4117-bb60-9ad692b817ab.sql +11 -0
- package/migrations/20260728132052_b692dcfe-a944-4a19-afd0-187f7600feb8.sql +63 -0
- package/migrations/20260728183318_05b05a47-ba05-4a66-ab8f-69f2688fea84.sql +209 -0
- package/migrations/20260728193035_84b68d27-c3f3-4516-a3a9-15fc6f476163.sql +17 -0
- package/migrations/20260728193707_8438ee2d-d589-4048-86e2-464fd209af80.sql +37 -0
- package/migrations/20260729055312_08862d7e-0279-4d4f-8709-a59b9f068c55.sql +5 -0
- package/migrations/20260729055348_418b7e36-4877-4304-831f-290ca73e56f3.sql +133 -0
- package/migrations/20260729055358_20b467e2-4efd-42c1-bd34-773ed6b77fc5.sql +3 -0
- package/migrations/20260729061039_306859eb-a38c-44ef-af1a-6c02a8fd84db.sql +43 -0
- package/migrations/20260730065600_54c25861-3355-4600-a72f-3d67b457ecb1.sql +54 -0
- package/migrations/20260730071745_d4904314-15ff-4c58-ac66-85e7371e04b5.sql +113 -0
- package/migrations/20260730071914_48163aa3-830e-4dcc-bdd4-c35be282ab87.sql +22 -0
- package/migrations/20260730085949_bcfc3270-ea80-4617-86a3-2555ed34a280.sql +24 -0
- package/migrations/20260730095256_c977c30e-b285-4041-8804-c0bd72b6d9b3.sql +13 -0
- package/migrations/20260730100057_e341b887-eb0e-4a74-850e-0ee5ffb70d7a.sql +38 -0
- package/migrations/20260730103703_d5c4e1b7-1e2c-4dd0-be4e-185c159ade69.sql +62 -0
- package/migrations/20260730105635_3557b7da-919e-48db-8c92-8221d68d3cf9.sql +29 -0
- package/migrations/20260730120308_12ff8cfb-43f9-4070-abde-61531ad3b75f.sql +73 -0
- package/migrations/20260730125024_1baf8926-12a2-48f4-8edc-7309941309e9.sql +18 -0
- package/migrations/20260730173510_feb974d1-510e-4f52-9e20-5fd0582d8952.sql +107 -0
- package/migrations/20260730175409_9f60743f-f5e1-418c-a700-c91fb3704db8.sql +9 -0
- package/migrations/20260730181224_1c52ff16-c7a2-4ef1-a0a8-e493c2c9106f.sql +7 -0
- package/migrations/20260730183129_933adb75-f985-4d82-ad5d-213b69fd42e5.sql +32 -0
- package/migrations/20260730183918_7bc7a827-2100-49f1-bc6e-24509249350b.sql +68 -0
- package/migrations/20260730202628_7ceade5e-644c-4bc2-91b8-594133329f31.sql +1 -0
- package/migrations/20260730211056_57f42049-00b4-473b-a2cd-65a13570b8d2.sql +16 -0
- package/migrations/20260730214340_98af2e55-9031-4c11-9659-c47a37a8d6b7.sql +1 -0
- package/migrations/20260731091630_8a6f28ce-5833-4901-ab17-27648cef32ec.sql +149 -0
- package/migrations/20260731094716_4c68a1df-83ed-4ba8-8019-fa2fe2b9681e.sql +65 -0
- package/migrations/20260731191655_fef97c3c-1397-47b4-8b56-afb020298e97.sql +165 -0
- package/migrations/20260731192212_95772a2f-19a1-4013-b463-2eb161e96737.sql +135 -0
- package/migrations/20260731192815_c490653e-1232-4ad9-a150-3c83d2b90f5e.sql +13 -0
- package/migrations/20260731193135_7966e740-5e86-4b14-8e1d-5119069666ba.sql +2 -0
- package/migrations/20260731193245_599790d0-ca5e-4b5d-a3e7-d69b2a79e1a3.sql +13 -0
- package/migrations/20260731212959_a748cb0d-4dc7-45dc-be90-f6bacbfab26c.sql +9 -0
- package/migrations/20260801003057_1195f4c1-fb54-42dc-9718-638a25e6a82e.sql +3 -0
- package/migrations/20260801003300_fc591960-cb41-41cf-837d-2eef23b29ca9.sql +40 -0
- package/migrations/20260802151445_a56b0023-eaf7-41e2-a7fb-d4beddfc587c.sql +3 -0
- package/migrations/20260802202621_d661af8a-8513-4b54-b375-f963ae824c8f.sql +225 -0
- package/migrations/20260802202702_c30cd837-744c-4641-85f1-edd3a7ebbcb6.sql +16 -0
- package/migrations/20260802202720_4418d18d-c851-42dc-9345-78fc330128ae.sql +2 -0
- package/migrations/20260803132514_78ba1f53-c85b-4457-96b1-7d49c5c8b494.sql +34 -0
- package/migrations/20260803181201_28c8aa09-baa8-431c-8338-90b1af9c8172.sql +22 -0
- package/migrations/20260803201518_6e8a6028-b70f-4a7d-8bc9-95c86304e7ae.sql +12 -0
- package/migrations/20260803201535_32a9a213-cb92-4035-b8cd-8211ca34af68.sql +12 -0
- package/migrations/20260811103505_29df753d-14fa-4a05-8fea-f4207785fba3.sql +35 -0
- package/migrations/20260811112455_217c4028-f587-4c98-9e4f-9e6c452ab761.sql +20 -0
- package/migrations/20260811114438_6687d67d-bad3-4803-9638-8b7670d2e5d4.sql +86 -0
- package/migrations/20260811130318_7cb95e95-9baa-40af-8f3f-de1d4e09cec4.sql +1 -0
- package/migrations/20260811220548_80d99ec2-bd4b-475d-a627-9408bb60ce56.sql +1 -0
- package/migrations/20260811222519_6b040f56-4de3-49e0-92e4-d9deec5ead6a.sql +3 -0
- package/migrations/20260812000000_seed_voyara_content.sql +390 -0
- package/migrations/20260818040722_1bdf1b8f-7fb0-4ae8-8ecf-73f1fadff3c2.sql +2 -0
- package/migrations/20260818040808_69a5e832-57e8-4aa7-aff8-7fd7777faa7c.sql +15 -0
- package/migrations/20260818040839_e6523163-bbf5-4a39-9233-91759059569a.sql +26 -0
- package/package.json +9 -4
- package/src/backoffice/media/ReviewImageUpload.tsx +104 -0
- package/src/backoffice/seo/page-seo-form.ts +216 -0
- package/src/platform/contract.ts +92 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
UPDATE public.content_sections
|
|
2
|
+
SET data = '[
|
|
3
|
+
{"href":"/utbildningen","label":{"sv":"Utbildningen","en":"The training"},"children":[
|
|
4
|
+
{"href":"/utbildningen","label":{"sv":"Om utbildningen","en":"About the training"}},
|
|
5
|
+
{"href":"/bartenderkurs","label":{"sv":"Bartenderkurs","en":"Bartender course"}}
|
|
6
|
+
]},
|
|
7
|
+
{"href":"/destinationer","label":{"sv":"Destinationer","en":"Destinations"},"children":[
|
|
8
|
+
{"href":"/destinationer","label":{"sv":"Ayia Napa","en":"Ayia Napa"}},
|
|
9
|
+
{"href":"/destinationer","label":{"sv":"Mallorca","en":"Mallorca"}}
|
|
10
|
+
]},
|
|
11
|
+
{"href":"/boka","label":{"sv":"Boka","en":"Book"},"children":[
|
|
12
|
+
{"href":"/datum","label":{"sv":"Kursdatum","en":"Course dates"}},
|
|
13
|
+
{"href":"/boka","label":{"sv":"Boka plats","en":"Book a seat"}}
|
|
14
|
+
]},
|
|
15
|
+
{"href":"/om","label":{"sv":"Om oss","en":"About us"}},
|
|
16
|
+
{"href":"/kontakt","label":{"sv":"Kontakt","en":"Contact"}}
|
|
17
|
+
]'::jsonb
|
|
18
|
+
WHERE key = 'nav';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
CREATE TABLE public.forms (
|
|
2
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
3
|
+
key text NOT NULL UNIQUE,
|
|
4
|
+
name text NOT NULL,
|
|
5
|
+
form_type text NOT NULL DEFAULT 'contact',
|
|
6
|
+
fields jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
7
|
+
public_settings jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
8
|
+
private_settings jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
9
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
10
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
11
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
12
|
+
updated_by uuid
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
GRANT SELECT (id, key, name, form_type, fields, public_settings, is_active) ON public.forms TO anon;
|
|
16
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.forms TO authenticated;
|
|
17
|
+
GRANT ALL ON public.forms TO service_role;
|
|
18
|
+
ALTER TABLE public.forms ENABLE ROW LEVEL SECURITY;
|
|
19
|
+
|
|
20
|
+
CREATE POLICY "forms public read active" ON public.forms
|
|
21
|
+
FOR SELECT TO anon USING (is_active = true);
|
|
22
|
+
CREATE POLICY "forms staff read" ON public.forms
|
|
23
|
+
FOR SELECT TO authenticated USING (public.is_staff(auth.uid()) OR public.has_role(auth.uid(), 'editor'));
|
|
24
|
+
CREATE POLICY "forms staff insert" ON public.forms
|
|
25
|
+
FOR INSERT TO authenticated WITH CHECK (public.is_staff(auth.uid()) OR public.has_role(auth.uid(), 'editor'));
|
|
26
|
+
CREATE POLICY "forms staff update" ON public.forms
|
|
27
|
+
FOR UPDATE TO authenticated USING (public.is_staff(auth.uid()) OR public.has_role(auth.uid(), 'editor'))
|
|
28
|
+
WITH CHECK (public.is_staff(auth.uid()) OR public.has_role(auth.uid(), 'editor'));
|
|
29
|
+
CREATE POLICY "forms admin delete" ON public.forms
|
|
30
|
+
FOR DELETE TO authenticated USING (public.is_staff(auth.uid()));
|
|
31
|
+
|
|
32
|
+
CREATE OR REPLACE FUNCTION public.touch_forms()
|
|
33
|
+
RETURNS trigger LANGUAGE plpgsql SET search_path = public AS $$
|
|
34
|
+
BEGIN NEW.updated_at = now(); NEW.updated_by = auth.uid(); RETURN NEW; END; $$;
|
|
35
|
+
|
|
36
|
+
CREATE TRIGGER touch_forms BEFORE UPDATE ON public.forms
|
|
37
|
+
FOR EACH ROW EXECUTE FUNCTION public.touch_forms();
|
|
38
|
+
|
|
39
|
+
CREATE TABLE public.form_settings (
|
|
40
|
+
id boolean PRIMARY KEY DEFAULT true,
|
|
41
|
+
notify_to text NOT NULL DEFAULT '',
|
|
42
|
+
autoreply_enabled boolean NOT NULL DEFAULT false,
|
|
43
|
+
autoreply_subject jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
44
|
+
autoreply_body jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
45
|
+
spam_honeypot boolean NOT NULL DEFAULT true,
|
|
46
|
+
spam_min_seconds integer NOT NULL DEFAULT 3,
|
|
47
|
+
spam_max_per_hour integer NOT NULL DEFAULT 10,
|
|
48
|
+
thankyou_mode text NOT NULL DEFAULT 'inline',
|
|
49
|
+
thankyou_message jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
50
|
+
thankyou_redirect text NOT NULL DEFAULT '',
|
|
51
|
+
consent_text jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
52
|
+
retention_days integer NOT NULL DEFAULT 0,
|
|
53
|
+
sheet_id text NOT NULL DEFAULT '',
|
|
54
|
+
sheet_tab text NOT NULL DEFAULT '',
|
|
55
|
+
sheet_live_append boolean NOT NULL DEFAULT false,
|
|
56
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
57
|
+
updated_by uuid,
|
|
58
|
+
CONSTRAINT form_settings_singleton CHECK (id = true)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
GRANT SELECT, INSERT, UPDATE ON public.form_settings TO authenticated;
|
|
62
|
+
GRANT ALL ON public.form_settings TO service_role;
|
|
63
|
+
ALTER TABLE public.form_settings ENABLE ROW LEVEL SECURITY;
|
|
64
|
+
|
|
65
|
+
CREATE POLICY "form settings staff read" ON public.form_settings
|
|
66
|
+
FOR SELECT TO authenticated USING (public.is_staff(auth.uid()) OR public.has_role(auth.uid(), 'editor'));
|
|
67
|
+
CREATE POLICY "form settings staff insert" ON public.form_settings
|
|
68
|
+
FOR INSERT TO authenticated WITH CHECK (public.is_staff(auth.uid()));
|
|
69
|
+
CREATE POLICY "form settings staff update" ON public.form_settings
|
|
70
|
+
FOR UPDATE TO authenticated USING (public.is_staff(auth.uid())) WITH CHECK (public.is_staff(auth.uid()));
|
|
71
|
+
|
|
72
|
+
CREATE OR REPLACE FUNCTION public.touch_form_settings()
|
|
73
|
+
RETURNS trigger LANGUAGE plpgsql SET search_path = public AS $$
|
|
74
|
+
BEGIN NEW.updated_at = now(); NEW.updated_by = auth.uid(); RETURN NEW; END; $$;
|
|
75
|
+
|
|
76
|
+
CREATE TRIGGER touch_form_settings BEFORE UPDATE ON public.form_settings
|
|
77
|
+
FOR EACH ROW EXECUTE FUNCTION public.touch_form_settings();
|
|
78
|
+
|
|
79
|
+
INSERT INTO public.form_settings (id) VALUES (true) ON CONFLICT (id) DO NOTHING;
|
|
80
|
+
|
|
81
|
+
CREATE TABLE public.form_submissions (
|
|
82
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
83
|
+
form_id uuid REFERENCES public.forms(id) ON DELETE SET NULL,
|
|
84
|
+
form_key text NOT NULL,
|
|
85
|
+
locale text NOT NULL DEFAULT 'sv',
|
|
86
|
+
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
87
|
+
consent_at timestamptz,
|
|
88
|
+
status text NOT NULL DEFAULT 'new',
|
|
89
|
+
sheet_synced_at timestamptz,
|
|
90
|
+
sheet_error text,
|
|
91
|
+
page_path text,
|
|
92
|
+
ip text,
|
|
93
|
+
user_agent text,
|
|
94
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
CREATE INDEX form_submissions_form_key_created_idx ON public.form_submissions (form_key, created_at DESC);
|
|
98
|
+
|
|
99
|
+
GRANT SELECT, UPDATE ON public.form_submissions TO authenticated;
|
|
100
|
+
GRANT ALL ON public.form_submissions TO service_role;
|
|
101
|
+
ALTER TABLE public.form_submissions ENABLE ROW LEVEL SECURITY;
|
|
102
|
+
|
|
103
|
+
CREATE POLICY "submissions staff read" ON public.form_submissions
|
|
104
|
+
FOR SELECT TO authenticated USING (public.is_staff(auth.uid()) OR public.has_role(auth.uid(), 'editor'));
|
|
105
|
+
CREATE POLICY "submissions staff update status" ON public.form_submissions
|
|
106
|
+
FOR UPDATE TO authenticated USING (public.is_staff(auth.uid()) OR public.has_role(auth.uid(), 'editor'))
|
|
107
|
+
WITH CHECK (public.is_staff(auth.uid()) OR public.has_role(auth.uid(), 'editor'));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
ALTER TABLE public.form_submissions
|
|
2
|
+
ADD COLUMN IF NOT EXISTS notified_at timestamptz,
|
|
3
|
+
ADD COLUMN IF NOT EXISTS autoreply_at timestamptz,
|
|
4
|
+
ADD COLUMN IF NOT EXISTS delivery_error text;
|
|
5
|
+
|
|
6
|
+
CREATE INDEX IF NOT EXISTS form_submissions_form_key_created_idx
|
|
7
|
+
ON public.form_submissions (form_key, created_at DESC);
|
|
8
|
+
CREATE INDEX IF NOT EXISTS form_submissions_status_idx
|
|
9
|
+
ON public.form_submissions (status);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ALTER TABLE public.form_settings
|
|
2
|
+
ADD COLUMN IF NOT EXISTS notify_enabled boolean NOT NULL DEFAULT true,
|
|
3
|
+
ADD COLUMN IF NOT EXISTS notify_subject jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
4
|
+
ADD COLUMN IF NOT EXISTS notify_body jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
5
|
+
ADD COLUMN IF NOT EXISTS notify_from_name text NOT NULL DEFAULT '',
|
|
6
|
+
ADD COLUMN IF NOT EXISTS notify_from_email text NOT NULL DEFAULT '',
|
|
7
|
+
ADD COLUMN IF NOT EXISTS notify_reply_to text NOT NULL DEFAULT '';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
ALTER TABLE public.form_settings ADD COLUMN IF NOT EXISTS save_partials boolean NOT NULL DEFAULT true;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS public.form_partials (
|
|
4
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
5
|
+
form_id uuid REFERENCES public.forms(id) ON DELETE SET NULL,
|
|
6
|
+
form_key text NOT NULL,
|
|
7
|
+
client_token text NOT NULL,
|
|
8
|
+
locale text NOT NULL DEFAULT 'sv',
|
|
9
|
+
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
10
|
+
page_path text,
|
|
11
|
+
ip text,
|
|
12
|
+
user_agent text,
|
|
13
|
+
completed_at timestamptz,
|
|
14
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
15
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
16
|
+
UNIQUE (form_key, client_token)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
GRANT SELECT, DELETE ON public.form_partials TO authenticated;
|
|
20
|
+
GRANT ALL ON public.form_partials TO service_role;
|
|
21
|
+
|
|
22
|
+
ALTER TABLE public.form_partials ENABLE ROW LEVEL SECURITY;
|
|
23
|
+
|
|
24
|
+
DROP POLICY IF EXISTS "Staff can read incomplete forms" ON public.form_partials;
|
|
25
|
+
CREATE POLICY "Staff can read incomplete forms" ON public.form_partials
|
|
26
|
+
FOR SELECT TO authenticated USING (public.is_staff(auth.uid()));
|
|
27
|
+
|
|
28
|
+
DROP POLICY IF EXISTS "Staff can delete incomplete forms" ON public.form_partials;
|
|
29
|
+
CREATE POLICY "Staff can delete incomplete forms" ON public.form_partials
|
|
30
|
+
FOR DELETE TO authenticated USING (public.is_staff(auth.uid()));
|
|
31
|
+
|
|
32
|
+
CREATE INDEX IF NOT EXISTS form_partials_form_key_idx ON public.form_partials (form_key, updated_at DESC);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
-- Delivery attempts: append-only diagnostics for every outbound delivery
|
|
2
|
+
CREATE TABLE public.form_delivery_attempts (
|
|
3
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4
|
+
submission_id uuid REFERENCES public.form_submissions(id) ON DELETE CASCADE,
|
|
5
|
+
form_key text NOT NULL,
|
|
6
|
+
channel text NOT NULL, -- notification | autoreply | sheet | webhook
|
|
7
|
+
status text NOT NULL, -- ok | failed
|
|
8
|
+
target text, -- recipient list, sheet id or webhook url
|
|
9
|
+
error text,
|
|
10
|
+
attempt integer NOT NULL DEFAULT 1,
|
|
11
|
+
duration_ms integer,
|
|
12
|
+
meta jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
13
|
+
resolved_at timestamptz,
|
|
14
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX form_delivery_attempts_status_idx
|
|
18
|
+
ON public.form_delivery_attempts (status, created_at DESC);
|
|
19
|
+
CREATE INDEX form_delivery_attempts_form_idx
|
|
20
|
+
ON public.form_delivery_attempts (form_key, created_at DESC);
|
|
21
|
+
CREATE INDEX form_delivery_attempts_submission_idx
|
|
22
|
+
ON public.form_delivery_attempts (submission_id);
|
|
23
|
+
|
|
24
|
+
GRANT SELECT, UPDATE, DELETE ON public.form_delivery_attempts TO authenticated;
|
|
25
|
+
GRANT ALL ON public.form_delivery_attempts TO service_role;
|
|
26
|
+
ALTER TABLE public.form_delivery_attempts ENABLE ROW LEVEL SECURITY;
|
|
27
|
+
|
|
28
|
+
CREATE POLICY "Staff can read delivery attempts"
|
|
29
|
+
ON public.form_delivery_attempts FOR SELECT TO authenticated
|
|
30
|
+
USING (public.is_staff(auth.uid()));
|
|
31
|
+
CREATE POLICY "Staff can resolve delivery attempts"
|
|
32
|
+
ON public.form_delivery_attempts FOR UPDATE TO authenticated
|
|
33
|
+
USING (public.is_staff(auth.uid()))
|
|
34
|
+
WITH CHECK (public.is_staff(auth.uid()));
|
|
35
|
+
CREATE POLICY "Superadmins can clear delivery attempts"
|
|
36
|
+
ON public.form_delivery_attempts FOR DELETE TO authenticated
|
|
37
|
+
USING (public.has_role(auth.uid(), 'superadmin'::app_role));
|
|
38
|
+
|
|
39
|
+
-- Version history: immutable snapshots of a form definition
|
|
40
|
+
CREATE TABLE public.form_versions (
|
|
41
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
42
|
+
form_id uuid REFERENCES public.forms(id) ON DELETE CASCADE,
|
|
43
|
+
form_key text NOT NULL,
|
|
44
|
+
version_no integer NOT NULL,
|
|
45
|
+
name text NOT NULL,
|
|
46
|
+
form_type text NOT NULL,
|
|
47
|
+
fields jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
48
|
+
public_settings jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
49
|
+
private_settings jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
50
|
+
note text,
|
|
51
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
52
|
+
created_by uuid,
|
|
53
|
+
created_by_email text,
|
|
54
|
+
UNIQUE (form_key, version_no)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE INDEX form_versions_form_idx ON public.form_versions (form_key, version_no DESC);
|
|
58
|
+
|
|
59
|
+
GRANT SELECT, INSERT ON public.form_versions TO authenticated;
|
|
60
|
+
GRANT ALL ON public.form_versions TO service_role;
|
|
61
|
+
ALTER TABLE public.form_versions ENABLE ROW LEVEL SECURITY;
|
|
62
|
+
|
|
63
|
+
CREATE POLICY "Staff can read form versions"
|
|
64
|
+
ON public.form_versions FOR SELECT TO authenticated
|
|
65
|
+
USING (public.is_staff(auth.uid()));
|
|
66
|
+
CREATE POLICY "Staff can create form versions"
|
|
67
|
+
ON public.form_versions FOR INSERT TO authenticated
|
|
68
|
+
WITH CHECK (public.is_staff(auth.uid()));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE public.form_settings ADD COLUMN IF NOT EXISTS privacy_policy_url text NOT NULL DEFAULT '';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
ALTER TABLE public.global_sections ADD COLUMN IF NOT EXISTS kind text NOT NULL DEFAULT 'section';
|
|
2
|
+
|
|
3
|
+
ALTER TABLE public.global_sections DROP CONSTRAINT IF EXISTS global_sections_kind_check;
|
|
4
|
+
ALTER TABLE public.global_sections ADD CONSTRAINT global_sections_kind_check CHECK (kind IN ('block','section'));
|
|
5
|
+
|
|
6
|
+
UPDATE public.global_sections gs
|
|
7
|
+
SET kind = CASE
|
|
8
|
+
WHEN COALESCE(jsonb_array_length(v.blocks), 0) <= 1 THEN 'block'
|
|
9
|
+
ELSE 'section'
|
|
10
|
+
END
|
|
11
|
+
FROM public.global_section_versions v
|
|
12
|
+
WHERE v.id = gs.current_version_id;
|
|
13
|
+
|
|
14
|
+
UPDATE public.global_sections
|
|
15
|
+
SET kind = 'block'
|
|
16
|
+
WHERE current_version_id IS NULL;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE public.page_versions ADD COLUMN IF NOT EXISTS created_by_email text;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
with pg as (
|
|
2
|
+
select id from public.pages where key = 'bartenderkurs'
|
|
3
|
+
), hero as (
|
|
4
|
+
select v.blocks -> 0 as hero_block
|
|
5
|
+
from public.page_versions v join pg on v.page_id = pg.id
|
|
6
|
+
order by v.version_no desc limit 1
|
|
7
|
+
), nv as (
|
|
8
|
+
insert into public.page_versions (page_id, version_no, note, blocks)
|
|
9
|
+
select pg.id,
|
|
10
|
+
coalesce((select max(version_no) from public.page_versions where page_id = pg.id), 0) + 1,
|
|
11
|
+
'Rebuilt from barskola.nu/bartenderkurs',
|
|
12
|
+
jsonb_build_array(
|
|
13
|
+
(select hero_block from hero),
|
|
14
|
+
jsonb_build_object(
|
|
15
|
+
'id','bk-prose-intro','type','prose','visible',true,'props',jsonb_build_object(
|
|
16
|
+
'eyebrow', jsonb_build_object('sv','Bartenderkurs'),
|
|
17
|
+
'title', jsonb_build_object('sv','Bartenderutbildning i Ayia Napa, Cypern och Mallorca'),
|
|
18
|
+
'lead', jsonb_build_object('sv','En bartenderkurs hos Scandinavian Bar Academy utbildar dig till internationell bartender på fyra veckor. Utbildningen hålls i Ayia Napa på Cypern eller på Mallorca, på svenska, och vi tar emot elever från hela Norden. Fyra veckor med fokus på kunskap, kvalitet och service — inga förkunskaper krävs.'),
|
|
19
|
+
'tone','white','layout','single',
|
|
20
|
+
'items', jsonb_build_array(
|
|
21
|
+
jsonb_build_object('id','bk-p1','heading',jsonb_build_object('sv','Legitimerad bartender'),'body',jsonb_build_object('sv','SBA gör dig till legitimerad bartender och med certifikatet du erhåller efter avklarad utbildning har du möjlighet att jobba världen över. Efter avklarad bartenderkurs och praktik erbjuder vi dig jobb på Cypern om intresset finns — så att du så snart som möjligt kan påbörja din karriär inom bartenderyrket.')),
|
|
22
|
+
jsonb_build_object('id','bk-p2','heading',jsonb_build_object('sv','Garanterat jobb'),'body',jsonb_build_object('sv','Tack vare Scandinavian Bar Academys kontakter med barer, klubbar och restauranger samt dess högt ansedda rykte kan barskolan garantera jobb efter avklarad bartenderkurs utomlands. Jobb garanteras på Cypern samtidigt som certifikatet värderas högt även i andra länder. SBA är ensamma om att kunna erbjuda denna fördelaktiga jobbgaranti.')),
|
|
23
|
+
jsonb_build_object('id','bk-p3','heading',jsonb_build_object('sv','Trygghetspaket'),'body',jsonb_build_object('sv','Just nu ingår ett trygghetspaket när du bokar vår bartenderkurs i Ayia Napa.'))
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
),
|
|
27
|
+
jsonb_build_object(
|
|
28
|
+
'id','bk-media-tropisk','type','mediaSplit','visible',true,'props',jsonb_build_object(
|
|
29
|
+
'style','editorial','mediaSide','right','mediaRatio','50','align','center','padding','normal','tone','paper','aspect','landscape','rounded',false,
|
|
30
|
+
'mediaType','image',
|
|
31
|
+
'image','https://hcfwpwfzbcapfdemvsnh.supabase.co/storage/v1/object/public/media/imported/1785401059675-isacflairstrand.jpg',
|
|
32
|
+
'imageAlt', jsonb_build_object('sv','Elev tränar flair på stranden i Ayia Napa'),
|
|
33
|
+
'eyebrow', jsonb_build_object('sv','Bartenderkurs utomlands'),
|
|
34
|
+
'title', jsonb_build_object('sv','Bartenderkurs i tropisk miljö'),
|
|
35
|
+
'lead', jsonb_build_object('sv','Under vår bartenderutbildning på Cypern går du igenom alla grundläggande moment för att bli en bartender av högsta klass.'),
|
|
36
|
+
'body', jsonb_build_object('sv','Förutom teoretisk lära om diverse spritsorter och kundpsykologi praktiserar du fysiskt på barskolan. Under praktiken sätts dina färdigheter på prov, vilket ger dig en verklig uppfattning om hur det är att arbeta som bartender.
|
|
37
|
+
|
|
38
|
+
I Ayia Napa studerar du under en palm med utsikt över kristallblått vatten och tränar flaring på några av Europas finaste stränder. Mjuk finkornig sand, omtyckta restauranger och fantastisk service kännetecknar Cypern. Här erbjuder SBA dessutom en rad aktiviteter: event, temakvällar och dagsutflykter.'),
|
|
39
|
+
'cta', jsonb_build_object('label', jsonb_build_object('sv','Läs mer om Ayia Napa'), 'href','/destinationer')
|
|
40
|
+
)
|
|
41
|
+
),
|
|
42
|
+
jsonb_build_object(
|
|
43
|
+
'id','bk-media-boende','type','mediaSplit','visible',true,'props',jsonb_build_object(
|
|
44
|
+
'style','editorial','mediaSide','left','mediaRatio','50','align','center','padding','normal','tone','white','aspect','landscape','rounded',false,
|
|
45
|
+
'mediaType','image',
|
|
46
|
+
'image','https://hcfwpwfzbcapfdemvsnh.supabase.co/storage/v1/object/public/media/imported/1785401060265-2-elever-pambos-napa-rocks.jpg',
|
|
47
|
+
'imageAlt', jsonb_build_object('sv','Elever vid poolen på Pambos Napa Rocks'),
|
|
48
|
+
'eyebrow', jsonb_build_object('sv','Boende'),
|
|
49
|
+
'title', jsonb_build_object('sv','Boende under bartenderkursen'),
|
|
50
|
+
'body', jsonb_build_object('sv','Boende på SBA Apartments i Ayia Napa ingår i SBA:s bartenderkurs. Lägenhetskomplexet ligger vägg i vägg med ett populärt hotell för skandinaviska ungdomar, med gångavstånd till allt du behöver under din vistelse.
|
|
51
|
+
|
|
52
|
+
Hotell Pambos Napa Rocks stoltserar med Ayia Napas största poolområde. Här finns två pooler, en härlig poolbar som serverar mat och dryck till fördelaktiga priser samt mängder av solstolar och parasoller som kan nyttjas utan extra kostnad.'),
|
|
53
|
+
'cta', jsonb_build_object('label', jsonb_build_object('sv','Läs mer om boendet'), 'href','/destinationer')
|
|
54
|
+
)
|
|
55
|
+
),
|
|
56
|
+
jsonb_build_object(
|
|
57
|
+
'id','bk-icons-ingar','type','iconGrid','visible',true,'props',jsonb_build_object(
|
|
58
|
+
'eyebrow', jsonb_build_object('sv','Alltid inkluderat'),
|
|
59
|
+
'title', jsonb_build_object('sv','Vad som alltid ingår när du bokar din utbildning hos oss'),
|
|
60
|
+
'tone','paper','columns',4,'iconStyle','circle','cardStyle','bordered',
|
|
61
|
+
'items', jsonb_build_array(
|
|
62
|
+
jsonb_build_object('id','i1','icon','briefcase','title',jsonb_build_object('sv','Jobbgaranti efter avklarad utbildning')),
|
|
63
|
+
jsonb_build_object('id','i2','icon','award','title',jsonb_build_object('sv','Internationellt diplom och referenser')),
|
|
64
|
+
jsonb_build_object('id','i3','icon','creditCard','title',jsonb_build_object('sv','Kostnadsfri delbetalning')),
|
|
65
|
+
jsonb_build_object('id','i4','icon','handshake','title',jsonb_build_object('sv','Unika samarbeten med barer och nattklubbar')),
|
|
66
|
+
jsonb_build_object('id','i5','icon','graduationCap','title',jsonb_build_object('sv','Högkvalitativ utbildning med välutbildad personal')),
|
|
67
|
+
jsonb_build_object('id','i6','icon','calendarClock','title',jsonb_build_object('sv','Fyra veckors teoretisk och praktisk kvalitetsutbildning')),
|
|
68
|
+
jsonb_build_object('id','i7','icon','sun','title',jsonb_build_object('sv','Utbildningen sker på en av Europas finaste öar, Cypern')),
|
|
69
|
+
jsonb_build_object('id','i8','icon','bookOpen','title',jsonb_build_object('sv','Studiematerial')),
|
|
70
|
+
jsonb_build_object('id','i9','icon','ticket','title',jsonb_build_object('sv','VIP-kort')),
|
|
71
|
+
jsonb_build_object('id','i10','icon','wallet','title',jsonb_build_object('sv','Rabatter på events')),
|
|
72
|
+
jsonb_build_object('id','i11','icon','bed','title',jsonb_build_object('sv','Boende fyra veckor i Ayia Napa')),
|
|
73
|
+
jsonb_build_object('id','i12','icon','flame','title',jsonb_build_object('sv','Flaring-lektioner')),
|
|
74
|
+
jsonb_build_object('id','i13','icon','users','title',jsonb_build_object('sv','Gemensamma utgångar')),
|
|
75
|
+
jsonb_build_object('id','i14','icon','partyPopper','title',jsonb_build_object('sv','Avslutningsmiddag och fest'))
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
),
|
|
79
|
+
jsonb_build_object(
|
|
80
|
+
'id','bk-faq-larande','type','faq','visible',true,'props',jsonb_build_object(
|
|
81
|
+
'title', jsonb_build_object('sv','Vad du lär dig hos Scandinavian Bar Academy'),
|
|
82
|
+
'columns',3,
|
|
83
|
+
'items', jsonb_build_array(
|
|
84
|
+
jsonb_build_object('id','f1','q',jsonb_build_object('sv','Utrustningen'),'a',jsonb_build_object('sv','Du lär dig att hantera en bartenders alla verktyg.')),
|
|
85
|
+
jsonb_build_object('id','f2','q',jsonb_build_object('sv','Baruppställning'),'a',jsonb_build_object('sv','Du får lära dig att ställa upp baren inför ett arbetspass.')),
|
|
86
|
+
jsonb_build_object('id','f3','q',jsonb_build_object('sv','Alkohollära'),'a',jsonb_build_object('sv','Här får du lära dig om de olika spritsorterna en bartender arbetar med. Vi går igenom historian av produktionen och smaksättningen av olika spritsorter.')),
|
|
87
|
+
jsonb_build_object('id','f4','q',jsonb_build_object('sv','Hygien'),'a',jsonb_build_object('sv','Personlig hygien och renlighet är en självklar förutsättning för att du ska få arbeta som bartender. Vi går därför igenom hur du på bästa sätt förbereder dig inför ditt arbetspass.')),
|
|
88
|
+
jsonb_build_object('id','f5','q',jsonb_build_object('sv','Etikett'),'a',jsonb_build_object('sv','Att ha respekt för sin arbetsplats är en viktig del av att vara bartender — här får du lära dig om etikett bakom baren.')),
|
|
89
|
+
jsonb_build_object('id','f6','q',jsonb_build_object('sv','Dekoration'),'a',jsonb_build_object('sv','Du får lära dig hur du dekorerar olika cocktails och drinkar på olika sätt.')),
|
|
90
|
+
jsonb_build_object('id','f7','q',jsonb_build_object('sv','Mixologi'),'a',jsonb_build_object('sv','Här lär du dig att blanda drinkar. Denna del av utbildningen lär dig principerna för mixologi och hur du bygger en drink i lager/nivåer. Du kommer att få lära dig över 100 av de populäraste och vanligaste cocktailsen.')),
|
|
91
|
+
jsonb_build_object('id','f8','q',jsonb_build_object('sv','Speed Mixing/Pouring'),'a',jsonb_build_object('sv','I den här delen av bartenderutbildningen lär du dig att förbereda ett flertal drinkar snabbt och exakt utan att behöva använda dig av mätmått.')),
|
|
92
|
+
jsonb_build_object('id','f9','q',jsonb_build_object('sv','Kundservice'),'a',jsonb_build_object('sv','Du lär dig att ge effektiv, tillmötesgående och framförallt trevlig service. En nöjd kund kommer tillbaka, ger dricks och sprider ett gott ord om din bar. Vi går även igenom hur man hanterar berusade gäster.')),
|
|
93
|
+
jsonb_build_object('id','f10','q',jsonb_build_object('sv','Flairing'),'a',jsonb_build_object('sv','Vi lär dig hur workingflair går till — du lär dig att showa i baren samtidigt som du blandar drinkar. Det finns möjlighet att gå extra flairlektioner.')),
|
|
94
|
+
jsonb_build_object('id','f11','q',jsonb_build_object('sv','Serveringstillstånd'),'a',jsonb_build_object('sv','Vi går igenom de olika regler och lagar som gäller i Sverige vid alkoholservering. Du får lära dig att servera alkohol med medvetenhet.')),
|
|
95
|
+
jsonb_build_object('id','f12','q',jsonb_build_object('sv','Prov'),'a',jsonb_build_object('sv','Under kursen har vi flera delprov som spelar in på ditt slutbetyg — allt för att se till att du är i fas med din inläsning och att du har vad som krävs för att bli bartender.'))
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
),
|
|
99
|
+
jsonb_build_object(
|
|
100
|
+
'id','bk-prose-minnen','type','prose','visible',true,'props',jsonb_build_object(
|
|
101
|
+
'eyebrow', jsonb_build_object('sv','Mer än en kurs'),
|
|
102
|
+
'title', jsonb_build_object('sv','En bartenderkurs utomlands som ger dig minnen för livet'),
|
|
103
|
+
'lead', jsonb_build_object('sv','SBA är mycket mer än bara en bartenderkurs utomlands. Det är ett äventyr som berikar dig med minnen för livet.'),
|
|
104
|
+
'tone','white','layout','single',
|
|
105
|
+
'items', jsonb_build_array(
|
|
106
|
+
jsonb_build_object('id','bk-m1','heading',jsonb_build_object('sv','Vänner för livet'),'body',jsonb_build_object('sv','Under utbildningens gång träffar du mängder av olika personer. Du lär känna turister under praktiken, men också personalen som jobbar på orternas barer och nattklubbar. Du knyter vänskapsband för livet — en bidragande anledning till att majoriteten av våra elever väljer att stanna kvar i Ayia Napa efter avslutad bartenderkurs.')),
|
|
107
|
+
jsonb_build_object('id','bk-m2','heading',jsonb_build_object('sv','Upplevelser på plats'),'body',jsonb_build_object('sv','Under din vistelse i Ayia Napa får du möjlighet att ta del av många upplevelser: havsgrottor, stränder i världsklass, event som barrundor och partybåt och mycket mer.'))
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
),
|
|
111
|
+
jsonb_build_object(
|
|
112
|
+
'id','bk-icons-tillval','type','iconGrid','visible',true,'props',jsonb_build_object(
|
|
113
|
+
'eyebrow', jsonb_build_object('sv','Tillval'),
|
|
114
|
+
'title', jsonb_build_object('sv','Vid bokning finns även dessa tillval'),
|
|
115
|
+
'tone','ice','columns',3,'iconStyle','circle','cardStyle','bordered',
|
|
116
|
+
'items', jsonb_build_array(
|
|
117
|
+
jsonb_build_object('id','t1','icon','shieldCheck','title',jsonb_build_object('sv','Avbeställningsskydd'),'meta',jsonb_build_object('sv','399 SEK'),'body',jsonb_build_object('sv','Skyddar dig ifall du blir sjuk och inte kan genomföra kursen.')),
|
|
118
|
+
jsonb_build_object('id','t2','icon','calendarCheck','title',jsonb_build_object('sv','Flexibel bokning'),'meta',jsonb_build_object('sv','700 SEK'),'body',jsonb_build_object('sv','Boka om din kurs utan extra kostnad.')),
|
|
119
|
+
jsonb_build_object('id','t3','icon','badgeCheck','title',jsonb_build_object('sv','Utbildningsgaranti'),'meta',jsonb_build_object('sv','895 SEK'),'body',jsonb_build_object('sv','Vi hjälper dig tills du klarar kursen. Om du inte klarar kursen har du med detta tillägg möjlighet att gå om den. (Boende ingår ej.)'))
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
),
|
|
123
|
+
jsonb_build_object(
|
|
124
|
+
'id','bk-cta','type','ctaBanner','visible',true,'props',jsonb_build_object(
|
|
125
|
+
'eyebrow', jsonb_build_object('sv','Exklusivt erbjudande'),
|
|
126
|
+
'title', jsonb_build_object('sv','Vill du ta del av ett exklusivt erbjudande?'),
|
|
127
|
+
'body', jsonb_build_object('sv','Få med dina vänner på utbildningen och få rabatt på din egen!'),
|
|
128
|
+
'tone','ink',
|
|
129
|
+
'cta', jsonb_build_object('label', jsonb_build_object('sv','Boka nu'), 'href','/boka'),
|
|
130
|
+
'secondaryCta', jsonb_build_object('label', jsonb_build_object('sv','Se kursdatum'), 'href','/datum')
|
|
131
|
+
)
|
|
132
|
+
),
|
|
133
|
+
jsonb_build_object('id','bk-contact','type','contactCta','visible',true,'props', '{}'::jsonb)
|
|
134
|
+
)
|
|
135
|
+
from pg
|
|
136
|
+
returning id, page_id
|
|
137
|
+
), deact as (
|
|
138
|
+
update public.page_publications p set is_active = false
|
|
139
|
+
from nv where p.page_id = nv.page_id and p.is_active
|
|
140
|
+
returning 1
|
|
141
|
+
)
|
|
142
|
+
insert into public.page_publications (page_id, version_id, is_active, note)
|
|
143
|
+
select nv.page_id, nv.id, true, 'Rebuilt from barskola.nu/bartenderkurs' from nv;
|
|
144
|
+
|
|
145
|
+
update public.pages p
|
|
146
|
+
set current_version_id = (
|
|
147
|
+
select v.id from public.page_versions v where v.page_id = p.id order by v.version_no desc limit 1
|
|
148
|
+
)
|
|
149
|
+
where p.key = 'bartenderkurs';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
with src as (
|
|
2
|
+
select v.page_id, v.blocks::jsonb as blocks
|
|
3
|
+
from page_versions v join pages p on p.id = v.page_id
|
|
4
|
+
where p.key = 'bartenderkurs'
|
|
5
|
+
order by v.version_no desc limit 1
|
|
6
|
+
), imgs as (
|
|
7
|
+
select jsonb_agg(jsonb_build_object(
|
|
8
|
+
'id', 'bkm-' || i,
|
|
9
|
+
'image', 'https://hcfwpwfzbcapfdemvsnh.supabase.co/storage/v1/object/public/media/' || n || '-1920.webp',
|
|
10
|
+
'alt', jsonb_build_object('sv', a)
|
|
11
|
+
) order by i) as items
|
|
12
|
+
from (values
|
|
13
|
+
(1,'student-in-the-bar-1-dsc00521','Elev bakom baren under bartenderkursen'),
|
|
14
|
+
(2,'student-pour-1-dsc00573','Elev häller upp en drink under utbildningen'),
|
|
15
|
+
(3,'students-by-the-bar-1dsc00821','Elever samlade vid baren i Ayia Napa'),
|
|
16
|
+
(4,'student-flair-dsc01358','Elev tränar flairbartending'),
|
|
17
|
+
(5,'konstantions-flair-dsc01525','Instruktör visar flairbartending'),
|
|
18
|
+
(6,'students-study-at-beach-dsc01566','Elever pluggar bartenderteori på stranden'),
|
|
19
|
+
(7,'shaker-on-the-beach-dsc01775','Shaker på stranden i Ayia Napa'),
|
|
20
|
+
(8,'cocktail-dsc02143','Färdig cocktail från bartenderkursen'),
|
|
21
|
+
(9,'students-and-cocktails-dsc02105','Elever med egengjorda cocktails'),
|
|
22
|
+
(10,'students-on-the-beach-dsc02093','Elever på stranden mellan lektionerna'),
|
|
23
|
+
(11,'toni-and-students-dsc09949','Instruktör och elever under en lektion'),
|
|
24
|
+
(12,'drink-tasting-students-dsc02156','Elever provsmakar drinkar under utbildningen')
|
|
25
|
+
) as t(i,n,a)
|
|
26
|
+
), nb as (
|
|
27
|
+
select src.page_id,
|
|
28
|
+
jsonb_set(src.blocks, '{1}',
|
|
29
|
+
jsonb_build_object(
|
|
30
|
+
'id', src.blocks->1->>'id',
|
|
31
|
+
'type', 'imageMarquee',
|
|
32
|
+
'visible', true,
|
|
33
|
+
'props', (src.blocks->1->'props') - 'items' - 'layout'
|
|
34
|
+
|| jsonb_build_object(
|
|
35
|
+
'items2', src.blocks->1->'props'->'items',
|
|
36
|
+
'items', imgs.items,
|
|
37
|
+
'textPosition', 'left',
|
|
38
|
+
'columns', 2,
|
|
39
|
+
'firstDirection', 'up',
|
|
40
|
+
'speed', 'normal',
|
|
41
|
+
'aspect', 'mixed',
|
|
42
|
+
'gap', 'md',
|
|
43
|
+
'rounded', false,
|
|
44
|
+
'tone', 'white'
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
) as blocks
|
|
48
|
+
from src, imgs
|
|
49
|
+
), ins as (
|
|
50
|
+
insert into page_versions (page_id, version_no, blocks, note)
|
|
51
|
+
select nb.page_id,
|
|
52
|
+
(select coalesce(max(version_no),0)+1 from page_versions where page_id = nb.page_id),
|
|
53
|
+
nb.blocks, 'Intro section becomes image marquee'
|
|
54
|
+
from nb
|
|
55
|
+
returning id, page_id
|
|
56
|
+
), upd as (
|
|
57
|
+
update pages set current_version_id = ins.id, status = 'published'
|
|
58
|
+
from ins where pages.id = ins.page_id returning 1
|
|
59
|
+
), deact as (
|
|
60
|
+
update page_publications set is_active = false
|
|
61
|
+
where page_id = (select page_id from ins) and is_active = true
|
|
62
|
+
returning 1
|
|
63
|
+
)
|
|
64
|
+
insert into page_publications (page_id, version_id, is_active)
|
|
65
|
+
select page_id, id, true from ins;
|