@rimelight/cms 0.0.2 → 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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bylines-Csggkl-g.d.mts +405 -0
  3. package/dist/index.d.mts +724 -0
  4. package/dist/index.mjs +2382 -0
  5. package/dist/integration.d.mts +33 -0
  6. package/dist/integration.mjs +161 -0
  7. package/dist/schema/index.d.mts +2063 -0
  8. package/dist/schema/index.mjs +176 -0
  9. package/dist/schema/sqlite.d.mts +2132 -0
  10. package/dist/schema/sqlite.mjs +137 -0
  11. package/dist/storage/index.d.mts +59 -0
  12. package/dist/storage/index.mjs +133 -0
  13. package/package.json +36 -18
  14. package/src/admin/api/media-file.ts +39 -0
  15. package/src/admin/api/media.ts +224 -0
  16. package/src/admin/layouts/CMSDashboardLayout.astro +191 -0
  17. package/src/admin/layouts/DefaultParentLayout.astro +50 -0
  18. package/src/admin/pages/assets.astro +424 -0
  19. package/src/admin/pages/byline-schema.astro +62 -0
  20. package/src/admin/pages/bylines.astro +16 -0
  21. package/src/admin/pages/categories.astro +21 -0
  22. package/src/admin/pages/content-types.astro +55 -0
  23. package/src/admin/pages/dashboard.astro +61 -0
  24. package/src/admin/pages/pages-edit.astro +595 -0
  25. package/src/admin/pages/pages-index.astro +146 -0
  26. package/src/admin/pages/pages-new.astro +130 -0
  27. package/src/admin/pages/pages-preview.astro +111 -0
  28. package/src/admin/pages/pages-review.astro +236 -0
  29. package/src/admin/pages/settings.astro +203 -0
  30. package/src/admin/pages/tags.astro +21 -0
  31. package/src/admin/pages/templates-edit.astro +234 -0
  32. package/src/admin/pages/templates-index.astro +95 -0
  33. package/src/admin/pages/templates-new.astro +193 -0
  34. package/src/admin/pages/users.astro +74 -0
  35. package/src/admin/pages/versions.astro +74 -0
  36. package/src/astro/BlockRenderer.astro +44 -102
  37. package/src/astro/PageRenderer.astro +12 -5
  38. package/src/astro/blocks/CalloutBlock.astro +7 -7
  39. package/src/astro/blocks/CardBlock.astro +5 -5
  40. package/src/astro/blocks/CodeBlock.astro +2 -2
  41. package/src/astro/blocks/FileTreeBlock.astro +7 -7
  42. package/src/astro/blocks/ImageBlock.astro +2 -2
  43. package/src/astro/blocks/OrderedListBlock.astro +2 -2
  44. package/src/astro/blocks/ParagraphBlock.astro +3 -3
  45. package/src/astro/blocks/SectionBlock.astro +6 -6
  46. package/src/astro/blocks/StepItemBlock.astro +3 -3
  47. package/src/astro/blocks/StepsBlock.astro +1 -1
  48. package/src/astro/blocks/TableBlock.astro +6 -6
  49. package/src/astro/blocks/TabsBlock.astro +9 -9
  50. package/src/astro/blocks/UnorderedListBlock.astro +2 -2
  51. package/src/astro/blocks/index.ts +35 -0
  52. package/src/client/components/RimelightBlock.tsx +19 -19
  53. package/src/client/components/RimelightBlockPicker.tsx +9 -13
  54. package/src/client/components/RimelightBylinesManager.tsx +438 -0
  55. package/src/client/components/RimelightEditor.tsx +3 -3
  56. package/src/client/components/RimelightPreview.tsx +37 -45
  57. package/src/client/components/RimelightPropertiesPanel.tsx +368 -26
  58. package/src/client/components/RimelightTaxonomyManager.tsx +369 -0
  59. package/src/client/components/RimelightTaxonomyMasterDetail.tsx +412 -0
  60. package/src/client/components/RimelightTemplateEditor.tsx +6 -8
  61. package/src/client/components/editors/CalloutEditor.tsx +2 -4
  62. package/src/client/components/editors/CardsEditor.tsx +2 -4
  63. package/src/client/components/editors/CodeEditor.tsx +1 -3
  64. package/src/client/components/editors/FileTreeEditor.tsx +2 -4
  65. package/src/client/components/editors/ImageEditor.tsx +3 -3
  66. package/src/client/components/editors/ParagraphEditor.tsx +2 -2
  67. package/src/client/components/editors/SceneEditor.tsx +6 -12
  68. package/src/client/components/editors/ScriptEditor.tsx +8 -16
  69. package/src/client/components/editors/SectionEditor.tsx +4 -4
  70. package/src/client/components/editors/StepItemEditor.tsx +2 -2
  71. package/src/client/components/editors/StepsEditor.tsx +2 -4
  72. package/src/client/components/editors/TabItemEditor.tsx +3 -3
  73. package/src/client/components/editors/TableEditor.tsx +5 -5
  74. package/src/client/components/editors/TabsEditor.tsx +2 -4
  75. package/src/client/components/index.ts +3 -0
  76. package/src/client/loader.ts +3 -0
  77. package/src/core/diff.ts +145 -0
  78. package/src/core/excerpt.ts +159 -0
  79. package/src/core/preview.ts +87 -0
  80. package/src/core/registry.ts +0 -86
  81. package/src/core/types/blocks.ts +6 -1
  82. package/src/core/types/bylines.ts +14 -0
  83. package/src/core/types/media.ts +13 -0
  84. package/src/core/types/taxonomies.ts +20 -0
  85. package/src/cron/scheduled.ts +57 -0
  86. package/src/docs/agent.ts +116 -0
  87. package/src/docs/config.ts +25 -0
  88. package/src/docs/index.ts +5 -0
  89. package/src/docs/routing.ts +104 -0
  90. package/src/docs/sidebar.ts +251 -0
  91. package/src/docs/toc.ts +47 -0
  92. package/src/env.d.ts +19 -0
  93. package/src/index.ts +57 -12
  94. package/src/integration.ts +250 -0
  95. package/src/loader/live.ts +254 -0
  96. package/src/markdown/index.ts +0 -1
  97. package/src/mcp/index.ts +228 -0
  98. package/src/schema/bylines.ts +23 -0
  99. package/src/schema/index.ts +3 -4
  100. package/src/schema/page_versions.ts +2 -0
  101. package/src/schema/pages.ts +2 -0
  102. package/src/schema/site_settings.ts +41 -0
  103. package/src/schema/sqlite.ts +344 -0
  104. package/src/schema/taxonomies.ts +49 -0
  105. package/src/services/bylines.ts +74 -0
  106. package/src/services/search-indexer.ts +61 -0
  107. package/src/services/site-settings.ts +108 -0
  108. package/src/services/taxonomies.ts +276 -0
  109. package/src/storage/index.ts +256 -0
  110. package/src/astro/blocks/ApiDocumentationBlock.astro +0 -350
  111. package/src/astro/blocks/DialogueBlock.astro +0 -23
  112. package/src/astro/blocks/LivePreviewBlock.astro +0 -27
  113. package/src/astro/blocks/SceneBlock.astro +0 -65
  114. package/src/astro/blocks/ScriptBlock.astro +0 -59
  115. package/src/core/template-sync.ts +0 -75
  116. package/src/markdown/parser.ts +0 -347
  117. package/src/migration-tools.ts +0 -334
  118. package/src/migration.ts +0 -155
  119. package/src/schema/component_dependencies.ts +0 -21
  120. package/src/schema/component_metadata.ts +0 -31
  121. package/src/schema/component_metadata_cache.ts +0 -15
  122. package/src/schema/component_versions.ts +0 -19
  123. package/src/search/sync.ts +0 -32
  124. package/src/services/component-metadata.ts +0 -134
