@jant/core 0.1.2 → 0.2.0
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.d.ts +34 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +474 -0
- package/dist/assets/datastar.min.js +1775 -0
- package/dist/auth.d.ts +23 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +34 -0
- package/{src/client.ts → dist/client.d.ts} +1 -1
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +4 -0
- package/dist/db/index.d.ts +10 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +10 -0
- package/dist/db/schema.d.ts +1507 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +183 -0
- package/{src/i18n/Trans.tsx → dist/i18n/Trans.d.ts} +4 -10
- package/dist/i18n/Trans.d.ts.map +1 -0
- package/dist/i18n/Trans.js +24 -0
- package/dist/i18n/context.d.ts +69 -0
- package/dist/i18n/context.d.ts.map +1 -0
- package/dist/i18n/context.js +61 -0
- package/dist/i18n/detect.d.ts +31 -0
- package/dist/i18n/detect.d.ts.map +1 -0
- package/dist/i18n/detect.js +77 -0
- package/{src/i18n/i18n.ts → dist/i18n/i18n.d.ts} +5 -25
- package/dist/i18n/i18n.d.ts.map +1 -0
- package/dist/i18n/i18n.js +55 -0
- package/dist/i18n/index.d.ts +41 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/{src/i18n/index.ts → dist/i18n/index.js} +3 -24
- package/dist/i18n/locales/en.d.ts +3 -0
- package/dist/i18n/locales/en.d.ts.map +1 -0
- package/dist/i18n/locales/en.js +1 -0
- package/dist/i18n/locales/zh-Hans.d.ts +3 -0
- package/dist/i18n/locales/zh-Hans.d.ts.map +1 -0
- package/dist/i18n/locales/zh-Hans.js +1 -0
- package/dist/i18n/locales/zh-Hant.d.ts +3 -0
- package/dist/i18n/locales/zh-Hant.d.ts.map +1 -0
- package/dist/i18n/locales/zh-Hant.js +1 -0
- package/dist/i18n/locales.d.ts +11 -0
- package/dist/i18n/locales.d.ts.map +1 -0
- package/dist/i18n/locales.js +13 -0
- package/dist/i18n/middleware.d.ts +24 -0
- package/dist/i18n/middleware.d.ts.map +1 -0
- package/dist/i18n/middleware.js +41 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +1 -28
- package/dist/lib/assets.d.ts +19 -0
- package/dist/lib/assets.d.ts.map +1 -0
- package/dist/lib/assets.js +33 -0
- package/dist/lib/constants.d.ts +36 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +50 -0
- package/{src/lib/image.ts → dist/lib/image.d.ts} +13 -47
- package/dist/lib/image.d.ts.map +1 -0
- package/dist/lib/image.js +77 -0
- package/{src/lib/index.ts → dist/lib/index.d.ts} +1 -1
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +7 -0
- package/dist/lib/markdown.d.ts +60 -0
- package/dist/lib/markdown.d.ts.map +1 -0
- package/{src/lib/markdown.ts → dist/lib/markdown.js} +16 -26
- package/dist/lib/schemas.d.ts +113 -0
- package/dist/lib/schemas.d.ts.map +1 -0
- package/dist/lib/schemas.js +71 -0
- package/dist/lib/sqid.d.ts +60 -0
- package/dist/lib/sqid.d.ts.map +1 -0
- package/{src/lib/sqid.ts → dist/lib/sqid.js} +15 -22
- package/dist/lib/sse.d.ts +95 -0
- package/dist/lib/sse.d.ts.map +1 -0
- package/dist/lib/sse.js +81 -0
- package/dist/lib/time.d.ts +90 -0
- package/dist/lib/time.d.ts.map +1 -0
- package/{src/lib/time.ts → dist/lib/time.js} +20 -33
- package/{src/lib/url.ts → dist/lib/url.d.ts} +5 -30
- package/dist/lib/url.d.ts.map +1 -0
- package/dist/lib/url.js +89 -0
- package/dist/middleware/auth.d.ts +24 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +52 -0
- package/dist/routes/api/posts.d.ts +13 -0
- package/dist/routes/api/posts.d.ts.map +1 -0
- package/dist/routes/api/posts.js +124 -0
- package/dist/routes/api/search.d.ts +13 -0
- package/dist/routes/api/search.d.ts.map +1 -0
- package/dist/routes/api/search.js +49 -0
- package/dist/routes/api/upload.d.ts +16 -0
- package/dist/routes/api/upload.d.ts.map +1 -0
- package/dist/routes/api/upload.js +227 -0
- package/dist/routes/dash/collections.d.ts +13 -0
- package/dist/routes/dash/collections.d.ts.map +1 -0
- package/dist/routes/dash/collections.js +512 -0
- package/dist/routes/dash/index.d.ts +15 -0
- package/dist/routes/dash/index.d.ts.map +1 -0
- package/dist/routes/dash/index.js +117 -0
- package/dist/routes/dash/media.d.ts +16 -0
- package/dist/routes/dash/media.d.ts.map +1 -0
- package/dist/routes/dash/media.js +589 -0
- package/dist/routes/dash/pages.d.ts +15 -0
- package/dist/routes/dash/pages.d.ts.map +1 -0
- package/dist/routes/dash/pages.js +290 -0
- package/dist/routes/dash/posts.d.ts +13 -0
- package/dist/routes/dash/posts.d.ts.map +1 -0
- package/dist/routes/dash/posts.js +226 -0
- package/dist/routes/dash/redirects.d.ts +13 -0
- package/dist/routes/dash/redirects.d.ts.map +1 -0
- package/dist/routes/dash/redirects.js +237 -0
- package/dist/routes/dash/settings.d.ts +13 -0
- package/dist/routes/dash/settings.d.ts.map +1 -0
- package/dist/routes/dash/settings.js +154 -0
- package/dist/routes/feed/rss.d.ts +13 -0
- package/dist/routes/feed/rss.d.ts.map +1 -0
- package/dist/routes/feed/rss.js +95 -0
- package/dist/routes/feed/sitemap.d.ts +13 -0
- package/dist/routes/feed/sitemap.d.ts.map +1 -0
- package/dist/routes/feed/sitemap.js +59 -0
- package/dist/routes/pages/archive.d.ts +15 -0
- package/dist/routes/pages/archive.d.ts.map +1 -0
- package/dist/routes/pages/archive.js +255 -0
- package/dist/routes/pages/collection.d.ts +13 -0
- package/dist/routes/pages/collection.d.ts.map +1 -0
- package/dist/routes/pages/collection.js +93 -0
- package/dist/routes/pages/home.d.ts +13 -0
- package/dist/routes/pages/home.d.ts.map +1 -0
- package/dist/routes/pages/home.js +122 -0
- package/dist/routes/pages/page.d.ts +15 -0
- package/dist/routes/pages/page.d.ts.map +1 -0
- package/dist/routes/pages/page.js +69 -0
- package/dist/routes/pages/post.d.ts +13 -0
- package/dist/routes/pages/post.d.ts.map +1 -0
- package/dist/routes/pages/post.js +90 -0
- package/dist/routes/pages/search.d.ts +13 -0
- package/dist/routes/pages/search.d.ts.map +1 -0
- package/dist/routes/pages/search.js +180 -0
- package/dist/services/collection.d.ts +31 -0
- package/dist/services/collection.d.ts.map +1 -0
- package/dist/services/collection.js +108 -0
- package/dist/services/index.d.ts +28 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +20 -0
- package/dist/services/media.d.ts +27 -0
- package/dist/services/media.d.ts.map +1 -0
- package/dist/services/media.js +62 -0
- package/dist/services/post.d.ts +31 -0
- package/dist/services/post.d.ts.map +1 -0
- package/dist/services/post.js +191 -0
- package/dist/services/redirect.d.ts +15 -0
- package/dist/services/redirect.d.ts.map +1 -0
- package/dist/services/redirect.js +48 -0
- package/dist/services/search.d.ts +26 -0
- package/dist/services/search.d.ts.map +1 -0
- package/dist/services/search.js +61 -0
- package/dist/services/settings.d.ts +17 -0
- package/dist/services/settings.d.ts.map +1 -0
- package/dist/services/settings.js +65 -0
- package/dist/theme/components/ActionButtons.d.ts +43 -0
- package/dist/theme/components/ActionButtons.d.ts.map +1 -0
- package/dist/theme/components/ActionButtons.js +50 -0
- package/dist/theme/components/CrudPageHeader.d.ts +23 -0
- package/dist/theme/components/CrudPageHeader.d.ts.map +1 -0
- package/dist/theme/components/CrudPageHeader.js +22 -0
- package/dist/theme/components/DangerZone.d.ts +36 -0
- package/dist/theme/components/DangerZone.d.ts.map +1 -0
- package/dist/theme/components/DangerZone.js +39 -0
- package/dist/theme/components/EmptyState.d.ts +27 -0
- package/dist/theme/components/EmptyState.d.ts.map +1 -0
- package/dist/theme/components/EmptyState.js +27 -0
- package/dist/theme/components/ListItemRow.d.ts +15 -0
- package/dist/theme/components/ListItemRow.d.ts.map +1 -0
- package/dist/theme/components/ListItemRow.js +21 -0
- package/dist/theme/components/PageForm.d.ts +14 -0
- package/dist/theme/components/PageForm.d.ts.map +1 -0
- package/dist/theme/components/PageForm.js +173 -0
- package/dist/theme/components/Pagination.d.ts +46 -0
- package/dist/theme/components/Pagination.d.ts.map +1 -0
- package/dist/theme/components/Pagination.js +159 -0
- package/dist/theme/components/PostForm.d.ts +12 -0
- package/dist/theme/components/PostForm.d.ts.map +1 -0
- package/dist/theme/components/PostForm.js +230 -0
- package/dist/theme/components/PostList.d.ts +10 -0
- package/dist/theme/components/PostList.d.ts.map +1 -0
- package/dist/theme/components/PostList.js +73 -0
- package/dist/theme/components/ThreadView.d.ts +15 -0
- package/dist/theme/components/ThreadView.d.ts.map +1 -0
- package/dist/theme/components/ThreadView.js +111 -0
- package/dist/theme/components/TypeBadge.d.ts +12 -0
- package/dist/theme/components/TypeBadge.d.ts.map +1 -0
- package/dist/theme/components/TypeBadge.js +39 -0
- package/dist/theme/components/VisibilityBadge.d.ts +12 -0
- package/dist/theme/components/VisibilityBadge.d.ts.map +1 -0
- package/dist/theme/components/VisibilityBadge.js +37 -0
- package/{src/theme/components/index.ts → dist/theme/components/index.d.ts} +1 -0
- package/dist/theme/components/index.d.ts.map +1 -0
- package/dist/theme/components/index.js +12 -0
- package/dist/theme/index.d.ts +21 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/{src/theme/index.ts → dist/theme/index.js} +1 -4
- package/dist/theme/layouts/BaseLayout.d.ts +16 -0
- package/dist/theme/layouts/BaseLayout.d.ts.map +1 -0
- package/dist/theme/layouts/BaseLayout.js +58 -0
- package/dist/theme/layouts/DashLayout.d.ts +15 -0
- package/dist/theme/layouts/DashLayout.d.ts.map +1 -0
- package/dist/theme/layouts/DashLayout.js +139 -0
- package/{src/theme/layouts/index.ts → dist/theme/layouts/index.d.ts} +1 -0
- package/dist/theme/layouts/index.d.ts.map +1 -0
- package/dist/theme/layouts/index.js +2 -0
- package/dist/theme/styles/main.css +2 -0
- package/dist/types.d.ts +179 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/package.json +26 -26
- package/drizzle.config.ts +0 -10
- package/lingui.config.ts +0 -16
- package/src/app.tsx +0 -377
- package/src/assets/datastar.min.js +0 -8
- package/src/auth.ts +0 -38
- package/src/db/index.ts +0 -14
- package/src/db/migrations/0000_solid_moon_knight.sql +0 -118
- package/src/db/migrations/0001_add_search_fts.sql +0 -40
- package/src/db/migrations/0002_collection_path.sql +0 -2
- package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
- package/src/db/migrations/0004_media_uuid.sql +0 -35
- package/src/db/migrations/meta/0000_snapshot.json +0 -784
- package/src/db/migrations/meta/_journal.json +0 -41
- package/src/db/schema.ts +0 -159
- package/src/i18n/EXAMPLES.md +0 -235
- package/src/i18n/README.md +0 -296
- package/src/i18n/context.tsx +0 -101
- package/src/i18n/detect.ts +0 -100
- package/src/i18n/locales/en.po +0 -875
- package/src/i18n/locales/en.ts +0 -1
- package/src/i18n/locales/zh-Hans.po +0 -875
- package/src/i18n/locales/zh-Hans.ts +0 -1
- package/src/i18n/locales/zh-Hant.po +0 -875
- package/src/i18n/locales/zh-Hant.ts +0 -1
- package/src/i18n/locales.ts +0 -14
- package/src/i18n/middleware.ts +0 -59
- package/src/lib/assets.ts +0 -47
- package/src/lib/constants.ts +0 -67
- package/src/lib/schemas.ts +0 -92
- package/src/lib/sse.ts +0 -152
- package/src/middleware/auth.ts +0 -59
- package/src/routes/api/posts.ts +0 -127
- package/src/routes/api/search.ts +0 -53
- package/src/routes/api/upload.ts +0 -240
- package/src/routes/dash/collections.tsx +0 -341
- package/src/routes/dash/index.tsx +0 -89
- package/src/routes/dash/media.tsx +0 -551
- package/src/routes/dash/pages.tsx +0 -245
- package/src/routes/dash/posts.tsx +0 -202
- package/src/routes/dash/redirects.tsx +0 -155
- package/src/routes/dash/settings.tsx +0 -93
- package/src/routes/feed/rss.ts +0 -119
- package/src/routes/feed/sitemap.ts +0 -75
- package/src/routes/pages/archive.tsx +0 -223
- package/src/routes/pages/collection.tsx +0 -79
- package/src/routes/pages/home.tsx +0 -93
- package/src/routes/pages/page.tsx +0 -64
- package/src/routes/pages/post.tsx +0 -81
- package/src/routes/pages/search.tsx +0 -162
- package/src/services/collection.ts +0 -180
- package/src/services/index.ts +0 -40
- package/src/services/media.ts +0 -97
- package/src/services/post.ts +0 -279
- package/src/services/redirect.ts +0 -74
- package/src/services/search.ts +0 -117
- package/src/services/settings.ts +0 -76
- package/src/theme/components/ActionButtons.tsx +0 -98
- package/src/theme/components/CrudPageHeader.tsx +0 -48
- package/src/theme/components/DangerZone.tsx +0 -77
- package/src/theme/components/EmptyState.tsx +0 -56
- package/src/theme/components/ListItemRow.tsx +0 -24
- package/src/theme/components/PageForm.tsx +0 -114
- package/src/theme/components/Pagination.tsx +0 -196
- package/src/theme/components/PostForm.tsx +0 -122
- package/src/theme/components/PostList.tsx +0 -68
- package/src/theme/components/ThreadView.tsx +0 -118
- package/src/theme/components/TypeBadge.tsx +0 -28
- package/src/theme/components/VisibilityBadge.tsx +0 -33
- package/src/theme/layouts/BaseLayout.tsx +0 -49
- package/src/theme/layouts/DashLayout.tsx +0 -108
- package/src/theme/styles/main.css +0 -52
- package/src/types.ts +0 -222
- package/tsconfig.json +0 -16
- package/vite.config.ts +0 -82
- package/wrangler.toml +0 -21
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+7Wr2a\":[\"編輯: \",[\"title\"]],\"+MACwa\":[\"尚未有任何收藏。\"],\"+owNNn\":[\"文章\"],\"+zy2Nq\":[\"類型\"],\"/0D1Xp\":[\"編輯收藏集\"],\"/Rj5P4\":[\"您的姓名\"],\"0JkyS7\":[\"建立您的第一個頁面\"],\"0a6MpL\":[\"新重定向\"],\"1CU1Td\":[\"網址安全識別碼(小寫、數字、連字符)\"],\"1DBGsz\":[\"筆記\"],\"2N0qpv\":[\"文章標題...\"],\"2q/Q7x\":[\"可見性\"],\"2rJGtU\":[\"頁面標題...\"],\"3Yvsaz\":[\"302(臨時)\"],\"4/SFQS\":[\"查看網站\"],\"40TVQj\":[\"自訂路徑(選填)\"],\"4KzVT6\":[\"刪除頁面\"],\"4b3oEV\":[\"內容\"],\"4mDPGp\":[\"此頁面的 URL 路徑。使用小寫字母、數字和連字符。\"],\"6WdDG7\":[\"頁面\"],\"7G4SBz\":[\"頁面內容(支持Markdown)...\"],\"7Mk+/h\":[\"更新收藏集\"],\"7Q1KKN\":[\"來源路徑\"],\"7nGhhM\":[\"你在想什麼?\"],\"7p5kLi\":[\"儀表板\"],\"8ZsakT\":[\"密碼\"],\"90Luob\":[[\"count\"],\" 條回覆\"],\"A1taO8\":[\"搜尋\"],\"AyHO4m\":[\"這個收藏是關於什麼的?\"],\"B373X+\":[\"編輯文章\"],\"B495Gs\":[\"檔案館\"],\"BjF0Jv\":[\"僅限小寫字母、數字和連字符\"],\"D9Oea+\":[\"永久鏈接\"],\"DHhJ7s\":[\"上一頁\"],\"DoJzLz\":[\"收藏夾\"],\"E80cJw\":[\"刪除此媒體將永久從存儲中移除它。\"],\"EEYbdt\":[\"發佈\"],\"EGwzOK\":[\"完成設置\"],\"EkH9pt\":[\"更新\"],\"FGrimz\":[\"新帖子\"],\"FkMol5\":[\"精選\"],\"Fxf4jq\":[\"描述(可選)\"],\"GA5A5H\":[\"刪除收藏夾\"],\"GorKul\":[\"歡迎來到 Jant\"],\"GrZ6fH\":[\"新頁面\"],\"HfyyXl\":[\"我的部落格\"],\"HiETwV\":[\"安靜(正常)\"],\"Hzi9AA\":[\"未找到任何帖子。\"],\"I6gXOa\":[\"路徑\"],\"IagCbF\":[\"網址\"],\"J4FNfC\":[\"此集合中沒有帖子。\"],\"JIBC/T\":[\"支援的格式:JPEG、PNG、GIF、WebP、SVG。最大大小:10MB。\"],\"JiP4aa\":[\"已發佈的頁面可以通過其路徑訪問。草稿不可見。\"],\"K9NcLu\":[\"使用此 URL 將媒體嵌入到您的帖子中。\"],\"KiJn9B\":[\"備註\"],\"L85WcV\":[\"縮略名\"],\"LkvLQe\":[\"尚未有頁面。\"],\"M8kJqa\":[\"草稿\"],\"M9xgHy\":[\"重定向\"],\"MHrjPM\":[\"標題\"],\"MZbQHL\":[\"未找到結果。\"],\"Mhf/H/\":[\"建立重定向\"],\"MqghUt\":[\"搜尋帖子...\"],\"N40H+G\":[\"所有\"],\"O3oNi5\":[\"電子郵件\"],\"OCNZaU\":[\"重定向來源的路徑\"],\"ODiSoW\":[\"尚未有帖子。\"],\"ONWvwQ\":[\"上傳\"],\"Pbm2/N\":[\"創建收藏夾\"],\"RDjuBN\":[\"設置\"],\"Rj01Fz\":[\"連結\"],\"RwGhWy\":[\"包含 \",[\"count\"],\" 則帖子的主題\"],\"SJmfuf\":[\"網站名稱\"],\"ST+lN2\":[\"尚未上傳任何媒體。\"],\"Tt5T6+\":[\"文章\"],\"TxE+Mj\":[\"1 條回覆\"],\"Tz0i8g\":[\"設定\"],\"U5v6Gh\":[\"編輯頁面\"],\"UDMjsP\":[\"快速操作\"],\"UGT5vp\":[\"保存設定\"],\"VUSy8D\":[\"搜尋失敗。請再試一次。\"],\"WDcQq9\":[\"不公開\"],\"Weq9zb\":[\"一般設定\"],\"WmZ/rP\":[\"到路徑\"],\"Y+7JGK\":[\"創建頁面\"],\"ZQKLI1\":[\"危險區域\"],\"ZhhOwV\":[\"引用\"],\"an5hVd\":[\"圖片\"],\"b+/jO6\":[\"301(永久)\"],\"bHYIks\":[\"登出\"],\"biOepV\":[\"← 返回首頁\"],\"cnGeoo\":[\"刪除\"],\"dEgA5A\":[\"取消\"],\"e6Jr7Q\":[\"← 返回收藏夾\"],\"ePK91l\":[\"編輯\"],\"eWLklq\":[\"引用\"],\"eneWvv\":[\"草稿\"],\"f6e0Ry\":[\"文章\"],\"fG7BxZ\":[\"透過 API 上傳圖片:POST /api/upload,並使用文件表單字段。\"],\"fttd2R\":[\"我的收藏\"],\"hG89Ed\":[\"圖片\"],\"hXzOVo\":[\"下一頁\"],\"he3ygx\":[\"複製\"],\"iH8pgl\":[\"返回\"],\"ig4hg2\":[\"讓我們設置您的網站。\"],\"jpctdh\":[\"查看\"],\"mTOYla\":[\"查看所有文章 →\"],\"n1ekoW\":[\"登入\"],\"oYPBa0\":[\"更新頁面\"],\"pRhYH2\":[\"收藏中的帖子 (\",[\"count\"],\")\"],\"qMyM2u\":[\"來源網址(選填)\"],\"r1MpXi\":[\"安靜\"],\"rdUucN\":[\"預覽\"],\"rzNUSl\":[\"包含 1 則貼文的主題\"],\"sGajR7\":[\"線程開始\"],\"t/YqKh\":[\"移除\"],\"tfrt7B\":[\"未配置任何重定向。\"],\"tiq7kl\":[\"頁面 \",[\"page\"]],\"u2f7vd\":[\"網站描述\"],\"u3wRF+\":[\"已發佈\"],\"u6Hp4N\":[\"Markdown\"],\"uAQUqI\":[\"狀態\"],\"vXIe7J\":[\"語言\"],\"vzU4k9\":[\"新收藏集\"],\"wEF6Ix\":[\"目的地路徑或 URL\"],\"wK4OTM\":[\"標題(選填)\"],\"wM5UXj\":[\"刪除媒體\"],\"wRR604\":[\"頁面\"],\"wja8aL\":[\"無標題\"],\"x0mzE0\":[\"創建你的第一篇帖子\"],\"x4RuFo\":[\"回到首頁\"],\"xYilR2\":[\"媒體\"],\"y28hnO\":[\"文章\"],\"yQ2kGp\":[\"載入更多\"],\"yzF66j\":[\"連結\"],\"z8ajIE\":[\"找到 1 個結果\"],\"zH6KqE\":[\"找到 \",[\"count\"],\" 個結果\"]}")as Messages;
|
package/src/i18n/locales.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized locale configuration
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export const locales = ["en", "zh-Hans", "zh-Hant"] as const;
|
|
6
|
-
export type Locale = (typeof locales)[number];
|
|
7
|
-
export const baseLocale: Locale = "en";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Check if a value is a valid locale
|
|
11
|
-
*/
|
|
12
|
-
export function isLocale(value: unknown): value is Locale {
|
|
13
|
-
return typeof value === "string" && locales.includes(value as Locale);
|
|
14
|
-
}
|
package/src/i18n/middleware.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* i18n Hono Middleware
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { MiddlewareHandler } from "hono";
|
|
6
|
-
import type { I18n } from "@lingui/core";
|
|
7
|
-
import { detectLanguage } from "./detect.js";
|
|
8
|
-
import { createI18n, isLocale, type Locale } from "./i18n.js";
|
|
9
|
-
import type { Services } from "../services/index.js";
|
|
10
|
-
|
|
11
|
-
declare module "hono" {
|
|
12
|
-
interface ContextVariableMap {
|
|
13
|
-
lang: Locale;
|
|
14
|
-
i18n: I18n;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Hono middleware for internationalization.
|
|
20
|
-
* Creates a per-request i18n instance to avoid race conditions in concurrent environments.
|
|
21
|
-
*
|
|
22
|
-
* Language detection priority:
|
|
23
|
-
* 1. Cookie (user preference)
|
|
24
|
-
* 2. Database SITE_LANGUAGE setting (site default)
|
|
25
|
-
* 3. Accept-Language header
|
|
26
|
-
* 4. Default locale (en)
|
|
27
|
-
*/
|
|
28
|
-
export function i18nMiddleware(): MiddlewareHandler {
|
|
29
|
-
return async (c, next) => {
|
|
30
|
-
// First try cookie and Accept-Language header
|
|
31
|
-
let lang = detectLanguage(c);
|
|
32
|
-
|
|
33
|
-
// If no cookie is set, check database SITE_LANGUAGE setting
|
|
34
|
-
const cookies = c.req.raw.headers.get("Cookie") ?? "";
|
|
35
|
-
const hasCookie = cookies.includes("lang=");
|
|
36
|
-
|
|
37
|
-
if (!hasCookie) {
|
|
38
|
-
// Check database setting
|
|
39
|
-
const services = c.get("services") as Services | undefined;
|
|
40
|
-
if (services) {
|
|
41
|
-
try {
|
|
42
|
-
const siteLang = await services.settings.get("SITE_LANGUAGE");
|
|
43
|
-
if (siteLang && isLocale(siteLang)) {
|
|
44
|
-
lang = siteLang;
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
// Ignore errors, fall back to detected language
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Create a new i18n instance for this request to avoid race conditions
|
|
53
|
-
const i18n = createI18n(lang);
|
|
54
|
-
|
|
55
|
-
c.set("lang", lang);
|
|
56
|
-
c.set("i18n", i18n);
|
|
57
|
-
await next();
|
|
58
|
-
};
|
|
59
|
-
}
|
package/src/lib/assets.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Asset paths for SSR
|
|
3
|
-
*
|
|
4
|
-
* Development: Uses source paths served by Vite dev server
|
|
5
|
-
* Production: Uses paths that get patched at build time with actual hashes
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
interface Assets {
|
|
9
|
-
styles: string;
|
|
10
|
-
client: string;
|
|
11
|
-
datastar: string;
|
|
12
|
-
imageProcessor: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Development paths
|
|
16
|
-
const DEV_ASSETS: Assets = {
|
|
17
|
-
styles: "/node_modules/@jant/core/src/theme/styles/main.css",
|
|
18
|
-
client: "/node_modules/@jant/core/src/client.ts",
|
|
19
|
-
datastar: "/node_modules/@jant/core/static/assets/datastar.min.js",
|
|
20
|
-
imageProcessor: "/node_modules/@jant/core/static/assets/image-processor.js",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Production paths - these unique placeholders get replaced at build time
|
|
24
|
-
// Format: __JANT_ASSET_<NAME>__ to avoid accidental matches
|
|
25
|
-
const PROD_ASSETS: Assets = {
|
|
26
|
-
styles: "__JANT_ASSET_STYLES__",
|
|
27
|
-
client: "__JANT_ASSET_CLIENT__",
|
|
28
|
-
datastar: "__JANT_ASSET_DATASTAR__",
|
|
29
|
-
imageProcessor: "__JANT_ASSET_IMAGE_PROCESSOR__",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get assets based on environment
|
|
34
|
-
*/
|
|
35
|
-
export function getAssets(): Assets {
|
|
36
|
-
try {
|
|
37
|
-
// import.meta.env is injected by Vite
|
|
38
|
-
if (import.meta.env?.DEV) return DEV_ASSETS;
|
|
39
|
-
} catch {
|
|
40
|
-
// import.meta.env may not exist in all environments
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return PROD_ASSETS;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// For static imports
|
|
47
|
-
export const ASSETS = PROD_ASSETS;
|
package/src/lib/constants.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Application Constants
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Reserved URL paths that cannot be used for pages
|
|
7
|
-
*/
|
|
8
|
-
export const RESERVED_PATHS = [
|
|
9
|
-
"featured",
|
|
10
|
-
"signin",
|
|
11
|
-
"signout",
|
|
12
|
-
"setup",
|
|
13
|
-
"dash",
|
|
14
|
-
"api",
|
|
15
|
-
"feed",
|
|
16
|
-
"search",
|
|
17
|
-
"archive",
|
|
18
|
-
"notes",
|
|
19
|
-
"articles",
|
|
20
|
-
"links",
|
|
21
|
-
"quotes",
|
|
22
|
-
"media",
|
|
23
|
-
"pages",
|
|
24
|
-
"p",
|
|
25
|
-
"c",
|
|
26
|
-
"static",
|
|
27
|
-
"assets",
|
|
28
|
-
"health",
|
|
29
|
-
] as const;
|
|
30
|
-
|
|
31
|
-
export type ReservedPath = (typeof RESERVED_PATHS)[number];
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Check if a path is reserved
|
|
35
|
-
*/
|
|
36
|
-
export function isReservedPath(path: string): boolean {
|
|
37
|
-
const firstSegment = path.split("/")[0]?.toLowerCase();
|
|
38
|
-
return RESERVED_PATHS.includes(firstSegment as ReservedPath);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Default pagination size
|
|
43
|
-
*/
|
|
44
|
-
export const DEFAULT_PAGE_SIZE = 100;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Settings keys (match environment variable naming)
|
|
48
|
-
*/
|
|
49
|
-
export const SETTINGS_KEYS = {
|
|
50
|
-
ONBOARDING_STATUS: "ONBOARDING_STATUS",
|
|
51
|
-
SITE_NAME: "SITE_NAME",
|
|
52
|
-
SITE_DESCRIPTION: "SITE_DESCRIPTION",
|
|
53
|
-
SITE_LANGUAGE: "SITE_LANGUAGE",
|
|
54
|
-
THEME: "THEME",
|
|
55
|
-
} as const;
|
|
56
|
-
|
|
57
|
-
export type SettingsKey = (typeof SETTINGS_KEYS)[keyof typeof SETTINGS_KEYS];
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Onboarding status values
|
|
61
|
-
*/
|
|
62
|
-
export const ONBOARDING_STATUS = {
|
|
63
|
-
PENDING: "pending",
|
|
64
|
-
COMPLETED: "completed",
|
|
65
|
-
} as const;
|
|
66
|
-
|
|
67
|
-
export type OnboardingStatus = (typeof ONBOARDING_STATUS)[keyof typeof ONBOARDING_STATUS];
|
package/src/lib/schemas.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Zod schemas for validation
|
|
3
|
-
*
|
|
4
|
-
* These schemas ensure type-safe validation of user input
|
|
5
|
-
* from forms, API requests, and other external sources.
|
|
6
|
-
*
|
|
7
|
-
* IMPORTANT: Types are defined in types.ts as the single source of truth.
|
|
8
|
-
* This file only defines Zod validation schemas based on those types.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
import { POST_TYPES, VISIBILITY_LEVELS } from "../types.js";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Post type enum schema
|
|
16
|
-
* Based on POST_TYPES from types.ts
|
|
17
|
-
*/
|
|
18
|
-
export const PostTypeSchema = z.enum(POST_TYPES);
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Visibility enum schema
|
|
22
|
-
* Based on VISIBILITY_LEVELS from types.ts
|
|
23
|
-
*/
|
|
24
|
-
export const VisibilitySchema = z.enum(VISIBILITY_LEVELS);
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Redirect type enum schema
|
|
28
|
-
* Form input validation for redirect type (stored as number in DB)
|
|
29
|
-
*/
|
|
30
|
-
export const RedirectTypeSchema = z.enum(["301", "302"]);
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* API request body schema for creating a post
|
|
34
|
-
*/
|
|
35
|
-
export const CreatePostSchema = z.object({
|
|
36
|
-
type: PostTypeSchema,
|
|
37
|
-
title: z.string().optional(),
|
|
38
|
-
content: z.string(),
|
|
39
|
-
visibility: VisibilitySchema,
|
|
40
|
-
sourceUrl: z.string().url().optional().or(z.literal("")),
|
|
41
|
-
sourceName: z.string().optional(),
|
|
42
|
-
path: z.string().regex(/^[a-z0-9-]*$/).optional().or(z.literal("")),
|
|
43
|
-
replyToId: z.string().optional(), // Sqid format
|
|
44
|
-
publishedAt: z.number().int().positive().optional(),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* API request body schema for updating a post
|
|
49
|
-
*/
|
|
50
|
-
export const UpdatePostSchema = CreatePostSchema.partial();
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Form data helper: safely parse a FormData value with a schema
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```ts
|
|
57
|
-
* const type = parseFormData(formData, "type", PostTypeSchema);
|
|
58
|
-
* // type is PostType, throws if invalid
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
export function parseFormData<T>(
|
|
62
|
-
formData: FormData,
|
|
63
|
-
key: string,
|
|
64
|
-
schema: z.ZodSchema<T>
|
|
65
|
-
): T {
|
|
66
|
-
const value = formData.get(key);
|
|
67
|
-
if (value === null) {
|
|
68
|
-
throw new Error(`Missing required field: ${key}`);
|
|
69
|
-
}
|
|
70
|
-
return schema.parse(value);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Form data helper: safely parse optional FormData value with a schema
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
* ```ts
|
|
78
|
-
* const slug = parseFormDataOptional(formData, "slug", z.string());
|
|
79
|
-
* // slug is string | undefined
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
export function parseFormDataOptional<T>(
|
|
83
|
-
formData: FormData,
|
|
84
|
-
key: string,
|
|
85
|
-
schema: z.ZodSchema<T>
|
|
86
|
-
): T | undefined {
|
|
87
|
-
const value = formData.get(key);
|
|
88
|
-
if (value === null || value === "") {
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
return schema.parse(value);
|
|
92
|
-
}
|
package/src/lib/sse.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server-Sent Events (SSE) utilities for Datastar
|
|
3
|
-
*
|
|
4
|
-
* Provides helpers for streaming SSE responses that Datastar can consume.
|
|
5
|
-
* Datastar uses SSE for real-time UI updates without page reloads.
|
|
6
|
-
*
|
|
7
|
-
* @see https://data-star.dev/
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* app.post("/api/example", (c) => {
|
|
12
|
-
* return sse(c, async (stream) => {
|
|
13
|
-
* await stream.patchSignals({ loading: false });
|
|
14
|
-
* await stream.patchElements("#result", "<div>Done!</div>");
|
|
15
|
-
* });
|
|
16
|
-
* });
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import type { Context } from "hono";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Patch modes for DOM updates
|
|
24
|
-
*/
|
|
25
|
-
export type PatchMode = "morph" | "inner" | "outer" | "append" | "prepend" | "remove";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* SSE stream writer for Datastar events
|
|
29
|
-
*/
|
|
30
|
-
export interface SSEStream {
|
|
31
|
-
/**
|
|
32
|
-
* Update reactive signals on the client
|
|
33
|
-
*
|
|
34
|
-
* @param signals - Object containing signal values to update
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```ts
|
|
38
|
-
* await stream.patchSignals({ count: 42, loading: false });
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
patchSignals(signals: Record<string, unknown>): Promise<void>;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Update DOM elements
|
|
45
|
-
*
|
|
46
|
-
* @param html - HTML content (must include element with id for targeting)
|
|
47
|
-
* @param options - Optional mode and selector
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```ts
|
|
51
|
-
* // Replace element with matching id (default: morph)
|
|
52
|
-
* await stream.patchElements('<div id="content">New content</div>');
|
|
53
|
-
*
|
|
54
|
-
* // Append to a container
|
|
55
|
-
* await stream.patchElements('<div>New item</div>', {
|
|
56
|
-
* mode: 'append',
|
|
57
|
-
* selector: '#list'
|
|
58
|
-
* });
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
patchElements(
|
|
62
|
-
html: string,
|
|
63
|
-
options?: { mode?: PatchMode; selector?: string }
|
|
64
|
-
): Promise<void>;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Execute JavaScript on the client
|
|
68
|
-
*
|
|
69
|
-
* @param script - JavaScript code to execute
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* ```ts
|
|
73
|
-
* await stream.executeScript('console.log("Hello from server")');
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
executeScript(script: string): Promise<void>;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Create an SSE response for Datastar
|
|
81
|
-
*
|
|
82
|
-
* @param c - Hono context
|
|
83
|
-
* @param handler - Async function that writes to the SSE stream
|
|
84
|
-
* @returns Response with SSE content-type
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* ```ts
|
|
88
|
-
* app.post("/api/upload", (c) => {
|
|
89
|
-
* return sse(c, async (stream) => {
|
|
90
|
-
* // Process upload...
|
|
91
|
-
* await stream.patchSignals({ uploading: false });
|
|
92
|
-
* await stream.patchElements('<div id="new-item">...</div>', {
|
|
93
|
-
* mode: 'append',
|
|
94
|
-
* selector: '#items'
|
|
95
|
-
* });
|
|
96
|
-
* });
|
|
97
|
-
* });
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
|
-
export function sse(
|
|
101
|
-
c: Context,
|
|
102
|
-
handler: (stream: SSEStream) => Promise<void>
|
|
103
|
-
): Response {
|
|
104
|
-
const encoder = new TextEncoder();
|
|
105
|
-
|
|
106
|
-
const stream = new ReadableStream({
|
|
107
|
-
async start(controller) {
|
|
108
|
-
const write = (data: string) => {
|
|
109
|
-
controller.enqueue(encoder.encode(data));
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const sseStream: SSEStream = {
|
|
113
|
-
async patchSignals(signals) {
|
|
114
|
-
write(`event: datastar-patch-signals\n`);
|
|
115
|
-
write(`data: signals ${JSON.stringify(signals)}\n\n`);
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
async patchElements(html, options = {}) {
|
|
119
|
-
write(`event: datastar-patch-elements\n`);
|
|
120
|
-
if (options.mode) {
|
|
121
|
-
write(`data: mode ${options.mode}\n`);
|
|
122
|
-
}
|
|
123
|
-
if (options.selector) {
|
|
124
|
-
write(`data: selector ${options.selector}\n`);
|
|
125
|
-
}
|
|
126
|
-
// Escape newlines in HTML for SSE format
|
|
127
|
-
const escapedHtml = html.replace(/\n/g, "\ndata: ");
|
|
128
|
-
write(`data: elements ${escapedHtml}\n\n`);
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
async executeScript(script) {
|
|
132
|
-
write(`event: datastar-execute-script\n`);
|
|
133
|
-
write(`data: script ${script}\n\n`);
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
await handler(sseStream);
|
|
139
|
-
} finally {
|
|
140
|
-
controller.close();
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
return new Response(stream, {
|
|
146
|
-
headers: {
|
|
147
|
-
"Content-Type": "text/event-stream",
|
|
148
|
-
"Cache-Control": "no-cache",
|
|
149
|
-
Connection: "keep-alive",
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
}
|
package/src/middleware/auth.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authentication Middleware
|
|
3
|
-
*
|
|
4
|
-
* Protects routes by requiring authentication
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { MiddlewareHandler } from "hono";
|
|
8
|
-
import type { Bindings } from "../types.js";
|
|
9
|
-
import type { AppVariables } from "../app.js";
|
|
10
|
-
|
|
11
|
-
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Middleware that requires authentication.
|
|
15
|
-
* Redirects to signin page if not authenticated.
|
|
16
|
-
*/
|
|
17
|
-
export function requireAuth(redirectTo = "/signin"): MiddlewareHandler<Env> {
|
|
18
|
-
return async (c, next) => {
|
|
19
|
-
if (!c.var.auth) {
|
|
20
|
-
return c.redirect(redirectTo);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const session = await c.var.auth.api.getSession({ headers: c.req.raw.headers });
|
|
25
|
-
|
|
26
|
-
if (!session?.user) {
|
|
27
|
-
return c.redirect(redirectTo);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
await next();
|
|
31
|
-
} catch {
|
|
32
|
-
return c.redirect(redirectTo);
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Middleware for API routes that requires authentication.
|
|
39
|
-
* Returns 401 if not authenticated.
|
|
40
|
-
*/
|
|
41
|
-
export function requireAuthApi(): MiddlewareHandler<Env> {
|
|
42
|
-
return async (c, next) => {
|
|
43
|
-
if (!c.var.auth) {
|
|
44
|
-
return c.json({ error: "Authentication not configured" }, 500);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
const session = await c.var.auth.api.getSession({ headers: c.req.raw.headers });
|
|
49
|
-
|
|
50
|
-
if (!session?.user) {
|
|
51
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await next();
|
|
55
|
-
} catch {
|
|
56
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
}
|
package/src/routes/api/posts.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Posts API Routes
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Hono } from "hono";
|
|
6
|
-
import type { Bindings, PostType, Visibility } from "../../types.js";
|
|
7
|
-
import type { AppVariables } from "../../app.js";
|
|
8
|
-
import * as sqid from "../../lib/sqid.js";
|
|
9
|
-
import { CreatePostSchema, UpdatePostSchema } from "../../lib/schemas.js";
|
|
10
|
-
import { requireAuthApi } from "../../middleware/auth.js";
|
|
11
|
-
|
|
12
|
-
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
13
|
-
|
|
14
|
-
export const postsApiRoutes = new Hono<Env>();
|
|
15
|
-
|
|
16
|
-
// List posts
|
|
17
|
-
postsApiRoutes.get("/", async (c) => {
|
|
18
|
-
const type = c.req.query("type") as PostType | undefined;
|
|
19
|
-
const visibility = c.req.query("visibility") as Visibility | undefined;
|
|
20
|
-
const cursor = c.req.query("cursor");
|
|
21
|
-
const limit = parseInt(c.req.query("limit") ?? "100", 10);
|
|
22
|
-
|
|
23
|
-
const posts = await c.var.services.posts.list({
|
|
24
|
-
type,
|
|
25
|
-
visibility: visibility ? [visibility] : ["featured", "quiet"],
|
|
26
|
-
cursor: cursor ? sqid.decode(cursor) ?? undefined : undefined,
|
|
27
|
-
limit,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return c.json({
|
|
31
|
-
posts: posts.map((p) => ({
|
|
32
|
-
...p,
|
|
33
|
-
sqid: sqid.encode(p.id),
|
|
34
|
-
})),
|
|
35
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Array length check guarantees element exists
|
|
36
|
-
nextCursor: posts.length === limit ? sqid.encode(posts[posts.length - 1]!.id) : null,
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Get single post
|
|
41
|
-
postsApiRoutes.get("/:id", async (c) => {
|
|
42
|
-
const id = sqid.decode(c.req.param("id"));
|
|
43
|
-
if (!id) return c.json({ error: "Invalid ID" }, 400);
|
|
44
|
-
|
|
45
|
-
const post = await c.var.services.posts.getById(id);
|
|
46
|
-
if (!post) return c.json({ error: "Not found" }, 404);
|
|
47
|
-
|
|
48
|
-
return c.json({ ...post, sqid: sqid.encode(post.id) });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Create post (requires auth)
|
|
52
|
-
postsApiRoutes.post("/", requireAuthApi(), async (c) => {
|
|
53
|
-
|
|
54
|
-
const rawBody = await c.req.json();
|
|
55
|
-
|
|
56
|
-
// Validate request body
|
|
57
|
-
const parseResult = CreatePostSchema.safeParse(rawBody);
|
|
58
|
-
if (!parseResult.success) {
|
|
59
|
-
return c.json(
|
|
60
|
-
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
61
|
-
400
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const body = parseResult.data;
|
|
66
|
-
|
|
67
|
-
const post = await c.var.services.posts.create({
|
|
68
|
-
type: body.type,
|
|
69
|
-
title: body.title,
|
|
70
|
-
content: body.content,
|
|
71
|
-
visibility: body.visibility,
|
|
72
|
-
sourceUrl: body.sourceUrl || undefined,
|
|
73
|
-
sourceName: body.sourceName,
|
|
74
|
-
path: body.path || undefined,
|
|
75
|
-
replyToId: body.replyToId ? sqid.decode(body.replyToId) ?? undefined : undefined,
|
|
76
|
-
publishedAt: body.publishedAt,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return c.json({ ...post, sqid: sqid.encode(post.id) }, 201);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Update post (requires auth)
|
|
83
|
-
postsApiRoutes.put("/:id", requireAuthApi(), async (c) => {
|
|
84
|
-
|
|
85
|
-
const id = sqid.decode(c.req.param("id"));
|
|
86
|
-
if (!id) return c.json({ error: "Invalid ID" }, 400);
|
|
87
|
-
|
|
88
|
-
const rawBody = await c.req.json();
|
|
89
|
-
|
|
90
|
-
// Validate request body
|
|
91
|
-
const parseResult = UpdatePostSchema.safeParse(rawBody);
|
|
92
|
-
if (!parseResult.success) {
|
|
93
|
-
return c.json(
|
|
94
|
-
{ error: "Validation failed", details: parseResult.error.flatten() },
|
|
95
|
-
400
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const body = parseResult.data;
|
|
100
|
-
|
|
101
|
-
const post = await c.var.services.posts.update(id, {
|
|
102
|
-
type: body.type,
|
|
103
|
-
title: body.title,
|
|
104
|
-
content: body.content,
|
|
105
|
-
visibility: body.visibility,
|
|
106
|
-
sourceUrl: body.sourceUrl,
|
|
107
|
-
sourceName: body.sourceName,
|
|
108
|
-
path: body.path,
|
|
109
|
-
publishedAt: body.publishedAt,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
if (!post) return c.json({ error: "Not found" }, 404);
|
|
113
|
-
|
|
114
|
-
return c.json({ ...post, sqid: sqid.encode(post.id) });
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Delete post (requires auth)
|
|
118
|
-
postsApiRoutes.delete("/:id", requireAuthApi(), async (c) => {
|
|
119
|
-
|
|
120
|
-
const id = sqid.decode(c.req.param("id"));
|
|
121
|
-
if (!id) return c.json({ error: "Invalid ID" }, 400);
|
|
122
|
-
|
|
123
|
-
const success = await c.var.services.posts.delete(id);
|
|
124
|
-
if (!success) return c.json({ error: "Not found" }, 404);
|
|
125
|
-
|
|
126
|
-
return c.json({ success: true });
|
|
127
|
-
});
|
package/src/routes/api/search.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Search 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 * as sqid from "../../lib/sqid.js";
|
|
9
|
-
|
|
10
|
-
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
11
|
-
|
|
12
|
-
export const searchApiRoutes = new Hono<Env>();
|
|
13
|
-
|
|
14
|
-
// Search posts
|
|
15
|
-
searchApiRoutes.get("/", async (c) => {
|
|
16
|
-
const query = c.req.query("q");
|
|
17
|
-
|
|
18
|
-
if (!query || query.trim().length === 0) {
|
|
19
|
-
return c.json({ error: "Query parameter 'q' is required" }, 400);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (query.length > 200) {
|
|
23
|
-
return c.json({ error: "Query too long" }, 400);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const limitParam = c.req.query("limit");
|
|
27
|
-
const limit = limitParam ? Math.min(parseInt(limitParam, 10) || 20, 50) : 20;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const results = await c.var.services.search.search(query, {
|
|
31
|
-
limit,
|
|
32
|
-
visibility: ["featured", "quiet"],
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
return c.json({
|
|
36
|
-
query,
|
|
37
|
-
results: results.map((r) => ({
|
|
38
|
-
id: sqid.encode(r.post.id),
|
|
39
|
-
type: r.post.type,
|
|
40
|
-
title: r.post.title,
|
|
41
|
-
path: r.post.path,
|
|
42
|
-
snippet: r.snippet,
|
|
43
|
-
publishedAt: r.post.publishedAt,
|
|
44
|
-
url: `/p/${sqid.encode(r.post.id)}`,
|
|
45
|
-
})),
|
|
46
|
-
count: results.length,
|
|
47
|
-
});
|
|
48
|
-
} catch (err) {
|
|
49
|
-
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
50
|
-
console.error("Search error:", err);
|
|
51
|
-
return c.json({ error: "Search failed" }, 500);
|
|
52
|
-
}
|
|
53
|
-
});
|