create-smoodly-app 0.0.15 → 0.0.16
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/package.json +1 -1
- package/templates/blank/package.json +1 -1
- package/templates/blank/smoodly/collections.ts +1 -2
- package/templates/blank/supabase/migrations/00000000000000_smoodly.sql +15 -12
- package/templates/blank-intl/package.json +1 -1
- package/templates/blank-intl/smoodly/collections.ts +1 -2
- package/templates/blank-intl/supabase/migrations/00000000000000_smoodly.sql +15 -12
- package/templates/lorem/package.json +1 -1
- package/templates/lorem/scripts/seed.ts +1 -1
- package/templates/lorem/smoodly/collections.ts +2 -6
- package/templates/lorem/smoodly/shared.ts +1 -1
- package/templates/lorem/supabase/migrations/00000000000000_smoodly.sql +22 -18
- package/templates/lorem-intl/package.json +1 -1
- package/templates/lorem-intl/scripts/seed.ts +1 -1
- package/templates/lorem-intl/smoodly/collections.ts +2 -6
- package/templates/lorem-intl/smoodly/shared.ts +1 -1
- package/templates/lorem-intl/supabase/migrations/00000000000000_smoodly.sql +22 -18
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import { text } from "./toolsets";
|
|
|
4
4
|
// A collection: structured content with its own admin list, served at
|
|
5
5
|
// `path` by its entry page (components/pages/Post.tsx). Fields marked
|
|
6
6
|
// .localized() are stored per locale; the rest are shared across
|
|
7
|
-
// locales.
|
|
7
|
+
// locales. To add a
|
|
8
8
|
// collection: copy this block, add it to `collections`, write its view
|
|
9
9
|
// into a new migration with `collectionSQL` from "smoodly", and apply
|
|
10
10
|
// it (supabase db push, or supabase db reset locally).
|
|
@@ -14,7 +14,6 @@ export const posts = smoodly.collection({
|
|
|
14
14
|
titleField: "title",
|
|
15
15
|
path: "posts",
|
|
16
16
|
order: { by: "date", direction: "desc" },
|
|
17
|
-
versions: true,
|
|
18
17
|
fields: {
|
|
19
18
|
title: f.text().localized(),
|
|
20
19
|
slug: f.slug({ from: "title" }),
|
|
@@ -37,6 +37,7 @@ create table if not exists "page_locales" (
|
|
|
37
37
|
"draft_version_id" uuid,
|
|
38
38
|
"published_version_id" uuid,
|
|
39
39
|
"updated_at" timestamptz not null default now(),
|
|
40
|
+
"updated_by" text,
|
|
40
41
|
primary key ("page_id", "locale")
|
|
41
42
|
);
|
|
42
43
|
|
|
@@ -49,19 +50,20 @@ create or replace function "smoodly_reorder_pages"(p_ids uuid[]) returns void la
|
|
|
49
50
|
$$;
|
|
50
51
|
|
|
51
52
|
-- A version belongs to ONE locale of one page: each locale has its own
|
|
52
|
-
-- draft, its own publish and its own history.
|
|
53
|
+
-- draft, its own publish and its own history. One row per publish plus
|
|
54
|
+
-- the live draft (spec 2026-09-13 history §1): the unpublished draft row
|
|
55
|
+
-- is rewritten in place and saved_at is when its content last changed.
|
|
53
56
|
create table if not exists "page_versions" (
|
|
54
57
|
"id" uuid primary key default gen_random_uuid(),
|
|
55
58
|
"space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
|
|
56
59
|
"page_id" uuid not null references "pages" ("id") on delete cascade,
|
|
57
60
|
"locale" text not null,
|
|
58
61
|
"tree" jsonb not null,
|
|
59
|
-
"
|
|
60
|
-
"created_at" timestamptz not null default now()
|
|
62
|
+
"saved_at" timestamptz not null default now()
|
|
61
63
|
);
|
|
62
64
|
|
|
63
65
|
create index if not exists "page_versions_page_locale_idx"
|
|
64
|
-
on "page_versions" ("page_id", "locale", "
|
|
66
|
+
on "page_versions" ("page_id", "locale", "saved_at");
|
|
65
67
|
|
|
66
68
|
create table if not exists "entries" (
|
|
67
69
|
"id" uuid primary key default gen_random_uuid(),
|
|
@@ -98,6 +100,7 @@ create table if not exists "entry_locales" (
|
|
|
98
100
|
"draft_version_id" uuid,
|
|
99
101
|
"published_version_id" uuid,
|
|
100
102
|
"updated_at" timestamptz not null default now(),
|
|
103
|
+
"updated_by" text,
|
|
101
104
|
primary key ("entry_id", "locale")
|
|
102
105
|
);
|
|
103
106
|
|
|
@@ -107,19 +110,19 @@ create index if not exists "entry_locales_status_idx"
|
|
|
107
110
|
-- A version belongs to ONE locale of one entry and stores the MERGED
|
|
108
111
|
-- fields (shared + localized + slug) as they were at that save, so
|
|
109
112
|
-- restoring an old Finnish version restores the shared fields too.
|
|
110
|
-
-- Rows
|
|
113
|
+
-- Rows for every collection and every shared item (spec 2026-09-14 §1).
|
|
114
|
+
-- One row per publish plus the live draft (spec 2026-09-13 history §1).
|
|
111
115
|
create table if not exists "entry_versions" (
|
|
112
116
|
"id" uuid primary key default gen_random_uuid(),
|
|
113
117
|
"space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
|
|
114
118
|
"entry_id" uuid not null references "entries" ("id") on delete cascade,
|
|
115
119
|
"locale" text not null,
|
|
116
120
|
"fields" jsonb not null,
|
|
117
|
-
"
|
|
118
|
-
"created_at" timestamptz not null default now()
|
|
121
|
+
"saved_at" timestamptz not null default now()
|
|
119
122
|
);
|
|
120
123
|
|
|
121
124
|
create index if not exists "entry_versions_entry_locale_idx"
|
|
122
|
-
on "entry_versions" ("entry_id", "locale", "
|
|
125
|
+
on "entry_versions" ("entry_id", "locale", "saved_at");
|
|
123
126
|
|
|
124
127
|
-- v1 reorder (DESIGN.md §3 Manual ordering): rewrite sort 1..N in ONE
|
|
125
128
|
-- statement. Deliberately does not touch updated_at — a drag must not
|
|
@@ -506,12 +509,12 @@ select
|
|
|
506
509
|
e."sort",
|
|
507
510
|
e."created_at",
|
|
508
511
|
l."updated_at",
|
|
509
|
-
v."fields"->>'title' as "title",
|
|
510
|
-
(v."fields"->>'date')::timestamptz as "date",
|
|
511
|
-
v."fields"->'body' as "body"
|
|
512
|
+
coalesce(v."fields", e."fields" || l."fields")->>'title' as "title",
|
|
513
|
+
(coalesce(v."fields", e."fields" || l."fields")->>'date')::timestamptz as "date",
|
|
514
|
+
coalesce(v."fields", e."fields" || l."fields")->'body' as "body"
|
|
512
515
|
from "entries" e
|
|
513
516
|
join "entry_locales" l on l."entry_id" = e."id"
|
|
514
|
-
join "entry_versions" v on v."id" = l."published_version_id"
|
|
517
|
+
left join "entry_versions" v on v."id" = l."published_version_id"
|
|
515
518
|
where e."collection" = 'posts' and l."status" = 'published';
|
|
516
519
|
|
|
517
520
|
create index if not exists "entries_posts_date_idx"
|
|
@@ -4,7 +4,7 @@ import { text } from "./toolsets";
|
|
|
4
4
|
// A collection: structured content with its own admin list, served at
|
|
5
5
|
// `path` by its entry page (components/pages/Post.tsx). Fields marked
|
|
6
6
|
// .localized() are stored per locale; the rest are shared across
|
|
7
|
-
// locales.
|
|
7
|
+
// locales. To add a
|
|
8
8
|
// collection: copy this block, add it to `collections`, write its view
|
|
9
9
|
// into a new migration with `collectionSQL` from "smoodly", and apply
|
|
10
10
|
// it (supabase db push, or supabase db reset locally).
|
|
@@ -14,7 +14,6 @@ export const posts = smoodly.collection({
|
|
|
14
14
|
titleField: "title",
|
|
15
15
|
path: "posts",
|
|
16
16
|
order: { by: "date", direction: "desc" },
|
|
17
|
-
versions: true,
|
|
18
17
|
fields: {
|
|
19
18
|
title: f.text().localized(),
|
|
20
19
|
slug: f.slug({ from: "title" }),
|
|
@@ -37,6 +37,7 @@ create table if not exists "page_locales" (
|
|
|
37
37
|
"draft_version_id" uuid,
|
|
38
38
|
"published_version_id" uuid,
|
|
39
39
|
"updated_at" timestamptz not null default now(),
|
|
40
|
+
"updated_by" text,
|
|
40
41
|
primary key ("page_id", "locale")
|
|
41
42
|
);
|
|
42
43
|
|
|
@@ -49,19 +50,20 @@ create or replace function "smoodly_reorder_pages"(p_ids uuid[]) returns void la
|
|
|
49
50
|
$$;
|
|
50
51
|
|
|
51
52
|
-- A version belongs to ONE locale of one page: each locale has its own
|
|
52
|
-
-- draft, its own publish and its own history.
|
|
53
|
+
-- draft, its own publish and its own history. One row per publish plus
|
|
54
|
+
-- the live draft (spec 2026-09-13 history §1): the unpublished draft row
|
|
55
|
+
-- is rewritten in place and saved_at is when its content last changed.
|
|
53
56
|
create table if not exists "page_versions" (
|
|
54
57
|
"id" uuid primary key default gen_random_uuid(),
|
|
55
58
|
"space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
|
|
56
59
|
"page_id" uuid not null references "pages" ("id") on delete cascade,
|
|
57
60
|
"locale" text not null,
|
|
58
61
|
"tree" jsonb not null,
|
|
59
|
-
"
|
|
60
|
-
"created_at" timestamptz not null default now()
|
|
62
|
+
"saved_at" timestamptz not null default now()
|
|
61
63
|
);
|
|
62
64
|
|
|
63
65
|
create index if not exists "page_versions_page_locale_idx"
|
|
64
|
-
on "page_versions" ("page_id", "locale", "
|
|
66
|
+
on "page_versions" ("page_id", "locale", "saved_at");
|
|
65
67
|
|
|
66
68
|
create table if not exists "entries" (
|
|
67
69
|
"id" uuid primary key default gen_random_uuid(),
|
|
@@ -98,6 +100,7 @@ create table if not exists "entry_locales" (
|
|
|
98
100
|
"draft_version_id" uuid,
|
|
99
101
|
"published_version_id" uuid,
|
|
100
102
|
"updated_at" timestamptz not null default now(),
|
|
103
|
+
"updated_by" text,
|
|
101
104
|
primary key ("entry_id", "locale")
|
|
102
105
|
);
|
|
103
106
|
|
|
@@ -107,19 +110,19 @@ create index if not exists "entry_locales_status_idx"
|
|
|
107
110
|
-- A version belongs to ONE locale of one entry and stores the MERGED
|
|
108
111
|
-- fields (shared + localized + slug) as they were at that save, so
|
|
109
112
|
-- restoring an old Finnish version restores the shared fields too.
|
|
110
|
-
-- Rows
|
|
113
|
+
-- Rows for every collection and every shared item (spec 2026-09-14 §1).
|
|
114
|
+
-- One row per publish plus the live draft (spec 2026-09-13 history §1).
|
|
111
115
|
create table if not exists "entry_versions" (
|
|
112
116
|
"id" uuid primary key default gen_random_uuid(),
|
|
113
117
|
"space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
|
|
114
118
|
"entry_id" uuid not null references "entries" ("id") on delete cascade,
|
|
115
119
|
"locale" text not null,
|
|
116
120
|
"fields" jsonb not null,
|
|
117
|
-
"
|
|
118
|
-
"created_at" timestamptz not null default now()
|
|
121
|
+
"saved_at" timestamptz not null default now()
|
|
119
122
|
);
|
|
120
123
|
|
|
121
124
|
create index if not exists "entry_versions_entry_locale_idx"
|
|
122
|
-
on "entry_versions" ("entry_id", "locale", "
|
|
125
|
+
on "entry_versions" ("entry_id", "locale", "saved_at");
|
|
123
126
|
|
|
124
127
|
-- v1 reorder (DESIGN.md §3 Manual ordering): rewrite sort 1..N in ONE
|
|
125
128
|
-- statement. Deliberately does not touch updated_at — a drag must not
|
|
@@ -506,12 +509,12 @@ select
|
|
|
506
509
|
e."sort",
|
|
507
510
|
e."created_at",
|
|
508
511
|
l."updated_at",
|
|
509
|
-
v."fields"->>'title' as "title",
|
|
510
|
-
(v."fields"->>'date')::timestamptz as "date",
|
|
511
|
-
v."fields"->'body' as "body"
|
|
512
|
+
coalesce(v."fields", e."fields" || l."fields")->>'title' as "title",
|
|
513
|
+
(coalesce(v."fields", e."fields" || l."fields")->>'date')::timestamptz as "date",
|
|
514
|
+
coalesce(v."fields", e."fields" || l."fields")->'body' as "body"
|
|
512
515
|
from "entries" e
|
|
513
516
|
join "entry_locales" l on l."entry_id" = e."id"
|
|
514
|
-
join "entry_versions" v on v."id" = l."published_version_id"
|
|
517
|
+
left join "entry_versions" v on v."id" = l."published_version_id"
|
|
515
518
|
where e."collection" = 'posts' and l."status" = 'published';
|
|
516
519
|
|
|
517
520
|
create index if not exists "entries_posts_date_idx"
|
|
@@ -156,7 +156,7 @@ const body = doc(
|
|
|
156
156
|
cta("Ready to talk?", "Sed ut perspiciatis unde omnis iste natus error sit voluptatem.", "Get in touch", paths(EN).about),
|
|
157
157
|
);
|
|
158
158
|
|
|
159
|
-
//
|
|
159
|
+
// Every collection is versioned: every save below is a
|
|
160
160
|
// snapshot. Title and body are Latin in both locales; tags and slug are
|
|
161
161
|
// per locale, so the Finnish row reads Finnish where a reader would notice.
|
|
162
162
|
const article = await seedEntry("articles", {
|
|
@@ -15,11 +15,8 @@ export const people = smoodly.collection({
|
|
|
15
15
|
},
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
// Articles
|
|
19
|
-
//
|
|
20
|
-
// keeps serving the published snapshot while a draft moves on. The path
|
|
21
|
-
// is per locale — /posts/<slug> and /fi/artikkelit/<slug> — and the
|
|
22
|
-
// Articles fixed page (components/pages/Articles.tsx) claims the same
|
|
18
|
+
// Articles are served at a path that is per locale — /posts/<slug> and
|
|
19
|
+
// /fi/artikkelit/<slug> — and the Articles fixed page (components/pages/Articles.tsx) claims the same
|
|
23
20
|
// segments, which makes it the collection's index page. Tags are a list
|
|
24
21
|
// localized as a whole, so the Finnish row carries Finnish tags.
|
|
25
22
|
export const articles = smoodly.collection({
|
|
@@ -28,7 +25,6 @@ export const articles = smoodly.collection({
|
|
|
28
25
|
titleField: "title",
|
|
29
26
|
path: "posts",
|
|
30
27
|
order: { by: "date", direction: "desc" },
|
|
31
|
-
versions: true,
|
|
32
28
|
fields: {
|
|
33
29
|
title: f.text().localized(),
|
|
34
30
|
image: f.image({ aspect: "3:2" }),
|
|
@@ -9,7 +9,7 @@ import { Quote } from "@/components/sections/Quote";
|
|
|
9
9
|
|
|
10
10
|
export const header = smoodly.shared({ name: "header", title: "Header", section: Header });
|
|
11
11
|
|
|
12
|
-
export const footer = smoodly.shared({ name: "footer", title: "Footer", section: Footer
|
|
12
|
+
export const footer = smoodly.shared({ name: "footer", title: "Footer", section: Footer });
|
|
13
13
|
|
|
14
14
|
export const contact = smoodly.shared({
|
|
15
15
|
name: "contact",
|
|
@@ -37,6 +37,7 @@ create table if not exists "page_locales" (
|
|
|
37
37
|
"draft_version_id" uuid,
|
|
38
38
|
"published_version_id" uuid,
|
|
39
39
|
"updated_at" timestamptz not null default now(),
|
|
40
|
+
"updated_by" text,
|
|
40
41
|
primary key ("page_id", "locale")
|
|
41
42
|
);
|
|
42
43
|
|
|
@@ -49,19 +50,20 @@ create or replace function "smoodly_reorder_pages"(p_ids uuid[]) returns void la
|
|
|
49
50
|
$$;
|
|
50
51
|
|
|
51
52
|
-- A version belongs to ONE locale of one page: each locale has its own
|
|
52
|
-
-- draft, its own publish and its own history.
|
|
53
|
+
-- draft, its own publish and its own history. One row per publish plus
|
|
54
|
+
-- the live draft (spec 2026-09-13 history §1): the unpublished draft row
|
|
55
|
+
-- is rewritten in place and saved_at is when its content last changed.
|
|
53
56
|
create table if not exists "page_versions" (
|
|
54
57
|
"id" uuid primary key default gen_random_uuid(),
|
|
55
58
|
"space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
|
|
56
59
|
"page_id" uuid not null references "pages" ("id") on delete cascade,
|
|
57
60
|
"locale" text not null,
|
|
58
61
|
"tree" jsonb not null,
|
|
59
|
-
"
|
|
60
|
-
"created_at" timestamptz not null default now()
|
|
62
|
+
"saved_at" timestamptz not null default now()
|
|
61
63
|
);
|
|
62
64
|
|
|
63
65
|
create index if not exists "page_versions_page_locale_idx"
|
|
64
|
-
on "page_versions" ("page_id", "locale", "
|
|
66
|
+
on "page_versions" ("page_id", "locale", "saved_at");
|
|
65
67
|
|
|
66
68
|
create table if not exists "entries" (
|
|
67
69
|
"id" uuid primary key default gen_random_uuid(),
|
|
@@ -98,6 +100,7 @@ create table if not exists "entry_locales" (
|
|
|
98
100
|
"draft_version_id" uuid,
|
|
99
101
|
"published_version_id" uuid,
|
|
100
102
|
"updated_at" timestamptz not null default now(),
|
|
103
|
+
"updated_by" text,
|
|
101
104
|
primary key ("entry_id", "locale")
|
|
102
105
|
);
|
|
103
106
|
|
|
@@ -107,19 +110,19 @@ create index if not exists "entry_locales_status_idx"
|
|
|
107
110
|
-- A version belongs to ONE locale of one entry and stores the MERGED
|
|
108
111
|
-- fields (shared + localized + slug) as they were at that save, so
|
|
109
112
|
-- restoring an old Finnish version restores the shared fields too.
|
|
110
|
-
-- Rows
|
|
113
|
+
-- Rows for every collection and every shared item (spec 2026-09-14 §1).
|
|
114
|
+
-- One row per publish plus the live draft (spec 2026-09-13 history §1).
|
|
111
115
|
create table if not exists "entry_versions" (
|
|
112
116
|
"id" uuid primary key default gen_random_uuid(),
|
|
113
117
|
"space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
|
|
114
118
|
"entry_id" uuid not null references "entries" ("id") on delete cascade,
|
|
115
119
|
"locale" text not null,
|
|
116
120
|
"fields" jsonb not null,
|
|
117
|
-
"
|
|
118
|
-
"created_at" timestamptz not null default now()
|
|
121
|
+
"saved_at" timestamptz not null default now()
|
|
119
122
|
);
|
|
120
123
|
|
|
121
124
|
create index if not exists "entry_versions_entry_locale_idx"
|
|
122
|
-
on "entry_versions" ("entry_id", "locale", "
|
|
125
|
+
on "entry_versions" ("entry_id", "locale", "saved_at");
|
|
123
126
|
|
|
124
127
|
-- v1 reorder (DESIGN.md §3 Manual ordering): rewrite sort 1..N in ONE
|
|
125
128
|
-- statement. Deliberately does not touch updated_at — a drag must not
|
|
@@ -506,10 +509,11 @@ select
|
|
|
506
509
|
e."sort",
|
|
507
510
|
e."created_at",
|
|
508
511
|
l."updated_at",
|
|
509
|
-
e."fields"->>'name' as "name",
|
|
510
|
-
l."fields"->>'role' as "role"
|
|
512
|
+
coalesce(v."fields", e."fields" || l."fields")->>'name' as "name",
|
|
513
|
+
coalesce(v."fields", e."fields" || l."fields")->>'role' as "role"
|
|
511
514
|
from "entries" e
|
|
512
515
|
join "entry_locales" l on l."entry_id" = e."id"
|
|
516
|
+
left join "entry_versions" v on v."id" = l."published_version_id"
|
|
513
517
|
where e."collection" = 'people' and l."status" = 'published';
|
|
514
518
|
|
|
515
519
|
create or replace view "articles" with (security_invoker = true) as
|
|
@@ -520,16 +524,16 @@ select
|
|
|
520
524
|
e."sort",
|
|
521
525
|
e."created_at",
|
|
522
526
|
l."updated_at",
|
|
523
|
-
v."fields"->>'title' as "title",
|
|
524
|
-
v."fields"->'image' as "image",
|
|
525
|
-
(v."fields"->>'date')::timestamptz as "date",
|
|
526
|
-
v."fields"->>'author' as "author",
|
|
527
|
-
v."fields"->>'excerpt' as "excerpt",
|
|
528
|
-
v."fields"->'body' as "body",
|
|
529
|
-
v."fields"->'tags' as "tags"
|
|
527
|
+
coalesce(v."fields", e."fields" || l."fields")->>'title' as "title",
|
|
528
|
+
coalesce(v."fields", e."fields" || l."fields")->'image' as "image",
|
|
529
|
+
(coalesce(v."fields", e."fields" || l."fields")->>'date')::timestamptz as "date",
|
|
530
|
+
coalesce(v."fields", e."fields" || l."fields")->>'author' as "author",
|
|
531
|
+
coalesce(v."fields", e."fields" || l."fields")->>'excerpt' as "excerpt",
|
|
532
|
+
coalesce(v."fields", e."fields" || l."fields")->'body' as "body",
|
|
533
|
+
coalesce(v."fields", e."fields" || l."fields")->'tags' as "tags"
|
|
530
534
|
from "entries" e
|
|
531
535
|
join "entry_locales" l on l."entry_id" = e."id"
|
|
532
|
-
join "entry_versions" v on v."id" = l."published_version_id"
|
|
536
|
+
left join "entry_versions" v on v."id" = l."published_version_id"
|
|
533
537
|
where e."collection" = 'articles' and l."status" = 'published';
|
|
534
538
|
|
|
535
539
|
create index if not exists "entries_articles_date_idx"
|
|
@@ -156,7 +156,7 @@ const body = doc(
|
|
|
156
156
|
cta("Ready to talk?", "Sed ut perspiciatis unde omnis iste natus error sit voluptatem.", "Get in touch", paths(EN).about),
|
|
157
157
|
);
|
|
158
158
|
|
|
159
|
-
//
|
|
159
|
+
// Every collection is versioned: every save below is a
|
|
160
160
|
// snapshot. Title and body are Latin in both locales; tags and slug are
|
|
161
161
|
// per locale, so the Finnish row reads Finnish where a reader would notice.
|
|
162
162
|
const article = await seedEntry("articles", {
|
|
@@ -15,11 +15,8 @@ export const people = smoodly.collection({
|
|
|
15
15
|
},
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
// Articles
|
|
19
|
-
//
|
|
20
|
-
// keeps serving the published snapshot while a draft moves on. The path
|
|
21
|
-
// is per locale — /posts/<slug> and /fi/artikkelit/<slug> — and the
|
|
22
|
-
// Articles fixed page (components/pages/Articles.tsx) claims the same
|
|
18
|
+
// Articles are served at a path that is per locale — /posts/<slug> and
|
|
19
|
+
// /fi/artikkelit/<slug> — and the Articles fixed page (components/pages/Articles.tsx) claims the same
|
|
23
20
|
// segments, which makes it the collection's index page. Tags are a list
|
|
24
21
|
// localized as a whole, so the Finnish row carries Finnish tags.
|
|
25
22
|
export const articles = smoodly.collection({
|
|
@@ -28,7 +25,6 @@ export const articles = smoodly.collection({
|
|
|
28
25
|
titleField: "title",
|
|
29
26
|
path: { en: "posts", fi: "artikkelit" },
|
|
30
27
|
order: { by: "date", direction: "desc" },
|
|
31
|
-
versions: true,
|
|
32
28
|
fields: {
|
|
33
29
|
title: f.text().localized(),
|
|
34
30
|
image: f.image({ aspect: "3:2" }),
|
|
@@ -9,7 +9,7 @@ import { Quote } from "@/components/sections/Quote";
|
|
|
9
9
|
|
|
10
10
|
export const header = smoodly.shared({ name: "header", title: "Header", section: Header });
|
|
11
11
|
|
|
12
|
-
export const footer = smoodly.shared({ name: "footer", title: "Footer", section: Footer
|
|
12
|
+
export const footer = smoodly.shared({ name: "footer", title: "Footer", section: Footer });
|
|
13
13
|
|
|
14
14
|
export const contact = smoodly.shared({
|
|
15
15
|
name: "contact",
|
|
@@ -37,6 +37,7 @@ create table if not exists "page_locales" (
|
|
|
37
37
|
"draft_version_id" uuid,
|
|
38
38
|
"published_version_id" uuid,
|
|
39
39
|
"updated_at" timestamptz not null default now(),
|
|
40
|
+
"updated_by" text,
|
|
40
41
|
primary key ("page_id", "locale")
|
|
41
42
|
);
|
|
42
43
|
|
|
@@ -49,19 +50,20 @@ create or replace function "smoodly_reorder_pages"(p_ids uuid[]) returns void la
|
|
|
49
50
|
$$;
|
|
50
51
|
|
|
51
52
|
-- A version belongs to ONE locale of one page: each locale has its own
|
|
52
|
-
-- draft, its own publish and its own history.
|
|
53
|
+
-- draft, its own publish and its own history. One row per publish plus
|
|
54
|
+
-- the live draft (spec 2026-09-13 history §1): the unpublished draft row
|
|
55
|
+
-- is rewritten in place and saved_at is when its content last changed.
|
|
53
56
|
create table if not exists "page_versions" (
|
|
54
57
|
"id" uuid primary key default gen_random_uuid(),
|
|
55
58
|
"space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
|
|
56
59
|
"page_id" uuid not null references "pages" ("id") on delete cascade,
|
|
57
60
|
"locale" text not null,
|
|
58
61
|
"tree" jsonb not null,
|
|
59
|
-
"
|
|
60
|
-
"created_at" timestamptz not null default now()
|
|
62
|
+
"saved_at" timestamptz not null default now()
|
|
61
63
|
);
|
|
62
64
|
|
|
63
65
|
create index if not exists "page_versions_page_locale_idx"
|
|
64
|
-
on "page_versions" ("page_id", "locale", "
|
|
66
|
+
on "page_versions" ("page_id", "locale", "saved_at");
|
|
65
67
|
|
|
66
68
|
create table if not exists "entries" (
|
|
67
69
|
"id" uuid primary key default gen_random_uuid(),
|
|
@@ -98,6 +100,7 @@ create table if not exists "entry_locales" (
|
|
|
98
100
|
"draft_version_id" uuid,
|
|
99
101
|
"published_version_id" uuid,
|
|
100
102
|
"updated_at" timestamptz not null default now(),
|
|
103
|
+
"updated_by" text,
|
|
101
104
|
primary key ("entry_id", "locale")
|
|
102
105
|
);
|
|
103
106
|
|
|
@@ -107,19 +110,19 @@ create index if not exists "entry_locales_status_idx"
|
|
|
107
110
|
-- A version belongs to ONE locale of one entry and stores the MERGED
|
|
108
111
|
-- fields (shared + localized + slug) as they were at that save, so
|
|
109
112
|
-- restoring an old Finnish version restores the shared fields too.
|
|
110
|
-
-- Rows
|
|
113
|
+
-- Rows for every collection and every shared item (spec 2026-09-14 §1).
|
|
114
|
+
-- One row per publish plus the live draft (spec 2026-09-13 history §1).
|
|
111
115
|
create table if not exists "entry_versions" (
|
|
112
116
|
"id" uuid primary key default gen_random_uuid(),
|
|
113
117
|
"space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
|
|
114
118
|
"entry_id" uuid not null references "entries" ("id") on delete cascade,
|
|
115
119
|
"locale" text not null,
|
|
116
120
|
"fields" jsonb not null,
|
|
117
|
-
"
|
|
118
|
-
"created_at" timestamptz not null default now()
|
|
121
|
+
"saved_at" timestamptz not null default now()
|
|
119
122
|
);
|
|
120
123
|
|
|
121
124
|
create index if not exists "entry_versions_entry_locale_idx"
|
|
122
|
-
on "entry_versions" ("entry_id", "locale", "
|
|
125
|
+
on "entry_versions" ("entry_id", "locale", "saved_at");
|
|
123
126
|
|
|
124
127
|
-- v1 reorder (DESIGN.md §3 Manual ordering): rewrite sort 1..N in ONE
|
|
125
128
|
-- statement. Deliberately does not touch updated_at — a drag must not
|
|
@@ -506,10 +509,11 @@ select
|
|
|
506
509
|
e."sort",
|
|
507
510
|
e."created_at",
|
|
508
511
|
l."updated_at",
|
|
509
|
-
e."fields"->>'name' as "name",
|
|
510
|
-
l."fields"->>'role' as "role"
|
|
512
|
+
coalesce(v."fields", e."fields" || l."fields")->>'name' as "name",
|
|
513
|
+
coalesce(v."fields", e."fields" || l."fields")->>'role' as "role"
|
|
511
514
|
from "entries" e
|
|
512
515
|
join "entry_locales" l on l."entry_id" = e."id"
|
|
516
|
+
left join "entry_versions" v on v."id" = l."published_version_id"
|
|
513
517
|
where e."collection" = 'people' and l."status" = 'published';
|
|
514
518
|
|
|
515
519
|
create or replace view "articles" with (security_invoker = true) as
|
|
@@ -520,16 +524,16 @@ select
|
|
|
520
524
|
e."sort",
|
|
521
525
|
e."created_at",
|
|
522
526
|
l."updated_at",
|
|
523
|
-
v."fields"->>'title' as "title",
|
|
524
|
-
v."fields"->'image' as "image",
|
|
525
|
-
(v."fields"->>'date')::timestamptz as "date",
|
|
526
|
-
v."fields"->>'author' as "author",
|
|
527
|
-
v."fields"->>'excerpt' as "excerpt",
|
|
528
|
-
v."fields"->'body' as "body",
|
|
529
|
-
v."fields"->'tags' as "tags"
|
|
527
|
+
coalesce(v."fields", e."fields" || l."fields")->>'title' as "title",
|
|
528
|
+
coalesce(v."fields", e."fields" || l."fields")->'image' as "image",
|
|
529
|
+
(coalesce(v."fields", e."fields" || l."fields")->>'date')::timestamptz as "date",
|
|
530
|
+
coalesce(v."fields", e."fields" || l."fields")->>'author' as "author",
|
|
531
|
+
coalesce(v."fields", e."fields" || l."fields")->>'excerpt' as "excerpt",
|
|
532
|
+
coalesce(v."fields", e."fields" || l."fields")->'body' as "body",
|
|
533
|
+
coalesce(v."fields", e."fields" || l."fields")->'tags' as "tags"
|
|
530
534
|
from "entries" e
|
|
531
535
|
join "entry_locales" l on l."entry_id" = e."id"
|
|
532
|
-
join "entry_versions" v on v."id" = l."published_version_id"
|
|
536
|
+
left join "entry_versions" v on v."id" = l."published_version_id"
|
|
533
537
|
where e."collection" = 'articles' and l."status" = 'published';
|
|
534
538
|
|
|
535
539
|
create index if not exists "entries_articles_date_idx"
|