@@ -0,0 +1,137 @@
1
+ import { sql } from "drizzle-orm";
2
+ import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
3
+ //#region src/schema/sqlite.ts
4
+ const pageTemplates = sqliteTable("page_templates", {
5
+ id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
6
+ slug: text("slug").notNull().unique(),
7
+ title: text("title", { mode: "json" }).$type().notNull(),
8
+ description: text("description", { mode: "json" }).$type(),
9
+ pageType: text("page_type").$type().notNull(),
10
+ version: integer("version").default(1).notNull(),
11
+ allowedRoles: text("allowed_roles", { mode: "json" }).$type().default(sql`'[]'`).notNull(),
12
+ rolePermissions: text("role_permissions", { mode: "json" }).$type().default(sql`'{"whoCanCreate":[],"whoCanEdit":[],"whoCanReview":[],"whoCanView":[]}'`).notNull(),
13
+ requiredPermission: text("required_permission"),
14
+ approvalRules: text("approval_rules", { mode: "json" }).$type().default(sql`'{"allowSelfApproval":true,"minApprovals":1}'`).notNull(),
15
+ defaultProperties: text("default_properties", { mode: "json" }).default(sql`'{}'`).notNull(),
16
+ initialBlocks: text("initial_blocks", { mode: "json" }).$type().default(sql`'[]'`).notNull(),
17
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull(),
18
+ updatedAt: integer("updated_at", { mode: "timestamp" }).$onUpdate(() => /* @__PURE__ */ new Date())
19
+ });
20
+ const pageVersions = sqliteTable("page_versions", {
21
+ id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
22
+ pageId: text("page_id").notNull(),
23
+ versionNumber: integer("version_number").notNull(),
24
+ status: text("status").$type().default("pending").notNull(),
25
+ slug: text("slug").notNull(),
26
+ type: text("type").$type().notNull(),
27
+ title: text("title", { mode: "json" }).$type().notNull(),
28
+ description: text("description", { mode: "json" }).$type(),
29
+ tags: text("tags", { mode: "json" }).$type().default(sql`'[]'`).notNull(),
30
+ authorIds: text("author_ids", { mode: "json" }).$type().default(sql`'[]'`),
31
+ bylines: text("bylines", { mode: "json" }).$type().default(sql`'[]'`),
32
+ content: text("content", { mode: "json" }).$type().notNull(),
33
+ createdBy: text("created_by").notNull(),
34
+ approvedBy: text("approved_by", { mode: "json" }).$type().default(sql`'[]'`).notNull(),
35
+ approvedAt: integer("approved_at", { mode: "timestamp" }),
36
+ changeSummary: text("change_summary"),
37
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull()
38
+ }, (table) => [uniqueIndex("page_versions_page_id_version_idx").on(table.pageId, table.versionNumber)]);
39
+ const pages = sqliteTable("pages", {
40
+ id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
41
+ slug: text("slug").notNull(),
42
+ type: text("type").$type().notNull(),
43
+ templateId: text("template_id").references(() => pageTemplates.id, { onDelete: "set null" }),
44
+ templateVersion: integer("template_version").default(1).notNull(),
45
+ title: text("title", { mode: "json" }).$type().notNull(),
46
+ description: text("description", { mode: "json" }).$type(),
47
+ tags: text("tags", { mode: "json" }).$type().default(sql`'[]'`).notNull(),
48
+ authorIds: text("author_ids", { mode: "json" }).$type().default(sql`'[]'`),
49
+ bylines: text("bylines", { mode: "json" }).$type().default(sql`'[]'`),
50
+ content: text("content", { mode: "json" }).$type().notNull(),
51
+ publishedVersionId: text("published_version_id").references(() => pageVersions.id, { onDelete: "set null" }),
52
+ postedAt: integer("posted_at", { mode: "timestamp" }),
53
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull(),
54
+ updatedAt: integer("updated_at", { mode: "timestamp" }).$onUpdate(() => /* @__PURE__ */ new Date()),
55
+ deletedAt: integer("deleted_at", { mode: "timestamp" })
56
+ }, (table) => [uniqueIndex("pages_slug_active_unique_idx").on(table.slug).where(sql`deleted_at IS NULL`)]);
57
+ const pageDrafts = sqliteTable("page_drafts", {
58
+ pageId: text("page_id").notNull().primaryKey().references(() => pages.id, { onDelete: "cascade" }),
59
+ content: text("content", { mode: "json" }).$type().notNull(),
60
+ updatedBy: text("updated_by").notNull(),
61
+ updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull()
62
+ });
63
+ const pageDraftLocks = sqliteTable("page_draft_locks", {
64
+ pageId: text("page_id").notNull().primaryKey().references(() => pages.id, { onDelete: "cascade" }),
65
+ lockedByUserId: text("locked_by_user_id").notNull(),
66
+ lockedByUserName: text("locked_by_user_name").notNull(),
67
+ acquiredAt: integer("acquired_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull(),
68
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull()
69
+ });
70
+ const pageVersionApprovals = sqliteTable("page_version_approvals", {
71
+ id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
72
+ versionId: text("version_id").notNull().references(() => pageVersions.id, { onDelete: "cascade" }),
73
+ userId: text("user_id").notNull(),
74
+ userRole: text("user_role").notNull(),
75
+ approvedAt: integer("approved_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull()
76
+ });
77
+ const pageVersionComments = sqliteTable("page_version_comments", {
78
+ id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
79
+ versionId: text("version_id").notNull().references(() => pageVersions.id, { onDelete: "cascade" }),
80
+ userId: text("user_id").notNull(),
81
+ userRole: text("user_role").notNull(),
82
+ content: text("content").notNull(),
83
+ blockId: text("block_id"),
84
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull()
85
+ });
86
+ const contentSearchIndex = sqliteTable("content_search_index", {
87
+ id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
88
+ pageId: text("page_id").notNull().references(() => pages.id, { onDelete: "cascade" }),
89
+ locale: text("locale").notNull(),
90
+ titleText: text("title_text").notNull(),
91
+ contentText: text("content_text").notNull()
92
+ }, (table) => [index("content_search_index_page_id_idx").on(table.pageId)]);
93
+ const siteSettings = sqliteTable("site_settings", {
94
+ id: text("id").primaryKey().default("default"),
95
+ name: text("name").notNull(),
96
+ description: text("description").notNull(),
97
+ url: text("url").notNull(),
98
+ ogImage: text("og_image").notNull(),
99
+ author: text("author").notNull(),
100
+ email: text("email").notNull().default(""),
101
+ branding: text("branding", { mode: "json" }).$type().notNull(),
102
+ seo: text("seo", { mode: "json" }).$type().notNull(),
103
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull(),
104
+ updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).$onUpdate(() => /* @__PURE__ */ new Date()).notNull()
105
+ });
106
+ const taxonomyTerms = sqliteTable("taxonomy_terms", {
107
+ id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
108
+ taxonomy: text("taxonomy").notNull(),
109
+ slug: text("slug").notNull(),
110
+ label: text("label", { mode: "json" }).$type().notNull(),
111
+ description: text("description", { mode: "json" }).$type(),
112
+ parentId: text("parent_id").references(() => taxonomyTerms.id, { onDelete: "cascade" }),
113
+ displayOrder: integer("display_order").default(0).notNull(),
114
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull(),
115
+ updatedAt: integer("updated_at", { mode: "timestamp" }).$onUpdate(() => /* @__PURE__ */ new Date())
116
+ }, (table) => [uniqueIndex("taxonomy_terms_tax_slug_idx").on(table.taxonomy, table.slug)]);
117
+ const pageTaxonomyTerms = sqliteTable("page_taxonomy_terms", {
118
+ pageId: text("page_id").notNull().references(() => pages.id, { onDelete: "cascade" }),
119
+ termId: text("term_id").notNull().references(() => taxonomyTerms.id, { onDelete: "cascade" }),
120
+ assignedAt: integer("assigned_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull()
121
+ }, (table) => [uniqueIndex("page_taxonomy_terms_page_term_idx").on(table.pageId, table.termId)]);
122
+ const bylines = sqliteTable("bylines", {
123
+ id: text("id").$defaultFn(() => crypto.randomUUID()).primaryKey(),
124
+ name: text("name").notNull(),
125
+ slug: text("slug").notNull(),
126
+ websiteUrl: text("website_url"),
127
+ bio: text("bio"),
128
+ avatar: text("avatar"),
129
+ userId: text("user_id"),
130
+ socials: text("socials", { mode: "json" }).$type().default(sql`'{}'`),
131
+ metadata: text("metadata", { mode: "json" }).$type().default(sql`'{}'`),
132
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull(),
133
+ updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => /* @__PURE__ */ new Date()).notNull(),
134
+ deletedAt: integer("deleted_at", { mode: "timestamp" })
135
+ }, (table) => [uniqueIndex("bylines_slug_idx").on(table.slug)]);
136
+ //#endregion
137
+ export { bylines, contentSearchIndex, pageDraftLocks, pageDrafts, pageTaxonomyTerms, pageTemplates, pageVersionApprovals, pageVersionComments, pageVersions, pages, siteSettings, taxonomyTerms };
@@ -0,0 +1,59 @@
1
+ //#region src/storage/index.d.ts
2
+ interface StorageFile {
3
+ key: string;
4
+ name: string;
5
+ size: number;
6
+ mimeType: string;
7
+ uploadedAt: string;
8
+ url: string;
9
+ etag?: string;
10
+ customMetadata?: Record<string, string>;
11
+ }
12
+ interface StorageListResult {
13
+ files: StorageFile[];
14
+ truncated: boolean;
15
+ cursor?: string;
16
+ }
17
+ interface R2StorageOptions {
18
+ binding?: string | undefined;
19
+ publicUrl?: string | undefined;
20
+ prefix?: string | undefined;
21
+ }
22
+ interface StorageConfig {
23
+ provider: "r2" | "local" | "s3";
24
+ binding?: string | undefined;
25
+ publicUrl?: string | undefined;
26
+ prefix?: string | undefined;
27
+ }
28
+ interface StorageListOptions {
29
+ prefix?: string | undefined;
30
+ cursor?: string | undefined;
31
+ limit?: number | undefined;
32
+ publicUrl?: string | undefined;
33
+ apiBasePath?: string | undefined;
34
+ }
35
+ interface StorageUploadOptions {
36
+ key?: string | undefined;
37
+ prefix?: string | undefined;
38
+ publicUrl?: string | undefined;
39
+ apiBasePath?: string | undefined;
40
+ }
41
+ declare function r2(options?: R2StorageOptions): StorageConfig;
42
+ /**
43
+ * Resolves Cloudflare R2 bucket instance from Cloudflare env or Astro locals.
44
+ */
45
+ declare function getR2Bucket(locals?: any, bindingName?: string): Promise<any>;
46
+ /**
47
+ * Lists files from R2 Bucket under configured prefix.
48
+ */
49
+ declare function listR2Files(bucket: any, options?: StorageListOptions): Promise<StorageListResult>;
50
+ /**
51
+ * Uploads a file buffer/stream into R2 Bucket.
52
+ */
53
+ declare function uploadR2File(bucket: any, file: File, options?: StorageUploadOptions): Promise<StorageFile>;
54
+ /**
55
+ * Deletes a file from R2 Bucket by key.
56
+ */
57
+ declare function deleteR2File(bucket: any, key: string): Promise<boolean>;
58
+ //#endregion
59
+ export { R2StorageOptions, StorageConfig, StorageFile, StorageListOptions, StorageListResult, StorageUploadOptions, deleteR2File, getR2Bucket, listR2Files, r2, uploadR2File };
@@ -0,0 +1,133 @@
1
+ //#region src/storage/index.ts
2
+ function r2(options = {}) {
3
+ return {
4
+ provider: "r2",
5
+ binding: options.binding ?? "BLOB",
6
+ publicUrl: options.publicUrl,
7
+ prefix: options.prefix ?? "media/"
8
+ };
9
+ }
10
+ /**
11
+ * Resolves Cloudflare R2 bucket instance from Cloudflare env or Astro locals.
12
+ */
13
+ async function getR2Bucket(locals, bindingName = "BLOB") {
14
+ try {
15
+ const cf = await import("cloudflare:workers");
16
+ if (cf?.env && cf.env[bindingName]) return cf.env[bindingName];
17
+ } catch {}
18
+ if (locals) {
19
+ if (locals.runtime?.env && locals.runtime.env[bindingName]) return locals.runtime.env[bindingName];
20
+ if (locals.env && locals.env[bindingName]) return locals.env[bindingName];
21
+ if (locals[bindingName]) return locals[bindingName];
22
+ }
23
+ if (typeof process !== "undefined" && process.env && process.env[bindingName]) return process.env[bindingName];
24
+ return null;
25
+ }
26
+ /**
27
+ * Lists files from R2 Bucket under configured prefix.
28
+ */
29
+ async function listR2Files(bucket, options = {}) {
30
+ if (!bucket || typeof bucket.list !== "function") return {
31
+ files: [],
32
+ truncated: false
33
+ };
34
+ const prefix = options.prefix ?? "media/";
35
+ const limit = options.limit ?? 50;
36
+ const result = await bucket.list({
37
+ prefix,
38
+ cursor: options.cursor,
39
+ limit
40
+ });
41
+ return {
42
+ files: (result.objects || []).map((obj) => {
43
+ const rawKey = obj.key;
44
+ const name = rawKey.startsWith(prefix) ? rawKey.slice(prefix.length) : rawKey;
45
+ const cleanedName = name.includes("/") ? name.split("/").pop() : name;
46
+ const mimeType = obj.httpMetadata?.contentType || guessMimeType(rawKey);
47
+ let url = "";
48
+ if (options.publicUrl) url = `${options.publicUrl.endsWith("/") ? options.publicUrl.slice(0, -1) : options.publicUrl}/${rawKey}`;
49
+ else if (options.apiBasePath) url = `${options.apiBasePath.endsWith("/") ? options.apiBasePath.slice(0, -1) : options.apiBasePath}/${rawKey}`;
50
+ else url = `/cms/api/media/${rawKey}`;
51
+ return {
52
+ key: rawKey,
53
+ name: cleanedName,
54
+ size: obj.size,
55
+ mimeType,
56
+ uploadedAt: obj.uploaded ? new Date(obj.uploaded).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
57
+ url,
58
+ etag: obj.etag,
59
+ customMetadata: obj.customMetadata
60
+ };
61
+ }),
62
+ truncated: result.truncated,
63
+ cursor: result.cursor
64
+ };
65
+ }
66
+ /**
67
+ * Uploads a file buffer/stream into R2 Bucket.
68
+ */
69
+ async function uploadR2File(bucket, file, options = {}) {
70
+ if (!bucket || typeof bucket.put !== "function") throw new Error("R2 bucket binding is not available in runtime environment.");
71
+ const prefix = options.prefix ?? "media/";
72
+ const timestamp = Date.now();
73
+ const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, "_");
74
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
75
+ const key = options.key ?? `${prefix}${timestamp}-${randomSuffix}/${safeName}`;
76
+ const arrayBuffer = await file.arrayBuffer();
77
+ const r2Object = await bucket.put(key, arrayBuffer, {
78
+ httpMetadata: {
79
+ contentType: file.type || guessMimeType(safeName),
80
+ contentDisposition: `inline; filename="${safeName}"`
81
+ },
82
+ customMetadata: {
83
+ originalName: file.name,
84
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
85
+ }
86
+ });
87
+ let url = "";
88
+ if (options.publicUrl) url = `${options.publicUrl.endsWith("/") ? options.publicUrl.slice(0, -1) : options.publicUrl}/${key}`;
89
+ else if (options.apiBasePath) url = `${options.apiBasePath.endsWith("/") ? options.apiBasePath.slice(0, -1) : options.apiBasePath}/${key}`;
90
+ else url = `/cms/api/media/${key}`;
91
+ return {
92
+ key,
93
+ name: safeName,
94
+ size: file.size,
95
+ mimeType: file.type || guessMimeType(safeName),
96
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
97
+ url,
98
+ etag: r2Object?.etag
99
+ };
100
+ }
101
+ /**
102
+ * Deletes a file from R2 Bucket by key.
103
+ */
104
+ async function deleteR2File(bucket, key) {
105
+ if (!bucket || typeof bucket.delete !== "function") throw new Error("R2 bucket binding is not available in runtime environment.");
106
+ await bucket.delete(key);
107
+ return true;
108
+ }
109
+ /**
110
+ * MIME type guesser fallback.
111
+ */
112
+ function guessMimeType(filename) {
113
+ switch (filename.split(".").pop()?.toLowerCase()) {
114
+ case "jpg":
115
+ case "jpeg": return "image/jpeg";
116
+ case "png": return "image/png";
117
+ case "webp": return "image/webp";
118
+ case "gif": return "image/gif";
119
+ case "svg": return "image/svg+xml";
120
+ case "avif": return "image/avif";
121
+ case "mp4": return "video/mp4";
122
+ case "webm": return "video/webm";
123
+ case "mp3": return "audio/mpeg";
124
+ case "wav": return "audio/wav";
125
+ case "pdf": return "application/pdf";
126
+ case "json": return "application/json";
127
+ case "txt": return "text/plain";
128
+ case "zip": return "application/zip";
129
+ default: return "application/octet-stream";
130
+ }
131
+ }
132
+ //#endregion
133
+ export { deleteR2File, getR2Bucket, listR2Files, r2, uploadR2File };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/cms",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "description": "Rimelight Entertainment's Content Management System Package",
6
6
  "homepage": "https://rimelight.com/docs",
