@rimelight/cms 0.0.3 → 0.0.4

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.
@@ -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
  }
@@ -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
@@ -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
  }