@rimelight/cms 0.0.3 → 0.0.5
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/dist/bylines-Csggkl-g.d.mts +405 -0
- package/dist/index.d.mts +724 -0
- package/dist/index.mjs +2382 -0
- package/dist/integration.d.mts +33 -0
- package/dist/integration.mjs +161 -0
- package/dist/schema/index.d.mts +2063 -0
- package/dist/schema/index.mjs +176 -0
- package/dist/schema/sqlite.d.mts +2132 -0
- package/dist/schema/sqlite.mjs +137 -0
- package/dist/storage/index.d.mts +59 -0
- package/dist/storage/index.mjs +133 -0
- package/package.json +35 -14
- package/src/admin/api/media-file.ts +1 -1
- package/src/admin/api/media.ts +28 -3
- package/src/admin/layouts/CMSDashboardLayout.astro +16 -3
- package/src/admin/pages/assets.astro +11 -11
- package/src/admin/pages/pages-edit.astro +3 -3
- package/src/admin/pages/pages-index.astro +2 -2
- package/src/admin/pages/pages-preview.astro +3 -3
- package/src/admin/pages/pages-review.astro +2 -2
- package/src/admin/pages/templates-edit.astro +1 -1
- package/src/admin/pages/templates-index.astro +1 -1
- package/src/astro/BlockRenderer.astro +1 -1
- package/src/astro/PageRenderer.astro +4 -4
- package/src/astro/blocks/CalloutBlock.astro +1 -1
- package/src/astro/blocks/OrderedListBlock.astro +2 -2
- package/src/astro/blocks/ParagraphBlock.astro +1 -1
- package/src/astro/blocks/TabsBlock.astro +5 -5
- package/src/astro/blocks/UnorderedListBlock.astro +2 -2
- package/src/auth/editor-guards.ts +1 -1
- package/src/cache/purge.ts +2 -2
- package/src/client/components/RimelightBlock.tsx +1 -1
- package/src/client/components/RimelightBlockPicker.tsx +1 -1
- package/src/client/components/RimelightPreview.tsx +27 -27
- package/src/client/components/RimelightPropertiesPanel.tsx +4 -4
- package/src/client/components/RimelightTemplateEditor.tsx +9 -5
- package/src/client/components/editors/CalloutEditor.tsx +10 -10
- package/src/client/components/editors/CardEditor.tsx +5 -5
- package/src/client/components/editors/CardsEditor.tsx +2 -2
- package/src/client/components/editors/CodeEditor.tsx +5 -5
- package/src/client/components/editors/DialogueEditor.tsx +6 -6
- package/src/client/components/editors/FileTreeEditor.tsx +1 -1
- package/src/client/components/editors/ImageEditor.tsx +9 -9
- package/src/client/components/editors/ParagraphEditor.tsx +1 -1
- package/src/client/components/editors/SceneEditor.tsx +9 -9
- package/src/client/components/editors/ScriptEditor.tsx +8 -8
- package/src/client/components/editors/SectionEditor.tsx +3 -3
- package/src/client/components/editors/StepItemEditor.tsx +6 -5
- package/src/client/components/editors/StepsEditor.tsx +1 -1
- package/src/client/components/editors/TabItemEditor.tsx +3 -3
- package/src/client/components/editors/TableEditor.tsx +4 -4
- package/src/client/components/editors/TabsEditor.tsx +1 -1
- package/src/client/state/editor-store.ts +23 -23
- package/src/client/utils/sortable.ts +13 -12
- package/src/client/utils/tree-sanitizer.ts +1 -1
- package/src/core/excerpt.ts +16 -16
- package/src/core/page-definitions.ts +2 -2
- package/src/docs/agent.ts +1 -1
- package/src/docs/routing.ts +2 -2
- package/src/env.d.ts +6 -0
- package/src/index.ts +17 -4
- package/src/integration.ts +54 -0
- package/src/loader/live.ts +1 -1
- package/src/markdown/serializer.ts +26 -26
- package/src/schema/sqlite.ts +344 -0
- package/src/storage/index.ts +4 -1
package/src/docs/routing.ts
CHANGED
|
@@ -22,8 +22,8 @@ export function getDocsVersions(entries: (DocEntry | string)[], locale?: string)
|
|
|
22
22
|
continue
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
if (entry.properties?.version && String(entry.properties
|
|
26
|
-
versions.add(String(entry.properties
|
|
25
|
+
if (entry.properties?.["version"] && String(entry.properties["version"]).startsWith("v")) {
|
|
26
|
+
versions.add(String(entry.properties["version"]))
|
|
27
27
|
continue
|
|
28
28
|
}
|
|
29
29
|
|
package/src/env.d.ts
CHANGED
|
@@ -15,3 +15,9 @@ declare module "virtual:rimelight-cms/db" {
|
|
|
15
15
|
declare module "virtual:rimelight-cms/storage" {
|
|
16
16
|
export const storageConfig: any
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
declare module "virtual:rimelight-cms/auth" {
|
|
20
|
+
import type { AuthAdapter } from "@rimelight/auth"
|
|
21
|
+
export const authAdapter: AuthAdapter
|
|
22
|
+
export default authAdapter
|
|
23
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -23,11 +23,25 @@ export type {
|
|
|
23
23
|
export * from "./core/registry.ts"
|
|
24
24
|
export * from "./core/validator.ts"
|
|
25
25
|
|
|
26
|
-
// Auth &
|
|
26
|
+
// Universal Auth & Pluggable Adapters (RFC-0004 & RFC-0006)
|
|
27
27
|
export * from "./auth/permissions.ts"
|
|
28
28
|
export * from "./auth/filter-blocks.ts"
|
|
29
29
|
export * from "./auth/editor-guards.ts"
|
|
30
30
|
export * from "./auth/filter-templates.ts"
|
|
31
|
+
export {
|
|
32
|
+
cfAccessAuth,
|
|
33
|
+
auth0Auth,
|
|
34
|
+
mockAuth,
|
|
35
|
+
hasRole,
|
|
36
|
+
hasPermission,
|
|
37
|
+
evaluateAccess
|
|
38
|
+
} from "@rimelight/auth"
|
|
39
|
+
export type {
|
|
40
|
+
AuthAdapter as CmsAuthAdapter,
|
|
41
|
+
AuthAdapter,
|
|
42
|
+
UserSessionContext,
|
|
43
|
+
UserType
|
|
44
|
+
} from "@rimelight/auth"
|
|
31
45
|
|
|
32
46
|
// Edge Cache & Search
|
|
33
47
|
export * from "./cache/purge.ts"
|
|
@@ -36,9 +50,8 @@ export * from "./search/query.ts"
|
|
|
36
50
|
// Database Schemas & Migration
|
|
37
51
|
export * from "./schema/index.ts"
|
|
38
52
|
|
|
39
|
-
// Astro Components
|
|
40
|
-
export {
|
|
41
|
-
export { default as BlockRenderer } from "./astro/BlockRenderer.astro"
|
|
53
|
+
// Astro Integration & Components
|
|
54
|
+
export { rimelightCms, type RimelightCmsOptions } from "./integration.ts"
|
|
42
55
|
|
|
43
56
|
// Toast API
|
|
44
57
|
export * from "./client/toast.ts"
|
package/src/integration.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path"
|
|
|
3
3
|
import fs from "node:fs"
|
|
4
4
|
import { fileURLToPath } from "node:url"
|
|
5
5
|
import type { StorageConfig } from "./storage/index"
|
|
6
|
+
import type { AuthAdapter } from "@rimelight/auth"
|
|
6
7
|
|
|
7
8
|
export interface RimelightCmsOptions {
|
|
8
9
|
/**
|
|
@@ -27,6 +28,12 @@ export interface RimelightCmsOptions {
|
|
|
27
28
|
* "https://media.example.com" })).
|
|
28
29
|
*/
|
|
29
30
|
storage?: StorageConfig | undefined
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pluggable Auth Adapter instance or module path (RFC-0004 / RFC-0006). E.g. cfAccessAuth({ ...
|
|
34
|
+
* }), auth0Auth({ ... }), or mockAuth().
|
|
35
|
+
*/
|
|
36
|
+
auth?: AuthAdapter | string | undefined
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
export function rimelightCms(options: RimelightCmsOptions = {}): AstroIntegration {
|
|
@@ -66,6 +73,47 @@ export function rimelightCms(options: RimelightCmsOptions = {}): AstroIntegratio
|
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
// Resolve auth module / adapter code
|
|
77
|
+
let resolvedAuthCode: string
|
|
78
|
+
if (typeof options.auth === "string") {
|
|
79
|
+
const resolvedAuthPath = path.isAbsolute(options.auth)
|
|
80
|
+
? options.auth
|
|
81
|
+
: path.resolve(rootDir, options.auth)
|
|
82
|
+
resolvedAuthCode = `export { authAdapter, authAdapter as default } from ${JSON.stringify(resolvedAuthPath)};`
|
|
83
|
+
} else if (options.auth && typeof options.auth === "object") {
|
|
84
|
+
const authObj = options.auth as any
|
|
85
|
+
const adapterName = authObj.name || "mock"
|
|
86
|
+
const adapterOpts = authObj.options || {}
|
|
87
|
+
|
|
88
|
+
if (adapterName === "cf-access") {
|
|
89
|
+
resolvedAuthCode = `import { cfAccessAuth } from "@rimelight/auth/cf-access";
|
|
90
|
+
export const authAdapter = cfAccessAuth(${JSON.stringify(adapterOpts)});
|
|
91
|
+
export default authAdapter;`
|
|
92
|
+
} else if (adapterName === "auth0") {
|
|
93
|
+
resolvedAuthCode = `import { auth0Auth } from "@rimelight/auth/auth0";
|
|
94
|
+
export const authAdapter = auth0Auth(${JSON.stringify(adapterOpts)});
|
|
95
|
+
export default authAdapter;`
|
|
96
|
+
} else {
|
|
97
|
+
resolvedAuthCode = `import { mockAuth } from "@rimelight/auth/mock";
|
|
98
|
+
export const authAdapter = mockAuth(${JSON.stringify(adapterOpts)});
|
|
99
|
+
export default authAdapter;`
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
// Default fallback mock admin adapter for development
|
|
103
|
+
resolvedAuthCode = `import { mockAuth } from "@rimelight/auth/mock";
|
|
104
|
+
export const authAdapter = mockAuth({
|
|
105
|
+
session: {
|
|
106
|
+
userId: "cms-admin",
|
|
107
|
+
name: "CMS Administrator",
|
|
108
|
+
email: "admin@rimelight.com",
|
|
109
|
+
roles: ["admin", "editor"],
|
|
110
|
+
permissions: ["*"],
|
|
111
|
+
userType: "employee"
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
export default authAdapter;`
|
|
115
|
+
}
|
|
116
|
+
|
|
69
117
|
// 1. Inject CMS admin routes
|
|
70
118
|
injectRoute({
|
|
71
119
|
pattern: `${routePrefix}`,
|
|
@@ -166,6 +214,9 @@ export function rimelightCms(options: RimelightCmsOptions = {}): AstroIntegratio
|
|
|
166
214
|
if (id === "virtual:rimelight-cms/storage") {
|
|
167
215
|
return "\0virtual:rimelight-cms/storage"
|
|
168
216
|
}
|
|
217
|
+
if (id === "virtual:rimelight-cms/auth") {
|
|
218
|
+
return "\0virtual:rimelight-cms/auth"
|
|
219
|
+
}
|
|
169
220
|
return undefined
|
|
170
221
|
},
|
|
171
222
|
load(id) {
|
|
@@ -184,6 +235,9 @@ export function rimelightCms(options: RimelightCmsOptions = {}): AstroIntegratio
|
|
|
184
235
|
if (id === "\0virtual:rimelight-cms/storage") {
|
|
185
236
|
return `export const storageConfig = ${JSON.stringify(storageConfig)};`
|
|
186
237
|
}
|
|
238
|
+
if (id === "\0virtual:rimelight-cms/auth") {
|
|
239
|
+
return resolvedAuthCode
|
|
240
|
+
}
|
|
187
241
|
return undefined
|
|
188
242
|
}
|
|
189
243
|
}
|
package/src/loader/live.ts
CHANGED
|
@@ -56,7 +56,7 @@ function resolveLocalized(val: unknown, locale: string = "en"): string {
|
|
|
56
56
|
if (typeof val === "string") return val
|
|
57
57
|
if (typeof val === "object" && val !== null) {
|
|
58
58
|
const rec = val as Record<string, string>
|
|
59
|
-
return rec[locale] || rec
|
|
59
|
+
return rec[locale] || rec["en"] || Object.values(rec)[0] || ""
|
|
60
60
|
}
|
|
61
61
|
return typeof val === "number" ? String(val) : ""
|
|
62
62
|
}
|
|
@@ -46,18 +46,18 @@ export function blocksToMarkdown(
|
|
|
46
46
|
|
|
47
47
|
for (const block of blocks) {
|
|
48
48
|
if (!block) continue
|
|
49
|
-
const children = block.children || block.props?.children || []
|
|
49
|
+
const children = block.children || block.props?.["children"] || []
|
|
50
50
|
|
|
51
51
|
switch (block.type) {
|
|
52
52
|
case "SectionBlock": {
|
|
53
|
-
const level = (block.props?.level as HeadingLevel) || (baseLevel as HeadingLevel)
|
|
53
|
+
const level = (block.props?.["level"] as HeadingLevel) || (baseLevel as HeadingLevel)
|
|
54
54
|
const hashes = "#".repeat(Math.min(6, Math.max(1, level)))
|
|
55
|
-
const title = block.props?.title || ""
|
|
55
|
+
const title = block.props?.["title"] || ""
|
|
56
56
|
if (title) {
|
|
57
57
|
lines.push(`${hashes} ${title}`)
|
|
58
58
|
}
|
|
59
|
-
if (block.props?.description) {
|
|
60
|
-
lines.push(`_${block.props
|
|
59
|
+
if (block.props?.["description"]) {
|
|
60
|
+
lines.push(`_${block.props["description"]}_`)
|
|
61
61
|
}
|
|
62
62
|
if (children.length > 0) {
|
|
63
63
|
lines.push(blocksToMarkdown(children, { sectionLevel: Math.min(6, level + 1) }))
|
|
@@ -66,13 +66,13 @@ export function blocksToMarkdown(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
case "ParagraphBlock": {
|
|
69
|
-
const text = inlinesToMarkdown(block.props?.text)
|
|
69
|
+
const text = inlinesToMarkdown(block.props?.["text"])
|
|
70
70
|
if (text) lines.push(text)
|
|
71
71
|
break
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
case "CalloutBlock": {
|
|
75
|
-
const variant = (block.props?.variant || "info").toUpperCase()
|
|
75
|
+
const variant = (block.props?.["variant"] || "info").toUpperCase()
|
|
76
76
|
const childMd = blocksToMarkdown(children, options)
|
|
77
77
|
const calloutHeader = `> [!${variant}]`
|
|
78
78
|
if (childMd) {
|
|
@@ -88,16 +88,16 @@ export function blocksToMarkdown(
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
case "CodeBlock": {
|
|
91
|
-
const lang = block.props?.language || ""
|
|
92
|
-
const code = block.props?.code || ""
|
|
93
|
-
const caption = block.props?.caption ? `\n_${block.props
|
|
91
|
+
const lang = block.props?.["language"] || ""
|
|
92
|
+
const code = block.props?.["code"] || ""
|
|
93
|
+
const caption = block.props?.["caption"] ? `\n_${block.props["caption"]}_` : ""
|
|
94
94
|
lines.push(`\`\`\`${lang}\n${code}\n\`\`\`${caption}`)
|
|
95
95
|
break
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
case "TableBlock": {
|
|
99
|
-
const columns = block.props?.columns || []
|
|
100
|
-
const rows = block.props?.rows || []
|
|
99
|
+
const columns = block.props?.["columns"] || []
|
|
100
|
+
const rows = block.props?.["rows"] || []
|
|
101
101
|
if (columns.length > 0) {
|
|
102
102
|
const headerRow = `| ${columns.map((c: any) => c.header || c.key).join(" | ")} |`
|
|
103
103
|
const alignRow = `| ${columns
|
|
@@ -111,16 +111,16 @@ export function blocksToMarkdown(
|
|
|
111
111
|
(r: any) => `| ${columns.map((c: any) => r[c.key] || "").join(" | ")} |`
|
|
112
112
|
)
|
|
113
113
|
const tableMd = [headerRow, alignRow, ...bodyRows].join("\n")
|
|
114
|
-
const caption = block.props?.caption ? `\n_${block.props
|
|
114
|
+
const caption = block.props?.["caption"] ? `\n_${block.props["caption"]}_` : ""
|
|
115
115
|
lines.push(`${tableMd}${caption}`)
|
|
116
116
|
}
|
|
117
117
|
break
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
case "ImageBlock": {
|
|
121
|
-
const alt = block.props?.alt || ""
|
|
122
|
-
const src = block.props?.src || ""
|
|
123
|
-
const caption = block.props?.caption ? `\n_${block.props
|
|
121
|
+
const alt = block.props?.["alt"] || ""
|
|
122
|
+
const src = block.props?.["src"] || ""
|
|
123
|
+
const caption = block.props?.["caption"] ? `\n_${block.props["caption"]}_` : ""
|
|
124
124
|
lines.push(`${caption}`)
|
|
125
125
|
break
|
|
126
126
|
}
|
|
@@ -135,7 +135,7 @@ export function blocksToMarkdown(
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
case "TabItemBlock": {
|
|
138
|
-
const label = block.props?.label || "Tab"
|
|
138
|
+
const label = block.props?.["label"] || "Tab"
|
|
139
139
|
lines.push(`#### ${label}\n\n${blocksToMarkdown(children, options)}`)
|
|
140
140
|
break
|
|
141
141
|
}
|
|
@@ -156,8 +156,8 @@ export function blocksToMarkdown(
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
case "StepItemBlock": {
|
|
159
|
-
const title = block.props?.title || "Step"
|
|
160
|
-
const desc = block.props?.description ? ` - ${block.props
|
|
159
|
+
const title = block.props?.["title"] || "Step"
|
|
160
|
+
const desc = block.props?.["description"] ? ` - ${block.props["description"]}` : ""
|
|
161
161
|
lines.push(`- **${title}**${desc}`)
|
|
162
162
|
if (children.length > 0) {
|
|
163
163
|
lines.push(blocksToMarkdown(children, options))
|
|
@@ -176,9 +176,9 @@ export function blocksToMarkdown(
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
case "CardBlock": {
|
|
179
|
-
const title = block.props?.title || ""
|
|
180
|
-
const href = block.props?.href || "#"
|
|
181
|
-
const desc = block.props?.description ? `: ${block.props
|
|
179
|
+
const title = block.props?.["title"] || ""
|
|
180
|
+
const href = block.props?.["href"] || "#"
|
|
181
|
+
const desc = block.props?.["description"] ? `: ${block.props["description"]}` : ""
|
|
182
182
|
lines.push(`- [${title}](${href})${desc}`)
|
|
183
183
|
break
|
|
184
184
|
}
|
|
@@ -196,19 +196,19 @@ export function blocksToMarkdown(
|
|
|
196
196
|
}
|
|
197
197
|
return treeLines
|
|
198
198
|
}
|
|
199
|
-
const treeNodes = block.props?.tree || []
|
|
199
|
+
const treeNodes = block.props?.["tree"] || []
|
|
200
200
|
lines.push(`\`\`\`text\n${renderTree(treeNodes).join("\n")}\n\`\`\``)
|
|
201
201
|
break
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
case "DialogueBlock": {
|
|
205
|
-
lines.push(`**${block.props?.character}:** "${block.props?.line || ""}"`)
|
|
205
|
+
lines.push(`**${block.props?.["character"]}:** "${block.props?.["line"] || ""}"`)
|
|
206
206
|
break
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
default: {
|
|
210
|
-
if (block.props?.text) {
|
|
211
|
-
lines.push(inlinesToMarkdown(block.props
|
|
210
|
+
if (block.props?.["text"]) {
|
|
211
|
+
lines.push(inlinesToMarkdown(block.props["text"]))
|
|
212
212
|
}
|
|
213
213
|
break
|
|
214
214
|
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm"
|
|
2
|
+
import {
|
|
3
|
+
sqliteTable,
|
|
4
|
+
text,
|
|
5
|
+
integer,
|
|
6
|
+
uniqueIndex,
|
|
7
|
+
index,
|
|
8
|
+
type AnySQLiteColumn
|
|
9
|
+
} from "drizzle-orm/sqlite-core"
|
|
10
|
+
import type { BaseBlock } from "../core/types/blocks"
|
|
11
|
+
import type { Localized, PageType } from "../core/types/pages"
|
|
12
|
+
import type { BylineCredit } from "../core/types/bylines"
|
|
13
|
+
import type { PageVersionStatus } from "../core/types/versioning"
|
|
14
|
+
import type { TemplateApprovalRules, TemplateRolePermissions } from "../core/types/templates"
|
|
15
|
+
|
|
16
|
+
export interface SiteSettingsBranding {
|
|
17
|
+
logo: {
|
|
18
|
+
alt: string
|
|
19
|
+
}
|
|
20
|
+
favicon: {
|
|
21
|
+
svg: string
|
|
22
|
+
}
|
|
23
|
+
colors: {
|
|
24
|
+
themeColor: string
|
|
25
|
+
backgroundColor: string
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SiteSettingsSeo {
|
|
30
|
+
titleTemplate: string
|
|
31
|
+
ogImageFallback: string
|
|
32
|
+
maxDescriptionLength: number
|
|
33
|
+
authorHandle?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 1. Page Templates
|
|
37
|
+
export const pageTemplates = sqliteTable("page_templates", {
|
|
38
|
+
id: text("id")
|
|
39
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
40
|
+
.notNull()
|
|
41
|
+
.primaryKey(),
|
|
42
|
+
slug: text("slug").notNull().unique(),
|
|
43
|
+
title: text("title", { mode: "json" }).$type<Localized>().notNull(),
|
|
44
|
+
description: text("description", { mode: "json" }).$type<Localized>(),
|
|
45
|
+
pageType: text("page_type").$type<PageType>().notNull(),
|
|
46
|
+
version: integer("version").default(1).notNull(),
|
|
47
|
+
allowedRoles: text("allowed_roles", { mode: "json" })
|
|
48
|
+
.$type<string[]>()
|
|
49
|
+
.default(sql`'[]'`)
|
|
50
|
+
.notNull(),
|
|
51
|
+
rolePermissions: text("role_permissions", { mode: "json" })
|
|
52
|
+
.$type<TemplateRolePermissions>()
|
|
53
|
+
.default(sql`'{"whoCanCreate":[],"whoCanEdit":[],"whoCanReview":[],"whoCanView":[]}'`)
|
|
54
|
+
.notNull(),
|
|
55
|
+
requiredPermission: text("required_permission"),
|
|
56
|
+
approvalRules: text("approval_rules", { mode: "json" })
|
|
57
|
+
.$type<TemplateApprovalRules>()
|
|
58
|
+
.default(sql`'{"allowSelfApproval":true,"minApprovals":1}'`)
|
|
59
|
+
.notNull(),
|
|
60
|
+
defaultProperties: text("default_properties", { mode: "json" })
|
|
61
|
+
.default(sql`'{}'`)
|
|
62
|
+
.notNull(),
|
|
63
|
+
initialBlocks: text("initial_blocks", { mode: "json" })
|
|
64
|
+
.$type<BaseBlock[]>()
|
|
65
|
+
.default(sql`'[]'`)
|
|
66
|
+
.notNull(),
|
|
67
|
+
createdAt: integer("created_at", { mode: "timestamp" })
|
|
68
|
+
.$defaultFn(() => new Date())
|
|
69
|
+
.notNull(),
|
|
70
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).$onUpdate(() => new Date())
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// 2. Page Versions
|
|
74
|
+
export const pageVersions = sqliteTable(
|
|
75
|
+
"page_versions",
|
|
76
|
+
{
|
|
77
|
+
id: text("id")
|
|
78
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
79
|
+
.notNull()
|
|
80
|
+
.primaryKey(),
|
|
81
|
+
pageId: text("page_id").notNull(),
|
|
82
|
+
versionNumber: integer("version_number").notNull(),
|
|
83
|
+
status: text("status").$type<PageVersionStatus>().default("pending").notNull(),
|
|
84
|
+
slug: text("slug").notNull(),
|
|
85
|
+
type: text("type").$type<PageType>().notNull(),
|
|
86
|
+
title: text("title", { mode: "json" }).$type<Localized>().notNull(),
|
|
87
|
+
description: text("description", { mode: "json" }).$type<Localized>(),
|
|
88
|
+
tags: text("tags", { mode: "json" })
|
|
89
|
+
.$type<Localized[]>()
|
|
90
|
+
.default(sql`'[]'`)
|
|
91
|
+
.notNull(),
|
|
92
|
+
authorIds: text("author_ids", { mode: "json" })
|
|
93
|
+
.$type<string[]>()
|
|
94
|
+
.default(sql`'[]'`),
|
|
95
|
+
bylines: text("bylines", { mode: "json" })
|
|
96
|
+
.$type<BylineCredit[]>()
|
|
97
|
+
.default(sql`'[]'`),
|
|
98
|
+
content: text("content", { mode: "json" })
|
|
99
|
+
.$type<{
|
|
100
|
+
blocks: BaseBlock[]
|
|
101
|
+
properties: Record<string, any>
|
|
102
|
+
}>()
|
|
103
|
+
.notNull(),
|
|
104
|
+
createdBy: text("created_by").notNull(),
|
|
105
|
+
approvedBy: text("approved_by", { mode: "json" })
|
|
106
|
+
.$type<string[]>()
|
|
107
|
+
.default(sql`'[]'`)
|
|
108
|
+
.notNull(),
|
|
109
|
+
approvedAt: integer("approved_at", { mode: "timestamp" }),
|
|
110
|
+
changeSummary: text("change_summary"),
|
|
111
|
+
createdAt: integer("created_at", { mode: "timestamp" })
|
|
112
|
+
.$defaultFn(() => new Date())
|
|
113
|
+
.notNull()
|
|
114
|
+
},
|
|
115
|
+
(table) => [
|
|
116
|
+
uniqueIndex("page_versions_page_id_version_idx").on(table.pageId, table.versionNumber)
|
|
117
|
+
]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// 3. Pages
|
|
121
|
+
export const pages = sqliteTable(
|
|
122
|
+
"pages",
|
|
123
|
+
{
|
|
124
|
+
id: text("id")
|
|
125
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
126
|
+
.notNull()
|
|
127
|
+
.primaryKey(),
|
|
128
|
+
slug: text("slug").notNull(),
|
|
129
|
+
type: text("type").$type<PageType>().notNull(),
|
|
130
|
+
templateId: text("template_id").references(() => pageTemplates.id, { onDelete: "set null" }),
|
|
131
|
+
templateVersion: integer("template_version").default(1).notNull(),
|
|
132
|
+
title: text("title", { mode: "json" }).$type<Localized>().notNull(),
|
|
133
|
+
description: text("description", { mode: "json" }).$type<Localized>(),
|
|
134
|
+
tags: text("tags", { mode: "json" })
|
|
135
|
+
.$type<Localized[]>()
|
|
136
|
+
.default(sql`'[]'`)
|
|
137
|
+
.notNull(),
|
|
138
|
+
authorIds: text("author_ids", { mode: "json" })
|
|
139
|
+
.$type<string[]>()
|
|
140
|
+
.default(sql`'[]'`),
|
|
141
|
+
bylines: text("bylines", { mode: "json" })
|
|
142
|
+
.$type<BylineCredit[]>()
|
|
143
|
+
.default(sql`'[]'`),
|
|
144
|
+
content: text("content", { mode: "json" })
|
|
145
|
+
.$type<{
|
|
146
|
+
blocks: BaseBlock[]
|
|
147
|
+
properties: Record<string, any>
|
|
148
|
+
}>()
|
|
149
|
+
.notNull(),
|
|
150
|
+
publishedVersionId: text("published_version_id").references(() => pageVersions.id, {
|
|
151
|
+
onDelete: "set null"
|
|
152
|
+
}),
|
|
153
|
+
postedAt: integer("posted_at", { mode: "timestamp" }),
|
|
154
|
+
createdAt: integer("created_at", { mode: "timestamp" })
|
|
155
|
+
.$defaultFn(() => new Date())
|
|
156
|
+
.notNull(),
|
|
157
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).$onUpdate(() => new Date()),
|
|
158
|
+
deletedAt: integer("deleted_at", { mode: "timestamp" })
|
|
159
|
+
},
|
|
160
|
+
(table) => [
|
|
161
|
+
uniqueIndex("pages_slug_active_unique_idx")
|
|
162
|
+
.on(table.slug)
|
|
163
|
+
.where(sql`deleted_at IS NULL`)
|
|
164
|
+
]
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
// 4. Page Drafts
|
|
168
|
+
export const pageDrafts = sqliteTable("page_drafts", {
|
|
169
|
+
pageId: text("page_id")
|
|
170
|
+
.notNull()
|
|
171
|
+
.primaryKey()
|
|
172
|
+
.references(() => pages.id, { onDelete: "cascade" }),
|
|
173
|
+
content: text("content", { mode: "json" })
|
|
174
|
+
.$type<{
|
|
175
|
+
blocks: BaseBlock[]
|
|
176
|
+
properties: Record<string, any>
|
|
177
|
+
}>()
|
|
178
|
+
.notNull(),
|
|
179
|
+
updatedBy: text("updated_by").notNull(),
|
|
180
|
+
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
181
|
+
.$defaultFn(() => new Date())
|
|
182
|
+
.notNull()
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// 5. Page Draft Locks
|
|
186
|
+
export const pageDraftLocks = sqliteTable("page_draft_locks", {
|
|
187
|
+
pageId: text("page_id")
|
|
188
|
+
.notNull()
|
|
189
|
+
.primaryKey()
|
|
190
|
+
.references(() => pages.id, { onDelete: "cascade" }),
|
|
191
|
+
lockedByUserId: text("locked_by_user_id").notNull(),
|
|
192
|
+
lockedByUserName: text("locked_by_user_name").notNull(),
|
|
193
|
+
acquiredAt: integer("acquired_at", { mode: "timestamp" })
|
|
194
|
+
.$defaultFn(() => new Date())
|
|
195
|
+
.notNull(),
|
|
196
|
+
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// 6. Page Version Approvals
|
|
200
|
+
export const pageVersionApprovals = sqliteTable("page_version_approvals", {
|
|
201
|
+
id: text("id")
|
|
202
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
203
|
+
.notNull()
|
|
204
|
+
.primaryKey(),
|
|
205
|
+
versionId: text("version_id")
|
|
206
|
+
.notNull()
|
|
207
|
+
.references(() => pageVersions.id, { onDelete: "cascade" }),
|
|
208
|
+
userId: text("user_id").notNull(),
|
|
209
|
+
userRole: text("user_role").notNull(),
|
|
210
|
+
approvedAt: integer("approved_at", { mode: "timestamp" })
|
|
211
|
+
.$defaultFn(() => new Date())
|
|
212
|
+
.notNull()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// 7. Page Version Comments
|
|
216
|
+
export const pageVersionComments = sqliteTable("page_version_comments", {
|
|
217
|
+
id: text("id")
|
|
218
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
219
|
+
.notNull()
|
|
220
|
+
.primaryKey(),
|
|
221
|
+
versionId: text("version_id")
|
|
222
|
+
.notNull()
|
|
223
|
+
.references(() => pageVersions.id, { onDelete: "cascade" }),
|
|
224
|
+
userId: text("user_id").notNull(),
|
|
225
|
+
userRole: text("user_role").notNull(),
|
|
226
|
+
content: text("content").notNull(),
|
|
227
|
+
blockId: text("block_id"),
|
|
228
|
+
createdAt: integer("created_at", { mode: "timestamp" })
|
|
229
|
+
.$defaultFn(() => new Date())
|
|
230
|
+
.notNull()
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// 8. Search
|
|
234
|
+
export const contentSearchIndex = sqliteTable(
|
|
235
|
+
"content_search_index",
|
|
236
|
+
{
|
|
237
|
+
id: text("id")
|
|
238
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
239
|
+
.notNull()
|
|
240
|
+
.primaryKey(),
|
|
241
|
+
pageId: text("page_id")
|
|
242
|
+
.notNull()
|
|
243
|
+
.references(() => pages.id, { onDelete: "cascade" }),
|
|
244
|
+
locale: text("locale").notNull(),
|
|
245
|
+
titleText: text("title_text").notNull(),
|
|
246
|
+
contentText: text("content_text").notNull()
|
|
247
|
+
},
|
|
248
|
+
(table) => [index("content_search_index_page_id_idx").on(table.pageId)]
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
// 9. Site Settings
|
|
252
|
+
export const siteSettings = sqliteTable("site_settings", {
|
|
253
|
+
id: text("id").primaryKey().default("default"),
|
|
254
|
+
name: text("name").notNull(),
|
|
255
|
+
description: text("description").notNull(),
|
|
256
|
+
url: text("url").notNull(),
|
|
257
|
+
ogImage: text("og_image").notNull(),
|
|
258
|
+
author: text("author").notNull(),
|
|
259
|
+
email: text("email").notNull().default(""),
|
|
260
|
+
branding: text("branding", { mode: "json" }).$type<SiteSettingsBranding>().notNull(),
|
|
261
|
+
seo: text("seo", { mode: "json" }).$type<SiteSettingsSeo>().notNull(),
|
|
262
|
+
createdAt: integer("created_at", { mode: "timestamp" })
|
|
263
|
+
.$defaultFn(() => new Date())
|
|
264
|
+
.notNull(),
|
|
265
|
+
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
266
|
+
.$defaultFn(() => new Date())
|
|
267
|
+
.$onUpdate(() => new Date())
|
|
268
|
+
.notNull()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
// 10. Taxonomies
|
|
272
|
+
export const taxonomyTerms = sqliteTable(
|
|
273
|
+
"taxonomy_terms",
|
|
274
|
+
{
|
|
275
|
+
id: text("id")
|
|
276
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
277
|
+
.notNull()
|
|
278
|
+
.primaryKey(),
|
|
279
|
+
taxonomy: text("taxonomy").notNull(),
|
|
280
|
+
slug: text("slug").notNull(),
|
|
281
|
+
label: text("label", { mode: "json" }).$type<Localized | string>().notNull(),
|
|
282
|
+
description: text("description", { mode: "json" }).$type<Localized | string>(),
|
|
283
|
+
parentId: text("parent_id").references((): AnySQLiteColumn => taxonomyTerms.id, {
|
|
284
|
+
onDelete: "cascade"
|
|
285
|
+
}),
|
|
286
|
+
displayOrder: integer("display_order").default(0).notNull(),
|
|
287
|
+
createdAt: integer("created_at", { mode: "timestamp" })
|
|
288
|
+
.$defaultFn(() => new Date())
|
|
289
|
+
.notNull(),
|
|
290
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).$onUpdate(() => new Date())
|
|
291
|
+
},
|
|
292
|
+
(table) => [uniqueIndex("taxonomy_terms_tax_slug_idx").on(table.taxonomy, table.slug)]
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
export const pageTaxonomyTerms = sqliteTable(
|
|
296
|
+
"page_taxonomy_terms",
|
|
297
|
+
{
|
|
298
|
+
pageId: text("page_id")
|
|
299
|
+
.notNull()
|
|
300
|
+
.references(() => pages.id, { onDelete: "cascade" }),
|
|
301
|
+
termId: text("term_id")
|
|
302
|
+
.notNull()
|
|
303
|
+
.references(() => taxonomyTerms.id, { onDelete: "cascade" }),
|
|
304
|
+
assignedAt: integer("assigned_at", { mode: "timestamp" })
|
|
305
|
+
.$defaultFn(() => new Date())
|
|
306
|
+
.notNull()
|
|
307
|
+
},
|
|
308
|
+
(table) => [uniqueIndex("page_taxonomy_terms_page_term_idx").on(table.pageId, table.termId)]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
// 11. Bylines
|
|
312
|
+
export const bylines = sqliteTable(
|
|
313
|
+
"bylines",
|
|
314
|
+
{
|
|
315
|
+
id: text("id")
|
|
316
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
317
|
+
.primaryKey(),
|
|
318
|
+
name: text("name").notNull(),
|
|
319
|
+
slug: text("slug").notNull(),
|
|
320
|
+
websiteUrl: text("website_url"),
|
|
321
|
+
bio: text("bio"),
|
|
322
|
+
avatar: text("avatar"),
|
|
323
|
+
userId: text("user_id"),
|
|
324
|
+
socials: text("socials", { mode: "json" })
|
|
325
|
+
.$type<Record<string, string>>()
|
|
326
|
+
.default(sql`'{}'`),
|
|
327
|
+
metadata: text("metadata", { mode: "json" })
|
|
328
|
+
.$type<Record<string, any>>()
|
|
329
|
+
.default(sql`'{}'`),
|
|
330
|
+
createdAt: integer("created_at", { mode: "timestamp" })
|
|
331
|
+
.$defaultFn(() => new Date())
|
|
332
|
+
.notNull(),
|
|
333
|
+
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
334
|
+
.$defaultFn(() => new Date())
|
|
335
|
+
.notNull(),
|
|
336
|
+
deletedAt: integer("deleted_at", { mode: "timestamp" })
|
|
337
|
+
},
|
|
338
|
+
(table) => [uniqueIndex("bylines_slug_idx").on(table.slug)]
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
export type Byline = typeof bylines.$inferSelect
|
|
342
|
+
export type NewByline = typeof bylines.$inferInsert
|
|
343
|
+
export type SiteSettings = typeof siteSettings.$inferSelect
|
|
344
|
+
export type InsertSiteSettings = typeof siteSettings.$inferInsert
|
package/src/storage/index.ts
CHANGED
|
@@ -65,8 +65,11 @@ export async function getR2Bucket(locals?: any, bindingName = "BLOB"): Promise<a
|
|
|
65
65
|
}
|
|
66
66
|
} catch {}
|
|
67
67
|
|
|
68
|
-
// 2. Direct locals property check (locals.env or locals[bindingName])
|
|
68
|
+
// 2. Direct locals property check (locals.runtime.env, locals.env or locals[bindingName])
|
|
69
69
|
if (locals) {
|
|
70
|
+
if (locals.runtime?.env && locals.runtime.env[bindingName]) {
|
|
71
|
+
return locals.runtime.env[bindingName]
|
|
72
|
+
}
|
|
70
73
|
if (locals.env && locals.env[bindingName]) {
|
|
71
74
|
return locals.env[bindingName]
|
|
72
75
|
}
|