@kyro-cms/admin 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -51
- package/package.json +53 -6
- package/src/collections/auth/index.ts +2 -2
- package/src/collections/portfolio/index.ts +343 -0
- package/src/components/ActionBar.tsx +153 -16
- package/src/components/Admin.tsx +136 -27
- package/src/components/ApiExplorer.tsx +325 -0
- package/src/components/ApiKeysManager.tsx +563 -0
- package/src/components/AuditLogsPage.tsx +664 -0
- package/src/components/AutoForm.tsx +1417 -661
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +3 -3
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +199 -57
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +786 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +191 -53
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +149 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- package/src/components/UserManagement.tsx +204 -0
- package/src/components/VersionHistoryPanel.tsx +3 -3
- package/src/components/WebhookManager.tsx +608 -0
- package/src/components/blocks/AccordionBlock.tsx +97 -0
- package/src/components/blocks/ArrayBlock.tsx +75 -0
- package/src/components/blocks/BlockEditModal.MARKER +12 -0
- package/src/components/blocks/BlockEditModal.tsx +774 -0
- package/src/components/blocks/ButtonBlock.tsx +165 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +66 -0
- package/src/components/blocks/ColumnsBlock.tsx +151 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +64 -0
- package/src/components/blocks/HeadingBlock.tsx +81 -0
- package/src/components/blocks/HeroBlock.tsx +157 -0
- package/src/components/blocks/ImageBlock.tsx +83 -0
- package/src/components/blocks/LinkBlock.tsx +71 -0
- package/src/components/blocks/ListBlock.tsx +39 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +279 -0
- package/src/components/blocks/VStackBlock.tsx +75 -0
- package/src/components/blocks/VideoBlock.tsx +45 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/CheckboxField.tsx +15 -9
- package/src/components/fields/CodeField.tsx +234 -0
- package/src/components/fields/DateField.tsx +38 -11
- package/src/components/fields/EditorClient.tsx +271 -0
- package/src/components/fields/FileField.tsx +390 -0
- package/src/components/fields/HybridContentField.tsx +109 -0
- package/src/components/fields/ImageField.tsx +429 -0
- package/src/components/fields/JSONField.tsx +361 -0
- package/src/components/fields/MarkdownField.tsx +282 -0
- package/src/components/fields/NumberField.tsx +42 -12
- package/src/components/fields/PortableTextField.tsx +143 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipField.tsx +231 -59
- package/src/components/fields/SelectField.tsx +25 -15
- package/src/components/fields/TextField.tsx +45 -14
- package/src/components/fields/extensions/blockComponents.tsx +237 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +13 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +2 -2
- package/src/components/ui/Badge.tsx +9 -4
- package/src/components/ui/BlockDrawer.tsx +79 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/CommandPalette.tsx +362 -0
- package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
- package/src/components/ui/Dropdown.tsx +1 -1
- package/src/components/ui/Modal.tsx +37 -12
- package/src/components/ui/PromptModal.tsx +94 -0
- package/src/components/ui/SlidePanel.tsx +43 -16
- package/src/components/ui/Toast.tsx +80 -14
- package/src/env.d.ts +16 -0
- package/src/env.ts +20 -0
- package/src/index.ts +0 -1
- package/src/layouts/AdminLayout.astro +164 -170
- package/src/layouts/AuthLayout.astro +23 -6
- package/src/lib/MediaService.ts +541 -0
- package/src/lib/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +22 -6
- package/src/lib/dataStore.ts +132 -74
- package/src/lib/db/adapter.ts +54 -0
- package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
- package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
- package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
- package/src/lib/db/index.ts +449 -0
- package/src/lib/db/mongodb-adapter.ts +207 -0
- package/src/lib/db/mongodb-auth-adapter.ts +305 -0
- package/src/lib/db/schema/mysql-auth.ts +113 -0
- package/src/lib/db/schema/mysql-content.ts +20 -0
- package/src/lib/db/schema/postgres-auth.ts +116 -0
- package/src/lib/db/schema/postgres-content.ts +35 -0
- package/src/lib/db/schema/postgres-media.ts +52 -0
- package/src/lib/db/schema/postgres-settings.ts +11 -0
- package/src/lib/db/schema/sqlite-auth.ts +112 -0
- package/src/lib/db/schema/sqlite-content.ts +20 -0
- package/src/lib/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -0
- package/src/middleware.ts +70 -11
- package/src/pages/[collection]/[id].astro +178 -122
- package/src/pages/[collection]/index.astro +24 -156
- package/src/pages/admin/api-explorer.astro +98 -0
- package/src/pages/admin/graphql-explorer.astro +40 -0
- package/src/pages/admin/graphql.astro +97 -0
- package/src/pages/admin/index.astro +200 -139
- package/src/pages/admin/keys.astro +8 -0
- package/src/pages/admin/rest-playground.astro +44 -0
- package/src/pages/admin/webhooks.astro +8 -0
- package/src/pages/api/[collection]/[id]/publish.ts +44 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +36 -0
- package/src/pages/api/[collection]/[id].ts +102 -159
- package/src/pages/api/[collection]/index.ts +151 -230
- package/src/pages/api/auth/[id].ts +48 -69
- package/src/pages/api/auth/audit-logs.ts +20 -43
- package/src/pages/api/auth/login.ts +159 -45
- package/src/pages/api/auth/logout.ts +42 -24
- package/src/pages/api/auth/refresh.ts +119 -0
- package/src/pages/api/auth/register.ts +110 -40
- package/src/pages/api/auth/users.ts +22 -97
- package/src/pages/api/collections.ts +59 -0
- package/src/pages/api/globals/[slug]/test.ts +172 -0
- package/src/pages/api/globals/[slug].ts +42 -0
- package/src/pages/api/graphql.ts +90 -0
- package/src/pages/api/health.ts +417 -40
- package/src/pages/api/keys/[id].ts +26 -0
- package/src/pages/api/keys/index.ts +75 -0
- package/src/pages/api/media/[id].ts +309 -0
- package/src/pages/api/media/folders.ts +609 -0
- package/src/pages/api/media/index.ts +146 -0
- package/src/pages/api/media/resize.ts +267 -0
- package/src/pages/api/search.ts +82 -0
- package/src/pages/api/slug-availability.ts +70 -0
- package/src/pages/api/storage-config.ts +20 -0
- package/src/pages/api/storage-status.ts +206 -0
- package/src/pages/api/upload.ts +334 -0
- package/src/pages/api/webhooks/index.ts +71 -0
- package/src/pages/audit/index.astro +2 -104
- package/src/pages/login.astro +11 -11
- package/src/pages/media.astro +10 -0
- package/src/pages/preview/[collection]/[id].astro +178 -0
- package/src/pages/register.astro +13 -13
- package/src/pages/roles/index.astro +21 -21
- package/src/pages/settings/[slug].astro +162 -0
- package/src/pages/settings/index.astro +9 -0
- package/src/pages/users/[id].astro +29 -21
- package/src/pages/users/index.astro +22 -17
- package/src/pages/users/new.astro +18 -17
- package/src/styles/main.css +553 -128
- 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-[
|
|
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-
|
|
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-
|
|
26
|
-
<button class="flex items-center gap-3 px-8 py-3 bg-[
|
|
27
|
-
<
|
|
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
|
-
<!--
|
|
33
|
+
<!-- Quick Links Section -->
|
|
42
34
|
<div class="surface-tile overflow-hidden">
|
|
43
|
-
<div class="flex items-center justify-between p-
|
|
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-
|
|
46
|
-
|
|
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-[
|
|
49
|
-
|
|
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-
|
|
61
|
-
{
|
|
62
|
-
<a href={`/${
|
|
63
|
-
<div class="flex items-center gap-
|
|
64
|
-
<div class=
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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-
|
|
104
|
-
<h2 class="text-
|
|
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-[
|
|
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="/
|
|
113
|
-
<div class="w-10 h-10 rounded-lg bg-orange-
|
|
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-[
|
|
119
|
-
<p class="text-xs text-[
|
|
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-
|
|
123
|
-
<div class="w-10 h-10 rounded-lg bg-red-
|
|
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-[
|
|
129
|
-
<p class="text-xs text-[
|
|
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-
|
|
133
|
-
<div class="w-10 h-10 rounded-lg bg-indigo-
|
|
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-[
|
|
139
|
-
<p class="text-xs text-[
|
|
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-
|
|
143
|
-
<div class="w-10 h-10 rounded-lg bg-green-
|
|
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-[
|
|
149
|
-
<p class="text-xs text-[
|
|
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
|
-
<!--
|
|
155
|
-
<div class="surface-tile
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
<
|
|
205
|
-
|
|
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
|
-
|
|
211
|
-
<div class="
|
|
212
|
-
<div class="
|
|
213
|
-
<
|
|
214
|
-
<
|
|
215
|
-
|
|
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
|
-
<
|
|
219
|
-
|
|
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
|
-
</
|
|
221
|
+
</div>
|
|
223
222
|
</div>
|
|
224
|
-
|
|
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,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,44 @@
|
|
|
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(collection, id, {
|
|
29
|
+
status: "published",
|
|
30
|
+
publishedAt: now,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return new Response(JSON.stringify({ success: true, data: updated }), {
|
|
34
|
+
status: 200,
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
});
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("Publish error:", error);
|
|
39
|
+
return new Response(JSON.stringify({ error: "Failed to publish" }), {
|
|
40
|
+
status: 500,
|
|
41
|
+
headers: { "Content-Type": "application/json" },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { APIRoute } from "astro";
|
|
2
|
+
import { dataStore } from "@/lib/dataStore";
|
|
3
|
+
|
|
4
|
+
export const GET: APIRoute = async ({ params }) => {
|
|
5
|
+
const { collection, id } = params;
|
|
6
|
+
if (!collection || !id) return new Response(null, { status: 400 });
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const versions = await dataStore.findVersions(collection, id);
|
|
10
|
+
return new Response(JSON.stringify({ docs: versions }), {
|
|
11
|
+
status: 200,
|
|
12
|
+
headers: { "Content-Type": "application/json" }
|
|
13
|
+
});
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return new Response(JSON.stringify({ error: "Failed to fetch versions" }), { status: 500 });
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const POST: APIRoute = async ({ params, request }) => {
|
|
20
|
+
const { collection, id } = params;
|
|
21
|
+
if (!collection || !id) return new Response(null, { status: 400 });
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const { versionId, action } = await request.json();
|
|
25
|
+
|
|
26
|
+
if (action === 'restore' && versionId) {
|
|
27
|
+
const restored = await dataStore.restoreVersion(collection, id, versionId);
|
|
28
|
+
if (!restored) return new Response(JSON.stringify({ error: "Restore failed" }), { status: 400 });
|
|
29
|
+
return new Response(JSON.stringify({ data: restored }), { status: 200 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new Response(JSON.stringify({ error: "Invalid action" }), { status: 400 });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return new Response(JSON.stringify({ error: "Failed to perform version action" }), { status: 500 });
|
|
35
|
+
}
|
|
36
|
+
};
|