@jant/core 0.3.27 → 0.3.28
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/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +21 -15
- package/src/__tests__/helpers/app.ts +19 -3
- package/src/__tests__/helpers/db.ts +44 -0
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
- package/src/app.tsx +111 -174
- package/src/client.ts +13 -0
- package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
- package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
- package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
- package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
- package/src/db/schema.ts +24 -4
- package/src/i18n/locales/en.po +810 -385
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +733 -522
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +733 -522
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +7 -11
- package/src/index.ts +1 -1
- package/src/lib/__tests__/icons.test.ts +178 -0
- package/src/lib/__tests__/resolve-config.test.ts +184 -0
- package/src/lib/__tests__/schemas.test.ts +12 -6
- package/src/lib/__tests__/theme.test.ts +62 -0
- package/src/lib/__tests__/timezones.test.ts +1 -1
- package/src/lib/__tests__/url.test.ts +12 -0
- package/src/lib/__tests__/view.test.ts +1 -5
- package/src/lib/avatar-upload.ts +18 -10
- package/src/lib/collection-form-bridge.ts +52 -0
- package/src/lib/collections-reorder.ts +28 -0
- package/src/lib/compose-bridge.ts +251 -0
- package/src/lib/errors.ts +116 -0
- package/src/lib/excerpt.ts +1 -1
- package/src/lib/favicon.ts +3 -5
- package/src/lib/html.ts +22 -0
- package/src/lib/icon-catalog.ts +181 -0
- package/src/lib/icons.ts +202 -0
- package/src/lib/navigation.ts +18 -33
- package/src/lib/pagination.ts +3 -2
- package/src/lib/post-form-bridge.ts +136 -0
- package/src/lib/render.tsx +11 -4
- package/src/lib/resolve-config.ts +157 -0
- package/src/lib/schemas.ts +76 -12
- package/src/lib/settings-bridge.ts +139 -0
- package/src/lib/storage.ts +37 -16
- package/src/lib/theme.ts +5 -7
- package/src/lib/timeline.ts +4 -8
- package/src/lib/toast.ts +134 -0
- package/src/lib/upload.ts +71 -0
- package/src/lib/url.ts +9 -1
- package/src/lib/version.ts +16 -0
- package/src/lib/view.ts +9 -10
- package/src/middleware/__tests__/auth.test.ts +6 -28
- package/src/middleware/__tests__/onboarding.test.ts +1 -1
- package/src/middleware/auth.ts +6 -12
- package/src/middleware/config.ts +51 -0
- package/src/middleware/error-handler.ts +56 -0
- package/src/middleware/onboarding.ts +1 -1
- package/src/preset.css +6 -0
- package/src/routes/__tests__/compose.test.ts +104 -17
- package/src/routes/api/__tests__/collections.test.ts +93 -2
- package/src/routes/api/__tests__/posts.test.ts +2 -1
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/collections.ts +64 -68
- package/src/routes/api/nav-items.ts +21 -59
- package/src/routes/api/pages.ts +18 -46
- package/src/routes/api/posts.ts +64 -86
- package/src/routes/api/search.ts +6 -4
- package/src/routes/api/settings.ts +8 -24
- package/src/routes/api/upload.ts +55 -53
- package/src/routes/auth/__tests__/setup.test.ts +118 -0
- package/src/routes/auth/reset.tsx +17 -66
- package/src/routes/auth/setup.tsx +67 -11
- package/src/routes/auth/signin.tsx +44 -8
- package/src/routes/compose.tsx +194 -0
- package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
- package/src/routes/dash/__tests__/pages.test.ts +2 -2
- package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
- package/src/routes/dash/appearance.tsx +173 -0
- package/src/routes/dash/collections.tsx +80 -14
- package/src/routes/dash/index.tsx +12 -14
- package/src/routes/dash/media.tsx +46 -49
- package/src/routes/dash/pages.tsx +85 -37
- package/src/routes/dash/posts.tsx +60 -23
- package/src/routes/dash/redirects.tsx +43 -33
- package/src/routes/dash/settings.tsx +234 -214
- package/src/routes/feed/__tests__/rss.test.ts +7 -3
- package/src/routes/feed/rss.ts +11 -16
- package/src/routes/feed/sitemap.ts +15 -9
- package/src/routes/pages/__tests__/collections.test.ts +9 -8
- package/src/routes/pages/archive.tsx +2 -2
- package/src/routes/pages/collection.tsx +76 -9
- package/src/routes/pages/collections.tsx +3 -1
- package/src/routes/pages/featured.tsx +2 -2
- package/src/routes/pages/home.tsx +3 -3
- package/src/routes/pages/latest.tsx +2 -2
- package/src/routes/pages/page.tsx +2 -2
- package/src/routes/pages/post.tsx +2 -2
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +324 -34
- package/src/services/__tests__/media.test.ts +1 -1
- package/src/services/__tests__/page.test.ts +116 -1
- package/src/services/auth.ts +88 -0
- package/src/services/collection.ts +169 -30
- package/src/services/index.ts +8 -3
- package/src/services/media.ts +39 -12
- package/src/services/navigation.ts +17 -5
- package/src/services/page.ts +24 -4
- package/src/services/post.ts +87 -19
- package/src/services/search.ts +0 -1
- package/src/services/settings.ts +21 -13
- package/src/style.css +3 -0
- package/src/styles/components.css +42 -1
- package/src/styles/tokens.css +4 -0
- package/src/styles/ui.css +902 -73
- package/src/types/app-context.ts +25 -0
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +60 -23
- package/src/types/entities.ts +12 -2
- package/src/types/lingui-react-macro.d.ts +3 -3
- package/src/types/operations.ts +2 -4
- package/src/types/views.ts +1 -3
- package/src/ui/__tests__/font-themes.test.ts +27 -8
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
- package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
- package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
- package/src/ui/components/collection-types.ts +45 -0
- package/src/ui/components/compose-types.ts +75 -0
- package/src/ui/components/jant-collection-form.ts +512 -0
- package/src/ui/components/jant-compose-dialog.ts +494 -0
- package/src/ui/components/jant-compose-editor.ts +799 -0
- package/src/ui/components/jant-post-form.ts +290 -0
- package/src/ui/components/jant-settings-avatar.ts +231 -0
- package/src/ui/components/jant-settings-general.ts +436 -0
- package/src/ui/components/post-form-template.ts +260 -0
- package/src/ui/components/post-form-types.ts +87 -0
- package/src/ui/components/settings-types.ts +62 -0
- package/src/ui/compose/ComposeDialog.tsx +141 -385
- package/src/ui/compose/ComposePrompt.tsx +3 -3
- package/src/ui/dash/PostList.tsx +55 -61
- package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
- package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
- package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
- package/src/ui/dash/collections/CollectionForm.tsx +130 -117
- package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
- package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
- package/src/ui/dash/index.ts +1 -1
- package/src/ui/dash/posts/PostForm.tsx +248 -0
- package/src/ui/dash/settings/AccountContent.tsx +69 -80
- package/src/ui/dash/settings/GeneralContent.tsx +159 -478
- package/src/ui/dash/settings/SettingsNav.tsx +4 -4
- package/src/ui/font-themes.ts +115 -32
- package/src/ui/layouts/BaseLayout.tsx +49 -19
- package/src/ui/layouts/DashLayout.tsx +14 -9
- package/src/ui/layouts/SiteLayout.tsx +38 -23
- package/src/ui/pages/CollectionPage.tsx +12 -2
- package/src/ui/pages/CollectionsPage.tsx +27 -27
- package/src/ui/pages/HomePage.tsx +15 -6
- package/src/ui/pages/SearchPage.tsx +1 -2
- package/src/ui/shared/CollectionsSidebar.tsx +59 -0
- package/src/ui/shared/Pagination.tsx +2 -2
- package/dist/app.js +0 -267
- package/dist/auth.js +0 -39
- package/dist/client.js +0 -13
- package/dist/db/index.js +0 -10
- package/dist/db/schema.js +0 -224
- package/dist/i18n/Trans.js +0 -24
- package/dist/i18n/context.js +0 -58
- package/dist/i18n/detect.js +0 -26
- package/dist/i18n/i18n.js +0 -49
- package/dist/i18n/index.js +0 -44
- package/dist/i18n/locales/en.js +0 -1
- package/dist/i18n/locales/zh-Hans.js +0 -1
- package/dist/i18n/locales/zh-Hant.js +0 -1
- package/dist/i18n/locales.js +0 -13
- package/dist/i18n/middleware.js +0 -30
- package/dist/lib/avatar-upload.js +0 -134
- package/dist/lib/config.js +0 -143
- package/dist/lib/constants.js +0 -50
- package/dist/lib/excerpt.js +0 -76
- package/dist/lib/favicon.js +0 -102
- package/dist/lib/feed.js +0 -123
- package/dist/lib/image-processor.js +0 -187
- package/dist/lib/image.js +0 -97
- package/dist/lib/index.js +0 -7
- package/dist/lib/markdown.js +0 -83
- package/dist/lib/media-helpers.js +0 -49
- package/dist/lib/media-upload.js +0 -104
- package/dist/lib/nav-reorder.js +0 -27
- package/dist/lib/navigation.js +0 -79
- package/dist/lib/pagination.js +0 -44
- package/dist/lib/render.js +0 -53
- package/dist/lib/schemas.js +0 -174
- package/dist/lib/sqid.js +0 -72
- package/dist/lib/sse.js +0 -218
- package/dist/lib/storage.js +0 -164
- package/dist/lib/theme.js +0 -65
- package/dist/lib/time.js +0 -159
- package/dist/lib/timeline.js +0 -95
- package/dist/lib/timezones.js +0 -388
- package/dist/lib/url.js +0 -89
- package/dist/lib/view.js +0 -217
- package/dist/middleware/auth.js +0 -52
- package/dist/middleware/onboarding.js +0 -41
- package/dist/routes/api/collections.js +0 -124
- package/dist/routes/api/nav-items.js +0 -104
- package/dist/routes/api/pages.js +0 -91
- package/dist/routes/api/posts.js +0 -218
- package/dist/routes/api/search.js +0 -48
- package/dist/routes/api/settings.js +0 -68
- package/dist/routes/api/upload.js +0 -246
- package/dist/routes/auth/reset.js +0 -221
- package/dist/routes/auth/setup.js +0 -194
- package/dist/routes/auth/signin.js +0 -176
- package/dist/routes/compose.js +0 -48
- package/dist/routes/dash/collections.js +0 -115
- package/dist/routes/dash/index.js +0 -118
- package/dist/routes/dash/media.js +0 -106
- package/dist/routes/dash/pages.js +0 -294
- package/dist/routes/dash/posts.js +0 -244
- package/dist/routes/dash/redirects.js +0 -257
- package/dist/routes/dash/settings.js +0 -379
- package/dist/routes/feed/rss.js +0 -62
- package/dist/routes/feed/sitemap.js +0 -49
- package/dist/routes/pages/archive.js +0 -62
- package/dist/routes/pages/collection.js +0 -34
- package/dist/routes/pages/collections.js +0 -28
- package/dist/routes/pages/featured.js +0 -36
- package/dist/routes/pages/home.js +0 -64
- package/dist/routes/pages/latest.js +0 -45
- package/dist/routes/pages/page.js +0 -68
- package/dist/routes/pages/post.js +0 -44
- package/dist/routes/pages/search.js +0 -54
- package/dist/services/collection.js +0 -109
- package/dist/services/index.js +0 -24
- package/dist/services/media.js +0 -117
- package/dist/services/navigation.js +0 -91
- package/dist/services/page.js +0 -84
- package/dist/services/post.js +0 -229
- package/dist/services/redirect.js +0 -48
- package/dist/services/search.js +0 -67
- package/dist/services/settings.js +0 -68
- package/dist/types/bindings.js +0 -3
- package/dist/types/config.js +0 -147
- package/dist/types/constants.js +0 -27
- package/dist/types/entities.js +0 -3
- package/dist/types/lingui-react-macro.d.js +0 -9
- package/dist/types/operations.js +0 -3
- package/dist/types/props.js +0 -3
- package/dist/types/sortablejs.d.js +0 -5
- package/dist/types/views.js +0 -5
- package/dist/types.js +0 -11
- package/dist/ui/color-themes.js +0 -268
- package/dist/ui/compose/ComposeDialog.js +0 -467
- package/dist/ui/compose/ComposePrompt.js +0 -55
- package/dist/ui/dash/ActionButtons.js +0 -46
- package/dist/ui/dash/CrudPageHeader.js +0 -22
- package/dist/ui/dash/DangerZone.js +0 -36
- package/dist/ui/dash/FormatBadge.js +0 -27
- package/dist/ui/dash/ListItemRow.js +0 -21
- package/dist/ui/dash/PageForm.js +0 -195
- package/dist/ui/dash/PostForm.js +0 -395
- package/dist/ui/dash/PostList.js +0 -83
- package/dist/ui/dash/StatusBadge.js +0 -46
- package/dist/ui/dash/collections/CollectionForm.js +0 -152
- package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
- package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
- package/dist/ui/dash/index.js +0 -10
- package/dist/ui/dash/media/MediaListContent.js +0 -166
- package/dist/ui/dash/media/ViewMediaContent.js +0 -212
- package/dist/ui/dash/pages/LinkFormContent.js +0 -130
- package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
- package/dist/ui/dash/settings/AccountContent.js +0 -209
- package/dist/ui/dash/settings/AppearanceContent.js +0 -259
- package/dist/ui/dash/settings/GeneralContent.js +0 -536
- package/dist/ui/dash/settings/SettingsNav.js +0 -41
- package/dist/ui/feed/LinkCard.js +0 -72
- package/dist/ui/feed/NoteCard.js +0 -58
- package/dist/ui/feed/QuoteCard.js +0 -63
- package/dist/ui/feed/ThreadPreview.js +0 -48
- package/dist/ui/feed/TimelineFeed.js +0 -41
- package/dist/ui/feed/TimelineItem.js +0 -27
- package/dist/ui/font-themes.js +0 -36
- package/dist/ui/layouts/BaseLayout.js +0 -153
- package/dist/ui/layouts/DashLayout.js +0 -141
- package/dist/ui/layouts/SiteLayout.js +0 -169
- package/dist/ui/pages/ArchivePage.js +0 -143
- package/dist/ui/pages/CollectionPage.js +0 -70
- package/dist/ui/pages/CollectionsPage.js +0 -76
- package/dist/ui/pages/FeaturedPage.js +0 -24
- package/dist/ui/pages/HomePage.js +0 -24
- package/dist/ui/pages/PostPage.js +0 -55
- package/dist/ui/pages/SearchPage.js +0 -122
- package/dist/ui/pages/SinglePage.js +0 -23
- package/dist/ui/shared/EmptyState.js +0 -27
- package/dist/ui/shared/MediaGallery.js +0 -35
- package/dist/ui/shared/Pagination.js +0 -195
- package/dist/ui/shared/ThreadView.js +0 -108
- package/dist/ui/shared/index.js +0 -5
- package/dist/vendor/datastar.js +0 -1606
- package/src/lib/__tests__/config.test.ts +0 -192
- package/src/lib/config.ts +0 -167
- package/src/routes/compose.ts +0 -63
- package/src/ui/dash/PostForm.tsx +0 -360
- package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
package/dist/lib/view.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* View Model Conversions (v2)
|
|
3
|
-
*
|
|
4
|
-
* Transforms raw database models into render-ready View types.
|
|
5
|
-
* Theme components receive only View types -- no lib/ imports needed.
|
|
6
|
-
*/ import { encode } from "./sqid.js";
|
|
7
|
-
import { toISOString, formatDate, formatTime, formatRelativeTime } from "./time.js";
|
|
8
|
-
import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "./image.js";
|
|
9
|
-
import { getHtmlExcerpt } from "./excerpt.js";
|
|
10
|
-
/**
|
|
11
|
-
* Creates a MediaContext from Hono context environment variables.
|
|
12
|
-
*
|
|
13
|
-
* @param c - Hono context
|
|
14
|
-
* @returns MediaContext with env values
|
|
15
|
-
*/ export function createMediaContext(c) {
|
|
16
|
-
return {
|
|
17
|
-
r2PublicUrl: c.env.R2_PUBLIC_URL,
|
|
18
|
-
imageTransformUrl: c.env.IMAGE_TRANSFORM_URL,
|
|
19
|
-
s3PublicUrl: c.env.S3_PUBLIC_URL
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Media Conversions
|
|
24
|
-
// =============================================================================
|
|
25
|
-
/**
|
|
26
|
-
* Converts a raw Media record to a render-ready MediaView.
|
|
27
|
-
*
|
|
28
|
-
* @param media - Raw media record from database
|
|
29
|
-
* @param ctx - Media context with URL configuration
|
|
30
|
-
* @returns Render-ready MediaView with pre-computed URLs
|
|
31
|
-
*/ export function toMediaView(media, ctx) {
|
|
32
|
-
const publicUrl = getPublicUrlForProvider(media.provider, ctx.r2PublicUrl, ctx.s3PublicUrl);
|
|
33
|
-
const url = getMediaUrl(media.storageKey, publicUrl);
|
|
34
|
-
const thumbnailUrl = getImageUrl(url, ctx.imageTransformUrl, {
|
|
35
|
-
width: 400,
|
|
36
|
-
quality: 80,
|
|
37
|
-
format: "auto",
|
|
38
|
-
fit: "cover"
|
|
39
|
-
});
|
|
40
|
-
return {
|
|
41
|
-
id: media.id,
|
|
42
|
-
url,
|
|
43
|
-
thumbnailUrl,
|
|
44
|
-
mimeType: media.mimeType,
|
|
45
|
-
altText: media.alt ?? undefined,
|
|
46
|
-
width: media.width ?? undefined,
|
|
47
|
-
height: media.height ?? undefined,
|
|
48
|
-
size: media.size
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
// =============================================================================
|
|
52
|
-
// Post Conversions
|
|
53
|
-
// =============================================================================
|
|
54
|
-
/**
|
|
55
|
-
* Converts a PostWithMedia to a render-ready PostView.
|
|
56
|
-
*
|
|
57
|
-
* @param post - Post with media attachments from database
|
|
58
|
-
* @param _ctx - Media context with URL configuration
|
|
59
|
-
* @returns Render-ready PostView with pre-computed fields
|
|
60
|
-
*/ export function toPostView(post, _ctx) {
|
|
61
|
-
const permalink = post.path ? `/${post.path}` : `/p/${encode(post.id)}`;
|
|
62
|
-
// Pre-compute excerpt from raw body
|
|
63
|
-
let excerpt;
|
|
64
|
-
if (post.body) {
|
|
65
|
-
excerpt = post.body.length > 160 ? post.body.slice(0, 160) + "..." : post.body;
|
|
66
|
-
}
|
|
67
|
-
// Pre-compute HTML summary for article-style posts (with title)
|
|
68
|
-
let summaryHtml;
|
|
69
|
-
let summaryHasMore;
|
|
70
|
-
if (post.title && post.bodyHtml) {
|
|
71
|
-
const result = getHtmlExcerpt(post.bodyHtml);
|
|
72
|
-
summaryHtml = result.excerpt;
|
|
73
|
-
summaryHasMore = result.hasMore;
|
|
74
|
-
}
|
|
75
|
-
// Convert media attachments
|
|
76
|
-
const media = post.mediaAttachments.map((m)=>({
|
|
77
|
-
id: m.id,
|
|
78
|
-
url: m.url,
|
|
79
|
-
thumbnailUrl: m.previewUrl,
|
|
80
|
-
mimeType: m.mimeType,
|
|
81
|
-
altText: m.alt ?? undefined,
|
|
82
|
-
width: m.width ?? undefined,
|
|
83
|
-
height: m.height ?? undefined
|
|
84
|
-
}));
|
|
85
|
-
return {
|
|
86
|
-
id: post.id,
|
|
87
|
-
permalink,
|
|
88
|
-
path: post.path ?? undefined,
|
|
89
|
-
title: post.title ?? undefined,
|
|
90
|
-
bodyHtml: post.bodyHtml ?? undefined,
|
|
91
|
-
excerpt,
|
|
92
|
-
summaryHtml,
|
|
93
|
-
summaryHasMore,
|
|
94
|
-
url: post.url ?? undefined,
|
|
95
|
-
quoteText: post.quoteText ?? undefined,
|
|
96
|
-
format: post.format,
|
|
97
|
-
status: post.status,
|
|
98
|
-
featured: post.featured === 1,
|
|
99
|
-
pinned: post.pinned === 1,
|
|
100
|
-
rating: post.rating ?? undefined,
|
|
101
|
-
collectionId: post.collectionId ?? undefined,
|
|
102
|
-
publishedAt: toISOString(post.publishedAt),
|
|
103
|
-
publishedAtFormatted: formatDate(post.publishedAt),
|
|
104
|
-
publishedAtTime: formatTime(post.publishedAt),
|
|
105
|
-
publishedAtRelative: formatRelativeTime(post.publishedAt),
|
|
106
|
-
updatedAt: toISOString(post.updatedAt),
|
|
107
|
-
media,
|
|
108
|
-
replyToId: post.replyToId ?? undefined,
|
|
109
|
-
threadRootId: post.threadId ?? undefined,
|
|
110
|
-
body: post.body ?? undefined
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Batch converts PostWithMedia[] to PostView[].
|
|
115
|
-
*/ export function toPostViews(posts, ctx) {
|
|
116
|
-
return posts.map((p)=>toPostView(p, ctx));
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Converts a bare Post (no media) to a PostView with empty media array.
|
|
120
|
-
*/ export function toPostViewFromPost(post, ctx) {
|
|
121
|
-
return toPostView({
|
|
122
|
-
...post,
|
|
123
|
-
mediaAttachments: []
|
|
124
|
-
}, ctx);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Batch converts Post[] (no media) to PostView[].
|
|
128
|
-
*/ export function toPostViewsFromPosts(posts, ctx) {
|
|
129
|
-
return posts.map((p)=>toPostViewFromPost(p, ctx));
|
|
130
|
-
}
|
|
131
|
-
// =============================================================================
|
|
132
|
-
// Page Conversions
|
|
133
|
-
// =============================================================================
|
|
134
|
-
/**
|
|
135
|
-
* Converts a Page to a render-ready PageView.
|
|
136
|
-
*/ export function toPageView(page) {
|
|
137
|
-
return {
|
|
138
|
-
id: page.id,
|
|
139
|
-
slug: page.slug,
|
|
140
|
-
title: page.title ?? undefined,
|
|
141
|
-
bodyHtml: page.bodyHtml ?? undefined,
|
|
142
|
-
status: page.status,
|
|
143
|
-
createdAt: toISOString(page.createdAt),
|
|
144
|
-
updatedAt: toISOString(page.updatedAt)
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
// =============================================================================
|
|
148
|
-
// Navigation Conversions
|
|
149
|
-
// =============================================================================
|
|
150
|
-
/**
|
|
151
|
-
* Converts a NavItem to a NavItemView with pre-computed state.
|
|
152
|
-
*/ export function toNavItemView(item, currentPath) {
|
|
153
|
-
const isExternal = item.url.startsWith("http://") || item.url.startsWith("https://");
|
|
154
|
-
let isActive = false;
|
|
155
|
-
if (!isExternal) {
|
|
156
|
-
if (item.url === "/") {
|
|
157
|
-
isActive = currentPath === "/";
|
|
158
|
-
} else {
|
|
159
|
-
isActive = currentPath === item.url || currentPath.startsWith(item.url + "/");
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
id: item.id,
|
|
164
|
-
type: item.type,
|
|
165
|
-
label: item.label,
|
|
166
|
-
url: item.url,
|
|
167
|
-
pageId: item.pageId ?? undefined,
|
|
168
|
-
isActive,
|
|
169
|
-
isExternal
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Batch converts NavItem[] to NavItemView[].
|
|
174
|
-
*/ export function toNavItemViews(items, currentPath) {
|
|
175
|
-
return items.map((item)=>toNavItemView(item, currentPath));
|
|
176
|
-
}
|
|
177
|
-
// =============================================================================
|
|
178
|
-
// Search Result Conversions
|
|
179
|
-
// =============================================================================
|
|
180
|
-
/**
|
|
181
|
-
* Converts a SearchResult to a SearchResultView with PostView.
|
|
182
|
-
*/ export function toSearchResultView(result, ctx) {
|
|
183
|
-
return {
|
|
184
|
-
post: toPostViewFromPost(result.post, ctx),
|
|
185
|
-
rank: result.rank,
|
|
186
|
-
snippet: result.snippet
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Batch converts SearchResult[] to SearchResultView[].
|
|
191
|
-
*/ export function toSearchResultViews(results, ctx) {
|
|
192
|
-
return results.map((r)=>toSearchResultView(r, ctx));
|
|
193
|
-
}
|
|
194
|
-
// =============================================================================
|
|
195
|
-
// Archive Group Conversions
|
|
196
|
-
// =============================================================================
|
|
197
|
-
/**
|
|
198
|
-
* Converts a grouped post map to typed ArchiveGroup[].
|
|
199
|
-
*/ export function toArchiveGroups(grouped, ctx) {
|
|
200
|
-
const groups = [];
|
|
201
|
-
for (const [yearMonth, posts] of grouped){
|
|
202
|
-
const [year, month] = yearMonth.split("-");
|
|
203
|
-
if (!year || !month) continue;
|
|
204
|
-
const date = new Date(parseInt(year, 10), parseInt(month, 10) - 1);
|
|
205
|
-
const label = date.toLocaleDateString("en-US", {
|
|
206
|
-
year: "numeric",
|
|
207
|
-
month: "long"
|
|
208
|
-
});
|
|
209
|
-
groups.push({
|
|
210
|
-
year,
|
|
211
|
-
month,
|
|
212
|
-
label,
|
|
213
|
-
posts: toPostViewsFromPosts(posts, ctx)
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
return groups;
|
|
217
|
-
}
|
package/dist/middleware/auth.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authentication Middleware
|
|
3
|
-
*
|
|
4
|
-
* Protects routes by requiring authentication
|
|
5
|
-
*/ /**
|
|
6
|
-
* Middleware that requires authentication.
|
|
7
|
-
* Redirects to signin page if not authenticated.
|
|
8
|
-
*/ export function requireAuth(redirectTo = "/signin") {
|
|
9
|
-
return async (c, next)=>{
|
|
10
|
-
if (!c.var.auth) {
|
|
11
|
-
return c.redirect(redirectTo);
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
const session = await c.var.auth.api.getSession({
|
|
15
|
-
headers: c.req.raw.headers
|
|
16
|
-
});
|
|
17
|
-
if (!session?.user) {
|
|
18
|
-
return c.redirect(redirectTo);
|
|
19
|
-
}
|
|
20
|
-
await next();
|
|
21
|
-
} catch {
|
|
22
|
-
return c.redirect(redirectTo);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Middleware for API routes that requires authentication.
|
|
28
|
-
* Returns 401 if not authenticated.
|
|
29
|
-
*/ export function requireAuthApi() {
|
|
30
|
-
return async (c, next)=>{
|
|
31
|
-
if (!c.var.auth) {
|
|
32
|
-
return c.json({
|
|
33
|
-
error: "Authentication not configured"
|
|
34
|
-
}, 500);
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const session = await c.var.auth.api.getSession({
|
|
38
|
-
headers: c.req.raw.headers
|
|
39
|
-
});
|
|
40
|
-
if (!session?.user) {
|
|
41
|
-
return c.json({
|
|
42
|
-
error: "Unauthorized"
|
|
43
|
-
}, 401);
|
|
44
|
-
}
|
|
45
|
-
await next();
|
|
46
|
-
} catch {
|
|
47
|
-
return c.json({
|
|
48
|
-
error: "Unauthorized"
|
|
49
|
-
}, 401);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Onboarding Middleware
|
|
3
|
-
*
|
|
4
|
-
* Redirects key page routes to /setup if onboarding hasn't been completed.
|
|
5
|
-
* Uses an allowlist approach: only explicitly listed page routes are redirected,
|
|
6
|
-
* so static assets, API endpoints, feeds, and other resources always pass through.
|
|
7
|
-
* Caches the result in memory so the DB is only queried once per isolate lifetime.
|
|
8
|
-
*/ /** In-memory cache — persists across requests within a Worker isolate */ let onboardingComplete = false;
|
|
9
|
-
/**
|
|
10
|
-
* Middleware that redirects to /setup if onboarding is not complete.
|
|
11
|
-
* Uses module-level caching: once onboarding is confirmed complete,
|
|
12
|
-
* no further DB queries are made for the lifetime of the Worker isolate.
|
|
13
|
-
*/ export function requireOnboarding() {
|
|
14
|
-
return async (c, next)=>{
|
|
15
|
-
if (onboardingComplete) {
|
|
16
|
-
return next();
|
|
17
|
-
}
|
|
18
|
-
const path = new URL(c.req.url).pathname;
|
|
19
|
-
if (!shouldRedirect(path)) {
|
|
20
|
-
return next();
|
|
21
|
-
}
|
|
22
|
-
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
23
|
-
if (isComplete) {
|
|
24
|
-
onboardingComplete = true;
|
|
25
|
-
return next();
|
|
26
|
-
}
|
|
27
|
-
return c.redirect("/setup");
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Only these page routes are redirected to /setup during onboarding.
|
|
32
|
-
* Everything else (assets, API, feeds, media, etc.) passes through.
|
|
33
|
-
*/ function shouldRedirect(path) {
|
|
34
|
-
return path === "/" || path === "/signin" || path === "/reset" || path.startsWith("/dash");
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Reset the onboarding cache. Only for testing.
|
|
38
|
-
* @internal
|
|
39
|
-
*/ export function resetOnboardingCache() {
|
|
40
|
-
onboardingComplete = false;
|
|
41
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Collections API Routes
|
|
3
|
-
*/ import { Hono } from "hono";
|
|
4
|
-
import { requireAuthApi } from "../../middleware/auth.js";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { SORT_ORDERS } from "../../types.js";
|
|
7
|
-
export const collectionsApiRoutes = new Hono();
|
|
8
|
-
const SortOrderSchema = z.enum(SORT_ORDERS);
|
|
9
|
-
const CreateCollectionSchema = z.object({
|
|
10
|
-
slug: z.string().min(1),
|
|
11
|
-
title: z.string().min(1),
|
|
12
|
-
description: z.string().optional(),
|
|
13
|
-
icon: z.string().optional(),
|
|
14
|
-
sortOrder: SortOrderSchema.optional(),
|
|
15
|
-
position: z.number().int().min(0).optional(),
|
|
16
|
-
showDivider: z.boolean().optional()
|
|
17
|
-
});
|
|
18
|
-
const UpdateCollectionSchema = z.object({
|
|
19
|
-
slug: z.string().min(1).optional(),
|
|
20
|
-
title: z.string().min(1).optional(),
|
|
21
|
-
description: z.string().nullable().optional(),
|
|
22
|
-
icon: z.string().nullable().optional(),
|
|
23
|
-
sortOrder: SortOrderSchema.optional(),
|
|
24
|
-
position: z.number().int().min(0).optional(),
|
|
25
|
-
showDivider: z.boolean().optional()
|
|
26
|
-
});
|
|
27
|
-
const ReorderSchema = z.object({
|
|
28
|
-
ids: z.array(z.number().int().positive())
|
|
29
|
-
});
|
|
30
|
-
// List collections (includes post counts)
|
|
31
|
-
collectionsApiRoutes.get("/", async (c)=>{
|
|
32
|
-
const collections = await c.var.services.collections.list();
|
|
33
|
-
const postCounts = await c.var.services.collections.getPostCounts();
|
|
34
|
-
return c.json({
|
|
35
|
-
collections: collections.map((col)=>({
|
|
36
|
-
...col,
|
|
37
|
-
postCount: postCounts.get(col.id) ?? 0
|
|
38
|
-
}))
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
// Get single collection
|
|
42
|
-
collectionsApiRoutes.get("/:id", async (c)=>{
|
|
43
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
44
|
-
if (isNaN(id)) return c.json({
|
|
45
|
-
error: "Invalid ID"
|
|
46
|
-
}, 400);
|
|
47
|
-
const collection = await c.var.services.collections.getById(id);
|
|
48
|
-
if (!collection) return c.json({
|
|
49
|
-
error: "Not found"
|
|
50
|
-
}, 404);
|
|
51
|
-
return c.json(collection);
|
|
52
|
-
});
|
|
53
|
-
// Reorder collections (requires auth) — must be before /:id
|
|
54
|
-
collectionsApiRoutes.put("/reorder", requireAuthApi(), async (c)=>{
|
|
55
|
-
const rawBody = await c.req.json();
|
|
56
|
-
const parseResult = ReorderSchema.safeParse(rawBody);
|
|
57
|
-
if (!parseResult.success) {
|
|
58
|
-
return c.json({
|
|
59
|
-
error: "Validation failed",
|
|
60
|
-
details: parseResult.error.flatten()
|
|
61
|
-
}, 400);
|
|
62
|
-
}
|
|
63
|
-
await c.var.services.collections.reorder(parseResult.data.ids);
|
|
64
|
-
const collections = await c.var.services.collections.list();
|
|
65
|
-
return c.json({
|
|
66
|
-
collections
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
// Create collection (requires auth)
|
|
70
|
-
collectionsApiRoutes.post("/", requireAuthApi(), async (c)=>{
|
|
71
|
-
const rawBody = await c.req.json();
|
|
72
|
-
const parseResult = CreateCollectionSchema.safeParse(rawBody);
|
|
73
|
-
if (!parseResult.success) {
|
|
74
|
-
return c.json({
|
|
75
|
-
error: "Validation failed",
|
|
76
|
-
details: parseResult.error.flatten()
|
|
77
|
-
}, 400);
|
|
78
|
-
}
|
|
79
|
-
const body = parseResult.data;
|
|
80
|
-
const collection = await c.var.services.collections.create({
|
|
81
|
-
slug: body.slug,
|
|
82
|
-
title: body.title,
|
|
83
|
-
description: body.description,
|
|
84
|
-
icon: body.icon,
|
|
85
|
-
sortOrder: body.sortOrder,
|
|
86
|
-
position: body.position,
|
|
87
|
-
showDivider: body.showDivider
|
|
88
|
-
});
|
|
89
|
-
return c.json(collection, 201);
|
|
90
|
-
});
|
|
91
|
-
// Update collection (requires auth)
|
|
92
|
-
collectionsApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
|
|
93
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
94
|
-
if (isNaN(id)) return c.json({
|
|
95
|
-
error: "Invalid ID"
|
|
96
|
-
}, 400);
|
|
97
|
-
const rawBody = await c.req.json();
|
|
98
|
-
const parseResult = UpdateCollectionSchema.safeParse(rawBody);
|
|
99
|
-
if (!parseResult.success) {
|
|
100
|
-
return c.json({
|
|
101
|
-
error: "Validation failed",
|
|
102
|
-
details: parseResult.error.flatten()
|
|
103
|
-
}, 400);
|
|
104
|
-
}
|
|
105
|
-
const collection = await c.var.services.collections.update(id, parseResult.data);
|
|
106
|
-
if (!collection) return c.json({
|
|
107
|
-
error: "Not found"
|
|
108
|
-
}, 404);
|
|
109
|
-
return c.json(collection);
|
|
110
|
-
});
|
|
111
|
-
// Delete collection (requires auth)
|
|
112
|
-
collectionsApiRoutes.delete("/:id", requireAuthApi(), async (c)=>{
|
|
113
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
114
|
-
if (isNaN(id)) return c.json({
|
|
115
|
-
error: "Invalid ID"
|
|
116
|
-
}, 400);
|
|
117
|
-
const success = await c.var.services.collections.delete(id);
|
|
118
|
-
if (!success) return c.json({
|
|
119
|
-
error: "Not found"
|
|
120
|
-
}, 404);
|
|
121
|
-
return c.json({
|
|
122
|
-
success: true
|
|
123
|
-
});
|
|
124
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nav Items API Routes
|
|
3
|
-
*/ import { Hono } from "hono";
|
|
4
|
-
import { requireAuthApi } from "../../middleware/auth.js";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
export const navItemsApiRoutes = new Hono();
|
|
7
|
-
const NavItemTypeSchema = z.enum([
|
|
8
|
-
"link",
|
|
9
|
-
"page"
|
|
10
|
-
]);
|
|
11
|
-
const CreateNavItemSchema = z.object({
|
|
12
|
-
type: NavItemTypeSchema,
|
|
13
|
-
label: z.string().min(1),
|
|
14
|
-
url: z.string().min(1),
|
|
15
|
-
pageId: z.number().int().positive().optional(),
|
|
16
|
-
position: z.number().int().min(0).optional()
|
|
17
|
-
});
|
|
18
|
-
const UpdateNavItemSchema = z.object({
|
|
19
|
-
type: NavItemTypeSchema.optional(),
|
|
20
|
-
label: z.string().min(1).optional(),
|
|
21
|
-
url: z.string().min(1).optional(),
|
|
22
|
-
pageId: z.number().int().positive().nullable().optional(),
|
|
23
|
-
position: z.number().int().min(0).optional()
|
|
24
|
-
});
|
|
25
|
-
const ReorderSchema = z.object({
|
|
26
|
-
ids: z.array(z.number().int().positive())
|
|
27
|
-
});
|
|
28
|
-
// List nav items
|
|
29
|
-
navItemsApiRoutes.get("/", async (c)=>{
|
|
30
|
-
const items = await c.var.services.navItems.list();
|
|
31
|
-
return c.json({
|
|
32
|
-
navItems: items
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
// Reorder nav items (requires auth) — must be before /:id
|
|
36
|
-
navItemsApiRoutes.put("/reorder", requireAuthApi(), async (c)=>{
|
|
37
|
-
const rawBody = await c.req.json();
|
|
38
|
-
const parseResult = ReorderSchema.safeParse(rawBody);
|
|
39
|
-
if (!parseResult.success) {
|
|
40
|
-
return c.json({
|
|
41
|
-
error: "Validation failed",
|
|
42
|
-
details: parseResult.error.flatten()
|
|
43
|
-
}, 400);
|
|
44
|
-
}
|
|
45
|
-
await c.var.services.navItems.reorder(parseResult.data.ids);
|
|
46
|
-
const items = await c.var.services.navItems.list();
|
|
47
|
-
return c.json({
|
|
48
|
-
navItems: items
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
// Create nav item (requires auth)
|
|
52
|
-
navItemsApiRoutes.post("/", requireAuthApi(), async (c)=>{
|
|
53
|
-
const rawBody = await c.req.json();
|
|
54
|
-
const parseResult = CreateNavItemSchema.safeParse(rawBody);
|
|
55
|
-
if (!parseResult.success) {
|
|
56
|
-
return c.json({
|
|
57
|
-
error: "Validation failed",
|
|
58
|
-
details: parseResult.error.flatten()
|
|
59
|
-
}, 400);
|
|
60
|
-
}
|
|
61
|
-
const body = parseResult.data;
|
|
62
|
-
const item = await c.var.services.navItems.create({
|
|
63
|
-
type: body.type,
|
|
64
|
-
label: body.label,
|
|
65
|
-
url: body.url,
|
|
66
|
-
pageId: body.pageId,
|
|
67
|
-
position: body.position
|
|
68
|
-
});
|
|
69
|
-
return c.json(item, 201);
|
|
70
|
-
});
|
|
71
|
-
// Update nav item (requires auth)
|
|
72
|
-
navItemsApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
|
|
73
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
74
|
-
if (isNaN(id)) return c.json({
|
|
75
|
-
error: "Invalid ID"
|
|
76
|
-
}, 400);
|
|
77
|
-
const rawBody = await c.req.json();
|
|
78
|
-
const parseResult = UpdateNavItemSchema.safeParse(rawBody);
|
|
79
|
-
if (!parseResult.success) {
|
|
80
|
-
return c.json({
|
|
81
|
-
error: "Validation failed",
|
|
82
|
-
details: parseResult.error.flatten()
|
|
83
|
-
}, 400);
|
|
84
|
-
}
|
|
85
|
-
const item = await c.var.services.navItems.update(id, parseResult.data);
|
|
86
|
-
if (!item) return c.json({
|
|
87
|
-
error: "Not found"
|
|
88
|
-
}, 404);
|
|
89
|
-
return c.json(item);
|
|
90
|
-
});
|
|
91
|
-
// Delete nav item (requires auth)
|
|
92
|
-
navItemsApiRoutes.delete("/:id", requireAuthApi(), async (c)=>{
|
|
93
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
94
|
-
if (isNaN(id)) return c.json({
|
|
95
|
-
error: "Invalid ID"
|
|
96
|
-
}, 400);
|
|
97
|
-
const success = await c.var.services.navItems.delete(id);
|
|
98
|
-
if (!success) return c.json({
|
|
99
|
-
error: "Not found"
|
|
100
|
-
}, 404);
|
|
101
|
-
return c.json({
|
|
102
|
-
success: true
|
|
103
|
-
});
|
|
104
|
-
});
|
package/dist/routes/api/pages.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pages API Routes
|
|
3
|
-
*/ import { Hono } from "hono";
|
|
4
|
-
import { requireAuthApi } from "../../middleware/auth.js";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { StatusSchema } from "../../lib/schemas.js";
|
|
7
|
-
export const pagesApiRoutes = new Hono();
|
|
8
|
-
const CreatePageSchema = z.object({
|
|
9
|
-
slug: z.string().min(1),
|
|
10
|
-
title: z.string().optional(),
|
|
11
|
-
body: z.string().optional(),
|
|
12
|
-
status: StatusSchema.optional()
|
|
13
|
-
});
|
|
14
|
-
const UpdatePageSchema = z.object({
|
|
15
|
-
slug: z.string().min(1).optional(),
|
|
16
|
-
title: z.string().nullable().optional(),
|
|
17
|
-
body: z.string().nullable().optional(),
|
|
18
|
-
status: StatusSchema.optional()
|
|
19
|
-
});
|
|
20
|
-
// List pages
|
|
21
|
-
pagesApiRoutes.get("/", async (c)=>{
|
|
22
|
-
const pages = await c.var.services.pages.list();
|
|
23
|
-
return c.json({
|
|
24
|
-
pages
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
// Get single page
|
|
28
|
-
pagesApiRoutes.get("/:id", async (c)=>{
|
|
29
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
30
|
-
if (isNaN(id)) return c.json({
|
|
31
|
-
error: "Invalid ID"
|
|
32
|
-
}, 400);
|
|
33
|
-
const page = await c.var.services.pages.getById(id);
|
|
34
|
-
if (!page) return c.json({
|
|
35
|
-
error: "Not found"
|
|
36
|
-
}, 404);
|
|
37
|
-
return c.json(page);
|
|
38
|
-
});
|
|
39
|
-
// Create page (requires auth)
|
|
40
|
-
pagesApiRoutes.post("/", requireAuthApi(), async (c)=>{
|
|
41
|
-
const rawBody = await c.req.json();
|
|
42
|
-
const parseResult = CreatePageSchema.safeParse(rawBody);
|
|
43
|
-
if (!parseResult.success) {
|
|
44
|
-
return c.json({
|
|
45
|
-
error: "Validation failed",
|
|
46
|
-
details: parseResult.error.flatten()
|
|
47
|
-
}, 400);
|
|
48
|
-
}
|
|
49
|
-
const body = parseResult.data;
|
|
50
|
-
const page = await c.var.services.pages.create({
|
|
51
|
-
slug: body.slug,
|
|
52
|
-
title: body.title,
|
|
53
|
-
body: body.body,
|
|
54
|
-
status: body.status
|
|
55
|
-
});
|
|
56
|
-
return c.json(page, 201);
|
|
57
|
-
});
|
|
58
|
-
// Update page (requires auth)
|
|
59
|
-
pagesApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
|
|
60
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
61
|
-
if (isNaN(id)) return c.json({
|
|
62
|
-
error: "Invalid ID"
|
|
63
|
-
}, 400);
|
|
64
|
-
const rawBody = await c.req.json();
|
|
65
|
-
const parseResult = UpdatePageSchema.safeParse(rawBody);
|
|
66
|
-
if (!parseResult.success) {
|
|
67
|
-
return c.json({
|
|
68
|
-
error: "Validation failed",
|
|
69
|
-
details: parseResult.error.flatten()
|
|
70
|
-
}, 400);
|
|
71
|
-
}
|
|
72
|
-
const page = await c.var.services.pages.update(id, parseResult.data);
|
|
73
|
-
if (!page) return c.json({
|
|
74
|
-
error: "Not found"
|
|
75
|
-
}, 404);
|
|
76
|
-
return c.json(page);
|
|
77
|
-
});
|
|
78
|
-
// Delete page (requires auth)
|
|
79
|
-
pagesApiRoutes.delete("/:id", requireAuthApi(), async (c)=>{
|
|
80
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
81
|
-
if (isNaN(id)) return c.json({
|
|
82
|
-
error: "Invalid ID"
|
|
83
|
-
}, 400);
|
|
84
|
-
const success = await c.var.services.pages.delete(id);
|
|
85
|
-
if (!success) return c.json({
|
|
86
|
-
error: "Not found"
|
|
87
|
-
}, 404);
|
|
88
|
-
return c.json({
|
|
89
|
-
success: true
|
|
90
|
-
});
|
|
91
|
-
});
|