@bygghammar/cms-platform 0.11.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 +8 -4
- package/src/backoffice/media/ReviewImageUpload.tsx +104 -0
- package/src/platform/contract.ts +92 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
|
|
2
|
+
CREATE TYPE public.app_role AS ENUM ('admin', 'editor');
|
|
3
|
+
|
|
4
|
+
CREATE TABLE public.user_roles (
|
|
5
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
6
|
+
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
7
|
+
role public.app_role NOT NULL,
|
|
8
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
9
|
+
UNIQUE (user_id, role)
|
|
10
|
+
);
|
|
11
|
+
GRANT SELECT ON public.user_roles TO authenticated;
|
|
12
|
+
GRANT ALL ON public.user_roles TO service_role;
|
|
13
|
+
ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY;
|
|
14
|
+
|
|
15
|
+
CREATE POLICY "Users can view their own roles"
|
|
16
|
+
ON public.user_roles
|
|
17
|
+
FOR SELECT
|
|
18
|
+
TO authenticated
|
|
19
|
+
USING (auth.uid() = user_id);
|
|
20
|
+
|
|
21
|
+
CREATE OR REPLACE FUNCTION public.has_role(_user_id uuid, _role public.app_role)
|
|
22
|
+
RETURNS boolean
|
|
23
|
+
LANGUAGE sql
|
|
24
|
+
STABLE
|
|
25
|
+
SECURITY DEFINER
|
|
26
|
+
SET search_path = public
|
|
27
|
+
AS $$
|
|
28
|
+
SELECT EXISTS (
|
|
29
|
+
SELECT 1 FROM public.user_roles
|
|
30
|
+
WHERE user_id = _user_id AND role = _role
|
|
31
|
+
)
|
|
32
|
+
$$;
|
|
33
|
+
|
|
34
|
+
CREATE TABLE public.content_sections (
|
|
35
|
+
key text PRIMARY KEY,
|
|
36
|
+
data jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
37
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
38
|
+
updated_by uuid REFERENCES auth.users(id) ON DELETE SET NULL
|
|
39
|
+
);
|
|
40
|
+
GRANT SELECT ON public.content_sections TO anon, authenticated;
|
|
41
|
+
GRANT INSERT, UPDATE, DELETE ON public.content_sections TO authenticated;
|
|
42
|
+
GRANT ALL ON public.content_sections TO service_role;
|
|
43
|
+
ALTER TABLE public.content_sections ENABLE ROW LEVEL SECURITY;
|
|
44
|
+
|
|
45
|
+
CREATE POLICY "Anyone can read content sections"
|
|
46
|
+
ON public.content_sections
|
|
47
|
+
FOR SELECT
|
|
48
|
+
TO anon, authenticated
|
|
49
|
+
USING (true);
|
|
50
|
+
|
|
51
|
+
CREATE POLICY "Admins can insert content sections"
|
|
52
|
+
ON public.content_sections
|
|
53
|
+
FOR INSERT
|
|
54
|
+
TO authenticated
|
|
55
|
+
WITH CHECK (public.has_role(auth.uid(), 'admin'));
|
|
56
|
+
|
|
57
|
+
CREATE POLICY "Admins can update content sections"
|
|
58
|
+
ON public.content_sections
|
|
59
|
+
FOR UPDATE
|
|
60
|
+
TO authenticated
|
|
61
|
+
USING (public.has_role(auth.uid(), 'admin'))
|
|
62
|
+
WITH CHECK (public.has_role(auth.uid(), 'admin'));
|
|
63
|
+
|
|
64
|
+
CREATE POLICY "Admins can delete content sections"
|
|
65
|
+
ON public.content_sections
|
|
66
|
+
FOR DELETE
|
|
67
|
+
TO authenticated
|
|
68
|
+
USING (public.has_role(auth.uid(), 'admin'));
|
|
69
|
+
|
|
70
|
+
CREATE OR REPLACE FUNCTION public.touch_content_sections()
|
|
71
|
+
RETURNS trigger
|
|
72
|
+
LANGUAGE plpgsql
|
|
73
|
+
SET search_path = public
|
|
74
|
+
AS $$
|
|
75
|
+
BEGIN
|
|
76
|
+
NEW.updated_at = now();
|
|
77
|
+
NEW.updated_by = auth.uid();
|
|
78
|
+
RETURN NEW;
|
|
79
|
+
END;
|
|
80
|
+
$$;
|
|
81
|
+
|
|
82
|
+
CREATE TRIGGER content_sections_touch
|
|
83
|
+
BEFORE INSERT OR UPDATE ON public.content_sections
|
|
84
|
+
FOR EACH ROW EXECUTE FUNCTION public.touch_content_sections();
|
|
85
|
+
|
|
86
|
+
CREATE OR REPLACE FUNCTION public.grant_initial_admin()
|
|
87
|
+
RETURNS trigger
|
|
88
|
+
LANGUAGE plpgsql
|
|
89
|
+
SECURITY DEFINER
|
|
90
|
+
SET search_path = public
|
|
91
|
+
AS $$
|
|
92
|
+
BEGIN
|
|
93
|
+
IF NEW.email_confirmed_at IS NOT NULL
|
|
94
|
+
AND lower(NEW.email) = 'byggis@gmail.com' THEN
|
|
95
|
+
INSERT INTO public.user_roles (user_id, role)
|
|
96
|
+
VALUES (NEW.id, 'admin')
|
|
97
|
+
ON CONFLICT (user_id, role) DO NOTHING;
|
|
98
|
+
END IF;
|
|
99
|
+
RETURN NEW;
|
|
100
|
+
END;
|
|
101
|
+
$$;
|
|
102
|
+
|
|
103
|
+
CREATE TRIGGER on_auth_user_created_grant_admin
|
|
104
|
+
AFTER INSERT ON auth.users
|
|
105
|
+
FOR EACH ROW EXECUTE FUNCTION public.grant_initial_admin();
|
|
106
|
+
|
|
107
|
+
CREATE TRIGGER on_auth_user_confirmed_grant_admin
|
|
108
|
+
AFTER UPDATE OF email_confirmed_at ON auth.users
|
|
109
|
+
FOR EACH ROW
|
|
110
|
+
WHEN (OLD.email_confirmed_at IS NULL AND NEW.email_confirmed_at IS NOT NULL)
|
|
111
|
+
EXECUTE FUNCTION public.grant_initial_admin();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
REVOKE ALL ON FUNCTION public.touch_content_sections() FROM PUBLIC, anon, authenticated;
|
|
3
|
+
REVOKE ALL ON FUNCTION public.grant_initial_admin() FROM PUBLIC, anon, authenticated;
|
|
4
|
+
REVOKE ALL ON FUNCTION public.has_role(uuid, public.app_role) FROM PUBLIC;
|
|
5
|
+
GRANT EXECUTE ON FUNCTION public.has_role(uuid, public.app_role) TO authenticated;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
REVOKE EXECUTE ON FUNCTION public.has_role(uuid, public.app_role) FROM anon;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
-- Additive: per-page localized SEO overrides + slug-change redirects.
|
|
2
|
+
-- Never touches customer submission data.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE public.page_seo (
|
|
5
|
+
page_key text PRIMARY KEY,
|
|
6
|
+
slugs jsonb NOT NULL DEFAULT '{}'::jsonb, -- { sv: "boka", en: "book" }
|
|
7
|
+
seo jsonb NOT NULL DEFAULT '{}'::jsonb, -- { sv: { title, description, ogImage, ... }, en: {...} }
|
|
8
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
9
|
+
updated_by uuid
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
GRANT SELECT ON public.page_seo TO anon, authenticated;
|
|
13
|
+
GRANT INSERT, UPDATE, DELETE ON public.page_seo TO authenticated;
|
|
14
|
+
GRANT ALL ON public.page_seo TO service_role;
|
|
15
|
+
|
|
16
|
+
ALTER TABLE public.page_seo ENABLE ROW LEVEL SECURITY;
|
|
17
|
+
|
|
18
|
+
CREATE POLICY "Anyone can read page_seo"
|
|
19
|
+
ON public.page_seo FOR SELECT
|
|
20
|
+
TO anon, authenticated
|
|
21
|
+
USING (true);
|
|
22
|
+
|
|
23
|
+
CREATE POLICY "Admins insert page_seo"
|
|
24
|
+
ON public.page_seo FOR INSERT
|
|
25
|
+
TO authenticated
|
|
26
|
+
WITH CHECK (has_role(auth.uid(), 'admin'::app_role));
|
|
27
|
+
|
|
28
|
+
CREATE POLICY "Admins update page_seo"
|
|
29
|
+
ON public.page_seo FOR UPDATE
|
|
30
|
+
TO authenticated
|
|
31
|
+
USING (has_role(auth.uid(), 'admin'::app_role))
|
|
32
|
+
WITH CHECK (has_role(auth.uid(), 'admin'::app_role));
|
|
33
|
+
|
|
34
|
+
CREATE POLICY "Admins delete page_seo"
|
|
35
|
+
ON public.page_seo FOR DELETE
|
|
36
|
+
TO authenticated
|
|
37
|
+
USING (has_role(auth.uid(), 'admin'::app_role));
|
|
38
|
+
|
|
39
|
+
CREATE OR REPLACE FUNCTION public.touch_page_seo()
|
|
40
|
+
RETURNS trigger
|
|
41
|
+
LANGUAGE plpgsql
|
|
42
|
+
SET search_path = public
|
|
43
|
+
AS $$
|
|
44
|
+
BEGIN
|
|
45
|
+
NEW.updated_at = now();
|
|
46
|
+
RETURN NEW;
|
|
47
|
+
END;
|
|
48
|
+
$$;
|
|
49
|
+
|
|
50
|
+
REVOKE EXECUTE ON FUNCTION public.touch_page_seo() FROM public, anon;
|
|
51
|
+
|
|
52
|
+
CREATE TRIGGER page_seo_touch
|
|
53
|
+
BEFORE UPDATE ON public.page_seo
|
|
54
|
+
FOR EACH ROW EXECUTE FUNCTION public.touch_page_seo();
|
|
55
|
+
|
|
56
|
+
-- Slug-change redirects. Append-only from the app's perspective;
|
|
57
|
+
-- edits/deletes require admin. Old URLs stay resolvable forever.
|
|
58
|
+
CREATE TABLE public.redirects (
|
|
59
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
60
|
+
from_path text NOT NULL UNIQUE, -- e.g. "/boka"
|
|
61
|
+
to_path text NOT NULL, -- e.g. "/bokning" or "/en/book"
|
|
62
|
+
status smallint NOT NULL DEFAULT 301,
|
|
63
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
64
|
+
created_by uuid
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
GRANT SELECT ON public.redirects TO anon, authenticated;
|
|
68
|
+
GRANT INSERT, UPDATE, DELETE ON public.redirects TO authenticated;
|
|
69
|
+
GRANT ALL ON public.redirects TO service_role;
|
|
70
|
+
|
|
71
|
+
ALTER TABLE public.redirects ENABLE ROW LEVEL SECURITY;
|
|
72
|
+
|
|
73
|
+
CREATE POLICY "Anyone can read redirects"
|
|
74
|
+
ON public.redirects FOR SELECT
|
|
75
|
+
TO anon, authenticated
|
|
76
|
+
USING (true);
|
|
77
|
+
|
|
78
|
+
CREATE POLICY "Admins insert redirects"
|
|
79
|
+
ON public.redirects FOR INSERT
|
|
80
|
+
TO authenticated
|
|
81
|
+
WITH CHECK (has_role(auth.uid(), 'admin'::app_role));
|
|
82
|
+
|
|
83
|
+
CREATE POLICY "Admins update redirects"
|
|
84
|
+
ON public.redirects FOR UPDATE
|
|
85
|
+
TO authenticated
|
|
86
|
+
USING (has_role(auth.uid(), 'admin'::app_role))
|
|
87
|
+
WITH CHECK (has_role(auth.uid(), 'admin'::app_role));
|
|
88
|
+
|
|
89
|
+
CREATE POLICY "Admins delete redirects"
|
|
90
|
+
ON public.redirects FOR DELETE
|
|
91
|
+
TO authenticated
|
|
92
|
+
USING (has_role(auth.uid(), 'admin'::app_role));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
REVOKE EXECUTE ON FUNCTION public.touch_page_seo() FROM authenticated;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
CREATE TABLE public.locale_domains (
|
|
2
|
+
locale text NOT NULL PRIMARY KEY,
|
|
3
|
+
domain text NOT NULL,
|
|
4
|
+
is_default boolean NOT NULL DEFAULT false,
|
|
5
|
+
is_admin_domain boolean NOT NULL DEFAULT false,
|
|
6
|
+
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
|
7
|
+
updated_at timestamp with time zone NOT NULL DEFAULT now()
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
GRANT SELECT ON public.locale_domains TO anon;
|
|
11
|
+
GRANT SELECT ON public.locale_domains TO authenticated;
|
|
12
|
+
GRANT ALL ON public.locale_domains TO service_role;
|
|
13
|
+
|
|
14
|
+
ALTER TABLE public.locale_domains ENABLE ROW LEVEL SECURITY;
|
|
15
|
+
|
|
16
|
+
CREATE POLICY "Anyone can read locale domains"
|
|
17
|
+
ON public.locale_domains
|
|
18
|
+
FOR SELECT
|
|
19
|
+
TO anon, authenticated
|
|
20
|
+
USING (true);
|
|
21
|
+
|
|
22
|
+
CREATE POLICY "Only admins can insert locale domains"
|
|
23
|
+
ON public.locale_domains
|
|
24
|
+
FOR INSERT
|
|
25
|
+
TO authenticated
|
|
26
|
+
WITH CHECK (public.has_role(auth.uid(), 'admin'::public.app_role));
|
|
27
|
+
|
|
28
|
+
CREATE POLICY "Only admins can update locale domains"
|
|
29
|
+
ON public.locale_domains
|
|
30
|
+
FOR UPDATE
|
|
31
|
+
TO authenticated
|
|
32
|
+
USING (public.has_role(auth.uid(), 'admin'::public.app_role))
|
|
33
|
+
WITH CHECK (public.has_role(auth.uid(), 'admin'::public.app_role));
|
|
34
|
+
|
|
35
|
+
CREATE POLICY "Only admins can delete locale domains"
|
|
36
|
+
ON public.locale_domains
|
|
37
|
+
FOR DELETE
|
|
38
|
+
TO authenticated
|
|
39
|
+
USING (public.has_role(auth.uid(), 'admin'::public.app_role));
|
|
40
|
+
|
|
41
|
+
CREATE OR REPLACE FUNCTION public.touch_locale_domains()
|
|
42
|
+
RETURNS trigger
|
|
43
|
+
LANGUAGE plpgsql
|
|
44
|
+
SET search_path TO 'public'
|
|
45
|
+
AS $$
|
|
46
|
+
BEGIN
|
|
47
|
+
NEW.updated_at = now();
|
|
48
|
+
RETURN NEW;
|
|
49
|
+
END;
|
|
50
|
+
$$;
|
|
51
|
+
|
|
52
|
+
CREATE TRIGGER update_locale_domains_updated_at
|
|
53
|
+
BEFORE UPDATE ON public.locale_domains
|
|
54
|
+
FOR EACH ROW
|
|
55
|
+
EXECUTE FUNCTION public.touch_locale_domains();
|
|
56
|
+
|
|
57
|
+
INSERT INTO public.locale_domains (locale, domain, is_default, is_admin_domain)
|
|
58
|
+
VALUES ('sv', 'www.dev.barskola.nu', true, true)
|
|
59
|
+
ON CONFLICT (locale) DO UPDATE SET
|
|
60
|
+
domain = EXCLUDED.domain,
|
|
61
|
+
is_default = EXCLUDED.is_default,
|
|
62
|
+
is_admin_domain = EXCLUDED.is_admin_domain,
|
|
63
|
+
updated_at = now();
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
|
|
2
|
+
-- =========================================================
|
|
3
|
+
-- PAGES
|
|
4
|
+
-- =========================================================
|
|
5
|
+
CREATE TABLE public.pages (
|
|
6
|
+
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
7
|
+
key text NOT NULL UNIQUE,
|
|
8
|
+
name text NOT NULL,
|
|
9
|
+
is_system boolean NOT NULL DEFAULT false,
|
|
10
|
+
status text NOT NULL DEFAULT 'draft',
|
|
11
|
+
current_version_id uuid,
|
|
12
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
13
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
14
|
+
updated_by uuid
|
|
15
|
+
);
|
|
16
|
+
GRANT SELECT ON public.pages TO anon;
|
|
17
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.pages TO authenticated;
|
|
18
|
+
GRANT ALL ON public.pages TO service_role;
|
|
19
|
+
ALTER TABLE public.pages ENABLE ROW LEVEL SECURITY;
|
|
20
|
+
|
|
21
|
+
CREATE POLICY "Anyone can read pages" ON public.pages FOR SELECT USING (true);
|
|
22
|
+
CREATE POLICY "Admins insert pages" ON public.pages FOR INSERT TO authenticated
|
|
23
|
+
WITH CHECK (has_role(auth.uid(),'admin'::app_role));
|
|
24
|
+
CREATE POLICY "Admins update pages" ON public.pages FOR UPDATE TO authenticated
|
|
25
|
+
USING (has_role(auth.uid(),'admin'::app_role))
|
|
26
|
+
WITH CHECK (has_role(auth.uid(),'admin'::app_role));
|
|
27
|
+
CREATE POLICY "Admins delete pages" ON public.pages FOR DELETE TO authenticated
|
|
28
|
+
USING (has_role(auth.uid(),'admin'::app_role) AND is_system = false);
|
|
29
|
+
|
|
30
|
+
-- =========================================================
|
|
31
|
+
-- PAGE VERSIONS
|
|
32
|
+
-- =========================================================
|
|
33
|
+
CREATE TABLE public.page_versions (
|
|
34
|
+
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
35
|
+
page_id uuid NOT NULL REFERENCES public.pages(id) ON DELETE CASCADE,
|
|
36
|
+
version_no integer NOT NULL,
|
|
37
|
+
blocks jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
38
|
+
note text,
|
|
39
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
40
|
+
created_by uuid,
|
|
41
|
+
UNIQUE (page_id, version_no)
|
|
42
|
+
);
|
|
43
|
+
GRANT SELECT ON public.page_versions TO anon;
|
|
44
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.page_versions TO authenticated;
|
|
45
|
+
GRANT ALL ON public.page_versions TO service_role;
|
|
46
|
+
ALTER TABLE public.page_versions ENABLE ROW LEVEL SECURITY;
|
|
47
|
+
|
|
48
|
+
CREATE POLICY "Anyone can read page_versions" ON public.page_versions FOR SELECT USING (true);
|
|
49
|
+
CREATE POLICY "Admins insert page_versions" ON public.page_versions FOR INSERT TO authenticated
|
|
50
|
+
WITH CHECK (has_role(auth.uid(),'admin'::app_role));
|
|
51
|
+
CREATE POLICY "Admins update page_versions" ON public.page_versions FOR UPDATE TO authenticated
|
|
52
|
+
USING (has_role(auth.uid(),'admin'::app_role))
|
|
53
|
+
WITH CHECK (has_role(auth.uid(),'admin'::app_role));
|
|
54
|
+
CREATE POLICY "Admins delete page_versions" ON public.page_versions FOR DELETE TO authenticated
|
|
55
|
+
USING (has_role(auth.uid(),'admin'::app_role));
|
|
56
|
+
|
|
57
|
+
ALTER TABLE public.pages
|
|
58
|
+
ADD CONSTRAINT pages_current_version_fk
|
|
59
|
+
FOREIGN KEY (current_version_id) REFERENCES public.page_versions(id) ON DELETE SET NULL;
|
|
60
|
+
|
|
61
|
+
-- =========================================================
|
|
62
|
+
-- PAGE PUBLICATIONS
|
|
63
|
+
-- =========================================================
|
|
64
|
+
CREATE TABLE public.page_publications (
|
|
65
|
+
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
66
|
+
page_id uuid NOT NULL REFERENCES public.pages(id) ON DELETE CASCADE,
|
|
67
|
+
version_id uuid NOT NULL REFERENCES public.page_versions(id) ON DELETE CASCADE,
|
|
68
|
+
publish_at timestamptz NOT NULL DEFAULT now(),
|
|
69
|
+
unpublish_at timestamptz,
|
|
70
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
71
|
+
published_at timestamptz NOT NULL DEFAULT now(),
|
|
72
|
+
published_by uuid,
|
|
73
|
+
note text
|
|
74
|
+
);
|
|
75
|
+
CREATE INDEX page_publications_active_idx ON public.page_publications(page_id) WHERE is_active;
|
|
76
|
+
GRANT SELECT ON public.page_publications TO anon;
|
|
77
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.page_publications TO authenticated;
|
|
78
|
+
GRANT ALL ON public.page_publications TO service_role;
|
|
79
|
+
ALTER TABLE public.page_publications ENABLE ROW LEVEL SECURITY;
|
|
80
|
+
|
|
81
|
+
CREATE POLICY "Anyone can read page_publications" ON public.page_publications FOR SELECT USING (true);
|
|
82
|
+
CREATE POLICY "Admins manage page_publications" ON public.page_publications FOR ALL TO authenticated
|
|
83
|
+
USING (has_role(auth.uid(),'admin'::app_role))
|
|
84
|
+
WITH CHECK (has_role(auth.uid(),'admin'::app_role));
|
|
85
|
+
|
|
86
|
+
-- =========================================================
|
|
87
|
+
-- GLOBAL SECTIONS
|
|
88
|
+
-- =========================================================
|
|
89
|
+
CREATE TABLE public.global_sections (
|
|
90
|
+
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
91
|
+
key text NOT NULL UNIQUE,
|
|
92
|
+
name text NOT NULL,
|
|
93
|
+
current_version_id uuid,
|
|
94
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
95
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
96
|
+
updated_by uuid
|
|
97
|
+
);
|
|
98
|
+
GRANT SELECT ON public.global_sections TO anon;
|
|
99
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.global_sections TO authenticated;
|
|
100
|
+
GRANT ALL ON public.global_sections TO service_role;
|
|
101
|
+
ALTER TABLE public.global_sections ENABLE ROW LEVEL SECURITY;
|
|
102
|
+
|
|
103
|
+
CREATE POLICY "Anyone can read global_sections" ON public.global_sections FOR SELECT USING (true);
|
|
104
|
+
CREATE POLICY "Admins manage global_sections" ON public.global_sections FOR ALL TO authenticated
|
|
105
|
+
USING (has_role(auth.uid(),'admin'::app_role))
|
|
106
|
+
WITH CHECK (has_role(auth.uid(),'admin'::app_role));
|
|
107
|
+
|
|
108
|
+
CREATE TABLE public.global_section_versions (
|
|
109
|
+
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
110
|
+
section_id uuid NOT NULL REFERENCES public.global_sections(id) ON DELETE CASCADE,
|
|
111
|
+
version_no integer NOT NULL,
|
|
112
|
+
blocks jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
113
|
+
note text,
|
|
114
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
115
|
+
created_by uuid,
|
|
116
|
+
UNIQUE (section_id, version_no)
|
|
117
|
+
);
|
|
118
|
+
GRANT SELECT ON public.global_section_versions TO anon;
|
|
119
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.global_section_versions TO authenticated;
|
|
120
|
+
GRANT ALL ON public.global_section_versions TO service_role;
|
|
121
|
+
ALTER TABLE public.global_section_versions ENABLE ROW LEVEL SECURITY;
|
|
122
|
+
|
|
123
|
+
CREATE POLICY "Anyone can read global_section_versions" ON public.global_section_versions FOR SELECT USING (true);
|
|
124
|
+
CREATE POLICY "Admins manage global_section_versions" ON public.global_section_versions FOR ALL TO authenticated
|
|
125
|
+
USING (has_role(auth.uid(),'admin'::app_role))
|
|
126
|
+
WITH CHECK (has_role(auth.uid(),'admin'::app_role));
|
|
127
|
+
|
|
128
|
+
ALTER TABLE public.global_sections
|
|
129
|
+
ADD CONSTRAINT global_sections_current_version_fk
|
|
130
|
+
FOREIGN KEY (current_version_id) REFERENCES public.global_section_versions(id) ON DELETE SET NULL;
|
|
131
|
+
|
|
132
|
+
-- =========================================================
|
|
133
|
+
-- updated_at triggers
|
|
134
|
+
-- =========================================================
|
|
135
|
+
CREATE OR REPLACE FUNCTION public.touch_pages() RETURNS trigger
|
|
136
|
+
LANGUAGE plpgsql SET search_path = public AS $$
|
|
137
|
+
BEGIN NEW.updated_at = now(); NEW.updated_by = auth.uid(); RETURN NEW; END; $$;
|
|
138
|
+
CREATE TRIGGER touch_pages_upd BEFORE UPDATE ON public.pages
|
|
139
|
+
FOR EACH ROW EXECUTE FUNCTION public.touch_pages();
|
|
140
|
+
|
|
141
|
+
CREATE OR REPLACE FUNCTION public.touch_global_sections() RETURNS trigger
|
|
142
|
+
LANGUAGE plpgsql SET search_path = public AS $$
|
|
143
|
+
BEGIN NEW.updated_at = now(); NEW.updated_by = auth.uid(); RETURN NEW; END; $$;
|
|
144
|
+
CREATE TRIGGER touch_global_sections_upd BEFORE UPDATE ON public.global_sections
|
|
145
|
+
FOR EACH ROW EXECUTE FUNCTION public.touch_global_sections();
|
|
146
|
+
|
|
147
|
+
-- =========================================================
|
|
148
|
+
-- Seed built-in system pages with an initial empty version each
|
|
149
|
+
-- =========================================================
|
|
150
|
+
DO $$
|
|
151
|
+
DECLARE
|
|
152
|
+
page_rec RECORD;
|
|
153
|
+
new_page_id uuid;
|
|
154
|
+
new_version_id uuid;
|
|
155
|
+
seed_pages jsonb := '[
|
|
156
|
+
{"key":"home","name":"Home","blocks":[
|
|
157
|
+
{"id":"hero","type":"hero","props":{}},
|
|
158
|
+
{"id":"stats","type":"stats","props":{}},
|
|
159
|
+
{"id":"intro","type":"intro","props":{}},
|
|
160
|
+
{"id":"features","type":"features","props":{}},
|
|
161
|
+
{"id":"destinations","type":"destinations","props":{}},
|
|
162
|
+
{"id":"packages","type":"packages","props":{}},
|
|
163
|
+
{"id":"gallery","type":"gallery","props":{}},
|
|
164
|
+
{"id":"testimonials","type":"testimonials","props":{}},
|
|
165
|
+
{"id":"cta","type":"contactCta","props":{}}
|
|
166
|
+
]},
|
|
167
|
+
{"key":"utbildningen","name":"Utbildningen","blocks":[
|
|
168
|
+
{"id":"intro","type":"intro","props":{}},
|
|
169
|
+
{"id":"features","type":"features","props":{}}
|
|
170
|
+
]},
|
|
171
|
+
{"key":"destinationer","name":"Destinationer","blocks":[
|
|
172
|
+
{"id":"destinations","type":"destinations","props":{}}
|
|
173
|
+
]},
|
|
174
|
+
{"key":"datum","name":"Datum","blocks":[
|
|
175
|
+
{"id":"packages","type":"packages","props":{}}
|
|
176
|
+
]},
|
|
177
|
+
{"key":"om","name":"Om","blocks":[
|
|
178
|
+
{"id":"intro","type":"intro","props":{}},
|
|
179
|
+
{"id":"stats","type":"stats","props":{}},
|
|
180
|
+
{"id":"gallery","type":"gallery","props":{}}
|
|
181
|
+
]},
|
|
182
|
+
{"key":"kontakt","name":"Kontakt","blocks":[
|
|
183
|
+
{"id":"cta","type":"contactCta","props":{}}
|
|
184
|
+
]}
|
|
185
|
+
]'::jsonb;
|
|
186
|
+
item jsonb;
|
|
187
|
+
BEGIN
|
|
188
|
+
FOR item IN SELECT * FROM jsonb_array_elements(seed_pages) LOOP
|
|
189
|
+
INSERT INTO public.pages(key,name,is_system,status)
|
|
190
|
+
VALUES (item->>'key', item->>'name', true, 'published')
|
|
191
|
+
ON CONFLICT (key) DO NOTHING
|
|
192
|
+
RETURNING id INTO new_page_id;
|
|
193
|
+
|
|
194
|
+
IF new_page_id IS NULL THEN
|
|
195
|
+
SELECT id INTO new_page_id FROM public.pages WHERE key = item->>'key';
|
|
196
|
+
END IF;
|
|
197
|
+
|
|
198
|
+
IF NOT EXISTS (SELECT 1 FROM public.page_versions WHERE page_id = new_page_id) THEN
|
|
199
|
+
INSERT INTO public.page_versions(page_id, version_no, blocks, note)
|
|
200
|
+
VALUES (new_page_id, 1, item->'blocks', 'Initial seed')
|
|
201
|
+
RETURNING id INTO new_version_id;
|
|
202
|
+
|
|
203
|
+
UPDATE public.pages SET current_version_id = new_version_id WHERE id = new_page_id;
|
|
204
|
+
|
|
205
|
+
INSERT INTO public.page_publications(page_id, version_id, is_active, note)
|
|
206
|
+
VALUES (new_page_id, new_version_id, true, 'Initial publish');
|
|
207
|
+
END IF;
|
|
208
|
+
END LOOP;
|
|
209
|
+
END $$;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
CREATE POLICY "Admins can read media"
|
|
3
|
+
ON storage.objects FOR SELECT TO authenticated
|
|
4
|
+
USING (bucket_id = 'media' AND public.has_role(auth.uid(), 'admin'));
|
|
5
|
+
|
|
6
|
+
CREATE POLICY "Admins can upload media"
|
|
7
|
+
ON storage.objects FOR INSERT TO authenticated
|
|
8
|
+
WITH CHECK (bucket_id = 'media' AND public.has_role(auth.uid(), 'admin'));
|
|
9
|
+
|
|
10
|
+
CREATE POLICY "Admins can update media"
|
|
11
|
+
ON storage.objects FOR UPDATE TO authenticated
|
|
12
|
+
USING (bucket_id = 'media' AND public.has_role(auth.uid(), 'admin'))
|
|
13
|
+
WITH CHECK (bucket_id = 'media' AND public.has_role(auth.uid(), 'admin'));
|
|
14
|
+
|
|
15
|
+
CREATE POLICY "Admins can delete media"
|
|
16
|
+
ON storage.objects FOR DELETE TO authenticated
|
|
17
|
+
USING (bucket_id = 'media' AND public.has_role(auth.uid(), 'admin'));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
CREATE TABLE public.block_templates (
|
|
3
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4
|
+
name text NOT NULL,
|
|
5
|
+
description text,
|
|
6
|
+
block_type text NOT NULL,
|
|
7
|
+
block jsonb NOT NULL,
|
|
8
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
9
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
10
|
+
created_by uuid
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
GRANT SELECT ON public.block_templates TO anon, authenticated;
|
|
14
|
+
GRANT INSERT, UPDATE, DELETE ON public.block_templates TO authenticated;
|
|
15
|
+
GRANT ALL ON public.block_templates TO service_role;
|
|
16
|
+
|
|
17
|
+
ALTER TABLE public.block_templates ENABLE ROW LEVEL SECURITY;
|
|
18
|
+
|
|
19
|
+
CREATE POLICY "Anyone can read block_templates"
|
|
20
|
+
ON public.block_templates FOR SELECT USING (true);
|
|
21
|
+
|
|
22
|
+
CREATE POLICY "Admins insert block_templates"
|
|
23
|
+
ON public.block_templates FOR INSERT TO authenticated
|
|
24
|
+
WITH CHECK (public.has_role(auth.uid(), 'admin'));
|
|
25
|
+
|
|
26
|
+
CREATE POLICY "Admins update block_templates"
|
|
27
|
+
ON public.block_templates FOR UPDATE TO authenticated
|
|
28
|
+
USING (public.has_role(auth.uid(), 'admin'))
|
|
29
|
+
WITH CHECK (public.has_role(auth.uid(), 'admin'));
|
|
30
|
+
|
|
31
|
+
CREATE POLICY "Admins delete block_templates"
|
|
32
|
+
ON public.block_templates FOR DELETE TO authenticated
|
|
33
|
+
USING (public.has_role(auth.uid(), 'admin'));
|
|
34
|
+
|
|
35
|
+
CREATE TRIGGER touch_block_templates_updated_at
|
|
36
|
+
BEFORE UPDATE ON public.block_templates
|
|
37
|
+
FOR EACH ROW EXECUTE FUNCTION public.touch_pages();
|