@kyro-cms/admin 0.3.2 → 0.3.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.
Files changed (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -1,146 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { getMediaService, type MediaItem } from "../../../lib/MediaService";
3
- import { constructMediaUrl, getStorageConfig } from "../../../lib/storage";
4
-
5
- export const GET: APIRoute = async ({ url }) => {
6
- let mediaService: any = null;
7
- try {
8
- mediaService = await getMediaService();
9
-
10
- const page = parseInt(url.searchParams.get("page") || "1");
11
- const limit = parseInt(url.searchParams.get("limit") || "30");
12
- const search = url.searchParams.get("search") || "";
13
- const type = url.searchParams.get("type") || "";
14
- const folder = url.searchParams.get("folder") || "";
15
- const sortBy = url.searchParams.get("sortBy") || "createdAt";
16
- const sortDir = (url.searchParams.get("sortDir") || "desc") as
17
- | "asc"
18
- | "desc";
19
-
20
- const result = await mediaService.find({
21
- page,
22
- limit,
23
- search: search || undefined,
24
- type: type || undefined,
25
- folder: folder || undefined,
26
- sortBy,
27
- sortDir,
28
- });
29
-
30
- // Get storage config to determine provider type
31
- const storageConfig = await getStorageConfig();
32
- const isLocalStorage = storageConfig.provider === "local";
33
-
34
- // Compute URLs dynamically from storage settings
35
- const isCloudStorage = !isLocalStorage;
36
- const docs = await Promise.all(
37
- result.docs.map(async (doc: MediaItem) => {
38
- // For cloud storage, use the stored URL from DB directly
39
- // For local storage, construct URL from baseUrl + filename
40
- let mediaUrl: string;
41
- if (isCloudStorage) {
42
- // Cloudinary already stores full URL, use it directly from DB
43
- mediaUrl = doc.url || (await constructMediaUrl(doc.filename, null));
44
- } else {
45
- mediaUrl = await constructMediaUrl(doc.filename, doc.folder);
46
- }
47
-
48
- // For local storage, use resize API. For cloud storage, use direct URL
49
- let thumbnailUrl: string | undefined;
50
- if (doc.mimeType?.startsWith("image/")) {
51
- if (isLocalStorage) {
52
- // Local storage: use resize API
53
- thumbnailUrl = `/api/media/resize?url=${encodeURIComponent(mediaUrl)}&w=400&h=400`;
54
- } else {
55
- // Cloud storage: use the same URL
56
- thumbnailUrl = mediaUrl;
57
- }
58
- }
59
-
60
- return {
61
- id: doc.id,
62
- title: doc.title || doc.filename,
63
- filename: doc.filename,
64
- originalName: doc.originalName,
65
- url: mediaUrl,
66
- thumbnailUrl,
67
- type: doc.mimeType?.split("/")[0] || "other",
68
- mimeType: doc.mimeType,
69
- fileSize: doc.fileSize,
70
- folder: doc.folder,
71
- alt: doc.alt,
72
- caption: doc.caption,
73
- createdAt: doc.createdAt,
74
- updatedAt: doc.updatedAt,
75
- };
76
- }),
77
- );
78
-
79
- return new Response(
80
- JSON.stringify({
81
- docs,
82
- totalDocs: result.totalDocs,
83
- page: result.page,
84
- limit: result.limit,
85
- totalPages: result.totalPages,
86
- }),
87
- {
88
- status: 200,
89
- headers: { "Content-Type": "application/json" },
90
- },
91
- );
92
- } catch (error: any) {
93
- console.error("[media API] Error:", error?.message || error);
94
- return new Response(
95
- JSON.stringify({
96
- error: error?.message || "Failed to fetch media",
97
- docs: [],
98
- totalDocs: 0,
99
- }),
100
- {
101
- status: 500,
102
- headers: { "Content-Type": "application/json" },
103
- },
104
- );
105
- }
106
- };
107
-
108
- export const PATCH: APIRoute = async ({ request }) => {
109
- try {
110
- const mediaService = await getMediaService();
111
- const body = await request.json();
112
- const { id, ...data } = body;
113
-
114
- const updated = await mediaService.update(id, data);
115
-
116
- return new Response(JSON.stringify(updated), {
117
- status: 200,
118
- headers: { "Content-Type": "application/json" },
119
- });
120
- } catch (error: any) {
121
- return new Response(
122
- JSON.stringify({ error: error.message || "Failed to update media" }),
123
- { status: 500, headers: { "Content-Type": "application/json" } },
124
- );
125
- }
126
- };
127
-
128
- export const DELETE: APIRoute = async ({ request }) => {
129
- try {
130
- const mediaService = await getMediaService();
131
- const body = await request.json();
132
- const { id } = body;
133
-
134
- await mediaService.delete(id);
135
-
136
- return new Response(JSON.stringify({ success: true }), {
137
- status: 200,
138
- headers: { "Content-Type": "application/json" },
139
- });
140
- } catch (error: any) {
141
- return new Response(
142
- JSON.stringify({ error: error.message || "Failed to delete media" }),
143
- { status: 500, headers: { "Content-Type": "application/json" } },
144
- );
145
- }
146
- };
@@ -1,267 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import sharp from "sharp";
3
- import path from "path";
4
- import fs from "fs/promises";
5
- import fsSync from "fs";
6
- import https from "https";
7
- import { createHash } from "crypto";
8
- import { getStorageConfig } from "../../../lib/storage";
9
-
10
- // Cache configuration
11
- const CACHE_BASE = path.join(process.cwd(), ".cache", "kyro-media", "resize");
12
- const MAX_CACHE_SIZE = 100 * 1024 * 1024; // 100MB
13
- const MAX_CACHE_AGE_HOURS = 24 * 7; // 7 days
14
-
15
- // Ensure cache directory exists
16
- async function ensureCacheDir() {
17
- try {
18
- await fs.mkdir(CACHE_BASE, { recursive: true });
19
- } catch {}
20
- }
21
-
22
- // Generate cache key from URL and parameters
23
- function getCacheKey(
24
- imageUrl: string,
25
- width: number,
26
- height: number,
27
- quality: number,
28
- format: string,
29
- ): string {
30
- const input = `${imageUrl}-${width}-${height}-${quality}-${format}`;
31
- return createHash("md5").update(input).digest("hex") + `.${format}`;
32
- }
33
-
34
- // Get cache file path
35
- function getCachePath(key: string): string {
36
- return path.join(CACHE_BASE, key);
37
- }
38
-
39
- // Clean up old cache files when exceeding max size
40
- async function cleanCache(): Promise<void> {
41
- try {
42
- const entries = await fs.readdir(CACHE_BASE, { withFileTypes: true });
43
- const files = entries
44
- .filter((e) => e.isFile())
45
- .map((e) => ({
46
- name: e.name,
47
- path: path.join(CACHE_BASE, e.name),
48
- mtime: fsSync.statSync(path.join(CACHE_BASE, e.name)).mtime,
49
- size: fsSync.statSync(path.join(CACHE_BASE, e.name)).size,
50
- }))
51
- .sort((a, b) => a.mtime.getTime() - b.mtime.getTime()); // oldest first
52
-
53
- let totalSize = files.reduce((sum, f) => sum + f.size, 0);
54
- const maxSize = MAX_CACHE_SIZE;
55
-
56
- // Delete oldest files until under limit
57
- for (const file of files) {
58
- if (totalSize < maxSize) break;
59
- await fs.unlink(file.path);
60
- totalSize -= file.size;
61
- }
62
- } catch {}
63
- }
64
-
65
- // Check if cache is valid (auto-invalidation on source modification)
66
- async function isCacheValid(
67
- cachePath: string,
68
- sourcePath: string,
69
- ): Promise<boolean> {
70
- try {
71
- const cacheStat = await fs.stat(cachePath);
72
- const sourceStat = await fs.stat(sourcePath);
73
-
74
- // Cache invalid if source is newer than cache
75
- if (sourceStat.mtimeMs > cacheStat.mtimeMs) {
76
- return false;
77
- }
78
-
79
- // Also check cache age
80
- const cacheAgeHours = (Date.now() - cacheStat.mtimeMs) / (1000 * 60 * 60);
81
- if (cacheAgeHours > MAX_CACHE_AGE_HOURS) {
82
- return false;
83
- }
84
-
85
- return true;
86
- } catch {
87
- return false;
88
- }
89
- }
90
-
91
- // Get source file path for local files
92
- async function getSourcePath(
93
- imageUrl: string,
94
- uploadDir: string,
95
- baseUrl: string,
96
- ): Promise<string | null> {
97
- try {
98
- let relativePath = imageUrl;
99
- if (baseUrl !== "/" && imageUrl.startsWith(baseUrl)) {
100
- relativePath = imageUrl.slice(baseUrl.length);
101
- } else if (baseUrl === "/") {
102
- relativePath = imageUrl.slice(1);
103
- }
104
- return path.join(uploadDir, relativePath);
105
- } catch {
106
- return null;
107
- }
108
- }
109
-
110
- // HTTP agent for remote files
111
- const httpsAgent = new https.Agent({
112
- keepAlive: true,
113
- timeout: 30000,
114
- });
115
-
116
- // Fetch with custom agent
117
- async function fetchWithAgent(url: string): Promise<Buffer> {
118
- return new Promise((resolve, reject) => {
119
- const parsed = new URL(url);
120
- const options = {
121
- hostname: parsed.hostname,
122
- port: 443,
123
- path: parsed.pathname + parsed.search,
124
- method: "GET",
125
- agent: httpsAgent,
126
- };
127
-
128
- const req = https.request(options, (res) => {
129
- if (
130
- res.statusCode &&
131
- res.statusCode >= 300 &&
132
- res.statusCode < 400 &&
133
- res.headers.location
134
- ) {
135
- fetchWithAgent(res.headers.location).then(resolve).catch(reject);
136
- return;
137
- }
138
- if (!res.statusCode || res.statusCode >= 400) {
139
- reject(new Error(`HTTP ${res.statusCode}`));
140
- return;
141
- }
142
-
143
- const chunks: Buffer[] = [];
144
- res.on("data", (chunk) => chunks.push(chunk));
145
- res.on("end", () => resolve(Buffer.concat(chunks)));
146
- res.on("error", reject);
147
- });
148
-
149
- req.on("error", reject);
150
- req.end();
151
- });
152
- }
153
-
154
- export const GET: APIRoute = async ({ url }) => {
155
- const refresh = url.searchParams.get("refresh") === "true";
156
- let imageUrl = url.searchParams.get("url");
157
- const width = parseInt(url.searchParams.get("w") || "0");
158
- const height = parseInt(url.searchParams.get("h") || "0");
159
- const quality = parseInt(url.searchParams.get("q") || "80");
160
- const format = url.searchParams.get("f") || "webp";
161
-
162
- if (!imageUrl) return new Response("Missing URL", { status: 400 });
163
-
164
- // Prevent double-slash URLs
165
- if (!imageUrl.startsWith("http")) {
166
- imageUrl = imageUrl.replace(/^\/+/, "/");
167
- }
168
-
169
- const { uploadDir, baseUrl } = await getStorageConfig();
170
- const cacheKey = getCacheKey(imageUrl, width, height, quality, format);
171
- const cachePath = getCachePath(cacheKey);
172
-
173
- // Ensure cache directory exists
174
- await ensureCacheDir();
175
-
176
- try {
177
- let input: Buffer | null = null;
178
- let useCache = false;
179
-
180
- // Check cache first (if not refreshing)
181
- if (!refresh) {
182
- try {
183
- const sourcePath = await getSourcePath(imageUrl, uploadDir, baseUrl);
184
-
185
- if (sourcePath && (await isCacheValid(cachePath, sourcePath))) {
186
- // Use cache
187
- input = await fs.readFile(cachePath);
188
- useCache = true;
189
- console.log("[resize] Cache hit:", cacheKey);
190
- }
191
- } catch {}
192
- }
193
-
194
- // If not using cache, fetch and process
195
- if (input === null) {
196
- // Fetch original
197
- if (imageUrl.startsWith("/")) {
198
- // Local file
199
- let relativePath = imageUrl;
200
- if (baseUrl !== "/" && imageUrl.startsWith(baseUrl)) {
201
- relativePath = imageUrl.slice(baseUrl.length);
202
- } else if (baseUrl === "/") {
203
- relativePath = imageUrl.slice(1);
204
- }
205
-
206
- const localPath = path.join(uploadDir, relativePath);
207
- input = await fs.readFile(localPath);
208
- } else {
209
- // Remote file (S3/R2)
210
- input = await fetchWithAgent(imageUrl);
211
- }
212
-
213
- // Process
214
- let pipeline = sharp(input);
215
- if (width > 0 || height > 0) {
216
- pipeline = pipeline.resize(width || undefined, height || undefined, {
217
- fit: "cover",
218
- withoutEnlargement: true,
219
- });
220
- }
221
-
222
- if (format === "webp") pipeline = pipeline.webp({ quality });
223
- else if (format === "avif") pipeline = pipeline.avif({ quality });
224
- else if (format === "jpg" || format === "jpeg")
225
- pipeline = pipeline.jpeg({ quality });
226
-
227
- const output = await pipeline.toBuffer();
228
-
229
- // Write to cache
230
- await fs.writeFile(cachePath, output);
231
- console.log("[resize] Cached:", cacheKey);
232
-
233
- // Clean cache if needed
234
- await cleanCache();
235
-
236
- input = output;
237
- }
238
-
239
- if (!input) {
240
- throw new Error("Failed to process image");
241
- }
242
-
243
- return new Response(input as any, {
244
- headers: {
245
- "Content-Type": `image/${format}`,
246
- "Cache-Control": "public, max-age=86400", // 1 day browser cache
247
- },
248
- });
249
- } catch (error) {
250
- console.error("Resize error:", error);
251
-
252
- // Redirect to original on error
253
- if (imageUrl) {
254
- if (imageUrl.startsWith("http")) return Response.redirect(imageUrl, 302);
255
- try {
256
- const origin = url.origin;
257
- return Response.redirect(
258
- `${origin}${imageUrl.startsWith("/") ? "" : "/"}${imageUrl}`,
259
- 302,
260
- );
261
- } catch {
262
- return new Response("Error processing image", { status: 500 });
263
- }
264
- }
265
- return new Response("Error processing image", { status: 500 });
266
- }
267
- };
@@ -1,82 +0,0 @@
1
- import type { APIRoute } from "astro";
2
-
3
- export const prerender = false;
4
-
5
- export const GET: APIRoute = async ({ url }) => {
6
- const query = url.searchParams.get("q") || "";
7
- const limit = Math.min(parseInt(url.searchParams.get("limit") || "10"), 50);
8
-
9
- if (!query || query.length < 2) {
10
- return new Response(JSON.stringify({ results: [] }), {
11
- status: 200,
12
- headers: { "Content-Type": "application/json" },
13
- });
14
- }
15
-
16
- const regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
17
- const results: Array<{
18
- collection: string;
19
- label: string;
20
- id: string;
21
- title: string;
22
- }> = [];
23
-
24
- const demoResults = [
25
- {
26
- collection: "posts",
27
- label: "Posts",
28
- id: "1",
29
- title: "Welcome to Kyro CMS",
30
- },
31
- {
32
- collection: "posts",
33
- label: "Posts",
34
- id: "2",
35
- title: "Getting Started Guide",
36
- },
37
- {
38
- collection: "posts",
39
- label: "Posts",
40
- id: "3",
41
- title: "Advanced Features",
42
- },
43
- { collection: "pages", label: "Pages", id: "about", title: "About Us" },
44
- { collection: "pages", label: "Pages", id: "contact", title: "Contact" },
45
- {
46
- collection: "products",
47
- label: "Products",
48
- id: "prod-1",
49
- title: "Sample Product",
50
- },
51
- {
52
- collection: "products",
53
- label: "Products",
54
- id: "prod-2",
55
- title: "Premium Plan",
56
- },
57
- {
58
- collection: "categories",
59
- label: "Categories",
60
- id: "cat-1",
61
- title: "Technology",
62
- },
63
- {
64
- collection: "categories",
65
- label: "Categories",
66
- id: "cat-2",
67
- title: "Business",
68
- },
69
- ];
70
-
71
- for (const doc of demoResults) {
72
- if (regex.test(doc.title)) {
73
- results.push(doc);
74
- if (results.length >= limit) break;
75
- }
76
- }
77
-
78
- return new Response(JSON.stringify({ results: results.slice(0, limit) }), {
79
- status: 200,
80
- headers: { "Content-Type": "application/json" },
81
- });
82
- };
@@ -1,70 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { dataStore } from "../../lib/dataStore";
3
- import { collections } from "../../lib/config";
4
-
5
- dataStore.initialize(collections);
6
-
7
- export const POST: APIRoute = async ({ request }) => {
8
- try {
9
- const body = await request.json();
10
- const { collection, slug } = body;
11
-
12
- if (!collection || !slug) {
13
- return new Response(
14
- JSON.stringify({ error: "Collection and slug are required" }),
15
- {
16
- status: 400,
17
- headers: { "Content-Type": "application/json" },
18
- },
19
- );
20
- }
21
-
22
- if (!collections[collection]) {
23
- return new Response(JSON.stringify({ error: "Invalid collection" }), {
24
- status: 404,
25
- headers: { "Content-Type": "application/json" },
26
- });
27
- }
28
-
29
- // Check if slug exists in collection
30
- const existing = await dataStore.findOne(collection, { slug });
31
-
32
- if (existing) {
33
- // Generate a unique suggestion
34
- const baseSlug = slug.replace(/-[a-z0-9]+$/i, "");
35
- const suggested = `${baseSlug}-${Math.random().toString(36).slice(2, 6)}`;
36
-
37
- return new Response(
38
- JSON.stringify({
39
- available: false,
40
- slug,
41
- suggested,
42
- }),
43
- {
44
- status: 200,
45
- headers: { "Content-Type": "application/json" },
46
- },
47
- );
48
- }
49
-
50
- return new Response(
51
- JSON.stringify({
52
- available: true,
53
- slug,
54
- }),
55
- {
56
- status: 200,
57
- headers: { "Content-Type": "application/json" },
58
- },
59
- );
60
- } catch (error) {
61
- console.error("Slug availability check error:", error);
62
- return new Response(
63
- JSON.stringify({ error: "Failed to check slug availability" }),
64
- {
65
- status: 500,
66
- headers: { "Content-Type": "application/json" },
67
- },
68
- );
69
- }
70
- };
@@ -1,20 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { getStorageConfig } from "../../lib/storage";
3
-
4
- export const GET: APIRoute = async () => {
5
- try {
6
- const config = await getStorageConfig();
7
- return new Response(JSON.stringify(config), {
8
- status: 200,
9
- headers: { "Content-Type": "application/json" },
10
- });
11
- } catch (error) {
12
- return new Response(
13
- JSON.stringify({
14
- error: "Failed to get storage config",
15
- details: String(error),
16
- }),
17
- { status: 500, headers: { "Content-Type": "application/json" } },
18
- );
19
- }
20
- };