@jant/core 0.3.24 → 0.3.26
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/app.js +101 -571
- package/dist/client.js +1 -0
- package/dist/db/schema.js +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +3 -9
- package/dist/lib/avatar-upload.js +134 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/constants.js +10 -9
- package/dist/lib/favicon.js +102 -0
- package/dist/lib/image.js +13 -17
- package/dist/lib/media-helpers.js +2 -2
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +48 -3
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +16 -11
- package/dist/lib/schemas.js +34 -3
- package/dist/lib/theme.js +4 -4
- package/dist/lib/timeline.js +24 -48
- package/dist/lib/timezones.js +388 -0
- package/dist/lib/view.js +3 -3
- package/dist/routes/api/collections.js +124 -0
- package/dist/routes/api/nav-items.js +104 -0
- package/dist/routes/api/pages.js +91 -0
- package/dist/routes/api/posts.js +3 -3
- package/dist/routes/api/search.js +2 -2
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/api/upload.js +3 -3
- package/dist/routes/auth/reset.js +221 -0
- package/dist/routes/auth/setup.js +194 -0
- package/dist/routes/auth/signin.js +176 -0
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +24 -416
- package/dist/routes/dash/index.js +1 -1
- package/dist/routes/dash/media.js +13 -393
- package/dist/routes/dash/pages.js +112 -86
- package/dist/routes/dash/posts.js +3 -5
- package/dist/routes/dash/redirects.js +20 -14
- package/dist/routes/dash/settings.js +213 -518
- package/dist/routes/feed/rss.js +4 -3
- package/dist/routes/feed/sitemap.js +5 -3
- package/dist/routes/pages/archive.js +3 -6
- package/dist/routes/pages/collection.js +3 -6
- package/dist/routes/pages/collections.js +28 -0
- package/dist/routes/pages/featured.js +36 -0
- package/dist/routes/pages/home.js +33 -49
- package/dist/routes/pages/latest.js +45 -0
- package/dist/routes/pages/page.js +29 -32
- package/dist/routes/pages/post.js +3 -6
- package/dist/routes/pages/search.js +3 -6
- package/dist/services/page.js +5 -1
- package/dist/services/post.js +45 -31
- package/dist/services/search.js +1 -1
- package/dist/types/bindings.js +3 -0
- package/dist/types/config.js +147 -0
- package/dist/types/constants.js +27 -0
- package/dist/types/entities.js +3 -0
- package/dist/types/operations.js +3 -0
- package/dist/types/props.js +3 -0
- package/dist/types/views.js +5 -0
- package/dist/types.js +8 -111
- package/dist/{theme → ui}/color-themes.js +33 -33
- package/dist/ui/compose/ComposeDialog.js +467 -0
- package/dist/ui/compose/ComposePrompt.js +55 -0
- package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +1 -2
- package/dist/{theme/components → ui/dash}/PageForm.js +21 -15
- package/dist/{theme/components → ui/dash}/PostForm.js +22 -43
- package/dist/{theme/components → ui/dash}/PostList.js +6 -6
- package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
- package/dist/ui/dash/collections/CollectionForm.js +152 -0
- package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
- package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- package/dist/ui/dash/media/MediaListContent.js +166 -0
- package/dist/ui/dash/media/ViewMediaContent.js +212 -0
- package/dist/ui/dash/pages/LinkFormContent.js +130 -0
- package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
- package/dist/ui/dash/settings/AccountContent.js +209 -0
- package/dist/ui/dash/settings/AppearanceContent.js +259 -0
- package/dist/ui/dash/settings/GeneralContent.js +536 -0
- package/dist/ui/dash/settings/SettingsNav.js +41 -0
- package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
- package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
- package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
- package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
- package/dist/ui/feed/TimelineFeed.js +41 -0
- package/dist/ui/feed/TimelineItem.js +27 -0
- package/dist/ui/font-themes.js +36 -0
- package/dist/{theme → ui}/layouts/BaseLayout.js +34 -2
- package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
- package/dist/ui/layouts/SiteLayout.js +169 -0
- package/dist/{themes/threads → ui}/pages/ArchivePage.js +16 -14
- package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
- package/dist/ui/pages/CollectionsPage.js +76 -0
- package/dist/ui/pages/FeaturedPage.js +24 -0
- package/dist/ui/pages/HomePage.js +24 -0
- package/dist/{themes/threads → ui}/pages/PostPage.js +13 -8
- package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
- package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
- package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
- package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
- package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
- package/dist/ui/shared/index.js +5 -0
- package/package.json +1 -9
- package/src/__tests__/helpers/db.ts +3 -0
- package/src/app.tsx +131 -561
- package/src/client.ts +1 -0
- package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +1 -1
- package/src/i18n/locales/en.po +477 -261
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +477 -261
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +477 -261
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +7 -36
- package/src/lib/__tests__/config.test.ts +192 -0
- package/src/lib/__tests__/favicon.test.ts +151 -0
- package/src/lib/__tests__/image.test.ts +2 -6
- package/src/lib/__tests__/schemas.test.ts +60 -19
- package/src/lib/__tests__/timeline.test.ts +45 -81
- package/src/lib/__tests__/timezones.test.ts +61 -0
- package/src/lib/__tests__/view.test.ts +15 -9
- package/src/lib/avatar-upload.ts +165 -0
- package/src/lib/config.ts +47 -0
- package/src/lib/constants.ts +19 -10
- package/src/lib/favicon.ts +115 -0
- package/src/lib/image.ts +13 -21
- package/src/lib/media-helpers.ts +2 -2
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +73 -4
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +22 -15
- package/src/lib/schemas.ts +47 -6
- package/src/lib/theme.ts +5 -5
- package/src/lib/timeline.ts +28 -57
- package/src/lib/timezones.ts +325 -0
- package/src/lib/view.ts +3 -3
- package/src/preset.css +2 -1
- package/src/routes/__tests__/compose.test.ts +199 -0
- package/src/routes/api/__tests__/collections.test.ts +249 -0
- package/src/routes/api/__tests__/nav-items.test.ts +222 -0
- package/src/routes/api/__tests__/pages.test.ts +218 -0
- package/src/routes/api/__tests__/settings.test.ts +132 -0
- package/src/routes/api/collections.ts +143 -0
- package/src/routes/api/nav-items.ts +115 -0
- package/src/routes/api/pages.ts +101 -0
- package/src/routes/api/posts.ts +3 -3
- package/src/routes/api/search.ts +2 -2
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/api/upload.ts +2 -3
- package/src/routes/auth/reset.tsx +239 -0
- package/src/routes/auth/setup.tsx +189 -0
- package/src/routes/auth/signin.tsx +163 -0
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
- package/src/routes/dash/collections.tsx +18 -367
- package/src/routes/dash/index.tsx +1 -1
- package/src/routes/dash/media.tsx +13 -415
- package/src/routes/dash/pages.tsx +131 -98
- package/src/routes/dash/posts.tsx +3 -7
- package/src/routes/dash/redirects.tsx +22 -16
- package/src/routes/dash/settings.tsx +265 -478
- package/src/routes/feed/__tests__/rss.test.ts +141 -0
- package/src/routes/feed/rss.ts +5 -3
- package/src/routes/feed/sitemap.ts +5 -3
- package/src/routes/pages/__tests__/collections.test.ts +94 -0
- package/src/routes/pages/__tests__/featured.test.ts +94 -0
- package/src/routes/pages/archive.tsx +2 -6
- package/src/routes/pages/collection.tsx +2 -6
- package/src/routes/pages/collections.tsx +36 -0
- package/src/routes/pages/featured.tsx +44 -0
- package/src/routes/pages/home.tsx +30 -53
- package/src/routes/pages/latest.tsx +59 -0
- package/src/routes/pages/page.tsx +28 -30
- package/src/routes/pages/post.tsx +2 -5
- package/src/routes/pages/search.tsx +2 -6
- package/src/services/__tests__/page.test.ts +106 -0
- package/src/services/__tests__/post.test.ts +114 -15
- package/src/services/page.ts +13 -1
- package/src/services/post.ts +58 -40
- package/src/services/search.ts +2 -2
- package/src/styles/components.css +0 -65
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +475 -0
- package/src/types/bindings.ts +30 -0
- package/src/types/config.ts +183 -0
- package/src/types/constants.ts +26 -0
- package/src/types/entities.ts +109 -0
- package/src/types/operations.ts +88 -0
- package/src/types/props.ts +115 -0
- package/src/types/views.ts +172 -0
- package/src/types.ts +8 -774
- package/src/ui/__tests__/font-themes.test.ts +34 -0
- package/src/{theme → ui}/color-themes.ts +34 -34
- package/src/ui/compose/ComposeDialog.tsx +414 -0
- package/src/ui/compose/ComposePrompt.tsx +55 -0
- package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
- package/src/{theme/components → ui/dash}/PageForm.tsx +25 -19
- package/src/{theme/components → ui/dash}/PostForm.tsx +26 -45
- package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
- package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
- package/src/ui/dash/collections/CollectionForm.tsx +153 -0
- package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
- package/src/ui/dash/index.ts +10 -0
- package/src/ui/dash/media/MediaListContent.tsx +201 -0
- package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
- package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
- package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
- package/src/ui/dash/settings/AccountContent.tsx +176 -0
- package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
- package/src/ui/dash/settings/GeneralContent.tsx +533 -0
- package/src/ui/dash/settings/SettingsNav.tsx +56 -0
- package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
- package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
- package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
- package/src/ui/feed/TimelineFeed.tsx +49 -0
- package/src/ui/feed/TimelineItem.tsx +45 -0
- package/src/ui/font-themes.ts +54 -0
- package/src/{theme → ui}/layouts/BaseLayout.tsx +28 -1
- package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
- package/src/ui/layouts/SiteLayout.tsx +164 -0
- package/src/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
- package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
- package/src/ui/pages/CollectionsPage.tsx +73 -0
- package/src/ui/pages/FeaturedPage.tsx +31 -0
- package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
- package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
- package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
- package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
- package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
- package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
- package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
- package/src/ui/shared/__tests__/pagination.test.ts +46 -0
- package/src/ui/shared/index.ts +12 -0
- package/bin/jant.js +0 -185
- package/dist/lib/theme-components.js +0 -46
- package/dist/routes/dash/navigation.js +0 -289
- package/dist/theme/index.js +0 -18
- package/dist/theme/layouts/index.js +0 -2
- package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
- package/dist/themes/threads/index.js +0 -81
- package/dist/themes/threads/pages/HomePage.js +0 -25
- package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
- package/dist/themes/threads/timeline/TimelineItem.js +0 -36
- package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
- package/dist/themes/threads/timeline/groupByDate.js +0 -22
- package/dist/themes/threads/timeline/timelineMore.js +0 -107
- package/src/lib/__tests__/theme-components.test.ts +0 -105
- package/src/lib/theme-components.ts +0 -65
- package/src/routes/dash/navigation.tsx +0 -317
- package/src/theme/components/index.ts +0 -23
- package/src/theme/index.ts +0 -22
- package/src/theme/layouts/index.ts +0 -7
- package/src/themes/threads/ThreadsSiteLayout.tsx +0 -194
- package/src/themes/threads/index.ts +0 -100
- package/src/themes/threads/style.css +0 -336
- package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
- package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
- package/src/themes/threads/timeline/groupByDate.ts +0 -30
- package/src/themes/threads/timeline/timelineMore.tsx +0 -130
- /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
- /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
- /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
- /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
- /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
- /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
- /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
- /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
- /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
- /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
package/dist/app.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
1
|
/**
|
|
3
2
|
* Jant App Factory
|
|
4
3
|
*/ import { Hono } from "hono";
|
|
@@ -6,10 +5,11 @@ import { createDatabase } from "./db/index.js";
|
|
|
6
5
|
import { createServices } from "./services/index.js";
|
|
7
6
|
import { createAuth } from "./auth.js";
|
|
8
7
|
import { i18nMiddleware } from "./i18n/index.js";
|
|
9
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
10
8
|
import { SETTINGS_KEYS } from "./lib/constants.js";
|
|
11
|
-
|
|
12
|
-
import {
|
|
9
|
+
// Routes - Auth
|
|
10
|
+
import { setupRoutes } from "./routes/auth/setup.js";
|
|
11
|
+
import { signinRoutes } from "./routes/auth/signin.js";
|
|
12
|
+
import { resetRoutes } from "./routes/auth/reset.js";
|
|
13
13
|
// Routes - Pages
|
|
14
14
|
import { homeRoutes } from "./routes/pages/home.js";
|
|
15
15
|
import { postRoutes } from "./routes/pages/post.js";
|
|
@@ -17,6 +17,9 @@ import { pageRoutes } from "./routes/pages/page.js";
|
|
|
17
17
|
import { collectionRoutes } from "./routes/pages/collection.js";
|
|
18
18
|
import { archiveRoutes } from "./routes/pages/archive.js";
|
|
19
19
|
import { searchRoutes } from "./routes/pages/search.js";
|
|
20
|
+
import { featuredRoutes } from "./routes/pages/featured.js";
|
|
21
|
+
import { latestRoutes } from "./routes/pages/latest.js";
|
|
22
|
+
import { collectionsPageRoutes } from "./routes/pages/collections.js";
|
|
20
23
|
// Routes - Dashboard
|
|
21
24
|
import { dashIndexRoutes } from "./routes/dash/index.js";
|
|
22
25
|
import { postsRoutes as dashPostsRoutes } from "./routes/dash/posts.js";
|
|
@@ -25,22 +28,27 @@ import { mediaRoutes as dashMediaRoutes } from "./routes/dash/media.js";
|
|
|
25
28
|
import { settingsRoutes as dashSettingsRoutes } from "./routes/dash/settings.js";
|
|
26
29
|
import { redirectsRoutes as dashRedirectsRoutes } from "./routes/dash/redirects.js";
|
|
27
30
|
import { collectionsRoutes as dashCollectionsRoutes } from "./routes/dash/collections.js";
|
|
28
|
-
import { navigationRoutes as dashNavigationRoutes } from "./routes/dash/navigation.js";
|
|
29
31
|
// Routes - API
|
|
30
32
|
import { postsApiRoutes } from "./routes/api/posts.js";
|
|
33
|
+
import { pagesApiRoutes } from "./routes/api/pages.js";
|
|
34
|
+
import { navItemsApiRoutes } from "./routes/api/nav-items.js";
|
|
35
|
+
import { collectionsApiRoutes } from "./routes/api/collections.js";
|
|
36
|
+
import { settingsApiRoutes } from "./routes/api/settings.js";
|
|
31
37
|
import { uploadApiRoutes } from "./routes/api/upload.js";
|
|
32
38
|
import { searchApiRoutes } from "./routes/api/search.js";
|
|
39
|
+
// Routes - Compose
|
|
40
|
+
import { composeRoutes } from "./routes/compose.js";
|
|
33
41
|
// Routes - Feed
|
|
34
42
|
import { rssRoutes } from "./routes/feed/rss.js";
|
|
35
43
|
import { sitemapRoutes } from "./routes/feed/sitemap.js";
|
|
36
44
|
// Middleware
|
|
37
45
|
import { requireAuth } from "./middleware/auth.js";
|
|
38
46
|
import { requireOnboarding } from "./middleware/onboarding.js";
|
|
39
|
-
// Layouts for auth pages
|
|
40
|
-
import { BaseLayout } from "./theme/layouts/index.js";
|
|
41
|
-
import { dsRedirect, dsToast } from "./lib/sse.js";
|
|
42
47
|
import { getAvailableThemes, buildThemeStyle } from "./lib/theme.js";
|
|
43
48
|
import { createStorageDriver } from "./lib/storage.js";
|
|
49
|
+
import { BUILTIN_FONT_THEMES } from "./ui/font-themes.js";
|
|
50
|
+
import { getMediaUrl, getPublicUrlForProvider } from "./lib/image.js";
|
|
51
|
+
import { base64ToUint8Array } from "./lib/favicon.js";
|
|
44
52
|
/**
|
|
45
53
|
* Create a Jant application
|
|
46
54
|
*
|
|
@@ -56,35 +64,17 @@ import { createStorageDriver } from "./lib/storage.js";
|
|
|
56
64
|
* import { createApp } from "@jant/core";
|
|
57
65
|
*
|
|
58
66
|
* export default createApp({
|
|
59
|
-
*
|
|
67
|
+
* cssVariables: { "--card-radius": "0" },
|
|
60
68
|
* });
|
|
61
69
|
* ```
|
|
62
70
|
*/ export function createApp(config = {}) {
|
|
63
|
-
// Merge with default threads theme
|
|
64
|
-
const defaultTheme = threadsTheme();
|
|
65
71
|
const resolvedConfig = {
|
|
66
|
-
...config
|
|
67
|
-
theme: {
|
|
68
|
-
name: config.theme?.name ?? defaultTheme.name,
|
|
69
|
-
components: {
|
|
70
|
-
...defaultTheme.components,
|
|
71
|
-
...config.theme?.components
|
|
72
|
-
},
|
|
73
|
-
timelineMore: config.theme?.timelineMore ?? defaultTheme.timelineMore,
|
|
74
|
-
cssVariables: {
|
|
75
|
-
...defaultTheme.cssVariables,
|
|
76
|
-
...config.theme?.cssVariables
|
|
77
|
-
},
|
|
78
|
-
colorThemes: config.theme?.colorThemes ?? defaultTheme.colorThemes,
|
|
79
|
-
feed: config.theme?.feed
|
|
80
|
-
}
|
|
72
|
+
...config
|
|
81
73
|
};
|
|
82
74
|
const app = new Hono();
|
|
83
75
|
// Initialize services, auth, and config middleware
|
|
84
76
|
app.use("*", async (c, next)=>{
|
|
85
77
|
// Use withSession() to enable D1 Read Replication
|
|
86
|
-
// Automatically routes read queries to the nearest replica for lower latency
|
|
87
|
-
// See: https://developers.cloudflare.com/d1/best-practices/read-replication/
|
|
88
78
|
const session = c.env.DB.withSession();
|
|
89
79
|
// Note: Drizzle ORM doesn't officially support D1DatabaseSession yet (issue #2226)
|
|
90
80
|
// but it works at runtime. We use type assertion as a temporary workaround.
|
|
@@ -93,6 +83,10 @@ import { createStorageDriver } from "./lib/storage.js";
|
|
|
93
83
|
c.set("services", services);
|
|
94
84
|
c.set("config", resolvedConfig);
|
|
95
85
|
c.set("storage", createStorageDriver(c.env));
|
|
86
|
+
if (!c.env.AUTH_SECRET) {
|
|
87
|
+
// eslint-disable-next-line no-console -- Startup warning is intentional
|
|
88
|
+
console.warn("[Jant] AUTH_SECRET is not set. Authentication is disabled. Set AUTH_SECRET in .dev.vars or wrangler.toml to enable auth.");
|
|
89
|
+
}
|
|
96
90
|
if (c.env.AUTH_SECRET) {
|
|
97
91
|
const baseURL = c.env.SITE_URL || new URL(c.req.url).origin;
|
|
98
92
|
const auth = createAuth(session, {
|
|
@@ -105,13 +99,49 @@ import { createStorageDriver } from "./lib/storage.js";
|
|
|
105
99
|
});
|
|
106
100
|
// Onboarding gate — redirect to /setup if not yet initialized
|
|
107
101
|
app.use("*", requireOnboarding());
|
|
108
|
-
// Theme middleware - resolve active color theme and
|
|
102
|
+
// Theme middleware - resolve active color theme, font theme, custom CSS, and auth state
|
|
109
103
|
app.use("*", async (c, next)=>{
|
|
110
|
-
const themeId = await
|
|
104
|
+
const [themeId, fontThemeId, customCSS, noindexValue, avatarKey] = await Promise.all([
|
|
105
|
+
c.var.services.settings.get(SETTINGS_KEYS.THEME),
|
|
106
|
+
c.var.services.settings.get("FONT_THEME"),
|
|
107
|
+
c.var.services.settings.get(SETTINGS_KEYS.CUSTOM_CSS),
|
|
108
|
+
c.var.services.settings.get("NOINDEX"),
|
|
109
|
+
c.var.services.settings.get("SITE_AVATAR")
|
|
110
|
+
]);
|
|
111
111
|
const themes = getAvailableThemes(resolvedConfig);
|
|
112
112
|
const activeTheme = themeId ? themes.find((t)=>t.id === themeId) : undefined;
|
|
113
|
-
|
|
113
|
+
// Build font override CSS variables
|
|
114
|
+
const fontTheme = fontThemeId ? BUILTIN_FONT_THEMES.find((f)=>f.id === fontThemeId) : undefined;
|
|
115
|
+
const fontOverrides = {};
|
|
116
|
+
if (fontTheme) {
|
|
117
|
+
fontOverrides["--font-body"] = fontTheme.fontFamily;
|
|
118
|
+
}
|
|
119
|
+
const themeStyle = buildThemeStyle(activeTheme, {
|
|
120
|
+
...resolvedConfig.cssVariables,
|
|
121
|
+
...fontOverrides
|
|
122
|
+
});
|
|
114
123
|
c.set("themeStyle", themeStyle);
|
|
124
|
+
c.set("customCSS", customCSS ?? "");
|
|
125
|
+
// Noindex
|
|
126
|
+
c.set("noindex", noindexValue === "true");
|
|
127
|
+
// Resolve favicon from avatar storage key
|
|
128
|
+
if (avatarKey) {
|
|
129
|
+
const publicUrl = getPublicUrlForProvider(c.env.STORAGE_DRIVER || "r2", c.env.R2_PUBLIC_URL, c.env.S3_PUBLIC_URL);
|
|
130
|
+
c.set("faviconUrl", getMediaUrl(avatarKey, publicUrl));
|
|
131
|
+
}
|
|
132
|
+
// Check auth state for data-authenticated attribute on <body>
|
|
133
|
+
let isAuthenticated = false;
|
|
134
|
+
if (c.var.auth) {
|
|
135
|
+
try {
|
|
136
|
+
const session = await c.var.auth.api.getSession({
|
|
137
|
+
headers: c.req.raw.headers
|
|
138
|
+
});
|
|
139
|
+
isAuthenticated = !!session;
|
|
140
|
+
} catch {
|
|
141
|
+
// Not authenticated
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
c.set("isAuthenticated", isAuthenticated);
|
|
115
145
|
await next();
|
|
116
146
|
});
|
|
117
147
|
// i18n middleware
|
|
@@ -144,6 +174,27 @@ import { createStorageDriver } from "./lib/storage.js";
|
|
|
144
174
|
auth: c.env.AUTH_SECRET ? "configured" : "missing",
|
|
145
175
|
authSecretLength: c.env.AUTH_SECRET?.length ?? 0
|
|
146
176
|
}));
|
|
177
|
+
// Favicon routes - serve from DB settings (small files, avoids R2 round-trip)
|
|
178
|
+
app.get("/favicon.ico", async (c)=>{
|
|
179
|
+
const data = await c.var.services.settings.get("SITE_FAVICON_ICO");
|
|
180
|
+
if (!data) return c.notFound();
|
|
181
|
+
return new Response(base64ToUint8Array(data), {
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "image/x-icon",
|
|
184
|
+
"Cache-Control": "public, max-age=86400"
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
app.get("/apple-touch-icon.png", async (c)=>{
|
|
189
|
+
const data = await c.var.services.settings.get("SITE_FAVICON_APPLE_TOUCH");
|
|
190
|
+
if (!data) return c.notFound();
|
|
191
|
+
return new Response(base64ToUint8Array(data), {
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "image/png",
|
|
194
|
+
"Cache-Control": "public, max-age=86400"
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
147
198
|
// better-auth handler
|
|
148
199
|
app.all("/api/auth/*", async (c)=>{
|
|
149
200
|
if (!c.var.auth) {
|
|
@@ -155,534 +206,14 @@ import { createStorageDriver } from "./lib/storage.js";
|
|
|
155
206
|
});
|
|
156
207
|
// API Routes
|
|
157
208
|
app.route("/api/posts", postsApiRoutes);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
/*#__PURE__*/ _jsxs("header", {
|
|
167
|
-
children: [
|
|
168
|
-
/*#__PURE__*/ _jsx("h2", {
|
|
169
|
-
children: $__i18n._({
|
|
170
|
-
id: "GorKul",
|
|
171
|
-
message: "Welcome to Jant"
|
|
172
|
-
})
|
|
173
|
-
}),
|
|
174
|
-
/*#__PURE__*/ _jsx("p", {
|
|
175
|
-
children: $__i18n._({
|
|
176
|
-
id: "GX2VMa",
|
|
177
|
-
message: "Create your admin account."
|
|
178
|
-
})
|
|
179
|
-
})
|
|
180
|
-
]
|
|
181
|
-
}),
|
|
182
|
-
/*#__PURE__*/ _jsx("section", {
|
|
183
|
-
children: /*#__PURE__*/ _jsxs("form", {
|
|
184
|
-
"data-signals": "{name: '', email: '', password: ''}",
|
|
185
|
-
"data-on:submit__prevent": "@post('/setup')",
|
|
186
|
-
"data-indicator": "_loading",
|
|
187
|
-
class: "flex flex-col gap-4",
|
|
188
|
-
children: [
|
|
189
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
190
|
-
class: "field",
|
|
191
|
-
children: [
|
|
192
|
-
/*#__PURE__*/ _jsx("label", {
|
|
193
|
-
class: "label",
|
|
194
|
-
children: $__i18n._({
|
|
195
|
-
id: "/Rj5P4",
|
|
196
|
-
message: "Your Name"
|
|
197
|
-
})
|
|
198
|
-
}),
|
|
199
|
-
/*#__PURE__*/ _jsx("input", {
|
|
200
|
-
type: "text",
|
|
201
|
-
"data-bind": "name",
|
|
202
|
-
class: "input",
|
|
203
|
-
required: true,
|
|
204
|
-
placeholder: "John Doe"
|
|
205
|
-
})
|
|
206
|
-
]
|
|
207
|
-
}),
|
|
208
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
209
|
-
class: "field",
|
|
210
|
-
children: [
|
|
211
|
-
/*#__PURE__*/ _jsx("label", {
|
|
212
|
-
class: "label",
|
|
213
|
-
children: $__i18n._({
|
|
214
|
-
id: "O3oNi5",
|
|
215
|
-
message: "Email"
|
|
216
|
-
})
|
|
217
|
-
}),
|
|
218
|
-
/*#__PURE__*/ _jsx("input", {
|
|
219
|
-
type: "email",
|
|
220
|
-
"data-bind": "email",
|
|
221
|
-
class: "input",
|
|
222
|
-
required: true,
|
|
223
|
-
placeholder: "you@example.com"
|
|
224
|
-
})
|
|
225
|
-
]
|
|
226
|
-
}),
|
|
227
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
228
|
-
class: "field",
|
|
229
|
-
children: [
|
|
230
|
-
/*#__PURE__*/ _jsx("label", {
|
|
231
|
-
class: "label",
|
|
232
|
-
children: $__i18n._({
|
|
233
|
-
id: "8ZsakT",
|
|
234
|
-
message: "Password"
|
|
235
|
-
})
|
|
236
|
-
}),
|
|
237
|
-
/*#__PURE__*/ _jsx("input", {
|
|
238
|
-
type: "password",
|
|
239
|
-
"data-bind": "password",
|
|
240
|
-
class: "input",
|
|
241
|
-
required: true,
|
|
242
|
-
minLength: 8
|
|
243
|
-
})
|
|
244
|
-
]
|
|
245
|
-
}),
|
|
246
|
-
/*#__PURE__*/ _jsxs("button", {
|
|
247
|
-
type: "submit",
|
|
248
|
-
class: "btn",
|
|
249
|
-
"data-attr-disabled": "$_loading",
|
|
250
|
-
children: [
|
|
251
|
-
/*#__PURE__*/ _jsx("span", {
|
|
252
|
-
"data-show": "!$_loading",
|
|
253
|
-
children: $__i18n._({
|
|
254
|
-
id: "EGwzOK",
|
|
255
|
-
message: "Complete Setup"
|
|
256
|
-
})
|
|
257
|
-
}),
|
|
258
|
-
/*#__PURE__*/ _jsx("span", {
|
|
259
|
-
"data-show": "$_loading",
|
|
260
|
-
children: $__i18n._({
|
|
261
|
-
id: "k1ifdL",
|
|
262
|
-
message: "Processing..."
|
|
263
|
-
})
|
|
264
|
-
})
|
|
265
|
-
]
|
|
266
|
-
})
|
|
267
|
-
]
|
|
268
|
-
})
|
|
269
|
-
})
|
|
270
|
-
]
|
|
271
|
-
})
|
|
272
|
-
});
|
|
273
|
-
};
|
|
274
|
-
// Setup page
|
|
275
|
-
app.get("/setup", async (c)=>{
|
|
276
|
-
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
277
|
-
if (isComplete) return c.redirect("/");
|
|
278
|
-
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
279
|
-
title: "Setup - Jant",
|
|
280
|
-
c: c,
|
|
281
|
-
children: /*#__PURE__*/ _jsx(SetupContent, {})
|
|
282
|
-
}));
|
|
283
|
-
});
|
|
284
|
-
app.post("/setup", async (c)=>{
|
|
285
|
-
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
286
|
-
if (isComplete) return c.redirect("/");
|
|
287
|
-
const body = await c.req.json();
|
|
288
|
-
const { name, email, password } = body;
|
|
289
|
-
if (!name || !email || !password) {
|
|
290
|
-
return dsToast("All fields are required", "error");
|
|
291
|
-
}
|
|
292
|
-
if (password.length < 8) {
|
|
293
|
-
return dsToast("Password must be at least 8 characters", "error");
|
|
294
|
-
}
|
|
295
|
-
if (!c.var.auth) {
|
|
296
|
-
return dsToast("AUTH_SECRET not configured", "error");
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
const signUpResponse = await c.var.auth.api.signUpEmail({
|
|
300
|
-
body: {
|
|
301
|
-
name,
|
|
302
|
-
email,
|
|
303
|
-
password
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
if (!signUpResponse || "error" in signUpResponse) {
|
|
307
|
-
return dsToast("Failed to create account", "error");
|
|
308
|
-
}
|
|
309
|
-
await c.var.services.settings.completeOnboarding();
|
|
310
|
-
return dsRedirect("/signin?setup");
|
|
311
|
-
} catch (err) {
|
|
312
|
-
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
313
|
-
console.error("Setup error:", err);
|
|
314
|
-
return dsToast("Failed to create account", "error");
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
// Signin page component
|
|
318
|
-
const SigninContent = ({ demoEmail, demoPassword })=>{
|
|
319
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
320
|
-
const signals = JSON.stringify({
|
|
321
|
-
email: demoEmail || "",
|
|
322
|
-
password: demoPassword || ""
|
|
323
|
-
}).replace(/</g, "\\u003c");
|
|
324
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
325
|
-
class: "min-h-screen flex items-center justify-center",
|
|
326
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
327
|
-
class: "card max-w-md w-full",
|
|
328
|
-
children: [
|
|
329
|
-
/*#__PURE__*/ _jsx("header", {
|
|
330
|
-
children: /*#__PURE__*/ _jsx("h2", {
|
|
331
|
-
children: $__i18n._({
|
|
332
|
-
id: "n1ekoW",
|
|
333
|
-
message: "Sign In"
|
|
334
|
-
})
|
|
335
|
-
})
|
|
336
|
-
}),
|
|
337
|
-
/*#__PURE__*/ _jsxs("section", {
|
|
338
|
-
children: [
|
|
339
|
-
demoEmail && demoPassword && /*#__PURE__*/ _jsx("p", {
|
|
340
|
-
class: "text-muted-foreground text-sm mb-4",
|
|
341
|
-
children: $__i18n._({
|
|
342
|
-
id: "er8+x7",
|
|
343
|
-
message: "Demo account pre-filled. Just click Sign In."
|
|
344
|
-
})
|
|
345
|
-
}),
|
|
346
|
-
/*#__PURE__*/ _jsxs("form", {
|
|
347
|
-
"data-signals": signals,
|
|
348
|
-
"data-on:submit__prevent": "@post('/signin')",
|
|
349
|
-
"data-indicator": "_loading",
|
|
350
|
-
class: "flex flex-col gap-4",
|
|
351
|
-
children: [
|
|
352
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
353
|
-
class: "field",
|
|
354
|
-
children: [
|
|
355
|
-
/*#__PURE__*/ _jsx("label", {
|
|
356
|
-
class: "label",
|
|
357
|
-
children: $__i18n._({
|
|
358
|
-
id: "O3oNi5",
|
|
359
|
-
message: "Email"
|
|
360
|
-
})
|
|
361
|
-
}),
|
|
362
|
-
/*#__PURE__*/ _jsx("input", {
|
|
363
|
-
type: "email",
|
|
364
|
-
"data-bind": "email",
|
|
365
|
-
class: "input",
|
|
366
|
-
required: true
|
|
367
|
-
})
|
|
368
|
-
]
|
|
369
|
-
}),
|
|
370
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
371
|
-
class: "field",
|
|
372
|
-
children: [
|
|
373
|
-
/*#__PURE__*/ _jsx("label", {
|
|
374
|
-
class: "label",
|
|
375
|
-
children: $__i18n._({
|
|
376
|
-
id: "8ZsakT",
|
|
377
|
-
message: "Password"
|
|
378
|
-
})
|
|
379
|
-
}),
|
|
380
|
-
/*#__PURE__*/ _jsx("input", {
|
|
381
|
-
type: "password",
|
|
382
|
-
"data-bind": "password",
|
|
383
|
-
class: "input",
|
|
384
|
-
required: true
|
|
385
|
-
})
|
|
386
|
-
]
|
|
387
|
-
}),
|
|
388
|
-
/*#__PURE__*/ _jsxs("button", {
|
|
389
|
-
type: "submit",
|
|
390
|
-
class: "btn",
|
|
391
|
-
"data-attr-disabled": "$_loading",
|
|
392
|
-
children: [
|
|
393
|
-
/*#__PURE__*/ _jsx("span", {
|
|
394
|
-
"data-show": "!$_loading",
|
|
395
|
-
children: $__i18n._({
|
|
396
|
-
id: "n1ekoW",
|
|
397
|
-
message: "Sign In"
|
|
398
|
-
})
|
|
399
|
-
}),
|
|
400
|
-
/*#__PURE__*/ _jsx("span", {
|
|
401
|
-
"data-show": "$_loading",
|
|
402
|
-
children: $__i18n._({
|
|
403
|
-
id: "k1ifdL",
|
|
404
|
-
message: "Processing..."
|
|
405
|
-
})
|
|
406
|
-
})
|
|
407
|
-
]
|
|
408
|
-
})
|
|
409
|
-
]
|
|
410
|
-
})
|
|
411
|
-
]
|
|
412
|
-
})
|
|
413
|
-
]
|
|
414
|
-
})
|
|
415
|
-
});
|
|
416
|
-
};
|
|
417
|
-
// Signin page
|
|
418
|
-
app.get("/signin", async (c)=>{
|
|
419
|
-
const isSetup = c.req.query("setup") !== undefined;
|
|
420
|
-
const isReset = c.req.query("reset") !== undefined;
|
|
421
|
-
let toast;
|
|
422
|
-
if (isSetup) {
|
|
423
|
-
toast = {
|
|
424
|
-
message: "Account created successfully. Please sign in."
|
|
425
|
-
};
|
|
426
|
-
} else if (isReset) {
|
|
427
|
-
toast = {
|
|
428
|
-
message: "Password reset successfully. Please sign in."
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
432
|
-
title: "Sign In - Jant",
|
|
433
|
-
c: c,
|
|
434
|
-
toast: toast,
|
|
435
|
-
children: /*#__PURE__*/ _jsx(SigninContent, {
|
|
436
|
-
demoEmail: c.env.DEMO_EMAIL,
|
|
437
|
-
demoPassword: c.env.DEMO_PASSWORD
|
|
438
|
-
})
|
|
439
|
-
}));
|
|
440
|
-
});
|
|
441
|
-
app.post("/signin", async (c)=>{
|
|
442
|
-
if (!c.var.auth) {
|
|
443
|
-
return dsToast("Auth not configured", "error");
|
|
444
|
-
}
|
|
445
|
-
const body = await c.req.json();
|
|
446
|
-
const { email, password } = body;
|
|
447
|
-
try {
|
|
448
|
-
const { headers } = await c.var.auth.api.signInEmail({
|
|
449
|
-
returnHeaders: true,
|
|
450
|
-
body: {
|
|
451
|
-
email,
|
|
452
|
-
password
|
|
453
|
-
},
|
|
454
|
-
headers: c.req.raw.headers
|
|
455
|
-
});
|
|
456
|
-
return dsRedirect("/dash", {
|
|
457
|
-
headers
|
|
458
|
-
});
|
|
459
|
-
} catch {
|
|
460
|
-
return dsToast("Invalid email or password", "error");
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
app.get("/signout", async (c)=>{
|
|
464
|
-
if (c.var.auth) {
|
|
465
|
-
try {
|
|
466
|
-
await c.var.auth.api.signOut({
|
|
467
|
-
headers: c.req.raw.headers
|
|
468
|
-
});
|
|
469
|
-
} catch {
|
|
470
|
-
// Ignore signout errors
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
return c.redirect("/");
|
|
474
|
-
});
|
|
475
|
-
// Password reset via one-time token
|
|
476
|
-
const ResetContent = ({ token })=>{
|
|
477
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
478
|
-
const signals = JSON.stringify({
|
|
479
|
-
password: "",
|
|
480
|
-
confirmPassword: "",
|
|
481
|
-
token
|
|
482
|
-
}).replace(/</g, "\\u003c");
|
|
483
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
484
|
-
class: "min-h-screen flex items-center justify-center",
|
|
485
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
486
|
-
class: "card max-w-md w-full",
|
|
487
|
-
children: [
|
|
488
|
-
/*#__PURE__*/ _jsxs("header", {
|
|
489
|
-
children: [
|
|
490
|
-
/*#__PURE__*/ _jsx("h2", {
|
|
491
|
-
children: $__i18n._({
|
|
492
|
-
id: "KbS2K9",
|
|
493
|
-
message: "Reset Password"
|
|
494
|
-
})
|
|
495
|
-
}),
|
|
496
|
-
/*#__PURE__*/ _jsx("p", {
|
|
497
|
-
children: $__i18n._({
|
|
498
|
-
id: "hWOZIv",
|
|
499
|
-
message: "Enter your new password."
|
|
500
|
-
})
|
|
501
|
-
})
|
|
502
|
-
]
|
|
503
|
-
}),
|
|
504
|
-
/*#__PURE__*/ _jsx("section", {
|
|
505
|
-
children: /*#__PURE__*/ _jsxs("form", {
|
|
506
|
-
"data-signals": signals,
|
|
507
|
-
"data-on:submit__prevent": "@post('/reset')",
|
|
508
|
-
"data-indicator": "_loading",
|
|
509
|
-
class: "flex flex-col gap-4",
|
|
510
|
-
children: [
|
|
511
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
512
|
-
class: "field",
|
|
513
|
-
children: [
|
|
514
|
-
/*#__PURE__*/ _jsx("label", {
|
|
515
|
-
class: "label",
|
|
516
|
-
children: $__i18n._({
|
|
517
|
-
id: "7vhWI8",
|
|
518
|
-
message: "New Password"
|
|
519
|
-
})
|
|
520
|
-
}),
|
|
521
|
-
/*#__PURE__*/ _jsx("input", {
|
|
522
|
-
type: "password",
|
|
523
|
-
"data-bind": "password",
|
|
524
|
-
class: "input",
|
|
525
|
-
required: true,
|
|
526
|
-
minLength: 8,
|
|
527
|
-
autocomplete: "new-password"
|
|
528
|
-
})
|
|
529
|
-
]
|
|
530
|
-
}),
|
|
531
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
532
|
-
class: "field",
|
|
533
|
-
children: [
|
|
534
|
-
/*#__PURE__*/ _jsx("label", {
|
|
535
|
-
class: "label",
|
|
536
|
-
children: $__i18n._({
|
|
537
|
-
id: "p2/GCq",
|
|
538
|
-
message: "Confirm Password"
|
|
539
|
-
})
|
|
540
|
-
}),
|
|
541
|
-
/*#__PURE__*/ _jsx("input", {
|
|
542
|
-
type: "password",
|
|
543
|
-
"data-bind": "confirmPassword",
|
|
544
|
-
class: "input",
|
|
545
|
-
required: true,
|
|
546
|
-
minLength: 8,
|
|
547
|
-
autocomplete: "new-password"
|
|
548
|
-
})
|
|
549
|
-
]
|
|
550
|
-
}),
|
|
551
|
-
/*#__PURE__*/ _jsxs("button", {
|
|
552
|
-
type: "submit",
|
|
553
|
-
class: "btn",
|
|
554
|
-
"data-attr-disabled": "$_loading",
|
|
555
|
-
children: [
|
|
556
|
-
/*#__PURE__*/ _jsx("span", {
|
|
557
|
-
"data-show": "!$_loading",
|
|
558
|
-
children: $__i18n._({
|
|
559
|
-
id: "KbS2K9",
|
|
560
|
-
message: "Reset Password"
|
|
561
|
-
})
|
|
562
|
-
}),
|
|
563
|
-
/*#__PURE__*/ _jsx("span", {
|
|
564
|
-
"data-show": "$_loading",
|
|
565
|
-
children: $__i18n._({
|
|
566
|
-
id: "k1ifdL",
|
|
567
|
-
message: "Processing..."
|
|
568
|
-
})
|
|
569
|
-
})
|
|
570
|
-
]
|
|
571
|
-
})
|
|
572
|
-
]
|
|
573
|
-
})
|
|
574
|
-
})
|
|
575
|
-
]
|
|
576
|
-
})
|
|
577
|
-
});
|
|
578
|
-
};
|
|
579
|
-
const ResetErrorContent = ()=>{
|
|
580
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
581
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
582
|
-
class: "min-h-screen flex items-center justify-center",
|
|
583
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
584
|
-
class: "card max-w-md w-full",
|
|
585
|
-
children: [
|
|
586
|
-
/*#__PURE__*/ _jsx("header", {
|
|
587
|
-
children: /*#__PURE__*/ _jsx("h2", {
|
|
588
|
-
children: $__i18n._({
|
|
589
|
-
id: "7aECQB",
|
|
590
|
-
message: "Invalid or Expired Link"
|
|
591
|
-
})
|
|
592
|
-
})
|
|
593
|
-
}),
|
|
594
|
-
/*#__PURE__*/ _jsx("section", {
|
|
595
|
-
children: /*#__PURE__*/ _jsx("p", {
|
|
596
|
-
class: "text-muted-foreground",
|
|
597
|
-
children: $__i18n._({
|
|
598
|
-
id: "GbVAnd",
|
|
599
|
-
message: "This password reset link is invalid or has expired. Please generate a new one."
|
|
600
|
-
})
|
|
601
|
-
})
|
|
602
|
-
})
|
|
603
|
-
]
|
|
604
|
-
})
|
|
605
|
-
});
|
|
606
|
-
};
|
|
607
|
-
app.get("/reset", async (c)=>{
|
|
608
|
-
const token = c.req.query("token");
|
|
609
|
-
if (!token) {
|
|
610
|
-
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
611
|
-
title: "Reset Password - Jant",
|
|
612
|
-
c: c,
|
|
613
|
-
children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
|
|
614
|
-
}));
|
|
615
|
-
}
|
|
616
|
-
const stored = await c.var.services.settings.get(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
617
|
-
if (!stored) {
|
|
618
|
-
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
619
|
-
title: "Reset Password - Jant",
|
|
620
|
-
c: c,
|
|
621
|
-
children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
|
|
622
|
-
}));
|
|
623
|
-
}
|
|
624
|
-
const separatorIndex = stored.lastIndexOf(":");
|
|
625
|
-
const storedToken = stored.substring(0, separatorIndex);
|
|
626
|
-
const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
|
|
627
|
-
const now = Math.floor(Date.now() / 1000);
|
|
628
|
-
if (token !== storedToken || now > expiry) {
|
|
629
|
-
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
630
|
-
title: "Reset Password - Jant",
|
|
631
|
-
c: c,
|
|
632
|
-
children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
|
|
633
|
-
}));
|
|
634
|
-
}
|
|
635
|
-
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
636
|
-
title: "Reset Password - Jant",
|
|
637
|
-
c: c,
|
|
638
|
-
children: /*#__PURE__*/ _jsx(ResetContent, {
|
|
639
|
-
token: token
|
|
640
|
-
})
|
|
641
|
-
}));
|
|
642
|
-
});
|
|
643
|
-
app.post("/reset", async (c)=>{
|
|
644
|
-
const body = await c.req.json();
|
|
645
|
-
const { password, confirmPassword, token } = body;
|
|
646
|
-
// Validate token
|
|
647
|
-
const stored = await c.var.services.settings.get(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
648
|
-
if (!stored) {
|
|
649
|
-
return dsToast("Invalid or expired reset link.", "error");
|
|
650
|
-
}
|
|
651
|
-
const separatorIndex = stored.lastIndexOf(":");
|
|
652
|
-
const storedToken = stored.substring(0, separatorIndex);
|
|
653
|
-
const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
|
|
654
|
-
const now = Math.floor(Date.now() / 1000);
|
|
655
|
-
if (token !== storedToken || now > expiry) {
|
|
656
|
-
return dsToast("Invalid or expired reset link.", "error");
|
|
657
|
-
}
|
|
658
|
-
// Validate passwords
|
|
659
|
-
if (!password || password.length < 8) {
|
|
660
|
-
return dsToast("Password must be at least 8 characters.", "error");
|
|
661
|
-
}
|
|
662
|
-
if (password !== confirmPassword) {
|
|
663
|
-
return dsToast("Passwords do not match.", "error");
|
|
664
|
-
}
|
|
665
|
-
try {
|
|
666
|
-
const hashedPassword = await hashPassword(password);
|
|
667
|
-
const db = c.env.DB.withSession();
|
|
668
|
-
// Get admin user
|
|
669
|
-
const userResult = await db.prepare("SELECT id FROM user LIMIT 1").first();
|
|
670
|
-
if (!userResult) {
|
|
671
|
-
return dsToast("No user account found.", "error");
|
|
672
|
-
}
|
|
673
|
-
// Update password
|
|
674
|
-
await db.prepare("UPDATE account SET password = ? WHERE user_id = ? AND provider_id = 'credential'").bind(hashedPassword, userResult.id).run();
|
|
675
|
-
// Delete all sessions
|
|
676
|
-
await db.prepare("DELETE FROM session WHERE user_id = ?").bind(userResult.id).run();
|
|
677
|
-
// Delete the reset token
|
|
678
|
-
await c.var.services.settings.remove(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
679
|
-
return dsRedirect("/signin?reset");
|
|
680
|
-
} catch (err) {
|
|
681
|
-
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
682
|
-
console.error("Password reset error:", err);
|
|
683
|
-
return dsToast("Failed to reset password.", "error");
|
|
684
|
-
}
|
|
685
|
-
});
|
|
209
|
+
app.route("/api/pages", pagesApiRoutes);
|
|
210
|
+
app.route("/api/nav-items", navItemsApiRoutes);
|
|
211
|
+
app.route("/api/collections", collectionsApiRoutes);
|
|
212
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
213
|
+
// Auth routes
|
|
214
|
+
app.route("/", setupRoutes);
|
|
215
|
+
app.route("/", signinRoutes);
|
|
216
|
+
app.route("/", resetRoutes);
|
|
686
217
|
// Dashboard routes (protected)
|
|
687
218
|
app.use("/dash/*", requireAuth());
|
|
688
219
|
app.route("/dash", dashIndexRoutes);
|
|
@@ -692,40 +223,39 @@ import { createStorageDriver } from "./lib/storage.js";
|
|
|
692
223
|
app.route("/dash/settings", dashSettingsRoutes);
|
|
693
224
|
app.route("/dash/redirects", dashRedirectsRoutes);
|
|
694
225
|
app.route("/dash/collections", dashCollectionsRoutes);
|
|
695
|
-
app.route("/dash/navigation", dashNavigationRoutes);
|
|
696
226
|
// API routes
|
|
697
227
|
app.route("/api/upload", uploadApiRoutes);
|
|
698
228
|
app.route("/api/search", searchApiRoutes);
|
|
699
|
-
// Media files from storage (
|
|
700
|
-
app.get("/media
|
|
229
|
+
// Media files from storage (path matches storage key: media/YYYY/MM/uuid.ext)
|
|
230
|
+
app.get("/media/*", async (c)=>{
|
|
701
231
|
const storage = c.var.storage;
|
|
702
232
|
if (!storage) {
|
|
703
233
|
return c.notFound();
|
|
704
234
|
}
|
|
705
|
-
//
|
|
706
|
-
const
|
|
707
|
-
const
|
|
708
|
-
const media = await c.var.services.media.getById(mediaId);
|
|
709
|
-
if (!media) {
|
|
710
|
-
return c.notFound();
|
|
711
|
-
}
|
|
712
|
-
const object = await storage.get(media.storageKey);
|
|
235
|
+
// The storage key is the full path without the leading "/"
|
|
236
|
+
const storageKey = c.req.path.slice(1);
|
|
237
|
+
const object = await storage.get(storageKey);
|
|
713
238
|
if (!object) {
|
|
714
239
|
return c.notFound();
|
|
715
240
|
}
|
|
716
241
|
const headers = new Headers();
|
|
717
|
-
headers.set("Content-Type", object.contentType ||
|
|
242
|
+
headers.set("Content-Type", object.contentType || "application/octet-stream");
|
|
718
243
|
headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
719
244
|
return new Response(object.body, {
|
|
720
245
|
headers
|
|
721
246
|
});
|
|
722
247
|
});
|
|
248
|
+
// Compose route (auth enforced in route middleware)
|
|
249
|
+
app.route("/compose", composeRoutes);
|
|
723
250
|
// Feed routes
|
|
724
251
|
app.route("/feed", rssRoutes);
|
|
725
252
|
app.route("/", sitemapRoutes);
|
|
726
253
|
// Frontend routes
|
|
727
254
|
app.route("/search", searchRoutes);
|
|
728
255
|
app.route("/archive", archiveRoutes);
|
|
256
|
+
app.route("/featured", featuredRoutes);
|
|
257
|
+
app.route("/latest", latestRoutes);
|
|
258
|
+
app.route("/collections", collectionsPageRoutes);
|
|
729
259
|
app.route("/c", collectionRoutes);
|
|
730
260
|
app.route("/p", postRoutes);
|
|
731
261
|
app.route("/", homeRoutes);
|