@kyro-cms/admin 0.1.6 → 0.1.8

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 (179) hide show
  1. package/README.md +149 -51
  2. package/package.json +54 -5
  3. package/src/collections/auth/index.ts +2 -2
  4. package/src/collections/portfolio/index.ts +343 -0
  5. package/src/components/ActionBar.tsx +153 -16
  6. package/src/components/Admin.tsx +137 -28
  7. package/src/components/ApiExplorer.tsx +325 -0
  8. package/src/components/ApiKeysManager.tsx +563 -0
  9. package/src/components/AuditLogsPage.tsx +664 -0
  10. package/src/components/AutoForm.tsx +2155 -770
  11. package/src/components/BrandingHub.tsx +267 -0
  12. package/src/components/BulkActionsBar.tsx +3 -3
  13. package/src/components/CreateView.tsx +4 -4
  14. package/src/components/Dashboard.tsx +393 -0
  15. package/src/components/DetailView.tsx +200 -58
  16. package/src/components/DeveloperCenter.tsx +403 -0
  17. package/src/components/EnhancedListView.tsx +890 -0
  18. package/src/components/GraphQLExplorer.tsx +675 -0
  19. package/src/components/GraphQLPlayground.tsx +627 -0
  20. package/src/components/ListView.tsx +192 -54
  21. package/src/components/MediaGallery.tsx +1569 -0
  22. package/src/components/Modal.tsx +206 -0
  23. package/src/components/RestPlayground.tsx +951 -0
  24. package/src/components/Sidebar.astro +237 -0
  25. package/src/components/ThemeProvider.tsx +8 -2
  26. package/src/components/UserManagement.tsx +204 -0
  27. package/src/components/VersionHistoryPanel.tsx +3 -3
  28. package/src/components/WebhookManager.tsx +608 -0
  29. package/src/components/blocks/AccordionBlock.tsx +65 -0
  30. package/src/components/blocks/ArrayBlock.tsx +84 -0
  31. package/src/components/blocks/BlockEditModal.tsx +363 -0
  32. package/src/components/blocks/ButtonBlock.tsx +64 -0
  33. package/src/components/blocks/ChildBlocksTree.tsx +551 -0
  34. package/src/components/blocks/CodeBlock.tsx +114 -0
  35. package/src/components/blocks/ColumnsBlock.tsx +93 -0
  36. package/src/components/blocks/DividerBlock.tsx +43 -0
  37. package/src/components/blocks/FileBlock.tsx +63 -0
  38. package/src/components/blocks/HeadingBlock.tsx +59 -0
  39. package/src/components/blocks/HeroBlock.tsx +99 -0
  40. package/src/components/blocks/ImageBlock.tsx +82 -0
  41. package/src/components/blocks/LinkBlock.tsx +65 -0
  42. package/src/components/blocks/ListBlock.tsx +60 -0
  43. package/src/components/blocks/ParagraphBlock.tsx +61 -0
  44. package/src/components/blocks/RelationshipBlock.tsx +72 -0
  45. package/src/components/blocks/RichTextBlock.tsx +66 -0
  46. package/src/components/blocks/VStackBlock.tsx +61 -0
  47. package/src/components/blocks/VideoBlock.tsx +65 -0
  48. package/src/components/blocks/index.ts +10 -0
  49. package/src/components/fields/AccordionField.tsx +213 -0
  50. package/src/components/fields/ArrayField.tsx +241 -0
  51. package/src/components/fields/BlocksField.tsx +323 -0
  52. package/src/components/fields/ButtonField.tsx +53 -0
  53. package/src/components/fields/CheckboxField.tsx +18 -8
  54. package/src/components/fields/ChildrenField.tsx +48 -0
  55. package/src/components/fields/CodeField.tsx +294 -0
  56. package/src/components/fields/ColumnsField.tsx +137 -0
  57. package/src/components/fields/DateField.tsx +24 -12
  58. package/src/components/fields/EditorClient.tsx +537 -0
  59. package/src/components/fields/HeadingField.tsx +31 -0
  60. package/src/components/fields/HeroField.tsx +101 -0
  61. package/src/components/fields/JSONField.tsx +341 -0
  62. package/src/components/fields/LinkField.tsx +81 -0
  63. package/src/components/fields/ListField.tsx +74 -0
  64. package/src/components/fields/MarkdownField.tsx +260 -0
  65. package/src/components/fields/NumberField.tsx +25 -13
  66. package/src/components/fields/PortableTextField.tsx +155 -0
  67. package/src/components/fields/PortableTextRenderer.tsx +68 -0
  68. package/src/components/fields/RelationshipBlockField.tsx +233 -0
  69. package/src/components/fields/RelationshipField.tsx +278 -60
  70. package/src/components/fields/SelectField.tsx +28 -16
  71. package/src/components/fields/TextField.tsx +31 -15
  72. package/src/components/fields/UploadField.tsx +613 -0
  73. package/src/components/fields/VideoField.tsx +73 -0
  74. package/src/components/fields/extensions/blockComponents.tsx +247 -0
  75. package/src/components/fields/extensions/blocksStore.ts +273 -0
  76. package/src/components/fields/index.ts +24 -0
  77. package/src/components/index.ts +1 -2
  78. package/src/components/layout/Header.tsx +2 -2
  79. package/src/components/layout/Layout.tsx +3 -3
  80. package/src/components/ui/Badge.tsx +9 -4
  81. package/src/components/ui/BlockDrawer.tsx +79 -0
  82. package/src/components/ui/Button.tsx +1 -1
  83. package/src/components/ui/CommandPalette.tsx +362 -0
  84. package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
  85. package/src/components/ui/Dropdown.tsx +1 -1
  86. package/src/components/ui/Modal.tsx +37 -12
  87. package/src/components/ui/PromptModal.tsx +94 -0
  88. package/src/components/ui/SlidePanel.tsx +43 -16
  89. package/src/components/ui/Toast.tsx +80 -14
  90. package/src/env.d.ts +16 -0
  91. package/src/env.ts +20 -0
  92. package/src/index.ts +0 -1
  93. package/src/layouts/AdminLayout.astro +164 -170
  94. package/src/layouts/AuthLayout.astro +23 -6
  95. package/src/lib/MediaService.ts +541 -0
  96. package/src/lib/api.ts +163 -0
  97. package/src/lib/auth/sqlite-adapter.ts +319 -0
  98. package/src/lib/config.ts +23 -7
  99. package/src/lib/dataStore.ts +188 -73
  100. package/src/lib/date-utils.ts +69 -0
  101. package/src/lib/db/adapter.ts +54 -0
  102. package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
  103. package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
  104. package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
  105. package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
  106. package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
  107. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
  108. package/src/lib/db/index.ts +449 -0
  109. package/src/lib/db/mongodb-adapter.ts +207 -0
  110. package/src/lib/db/mongodb-auth-adapter.ts +305 -0
  111. package/src/lib/db/schema/mysql-auth.ts +113 -0
  112. package/src/lib/db/schema/mysql-content.ts +20 -0
  113. package/src/lib/db/schema/postgres-auth.ts +116 -0
  114. package/src/lib/db/schema/postgres-content.ts +35 -0
  115. package/src/lib/db/schema/postgres-media.ts +52 -0
  116. package/src/lib/db/schema/postgres-settings.ts +11 -0
  117. package/src/lib/db/schema/sqlite-auth.ts +112 -0
  118. package/src/lib/db/schema/sqlite-content.ts +20 -0
  119. package/src/lib/db/version-adapter.ts +248 -0
  120. package/src/lib/graphql/index.ts +1 -0
  121. package/src/lib/graphql/schema.ts +443 -0
  122. package/src/lib/i18n.tsx +353 -0
  123. package/src/lib/rate-limit.ts +267 -0
  124. package/src/lib/slugify.ts +15 -0
  125. package/src/lib/storage.ts +374 -0
  126. package/src/lib/store.ts +85 -0
  127. package/src/lib/validation.ts +250 -0
  128. package/src/middleware.ts +70 -11
  129. package/src/pages/[collection]/[id].astro +178 -122
  130. package/src/pages/[collection]/index.astro +24 -156
  131. package/src/pages/admin/api-explorer.astro +98 -0
  132. package/src/pages/admin/graphql-explorer.astro +40 -0
  133. package/src/pages/admin/graphql.astro +97 -0
  134. package/src/pages/admin/index.astro +200 -139
  135. package/src/pages/admin/keys.astro +8 -0
  136. package/src/pages/admin/rest-playground.astro +44 -0
  137. package/src/pages/admin/webhooks.astro +8 -0
  138. package/src/pages/api/[collection]/[id]/publish.ts +52 -0
  139. package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
  140. package/src/pages/api/[collection]/[id]/versions.ts +66 -0
  141. package/src/pages/api/[collection]/[id].ts +114 -159
  142. package/src/pages/api/[collection]/index.ts +150 -230
  143. package/src/pages/api/auth/[id].ts +48 -69
  144. package/src/pages/api/auth/audit-logs.ts +20 -43
  145. package/src/pages/api/auth/login.ts +159 -45
  146. package/src/pages/api/auth/logout.ts +42 -24
  147. package/src/pages/api/auth/refresh.ts +119 -0
  148. package/src/pages/api/auth/register.ts +110 -40
  149. package/src/pages/api/auth/users.ts +22 -97
  150. package/src/pages/api/collections.ts +59 -0
  151. package/src/pages/api/globals/[slug]/test.ts +172 -0
  152. package/src/pages/api/globals/[slug].ts +42 -0
  153. package/src/pages/api/graphql.ts +90 -0
  154. package/src/pages/api/health.ts +417 -40
  155. package/src/pages/api/keys/[id].ts +26 -0
  156. package/src/pages/api/keys/index.ts +75 -0
  157. package/src/pages/api/media/[id].ts +309 -0
  158. package/src/pages/api/media/folders.ts +609 -0
  159. package/src/pages/api/media/index.ts +146 -0
  160. package/src/pages/api/media/resize.ts +267 -0
  161. package/src/pages/api/search.ts +82 -0
  162. package/src/pages/api/slug-availability.ts +70 -0
  163. package/src/pages/api/storage-config.ts +20 -0
  164. package/src/pages/api/storage-status.ts +206 -0
  165. package/src/pages/api/upload.ts +334 -0
  166. package/src/pages/api/webhooks/index.ts +71 -0
  167. package/src/pages/audit/index.astro +2 -104
  168. package/src/pages/login.astro +11 -11
  169. package/src/pages/media.astro +10 -0
  170. package/src/pages/preview/[collection]/[id].astro +178 -0
  171. package/src/pages/register.astro +13 -13
  172. package/src/pages/roles/index.astro +21 -21
  173. package/src/pages/settings/[slug].astro +162 -0
  174. package/src/pages/settings/index.astro +9 -0
  175. package/src/pages/users/[id].astro +29 -21
  176. package/src/pages/users/index.astro +22 -17
  177. package/src/pages/users/new.astro +18 -17
  178. package/src/styles/main.css +563 -128
  179. package/src/components/layout/Sidebar.tsx +0 -497
