@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
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
3
|
+
import { settingsApiRoutes } from "../settings.js";
|
|
4
|
+
|
|
5
|
+
describe("Settings API Routes", () => {
|
|
6
|
+
describe("GET /api/settings", () => {
|
|
7
|
+
it("returns 401 when not authenticated", async () => {
|
|
8
|
+
const { app } = createTestApp({ authenticated: false });
|
|
9
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
10
|
+
|
|
11
|
+
const res = await app.request("/api/settings");
|
|
12
|
+
expect(res.status).toBe(401);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns default settings when none are stored", async () => {
|
|
16
|
+
const { app } = createTestApp({ authenticated: true });
|
|
17
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
18
|
+
|
|
19
|
+
const res = await app.request("/api/settings");
|
|
20
|
+
expect(res.status).toBe(200);
|
|
21
|
+
|
|
22
|
+
const body = await res.json();
|
|
23
|
+
expect(body.settings).toBeDefined();
|
|
24
|
+
expect(body.settings.SITE_NAME).toBe("Jant");
|
|
25
|
+
expect(body.settings.SITE_DESCRIPTION).toBe(
|
|
26
|
+
"A microblog powered by Jant",
|
|
27
|
+
);
|
|
28
|
+
expect(body.settings.SITE_LANGUAGE).toBe("en");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns stored settings overriding defaults", async () => {
|
|
32
|
+
const { app, services } = createTestApp({ authenticated: true });
|
|
33
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
34
|
+
|
|
35
|
+
await services.settings.set("SITE_NAME" as never, "My Blog");
|
|
36
|
+
|
|
37
|
+
const res = await app.request("/api/settings");
|
|
38
|
+
const body = await res.json();
|
|
39
|
+
|
|
40
|
+
expect(body.settings.SITE_NAME).toBe("My Blog");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("does not include env-only settings", async () => {
|
|
44
|
+
const { app } = createTestApp({ authenticated: true });
|
|
45
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
46
|
+
|
|
47
|
+
const res = await app.request("/api/settings");
|
|
48
|
+
const body = await res.json();
|
|
49
|
+
|
|
50
|
+
// Env-only keys should not be in the response
|
|
51
|
+
expect(body.settings.AUTH_SECRET).toBeUndefined();
|
|
52
|
+
expect(body.settings.SITE_URL).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("PUT /api/settings", () => {
|
|
57
|
+
it("returns 401 when not authenticated", async () => {
|
|
58
|
+
const { app } = createTestApp({ authenticated: false });
|
|
59
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
60
|
+
|
|
61
|
+
const res = await app.request("/api/settings", {
|
|
62
|
+
method: "PUT",
|
|
63
|
+
headers: { "Content-Type": "application/json" },
|
|
64
|
+
body: JSON.stringify({ SITE_NAME: "New Name" }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(res.status).toBe(401);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("updates editable settings", async () => {
|
|
71
|
+
const { app } = createTestApp({ authenticated: true });
|
|
72
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
73
|
+
|
|
74
|
+
const res = await app.request("/api/settings", {
|
|
75
|
+
method: "PUT",
|
|
76
|
+
headers: { "Content-Type": "application/json" },
|
|
77
|
+
body: JSON.stringify({ SITE_NAME: "Updated Blog" }),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(res.status).toBe(200);
|
|
81
|
+
const body = await res.json();
|
|
82
|
+
expect(body.settings.SITE_NAME).toBe("Updated Blog");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("rejects env-only keys", async () => {
|
|
86
|
+
const { app } = createTestApp({ authenticated: true });
|
|
87
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
88
|
+
|
|
89
|
+
const res = await app.request("/api/settings", {
|
|
90
|
+
method: "PUT",
|
|
91
|
+
headers: { "Content-Type": "application/json" },
|
|
92
|
+
body: JSON.stringify({ AUTH_SECRET: "should-not-work" }),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(res.status).toBe(400);
|
|
96
|
+
const body = await res.json();
|
|
97
|
+
expect(body.rejectedKeys).toContain("AUTH_SECRET");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("partially applies when mixing editable and env-only keys", async () => {
|
|
101
|
+
const { app } = createTestApp({ authenticated: true });
|
|
102
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
103
|
+
|
|
104
|
+
const res = await app.request("/api/settings", {
|
|
105
|
+
method: "PUT",
|
|
106
|
+
headers: { "Content-Type": "application/json" },
|
|
107
|
+
body: JSON.stringify({
|
|
108
|
+
SITE_NAME: "Mixed Update",
|
|
109
|
+
AUTH_SECRET: "ignored",
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(res.status).toBe(200);
|
|
114
|
+
const body = await res.json();
|
|
115
|
+
expect(body.settings.SITE_NAME).toBe("Mixed Update");
|
|
116
|
+
expect(body.rejectedKeys).toContain("AUTH_SECRET");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("returns 400 for invalid body", async () => {
|
|
120
|
+
const { app } = createTestApp({ authenticated: true });
|
|
121
|
+
app.route("/api/settings", settingsApiRoutes);
|
|
122
|
+
|
|
123
|
+
const res = await app.request("/api/settings", {
|
|
124
|
+
method: "PUT",
|
|
125
|
+
headers: { "Content-Type": "application/json" },
|
|
126
|
+
body: JSON.stringify("not an object"),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(res.status).toBe(400);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collections API Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import type { Bindings, SortOrder } from "../../types.js";
|
|
7
|
+
import type { AppVariables } from "../../app.js";
|
|
8
|
+
import { requireAuthApi } from "../../middleware/auth.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { SORT_ORDERS } from "../../types.js";
|
|
11
|
+
|
|
12
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
13
|
+
|
|
14
|
+
export const collectionsApiRoutes = new Hono<Env>();
|
|
15
|
+
|
|
16
|
+
const SortOrderSchema = z.enum(SORT_ORDERS);
|
|
17
|
+
|
|
18
|
+
const CreateCollectionSchema = z.object({
|
|
19
|
+
slug: z.string().min(1),
|
|
20
|
+
title: z.string().min(1),
|
|
21
|
+
description: z.string().optional(),
|
|
22
|
+
icon: z.string().optional(),
|
|
23
|
+
sortOrder: SortOrderSchema.optional(),
|
|
24
|
+
position: z.number().int().min(0).optional(),
|
|
25
|
+
showDivider: z.boolean().optional(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const UpdateCollectionSchema = z.object({
|
|
29
|
+
slug: z.string().min(1).optional(),
|
|
30
|
+
title: z.string().min(1).optional(),
|
|
31
|
+
description: z.string().nullable().optional(),
|
|
32
|
+
icon: z.string().nullable().optional(),
|
|
33
|
+
sortOrder: SortOrderSchema.optional(),
|
|
34
|
+
position: z.number().int().min(0).optional(),
|
|
35
|
+
showDivider: z.boolean().optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const ReorderSchema = z.object({
|
|
39
|
+
ids: z.array(z.number().int().positive()),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// List collections (includes post counts)
|
|
43
|
+
collectionsApiRoutes.get("/", async (c) => {
|
|
44
|
+
const collections = await c.var.services.collections.list();
|
|
45
|
+
const postCounts = await c.var.services.collections.getPostCounts();
|
|
46
|
+
|
|
47
|
+
return c.json({
|
|
48
|
+
collections: collections.map((col) => ({
|
|
49
|
+
...col,
|
|
50
|
+
postCount: postCounts.get(col.id) ?? 0,
|
|
51
|
+
})),
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Get single collection
|
|
56
|
+
collectionsApiRoutes.get("/:id", async (c) => {
|
|
57
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
58
|
+
if (isNaN(id)) return c.json({ error: "Invalid ID" }, 400);
|
|
59
|
+
|
|
60
|
+
const collection = await c.var.services.collections.getById(id);
|
|
61
|
+
if (!collection) return c.json({ error: "Not found" }, 404);
|
|
62
|
+
|
|
63
|
+
return c.json(collection);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Reorder collections (requires auth) — must be before /:id
|
|
67
|
+
collectionsApiRoutes.put("/reorder", requireAuthApi(), async (c) => {
|
|
68
|
+
const rawBody = await c.req.json();
|
|
69
|
+
|
|
70
|
+
const parseResult = ReorderSchema.safeParse(rawBody);
|
|
71
|
+
if (!parseResult.success) {
|
|
72
|
+
return c.json(
|
|
73
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
74
|
+
400,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await c.var.services.collections.reorder(parseResult.data.ids);
|
|
79
|
+
const collections = await c.var.services.collections.list();
|
|
80
|
+
return c.json({ collections });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Create collection (requires auth)
|
|
84
|
+
collectionsApiRoutes.post("/", requireAuthApi(), async (c) => {
|
|
85
|
+
const rawBody = await c.req.json();
|
|
86
|
+
|
|
87
|
+
const parseResult = CreateCollectionSchema.safeParse(rawBody);
|
|
88
|
+
if (!parseResult.success) {
|
|
89
|
+
return c.json(
|
|
90
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
91
|
+
400,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const body = parseResult.data;
|
|
96
|
+
|
|
97
|
+
const collection = await c.var.services.collections.create({
|
|
98
|
+
slug: body.slug,
|
|
99
|
+
title: body.title,
|
|
100
|
+
description: body.description,
|
|
101
|
+
icon: body.icon,
|
|
102
|
+
sortOrder: body.sortOrder as SortOrder | undefined,
|
|
103
|
+
position: body.position,
|
|
104
|
+
showDivider: body.showDivider,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return c.json(collection, 201);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Update collection (requires auth)
|
|
111
|
+
collectionsApiRoutes.put("/:id", requireAuthApi(), async (c) => {
|
|
112
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
113
|
+
if (isNaN(id)) return c.json({ error: "Invalid ID" }, 400);
|
|
114
|
+
|
|
115
|
+
const rawBody = await c.req.json();
|
|
116
|
+
|
|
117
|
+
const parseResult = UpdateCollectionSchema.safeParse(rawBody);
|
|
118
|
+
if (!parseResult.success) {
|
|
119
|
+
return c.json(
|
|
120
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
121
|
+
400,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const collection = await c.var.services.collections.update(
|
|
126
|
+
id,
|
|
127
|
+
parseResult.data,
|
|
128
|
+
);
|
|
129
|
+
if (!collection) return c.json({ error: "Not found" }, 404);
|
|
130
|
+
|
|
131
|
+
return c.json(collection);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Delete collection (requires auth)
|
|
135
|
+
collectionsApiRoutes.delete("/:id", requireAuthApi(), async (c) => {
|
|
136
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
137
|
+
if (isNaN(id)) return c.json({ error: "Invalid ID" }, 400);
|
|
138
|
+
|
|
139
|
+
const success = await c.var.services.collections.delete(id);
|
|
140
|
+
if (!success) return c.json({ error: "Not found" }, 404);
|
|
141
|
+
|
|
142
|
+
return c.json({ success: true });
|
|
143
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nav Items API Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import type { Bindings, NavItemType } from "../../types.js";
|
|
7
|
+
import type { AppVariables } from "../../app.js";
|
|
8
|
+
import { requireAuthApi } from "../../middleware/auth.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
12
|
+
|
|
13
|
+
export const navItemsApiRoutes = new Hono<Env>();
|
|
14
|
+
|
|
15
|
+
const NavItemTypeSchema = z.enum(["link", "page"]);
|
|
16
|
+
|
|
17
|
+
const CreateNavItemSchema = z.object({
|
|
18
|
+
type: NavItemTypeSchema,
|
|
19
|
+
label: z.string().min(1),
|
|
20
|
+
url: z.string().min(1),
|
|
21
|
+
pageId: z.number().int().positive().optional(),
|
|
22
|
+
position: z.number().int().min(0).optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const UpdateNavItemSchema = z.object({
|
|
26
|
+
type: NavItemTypeSchema.optional(),
|
|
27
|
+
label: z.string().min(1).optional(),
|
|
28
|
+
url: z.string().min(1).optional(),
|
|
29
|
+
pageId: z.number().int().positive().nullable().optional(),
|
|
30
|
+
position: z.number().int().min(0).optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const ReorderSchema = z.object({
|
|
34
|
+
ids: z.array(z.number().int().positive()),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// List nav items
|
|
38
|
+
navItemsApiRoutes.get("/", async (c) => {
|
|
39
|
+
const items = await c.var.services.navItems.list();
|
|
40
|
+
return c.json({ navItems: items });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Reorder nav items (requires auth) — must be before /:id
|
|
44
|
+
navItemsApiRoutes.put("/reorder", requireAuthApi(), async (c) => {
|
|
45
|
+
const rawBody = await c.req.json();
|
|
46
|
+
|
|
47
|
+
const parseResult = ReorderSchema.safeParse(rawBody);
|
|
48
|
+
if (!parseResult.success) {
|
|
49
|
+
return c.json(
|
|
50
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
51
|
+
400,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await c.var.services.navItems.reorder(parseResult.data.ids);
|
|
56
|
+
const items = await c.var.services.navItems.list();
|
|
57
|
+
return c.json({ navItems: items });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Create nav item (requires auth)
|
|
61
|
+
navItemsApiRoutes.post("/", requireAuthApi(), async (c) => {
|
|
62
|
+
const rawBody = await c.req.json();
|
|
63
|
+
|
|
64
|
+
const parseResult = CreateNavItemSchema.safeParse(rawBody);
|
|
65
|
+
if (!parseResult.success) {
|
|
66
|
+
return c.json(
|
|
67
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
68
|
+
400,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const body = parseResult.data;
|
|
73
|
+
|
|
74
|
+
const item = await c.var.services.navItems.create({
|
|
75
|
+
type: body.type as NavItemType,
|
|
76
|
+
label: body.label,
|
|
77
|
+
url: body.url,
|
|
78
|
+
pageId: body.pageId,
|
|
79
|
+
position: body.position,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return c.json(item, 201);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Update nav item (requires auth)
|
|
86
|
+
navItemsApiRoutes.put("/:id", requireAuthApi(), async (c) => {
|
|
87
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
88
|
+
if (isNaN(id)) return c.json({ error: "Invalid ID" }, 400);
|
|
89
|
+
|
|
90
|
+
const rawBody = await c.req.json();
|
|
91
|
+
|
|
92
|
+
const parseResult = UpdateNavItemSchema.safeParse(rawBody);
|
|
93
|
+
if (!parseResult.success) {
|
|
94
|
+
return c.json(
|
|
95
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
96
|
+
400,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const item = await c.var.services.navItems.update(id, parseResult.data);
|
|
101
|
+
if (!item) return c.json({ error: "Not found" }, 404);
|
|
102
|
+
|
|
103
|
+
return c.json(item);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Delete nav item (requires auth)
|
|
107
|
+
navItemsApiRoutes.delete("/:id", requireAuthApi(), async (c) => {
|
|
108
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
109
|
+
if (isNaN(id)) return c.json({ error: "Invalid ID" }, 400);
|
|
110
|
+
|
|
111
|
+
const success = await c.var.services.navItems.delete(id);
|
|
112
|
+
if (!success) return c.json({ error: "Not found" }, 404);
|
|
113
|
+
|
|
114
|
+
return c.json({ success: true });
|
|
115
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pages API Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import type { Bindings } from "../../types.js";
|
|
7
|
+
import type { AppVariables } from "../../app.js";
|
|
8
|
+
import { requireAuthApi } from "../../middleware/auth.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { StatusSchema } from "../../lib/schemas.js";
|
|
11
|
+
|
|
12
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
13
|
+
|
|
14
|
+
export const pagesApiRoutes = new Hono<Env>();
|
|
15
|
+
|
|
16
|
+
const CreatePageSchema = z.object({
|
|
17
|
+
slug: z.string().min(1),
|
|
18
|
+
title: z.string().optional(),
|
|
19
|
+
body: z.string().optional(),
|
|
20
|
+
status: StatusSchema.optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const UpdatePageSchema = z.object({
|
|
24
|
+
slug: z.string().min(1).optional(),
|
|
25
|
+
title: z.string().nullable().optional(),
|
|
26
|
+
body: z.string().nullable().optional(),
|
|
27
|
+
status: StatusSchema.optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// List pages
|
|
31
|
+
pagesApiRoutes.get("/", async (c) => {
|
|
32
|
+
const pages = await c.var.services.pages.list();
|
|
33
|
+
return c.json({ pages });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Get single page
|
|
37
|
+
pagesApiRoutes.get("/:id", async (c) => {
|
|
38
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
39
|
+
if (isNaN(id)) return c.json({ error: "Invalid ID" }, 400);
|
|
40
|
+
|
|
41
|
+
const page = await c.var.services.pages.getById(id);
|
|
42
|
+
if (!page) return c.json({ error: "Not found" }, 404);
|
|
43
|
+
|
|
44
|
+
return c.json(page);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create page (requires auth)
|
|
48
|
+
pagesApiRoutes.post("/", requireAuthApi(), async (c) => {
|
|
49
|
+
const rawBody = await c.req.json();
|
|
50
|
+
|
|
51
|
+
const parseResult = CreatePageSchema.safeParse(rawBody);
|
|
52
|
+
if (!parseResult.success) {
|
|
53
|
+
return c.json(
|
|
54
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
55
|
+
400,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const body = parseResult.data;
|
|
60
|
+
|
|
61
|
+
const page = await c.var.services.pages.create({
|
|
62
|
+
slug: body.slug,
|
|
63
|
+
title: body.title,
|
|
64
|
+
body: body.body,
|
|
65
|
+
status: body.status,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return c.json(page, 201);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Update page (requires auth)
|
|
72
|
+
pagesApiRoutes.put("/:id", requireAuthApi(), async (c) => {
|
|
73
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
74
|
+
if (isNaN(id)) return c.json({ error: "Invalid ID" }, 400);
|
|
75
|
+
|
|
76
|
+
const rawBody = await c.req.json();
|
|
77
|
+
|
|
78
|
+
const parseResult = UpdatePageSchema.safeParse(rawBody);
|
|
79
|
+
if (!parseResult.success) {
|
|
80
|
+
return c.json(
|
|
81
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
82
|
+
400,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const page = await c.var.services.pages.update(id, parseResult.data);
|
|
87
|
+
if (!page) return c.json({ error: "Not found" }, 404);
|
|
88
|
+
|
|
89
|
+
return c.json(page);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Delete page (requires auth)
|
|
93
|
+
pagesApiRoutes.delete("/:id", requireAuthApi(), async (c) => {
|
|
94
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
95
|
+
if (isNaN(id)) return c.json({ error: "Invalid ID" }, 400);
|
|
96
|
+
|
|
97
|
+
const success = await c.var.services.pages.delete(id);
|
|
98
|
+
if (!success) return c.json({ error: "Not found" }, 404);
|
|
99
|
+
|
|
100
|
+
return c.json({ success: true });
|
|
101
|
+
});
|
package/src/routes/api/posts.ts
CHANGED
|
@@ -36,7 +36,7 @@ function toMediaAttachment(
|
|
|
36
36
|
r2PublicUrl,
|
|
37
37
|
s3PublicUrl,
|
|
38
38
|
);
|
|
39
|
-
const url = getMediaUrl(m.
|
|
39
|
+
const url = getMediaUrl(m.storageKey, publicUrl);
|
|
40
40
|
const previewUrl = getImageUrl(url, imageTransformUrl, {
|
|
41
41
|
width: 400,
|
|
42
42
|
quality: 80,
|
|
@@ -151,7 +151,7 @@ postsApiRoutes.post("/", requireAuthApi(), async (c) => {
|
|
|
151
151
|
format: body.format,
|
|
152
152
|
title: body.title,
|
|
153
153
|
body: body.body,
|
|
154
|
-
|
|
154
|
+
path: body.path || undefined,
|
|
155
155
|
status: body.status,
|
|
156
156
|
featured: body.featured,
|
|
157
157
|
pinned: body.pinned,
|
|
@@ -225,7 +225,7 @@ postsApiRoutes.put("/:id", requireAuthApi(), async (c) => {
|
|
|
225
225
|
format: body.format,
|
|
226
226
|
title: body.title,
|
|
227
227
|
body: body.body,
|
|
228
|
-
|
|
228
|
+
path: body.path,
|
|
229
229
|
status: body.status,
|
|
230
230
|
featured: body.featured,
|
|
231
231
|
pinned: body.pinned,
|
package/src/routes/api/search.ts
CHANGED
|
@@ -38,10 +38,10 @@ searchApiRoutes.get("/", async (c) => {
|
|
|
38
38
|
id: sqid.encode(r.post.id),
|
|
39
39
|
format: r.post.format,
|
|
40
40
|
title: r.post.title,
|
|
41
|
-
|
|
41
|
+
path: r.post.path,
|
|
42
42
|
snippet: r.snippet,
|
|
43
43
|
publishedAt: r.post.publishedAt,
|
|
44
|
-
url: r.post.
|
|
44
|
+
url: r.post.path ? `/${r.post.path}` : `/p/${sqid.encode(r.post.id)}`,
|
|
45
45
|
})),
|
|
46
46
|
count: results.length,
|
|
47
47
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings API Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import type { Bindings } from "../../types.js";
|
|
7
|
+
import type { AppVariables } from "../../app.js";
|
|
8
|
+
import { requireAuthApi } from "../../middleware/auth.js";
|
|
9
|
+
import { CONFIG_FIELDS, type ConfigKey } from "../../types.js";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
13
|
+
|
|
14
|
+
export const settingsApiRoutes = new Hono<Env>();
|
|
15
|
+
|
|
16
|
+
/** Config keys that can be modified via the settings API */
|
|
17
|
+
const editableKeys = Object.entries(CONFIG_FIELDS)
|
|
18
|
+
.filter(([, field]) => !field.envOnly)
|
|
19
|
+
.map(([key]) => key as ConfigKey);
|
|
20
|
+
|
|
21
|
+
const UpdateSettingsSchema = z.record(z.string(), z.string());
|
|
22
|
+
|
|
23
|
+
// Get all settings (requires auth)
|
|
24
|
+
settingsApiRoutes.get("/", requireAuthApi(), async (c) => {
|
|
25
|
+
const allSettings = await c.var.services.settings.getAll();
|
|
26
|
+
|
|
27
|
+
// Include default values for editable keys not yet stored in DB
|
|
28
|
+
const result: Record<string, string> = {};
|
|
29
|
+
for (const key of editableKeys) {
|
|
30
|
+
result[key] = allSettings[key] ?? CONFIG_FIELDS[key].defaultValue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return c.json({ settings: result });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Update settings (requires auth)
|
|
37
|
+
settingsApiRoutes.put("/", requireAuthApi(), async (c) => {
|
|
38
|
+
const rawBody = await c.req.json();
|
|
39
|
+
|
|
40
|
+
const parseResult = UpdateSettingsSchema.safeParse(rawBody);
|
|
41
|
+
if (!parseResult.success) {
|
|
42
|
+
return c.json(
|
|
43
|
+
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
44
|
+
400,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const updates = parseResult.data;
|
|
49
|
+
|
|
50
|
+
// Filter to only editable keys
|
|
51
|
+
const filteredUpdates: Partial<Record<ConfigKey, string>> = {};
|
|
52
|
+
const rejectedKeys: string[] = [];
|
|
53
|
+
|
|
54
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
55
|
+
if (editableKeys.includes(key as ConfigKey)) {
|
|
56
|
+
filteredUpdates[key as ConfigKey] = value;
|
|
57
|
+
} else {
|
|
58
|
+
rejectedKeys.push(key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (rejectedKeys.length > 0 && Object.keys(filteredUpdates).length === 0) {
|
|
63
|
+
return c.json(
|
|
64
|
+
{
|
|
65
|
+
error: "None of the provided keys are editable",
|
|
66
|
+
rejectedKeys,
|
|
67
|
+
},
|
|
68
|
+
400,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Object.keys(filteredUpdates).length > 0) {
|
|
73
|
+
// Settings service expects SettingsKey, but our ConfigKeys that are
|
|
74
|
+
// editable (SITE_NAME, SITE_DESCRIPTION, SITE_LANGUAGE) are valid SettingsKeys
|
|
75
|
+
for (const [key, value] of Object.entries(filteredUpdates)) {
|
|
76
|
+
await c.var.services.settings.set(key as never, value as string);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Return updated state
|
|
81
|
+
const allSettings = await c.var.services.settings.getAll();
|
|
82
|
+
const result: Record<string, string> = {};
|
|
83
|
+
for (const key of editableKeys) {
|
|
84
|
+
result[key] = allSettings[key] ?? CONFIG_FIELDS[key].defaultValue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return c.json({
|
|
88
|
+
settings: result,
|
|
89
|
+
...(rejectedKeys.length > 0 && { rejectedKeys }),
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/routes/api/upload.ts
CHANGED
|
@@ -40,7 +40,7 @@ function renderMediaCard(
|
|
|
40
40
|
publicUrl?: string,
|
|
41
41
|
imageTransformUrl?: string,
|
|
42
42
|
): string {
|
|
43
|
-
const fullUrl = getMediaUrl(media.
|
|
43
|
+
const fullUrl = getMediaUrl(media.storageKey, publicUrl);
|
|
44
44
|
const thumbnailUrl = getImageUrl(fullUrl, imageTransformUrl, {
|
|
45
45
|
width: 300,
|
|
46
46
|
quality: 80,
|
|
@@ -229,7 +229,7 @@ uploadApiRoutes.post("/", async (c) => {
|
|
|
229
229
|
c.env.R2_PUBLIC_URL,
|
|
230
230
|
c.env.S3_PUBLIC_URL,
|
|
231
231
|
);
|
|
232
|
-
const publicUrl = getMediaUrl(
|
|
232
|
+
const publicUrl = getMediaUrl(storageKey, mediaPublicUrl);
|
|
233
233
|
return c.json({
|
|
234
234
|
id: media.id,
|
|
235
235
|
filename: media.filename,
|
|
@@ -263,7 +263,6 @@ uploadApiRoutes.get("/", async (c) => {
|
|
|
263
263
|
id: m.id,
|
|
264
264
|
filename: m.filename,
|
|
265
265
|
url: getMediaUrl(
|
|
266
|
-
m.id,
|
|
267
266
|
m.storageKey,
|
|
268
267
|
getPublicUrlForProvider(m.provider, r2PublicUrl, s3PublicUrl),
|
|
269
268
|
),
|