@@ -16,12 +16,31 @@
16
16
  "url": "git+https://github.com/Rimelight-Entertainment/rimelight.git"
17
17
  },
18
18
  "files": [
19
+ "dist",
19
20
  "src"
20
21
  ],
21
22
  "type": "module",
22
23
  "exports": {
23
- ".": "./src/index.ts",
24
- "./schema": "./src/schema/index.ts",
24
+ ".": {
25
+ "types": "./dist/index.d.mts",
26
+ "import": "./dist/index.mjs"
27
+ },
28
+ "./schema": {
29
+ "types": "./dist/schema/index.d.mts",
30
+ "import": "./dist/schema/index.mjs"
31
+ },
32
+ "./schema/sqlite": {
33
+ "types": "./dist/schema/sqlite.d.mts",
34
+ "import": "./dist/schema/sqlite.mjs"
35
+ },
36
+ "./integration": {
37
+ "types": "./dist/integration.d.mts",
38
+ "import": "./dist/integration.mjs"
39
+ },
40
+ "./storage": {
41
+ "types": "./dist/storage/index.d.mts",
42
+ "import": "./dist/storage/index.mjs"
43
+ },
25
44
  "./core/*": "./src/core/*",
26
45
  "./astro/*": "./src/astro/*",
27
46
  "./client/styles/*": "./src/client/styles/*",
@@ -33,37 +52,36 @@
33
52
  "./client/*": "./src/client/*.ts",
34
53
  "./auth/*": "./src/auth/*",
35
54
  "./cache/*": "./src/cache/*",
36
- "./search/*": "./src/search/*"
55
+ "./search/*": "./src/search/*",
56
+ "./admin/*": "./src/admin/*"
37
57
  },