@@ -15,83 +15,44 @@ const authItems = authCollections.map(slug => ({
15
15
  <!-- Header -->
16
16
  <div class="surface-tile p-6 flex items-center justify-between gap-8">
17
17
  <div class="relative flex-1 max-w-2xl">
18
- <div class="absolute inset-y-0 left-6 flex items-center pointer-events-none text-[#64748b]">
18
+ <div class="absolute inset-y-0 left-6 flex items-center pointer-events-none text-[var(--kyro-text-muted)]">
19
19
  <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
20
20
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
21
21
  </svg>
22
22
  </div>
23
- <input type="text" placeholder="Search anything..." class="w-full bg-gray-50/50 border border-transparent rounded-2xl py-4 pl-16 pr-8 text-lg font-medium focus:outline-none focus:bg-white focus:border-gray-100 transition-all shadow-inner" />
23
+ <input type="text" id="header-search-input" placeholder="Search anything..." class="w-full bg-[var(--kyro-surface-accent)] border border-transparent rounded-2xl py-4 pl-16 pr-8 text-lg font-medium focus:outline-none focus:bg-[var(--kyro-surface)] focus:border-[var(--kyro-border)] transition-all shadow-inner text-[var(--kyro-text-primary)] placeholder-[var(--kyro-text-muted)]" autocomplete="off" />
24
+ <div id="header-search-results" class="hidden absolute top-full left-0 right-0 mt-2 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl shadow-xl max-h-80 overflow-y-auto z-50"></div>
24
25
  </div>
25
- <div class="flex p-1.5 bg-gray-50/50 rounded-2xl">
26
- <button class="flex items-center gap-3 px-8 py-3 bg-[#0b1222] text-white rounded-xl font-bold shadow-lg transition-all active:scale-95">
27
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
28
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path>
29
- </svg>
30
- <span>Card</span>
31
- </button>
32
- <button class="flex items-center gap-3 px-8 py-3 text-[#64748b] rounded-xl font-bold hover:bg-white/50 transition-all">
33
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
34
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M4 6h16M4 12h16M4 18h16"></path>
35
- </svg>
36
- <span>List</span>
26
+ <div class="flex p-1.5 bg-[var(--kyro-surface-accent)] rounded-2xl">
27
+ <button id="cmd-k-btn" class="flex items-center gap-3 px-8 py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl font-bold shadow-lg transition-all active:scale-95 cursor-pointer">
28
+ <span>⌘ K</span>
37
29
  </button>
38
30
  </div>
39
31
  </div>
40
32
 
41
- <!-- Auth Management Section -->
33
+ <!-- Quick Links Section -->
42
34
  <div class="surface-tile overflow-hidden">
43
- <div class="flex items-center justify-between p-12 border-b border-gray-50/50">
35
+ <div class="flex items-center justify-between p-6 border-b border-[var(--kyro-border)]">
44
36
  <div class="flex-1">
45
- <h2 class="text-5xl font-black tracking-tighter text-[#0b1222]">
46
- Authentication
37
+ <h2 class="text-xl font-black tracking-tight text-[var(--kyro-text-primary)] flex items-center gap-2">
38
+ Quick Links
47
39
  </h2>
48
- <p class="text-[#64748b] font-bold mt-4 uppercase tracking-[0.2em] text-sm">
49
- Manage users, roles, and security
40
+ <p class="text-xs text-[var(--kyro-text-secondary)] font-medium mt-1">
41
+ Create new collection documents
50
42
  </p>
51
43
  </div>
52
- <a href="/users/new" class="flex items-center gap-2 px-6 py-3 bg-[#0b1222] text-white rounded-xl font-bold transition-all hover:bg-[#1a2332] active:scale-95">
53
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
54
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"></path>
55
- </svg>
56
- Add User
57
- </a>
58
44
  </div>
59
45
 
60
- <div class="grid grid-cols-1 md:grid-cols-3 gap-6 p-6">
61
- {authItems.map((item) => (
62
- <a href={`/${item.slug}`} class="group p-6 bg-gray-50/30 rounded-2xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50">
63
- <div class="flex items-center gap-4 mb-4">
64
- <div class={`w-12 h-12 rounded-xl flex items-center justify-center ${
65
- item.slug === 'users' ? 'bg-blue-50 text-blue-500' :
66
- item.slug === 'roles' ? 'bg-purple-50 text-purple-500' :
67
- 'bg-green-50 text-green-500'
68
- }`}>
69
- {item.icon === 'users' && (
70
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
71
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path>
72
- </svg>
73
- )}
74
- {item.icon === 'shield' && (
75
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
76
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
77
- </svg>
78
- )}
79
- {item.icon === 'file-text' && (
80
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
81
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
82
- </svg>
83
- )}
84
- </div>
85
- <div>
86
- <h3 class="text-xl font-black text-[#0b1222] tracking-tighter">{item.label}</h3>
87
- <p class="text-sm text-[#64748b] font-medium mt-1">Manage {item.slug.replace('_', ' ')}</p>
46
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 p-4">
47
+ {Object.entries(collections).filter(([slug]) => !['users', 'roles', 'audit_logs', 'media'].includes(slug)).slice(0, 6).map(([slug, config]: [string, any]) => (
48
+ <a href={`/${slug}/new`} class="group p-4 bg-[var(--kyro-surface-accent)] rounded-xl hover:bg-[var(--kyro-surface)] transition-all border border-transparent hover:border-[var(--kyro-border)] shadow-sm hover:shadow-md">
49
+ <div class="flex items-center gap-3">
50
+ <div class="w-8 h-8 rounded-lg flex items-center justify-center bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-secondary)] group-hover:bg-[var(--kyro-surface-accent)] group-hover:text-[var(--kyro-primary)] transition-all">
51
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14M5 12h14"></path>
53
+ </svg>
88
54
  </div>
89
- </div>
90
- <div class="flex items-center gap-2 text-[#64748b] font-bold text-sm group-hover:text-[#0b1222] transition-colors">
91
- <span>View all</span>
92
- <svg class="w-4 h-4 transform group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
93
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
94
- </svg>
55
+ <span class="text-sm font-bold text-[var(--kyro-text-primary)]">New {config.label || slug}</span>
95
56
  </div>
96
57
  </a>
97
58
  ))}
@@ -100,126 +61,226 @@ const authItems = authCollections.map(slug => ({
100
61
 
101
62
  <!-- Security Quick Actions -->
102
63
  <div class="surface-tile overflow-hidden">
103
- <div class="p-8 border-b border-gray-50/50">
104
- <h2 class="text-3xl font-black tracking-tighter text-[#0b1222]">
64
+ <div class="p-6 border-b border-[var(--kyro-border)]">
65
+ <h2 class="text-xl font-black tracking-tight text-[var(--kyro-text-primary)]">
105
66
  Security & Monitoring
106
67
  </h2>
107
- <p class="text-[#64748b] font-bold mt-2 text-sm">
68
+ <p class="text-xs text-[var(--kyro-text-secondary)] font-medium mt-1">
108
69
  Rate limiting, audit logs, and account lockout settings
109
70
  </p>
110
71
  </div>
111
72
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 p-6">
112
- <a href="/audit_logs" class="p-5 bg-gray-50/30 rounded-xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50 group">
113
- <div class="w-10 h-10 rounded-lg bg-orange-50 text-orange-500 flex items-center justify-center mb-3">
73
+ <a href="/audit" class="p-5 bg-[var(--kyro-surface-accent)] rounded-xl hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)] group">
74
+ <div class="w-10 h-10 rounded-lg bg-orange-500/10 text-orange-500 flex items-center justify-center mb-3">
114
75
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
115
76
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
116
77
  </svg>
117
78
  </div>
118
- <h4 class="font-bold text-[#0b1222] mb-1">Audit Logs</h4>
119
- <p class="text-xs text-[#64748b]">View last 30 days</p>
79
+ <h4 class="font-bold text-[var(--kyro-text-primary)] mb-1">Audit Logs</h4>
80
+ <p class="text-xs text-[var(--kyro-text-secondary)]">View last 30 days</p>
120
81
  </a>
121
82
 
122
- <a href="/users?locked=true" class="p-5 bg-gray-50/30 rounded-xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50 group">
123
- <div class="w-10 h-10 rounded-lg bg-red-50 text-red-500 flex items-center justify-center mb-3">
83
+ <a href="/users?locked=true" class="p-5 bg-[var(--kyro-surface-accent)] rounded-xl hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)] group">
84
+ <div class="w-10 h-10 rounded-lg bg-red-500/10 text-red-500 flex items-center justify-center mb-3">
124
85
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
125
86
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
126
87
  </svg>
127
88
  </div>
128
- <h4 class="font-bold text-[#0b1222] mb-1">Locked Accounts</h4>
129
- <p class="text-xs text-[#64748b]">Manage lockouts</p>
89
+ <h4 class="font-bold text-[var(--kyro-text-primary)] mb-1">Locked Accounts</h4>
90
+ <p class="text-xs text-[var(--kyro-text-secondary)]">Manage lockouts</p>
130
91
  </a>
131
92
 
132
- <a href="/roles" class="p-5 bg-gray-50/30 rounded-xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50 group">
133
- <div class="w-10 h-10 rounded-lg bg-indigo-50 text-indigo-500 flex items-center justify-center mb-3">
93
+ <a href="/roles" class="p-5 bg-[var(--kyro-surface-accent)] rounded-xl hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)] group">
94
+ <div class="w-10 h-10 rounded-lg bg-indigo-500/10 text-indigo-500 flex items-center justify-center mb-3">
134
95
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
135
96
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
136
97
  </svg>
137
98
  </div>
138
- <h4 class="font-bold text-[#0b1222] mb-1">Permissions</h4>
139
- <p class="text-xs text-[#64748b]">RBAC settings</p>
99
+ <h4 class="font-bold text-[var(--kyro-text-primary)] mb-1">Permissions</h4>
100
+ <p class="text-xs text-[var(--kyro-text-secondary)]">RBAC settings</p>
140
101
  </a>
141
102
 
142
- <a href="/api/health" target="_blank" class="p-5 bg-gray-50/30 rounded-xl hover:bg-gray-50/50 transition-all border border-transparent hover:border-gray-100/50 group">
143
- <div class="w-10 h-10 rounded-lg bg-green-50 text-green-500 flex items-center justify-center mb-3">
103
+ <a href="/api/health" target="_blank" class="p-5 bg-[var(--kyro-surface-accent)] rounded-xl hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)] group">
104
+ <div class="w-10 h-10 rounded-lg bg-green-500/10 text-green-500 flex items-center justify-center mb-3">
144
105
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
145
106
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
146
107
  </svg>
147
108
  </div>
148
- <h4 class="font-bold text-[#0b1222] mb-1">API Health</h4>
149
- <p class="text-xs text-[#64748b]">System status</p>
109
+ <h4 class="font-bold text-[var(--kyro-text-primary)] mb-1">API Health</h4>
110
+ <p class="text-xs text-[var(--kyro-text-secondary)]">System status</p>
150
111
  </a>
151
112
  </div>
152
113
  </div>
153
114
 
154
- <!-- Environment Info -->
155
- <div class="surface-tile p-6">
156
- <h3 class="text-xl font-black text-[#0b1222] tracking-tighter mb-4">Configuration</h3>
157
- <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
158
- <div class="bg-gray-50/50 rounded-xl p-4">
159
- <div class="text-xs text-[#64748b] font-bold uppercase tracking-wider mb-1">Database</div>
160
- <div class="text-sm font-bold text-[#0b1222]">PostgreSQL</div>
161
- </div>
162
- <div class="bg-gray-50/50 rounded-xl p-4">
163
- <div class="text-xs text-[#64748b] font-bold uppercase tracking-wider mb-1">Cache/Sessions</div>
164
- <div class="text-sm font-bold text-[#0b1222]">Redis</div>
165
- </div>
166
- <div class="bg-gray-50/50 rounded-xl p-4">
167
- <div class="text-xs text-[#64748b] font-bold uppercase tracking-wider mb-1">Auth Method</div>
168
- <div class="text-sm font-bold text-[#0b1222]">JWT + Redis</div>
169
- </div>
170
- <div class="bg-gray-50/50 rounded-xl p-4">
171
- <div class="text-xs text-[#64748b] font-bold uppercase tracking-wider mb-1">Email</div>
172
- <div class="text-sm font-bold text-[#0b1222]">Nodemailer</div>
173
- </div>
115
+ <!-- Developer Tools Section -->
116
+ <div class="surface-tile overflow-hidden">
117
+ <div class="p-6 border-b border-[var(--kyro-border)]">
118
+ <h2 class="text-xl font-black tracking-tight text-[var(--kyro-text-primary)]">
119
+ Developer Tools
120
+ </h2>
121
+ <p class="text-xs text-[var(--kyro-text-secondary)] font-medium mt-1">
122
+ REST API & GraphQL testing tools
123
+ </p>
174
124
  </div>
175
- <p class="text-xs text-[#64748b] mt-4 font-medium">
176
- Configure via <code class="bg-gray-100 px-2 py-0.5 rounded text-[#0b1222]">.env</code> file
177
- </p>
178
- </div>
179
125
 
180
- <!-- Quick Links -->
181
- <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
182
- <a href="/api/health" target="_blank" class="surface-tile group p-6 hover:shadow-2xl transition-all hover:translate-y-[-4px]">
183
- <div class="flex items-center gap-4">
184
- <div class="w-14 h-14 rounded-2xl bg-green-50 flex items-center justify-center text-green-500 group-hover:scale-110 transition-transform">
185
- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
186
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
187
- </svg>
188
- </div>
189
- <div>
190
- <h4 class="text-lg font-bold text-[#0b1222]">API Health</h4>
191
- <p class="text-sm text-[#64748b]">Check system status</p>
126
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 p-8">
127
+ <!-- REST API -->
128
+ <div class="bg-[var(--kyro-surface-accent)] rounded-2xl p-6">
129
+ <div class="flex items-center gap-3 mb-6">
130
+ <div class="w-10 h-10 rounded-xl bg-pink-500/10 flex items-center justify-center text-pink-500">
131
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
132
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
133
+ </svg>
134
+ </div>
135
+ <div>
136
+ <h3 class="text-xl font-black text-[var(--kyro-text-primary)]">REST API</h3>
137
+ <p class="text-xs text-[var(--kyro-text-secondary)]">HTTP endpoints</p>
138
+ </div>
192
139
  </div>
193
- </div>
194
- </a>
195
-
196
- <a href="/graphql" target="_blank" class="surface-tile group p-6 hover:shadow-2xl transition-all hover:translate-y-[-4px]">
197
- <div class="flex items-center gap-4">
198
- <div class="w-14 h-14 rounded-2xl bg-pink-50 flex items-center justify-center text-pink-500 group-hover:scale-110 transition-transform">
199
- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
200
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
201
- </svg>
140
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
141
+ <a href="/admin/api-explorer" class="group p-4 bg-[var(--kyro-surface)] rounded-xl hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)]">
142
+ <div class="flex items-center gap-3 mb-2">
143
+ <div class="w-8 h-8 rounded-lg bg-green-500/10 flex items-center justify-center text-green-500 group-hover:scale-110 transition-transform">
144
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
145
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
146
+ </svg>
147
+ </div>
148
+ <h4 class="font-bold text-[var(--kyro-text-primary)]">Explorer</h4>
149
+ </div>
150
+ <p class="text-xs text-[var(--kyro-text-secondary)]">Test endpoints interactively</p>
151
+ </a>
152
+ <a href="/admin/rest-playground" class="group p-4 bg-[var(--kyro-surface)] rounded-xl hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)]">
153
+ <div class="flex items-center gap-3 mb-2">
154
+ <div class="w-8 h-8 rounded-lg bg-blue-500/10 flex items-center justify-center text-blue-500 group-hover:scale-110 transition-transform">
155
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
156
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
157
+ </svg>
158
+ </div>
159
+ <h4 class="font-bold text-[var(--kyro-text-primary)]">Playground</h4>
160
+ </div>
161
+ <p class="text-xs text-[var(--kyro-text-secondary)]">Saved requests & history</p>
162
+ </a>
202
163
  </div>
203
- <div>
204
- <h4 class="text-lg font-bold text-[#0b1222]">GraphQL</h4>
205
- <p class="text-sm text-[#64748b]">API Playground</p>
164
+ <div class="mt-4 pt-4 border-t border-[var(--kyro-border)]">
165
+ <a href="/api/collections" target="_blank" class="flex items-center gap-2 text-xs text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-secondary)] transition-colors">
166
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
167
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
168
+ </svg>
169
+ View Collections JSON
170
+ </a>
206
171
  </div>
207
172
  </div>
208
- </a>
209
173
 
210
- <a href="/api/collections" target="_blank" class="surface-tile group p-6 hover:shadow-2xl transition-all hover:translate-y-[-4px]">
211
- <div class="flex items-center gap-4">
212
- <div class="w-14 h-14 rounded-2xl bg-blue-50 flex items-center justify-center text-blue-500 group-hover:scale-110 transition-transform">
213
- <svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
214
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"></path>
215
- </svg>
174
+ <!-- GraphQL -->
175
+ <div class="bg-[var(--kyro-surface-accent)] rounded-2xl p-6">
176
+ <div class="flex items-center gap-3 mb-6">
177
+ <div class="w-10 h-10 rounded-xl bg-pink-500/10 flex items-center justify-center text-pink-500">
178
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
179
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
180
+ </svg>
181
+ </div>
182
+ <div>
183
+ <h3 class="text-xl font-black text-[var(--kyro-text-primary)]">GraphQL</h3>
184
+ <p class="text-xs text-[var(--kyro-text-secondary)]">Query language API</p>
185
+ </div>
216
186
  </div>
217
- <div>
218
- <h4 class="text-lg font-bold text-[#0b1222]">REST API</h4>
219
- <p class="text-sm text-[#64748b]">Collections endpoint</p>
187
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
188
+ <a href="/admin/graphql" class="group p-4 bg-[var(--kyro-surface)] rounded-xl hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)]">
189
+ <div class="flex items-center gap-3 mb-2">
190
+ <div class="w-8 h-8 rounded-lg bg-purple-500/10 flex items-center justify-center text-purple-500 group-hover:scale-110 transition-transform">
191
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
192
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path>
193
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
194
+ </svg>
195
+ </div>
196
+ <h4 class="font-bold text-[var(--kyro-text-primary)]">Playground</h4>
197
+ </div>
198
+ <p class="text-xs text-[var(--kyro-text-secondary)]">Write & test queries</p>
199
+ </a>
200
+ <a href="/admin/graphql-explorer" class="group p-4 bg-[var(--kyro-surface)] rounded-xl hover:bg-[var(--kyro-surface-accent)] transition-all border border-transparent hover:border-[var(--kyro-border)]">
201
+ <div class="flex items-center gap-3 mb-2">
202
+ <div class="w-8 h-8 rounded-lg bg-orange-500/10 flex items-center justify-center text-orange-500 group-hover:scale-110 transition-transform">
203
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
204
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path>
205
+ </svg>
206
+ </div>
207
+ <h4 class="font-bold text-[var(--kyro-text-primary)]">Explorer</h4>
208
+ </div>
209
+ <p class="text-xs text-[var(--kyro-text-secondary)]">Schema documentation</p>
210
+ </a>
211
+ </div>
212
+ <div class="mt-4 pt-4 border-t border-[var(--kyro-border)]">
213
+ <a href="/api/graphql" target="_blank" class="flex items-center gap-2 text-xs text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-secondary)] transition-colors">
214
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
215
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
216
+ </svg>
217
+ View GraphQL Endpoint
218
+ </a>
220
219
  </div>
221
220
  </div>
222
- </a>
221
+ </div>
223
222
  </div>
224
- </div>
223
+
224
+ <script is:inline>
225
+ (function() {
226
+ var searchInput = document.getElementById('header-search-input');
227
+ var searchResults = document.getElementById('header-search-results');
228
+ if (!searchInput || !searchResults) return;
229
+
230
+ var debounceTimer = null;
231
+
232
+ function renderResults(results) {
233
+ var html = '';
234
+ if (!results || results.length === 0) {
235
+ html = '<div class="p-4 text-center text-[var(--kyro-text-secondary)] opacity-60">No documents found</div>';
236
+ } else {
237
+ for (var i = 0; i < Math.min(results.length, 6); i++) {
238
+ var r = results[i];
239
+ html += '<a href="/' + r.collection + '/' + r.id + '" class="flex items-center justify-between px-4 py-3 hover:bg-[var(--kyro-surface-accent)] transition-colors">';
240
+ html += '<div class="flex flex-col"><span class="font-bold text-sm text-[var(--kyro-text-primary)]">' + (r.title || 'Untitled') + '</span>';
241
+ html += '<span class="text-[10px] font-black uppercase tracking-widest opacity-40">' + r.label + '</span></div>';
242
+ html += '<span class="text-xs text-[var(--kyro-text-muted)]">View</span></a>';
243
+ }
244
+ }
245
+ searchResults.innerHTML = html;
246
+ searchResults.classList.remove('hidden');
247
+ }
248
+
249
+ function hideResults() {
250
+ searchResults.classList.add('hidden');
251
+ }
252
+
253
+ searchInput.addEventListener('input', function(e) {
254
+ var query = e.target.value.trim();
255
+ if (query.length < 2) {
256
+ searchResults.classList.add('hidden');
257
+ return;
258
+ }
259
+ if (debounceTimer) clearTimeout(debounceTimer);
260
+ debounceTimer = setTimeout(function() {
261
+ var xhr = new XMLHttpRequest();
262
+ xhr.open('GET', '/api/search?q=' + encodeURIComponent(query) + '&limit=10', true);
263
+ xhr.onload = function() {
264
+ if (xhr.status === 200) {
265
+ try {
266
+ var data = JSON.parse(xhr.responseText);
267
+ if (data.results) renderResults(data.results);
268
+ } catch(err) { console.error(err); }
269
+ }
270
+ };
271
+ xhr.send();
272
+ }, 300);
273
+ });
274
+
275
+ searchInput.addEventListener('blur', function() {
276
+ setTimeout(hideResults, 200);
277
+ });
278
+
279
+ document.addEventListener('click', function(e) {
280
+ if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) {
281
+ hideResults();
282
+ }
283
+ });
284
+ })();
285
+ </script>
225
286
  </AdminLayout>
@@ -0,0 +1,8 @@
1
+ ---
2
+ import AdminLayout from "../../layouts/AdminLayout.astro";
3
+ import { ApiKeysManager } from "../../components/ApiKeysManager";
4
+ ---
5
+
6
+ <AdminLayout title="API Keys">
7
+ <ApiKeysManager client:only="react" />
8
+ </AdminLayout>
@@ -0,0 +1,44 @@
1
+ ---
2
+ import AdminLayout from '../../layouts/AdminLayout.astro';
3
+ import { RestPlayground } from '../../components/RestPlayground';
4
+
5
+ const collectionsResponse = await fetch(`${Astro.url.origin}/api/collections`);
6
+ const collectionsData = await collectionsResponse.json();
7
+ const collections = collectionsData.collections || [];
8
+ ---
9
+
10
+ <AdminLayout title="REST Playground">
11
+ <div class="flex-1 overflow-hidden p-8 pr-12">
12
+ <!-- Header -->
13
+ <div class="mb-6">
14
+ <div class="flex items-center justify-between mb-4">
15
+ <div>
16
+ <h1 class="text-3xl font-black tracking-tighter text-[var(--kyro-text-primary)]">
17
+ REST Playground
18
+ </h1>
19
+ <p class="text-[var(--kyro-text-secondary)] font-bold mt-2 text-sm uppercase tracking-wider">
20
+ Saved collections, history, and environment variables
21
+ </p>
22
+ </div>
23
+ <div class="flex items-center gap-4">
24
+ <a
25
+ href="/admin/api-explorer"
26
+ class="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] rounded-lg font-bold text-sm hover:bg-[var(--kyro-surface)] transition-all border border-[var(--kyro-border)]"
27
+ >
28
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
29
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
30
+ </svg>
31
+ Explorer
32
+ </a>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Playground Container -->
38
+ <div class="h-[calc(100vh-200px)] overflow-hidden">
39
+ <div class="surface-tile h-full overflow-hidden">
40
+ <RestPlayground client:load collections={collections} />
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </AdminLayout>
@@ -0,0 +1,8 @@
1
+ ---
2
+ import AdminLayout from "../../layouts/AdminLayout.astro";
3
+ import { WebhookManager } from "../../components/WebhookManager";
4
+ ---
5
+
6
+ <AdminLayout title="Webhooks">
7
+ <WebhookManager client:only="react" />
8
+ </AdminLayout>
@@ -0,0 +1,52 @@
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 ({ params, request }) => {
8
+ const collection = params.collection as string;
9
+ const id = params.id as string;
10
+
11
+ if (!collection || !collections[collection]) {
12
+ return new Response(JSON.stringify({ error: "Invalid collection" }), {
13
+ status: 404,
14
+ headers: { "Content-Type": "application/json" },
15
+ });
16
+ }
17
+
18
+ try {
19
+ const doc = await dataStore.findById(collection, id);
20
+ if (!doc) {
21
+ return new Response(JSON.stringify({ error: "Document not found" }), {
22
+ status: 404,
23
+ headers: { "Content-Type": "application/json" },
24
+ });
25
+ }
26
+
27
+ const now = new Date().toISOString();
28
+ const updated = await dataStore.update(
29
+ collection,
30
+ id,
31
+ {
32
+ status: "published",
33
+ publishedAt: now,
34
+ },
35
+ {
36
+ versionStatus: "published",
37
+ changeDescription: "Published changes",
38
+ },
39
+ );
40
+
41
+ return new Response(JSON.stringify({ success: true, data: updated }), {
42
+ status: 200,
43
+ headers: { "Content-Type": "application/json" },
44
+ });
45
+ } catch (error) {
46
+ console.error("Publish error:", error);
47
+ return new Response(JSON.stringify({ error: "Failed to publish" }), {
48
+ status: 500,
49
+ headers: { "Content-Type": "application/json" },
50
+ });
51
+ }
52
+ };
@@ -0,0 +1,42 @@
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 ({ params, request }) => {
8
+ const collection = params.collection as string;
9
+ const id = params.id as string;
10
+
11
+ if (!collection || !collections[collection]) {
12
+ return new Response(JSON.stringify({ error: "Invalid collection" }), {
13
+ status: 404,
14
+ headers: { "Content-Type": "application/json" },
15
+ });
16
+ }
17
+
18
+ try {
19
+ const doc = await dataStore.findById(collection, id);
20
+ if (!doc) {
21
+ return new Response(JSON.stringify({ error: "Document not found" }), {
22
+ status: 404,
23
+ headers: { "Content-Type": "application/json" },
24
+ });
25
+ }
26
+
27
+ const updated = await dataStore.update(collection, id, {
28
+ status: "draft",
29
+ });
30
+
31
+ return new Response(JSON.stringify({ success: true, data: updated }), {
32
+ status: 200,
33
+ headers: { "Content-Type": "application/json" },
34
+ });
35
+ } catch (error) {
36
+ console.error("Unpublish error:", error);
37
+ return new Response(JSON.stringify({ error: "Failed to unpublish" }), {
38
+ status: 500,
39
+ headers: { "Content-Type": "application/json" },
40
+ });
41
+ }
42
+ };