38
58
  "publishConfig": {
39
59
  "access": "public"
40
60
  },
41
- "scripts": {
42
- "check": "pnpm audit --audit-level=moderate && vp check --fix && astro check"
43
- },
44
61
  "dependencies": {
45
- "@rimelight/i18n": "0.0.5",
46
- "@rimelight/ui": "0.0.43",
62
+ "@rimelight/auth": "0.0.3",
63
+ "@rimelight/i18n": "0.0.7",
64
+ "@rimelight/ui": "0.0.46",
47
65
  "drizzle-orm": "0.45.2",
48
66
  "solid-js": "1.9.15",
49
67
  "sortablejs": "1.15.7"
50
68
  },
51
69
  "devDependencies": {
52
70
  "@astrojs/check": "0.9.10",
53
- "@rimelight/config": "0.0.1",
71
+ "@rimelight/config": "0.0.4",
54
72
  "@types/sortablejs": "1.15.9",
55
- "astro": "7.2.2",
56
- "better-auth": "1.6.30",
73
+ "astro": "7.3.1",
57
74
  "typescript": "6.0.3",
58
- "unocss": "66.7.5",
59
- "vitest": "4.1.10"
75
+ "unocss": "66.10.0"
60
76
  },
61
77
  "peerDependencies": {
62
- "astro": ">=7.0.0",
63
- "better-auth": ">=1.0.0"
78
+ "astro": ">=7.0.0"
64
79
  },
65
80
  "engines": {
66
81
  "node": ">=26.7.0"
67
82
  },
68
- "packageManager": "pnpm@11.22.0"
69
- }
83
+ "scripts": {
84
+ "build": "vp pack",
85
+ "check": "vp check --fix && astro check"
86
+ }
87
+ }
@@ -0,0 +1,39 @@
1
+ import type { APIRoute } from "astro"
2
+ import { getR2Bucket } from "../../storage/index"
3
+ // @ts-ignore
4
+ import { storageConfig } from "virtual:rimelight-cms/storage"
5
+
6
+ export const prerender = false
7
+
8
+ export const GET: APIRoute = async ({ params, locals }) => {
9
+ try {
10
+ const bindingName = storageConfig?.binding ?? "BLOB"
11
+ const bucket = await getR2Bucket(locals, bindingName)
12
+
13
+ if (!bucket) {
14
+ return new Response("Storage binding not found.", { status: 500 })
15
+ }
16
+
17
+ const key = params.key as string
18
+ if (!key) {
19
+ return new Response("Missing file key.", { status: 400 })
20
+ }
21
+
22
+ const object = await bucket.get(key)
23
+ if (!object) {
24
+ return new Response("File not found in R2 storage.", { status: 404 })
25
+ }
26
+
27
+ const headers = new Headers()
28
+ object.writeHttpMetadata(headers)
29
+ headers.set("etag", object.httpEtag)
30
+ headers.set("Cache-Control", "public, max-age=31536000, immutable")
31
+
32
+ return new Response(object.body, {
33
+ status: 200,
34
+ headers
35
+ })
36
+ } catch (error: any) {
37
+ return new Response(error.message || "Failed to retrieve file", { status: 500 })
38
+ }
39
+ }