@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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time Utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Gets the current Unix timestamp in seconds.
|
|
6
|
+
*
|
|
7
|
+
* Returns the number of seconds since the Unix epoch (January 1, 1970 00:00:00 UTC).
|
|
8
|
+
* This is the standard time format used throughout the application for consistency
|
|
9
|
+
* and database storage.
|
|
10
|
+
*
|
|
11
|
+
* @returns Current Unix timestamp in seconds (not milliseconds)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const timestamp = now();
|
|
16
|
+
* // Returns: 1706745600 (example value for Feb 1, 2024)
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function now(): number;
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a Unix timestamp is within the last 30 days.
|
|
22
|
+
*
|
|
23
|
+
* Compares the given timestamp to the current time to determine if it falls within
|
|
24
|
+
* the last month (defined as 30 days). Useful for highlighting recent posts or
|
|
25
|
+
* filtering time-sensitive content.
|
|
26
|
+
*
|
|
27
|
+
* @param timestamp - Unix timestamp in seconds to check
|
|
28
|
+
* @returns `true` if the timestamp is within the last 30 days, `false` otherwise
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const recentPost = 1706745600; // Recent timestamp
|
|
33
|
+
* if (isWithinMonth(recentPost)) {
|
|
34
|
+
* // Show "new" badge
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function isWithinMonth(timestamp: number): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Converts a Unix timestamp to an ISO 8601 date-time string.
|
|
41
|
+
*
|
|
42
|
+
* Formats a Unix timestamp (in seconds) as an ISO 8601 string suitable for HTML
|
|
43
|
+
* `datetime` attributes and API responses. The output includes full date, time,
|
|
44
|
+
* and timezone information in UTC.
|
|
45
|
+
*
|
|
46
|
+
* @param timestamp - Unix timestamp in seconds to convert
|
|
47
|
+
* @returns ISO 8601 formatted string (e.g., "2024-02-01T12:00:00.000Z")
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const isoDate = toISOString(1706745600);
|
|
52
|
+
* // Returns: "2024-02-01T00:00:00.000Z"
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function toISOString(timestamp: number): string;
|
|
56
|
+
/**
|
|
57
|
+
* Formats a Unix timestamp as a human-readable date string.
|
|
58
|
+
*
|
|
59
|
+
* Converts a Unix timestamp (in seconds) to a localized date string in the format
|
|
60
|
+
* "MMM DD, YYYY" (e.g., "Jan 15, 2024"). Always uses UTC timezone to ensure
|
|
61
|
+
* consistent display regardless of server or client location.
|
|
62
|
+
*
|
|
63
|
+
* @param timestamp - Unix timestamp in seconds to format
|
|
64
|
+
* @returns Formatted date string in "MMM DD, YYYY" format
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const readable = formatDate(1706745600);
|
|
69
|
+
* // Returns: "Feb 1, 2024"
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function formatDate(timestamp: number): string;
|
|
73
|
+
/**
|
|
74
|
+
* Formats a Unix timestamp as a year-month string for grouping.
|
|
75
|
+
*
|
|
76
|
+
* Converts a Unix timestamp (in seconds) to a "YYYY-MM" format string, useful for
|
|
77
|
+
* grouping posts by month in archives or creating month-based URLs. Always uses
|
|
78
|
+
* UTC timezone for consistency.
|
|
79
|
+
*
|
|
80
|
+
* @param timestamp - Unix timestamp in seconds to format
|
|
81
|
+
* @returns Year-month string in "YYYY-MM" format
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const yearMonth = formatYearMonth(1706745600);
|
|
86
|
+
* // Returns: "2024-02"
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare function formatYearMonth(timestamp: number): string;
|
|
90
|
+
//# sourceMappingURL=time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/lib/time.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAOD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKzD"}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Time Utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
3
|
+
*/ /**
|
|
6
4
|
* Gets the current Unix timestamp in seconds.
|
|
7
5
|
*
|
|
8
6
|
* Returns the number of seconds since the Unix epoch (January 1, 1970 00:00:00 UTC).
|
|
@@ -16,16 +14,12 @@
|
|
|
16
14
|
* const timestamp = now();
|
|
17
15
|
* // Returns: 1706745600 (example value for Feb 1, 2024)
|
|
18
16
|
* ```
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
return Math.floor(Date.now() / 1000);
|
|
17
|
+
*/ export function now() {
|
|
18
|
+
return Math.floor(Date.now() / 1000);
|
|
22
19
|
}
|
|
23
|
-
|
|
24
20
|
/**
|
|
25
21
|
* One month in seconds
|
|
26
|
-
*/
|
|
27
|
-
const ONE_MONTH = 30 * 24 * 60 * 60;
|
|
28
|
-
|
|
22
|
+
*/ const ONE_MONTH = 30 * 24 * 60 * 60;
|
|
29
23
|
/**
|
|
30
24
|
* Checks if a Unix timestamp is within the last 30 days.
|
|
31
25
|
*
|
|
@@ -43,11 +37,9 @@ const ONE_MONTH = 30 * 24 * 60 * 60;
|
|
|
43
37
|
* // Show "new" badge
|
|
44
38
|
* }
|
|
45
39
|
* ```
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
return now() - timestamp < ONE_MONTH;
|
|
40
|
+
*/ export function isWithinMonth(timestamp) {
|
|
41
|
+
return now() - timestamp < ONE_MONTH;
|
|
49
42
|
}
|
|
50
|
-
|
|
51
43
|
/**
|
|
52
44
|
* Converts a Unix timestamp to an ISO 8601 date-time string.
|
|
53
45
|
*
|
|
@@ -63,11 +55,9 @@ export function isWithinMonth(timestamp: number): boolean {
|
|
|
63
55
|
* const isoDate = toISOString(1706745600);
|
|
64
56
|
* // Returns: "2024-02-01T00:00:00.000Z"
|
|
65
57
|
* ```
|
|
66
|
-
*/
|
|
67
|
-
|
|
68
|
-
return new Date(timestamp * 1000).toISOString();
|
|
58
|
+
*/ export function toISOString(timestamp) {
|
|
59
|
+
return new Date(timestamp * 1000).toISOString();
|
|
69
60
|
}
|
|
70
|
-
|
|
71
61
|
/**
|
|
72
62
|
* Formats a Unix timestamp as a human-readable date string.
|
|
73
63
|
*
|
|
@@ -83,16 +73,14 @@ export function toISOString(timestamp: number): string {
|
|
|
83
73
|
* const readable = formatDate(1706745600);
|
|
84
74
|
* // Returns: "Feb 1, 2024"
|
|
85
75
|
* ```
|
|
86
|
-
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
76
|
+
*/ export function formatDate(timestamp) {
|
|
77
|
+
return new Date(timestamp * 1000).toLocaleDateString("en-US", {
|
|
78
|
+
year: "numeric",
|
|
79
|
+
month: "short",
|
|
80
|
+
day: "numeric",
|
|
81
|
+
timeZone: "UTC"
|
|
82
|
+
});
|
|
94
83
|
}
|
|
95
|
-
|
|
96
84
|
/**
|
|
97
85
|
* Formats a Unix timestamp as a year-month string for grouping.
|
|
98
86
|
*
|
|
@@ -108,10 +96,9 @@ export function formatDate(timestamp: number): string {
|
|
|
108
96
|
* const yearMonth = formatYearMonth(1706745600);
|
|
109
97
|
* // Returns: "2024-02"
|
|
110
98
|
* ```
|
|
111
|
-
*/
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return `${year}-${month}`;
|
|
99
|
+
*/ export function formatYearMonth(timestamp) {
|
|
100
|
+
const date = new Date(timestamp * 1000);
|
|
101
|
+
const year = date.getUTCFullYear();
|
|
102
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
103
|
+
return `${year}-${month}`;
|
|
117
104
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* URL Utilities
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
4
|
/**
|
|
6
5
|
* Extracts the hostname (domain) from a URL string.
|
|
7
6
|
*
|
|
@@ -20,15 +19,7 @@
|
|
|
20
19
|
* // Returns: null
|
|
21
20
|
* ```
|
|
22
21
|
*/
|
|
23
|
-
export function extractDomain(url: string): string | null
|
|
24
|
-
try {
|
|
25
|
-
const parsed = new URL(url);
|
|
26
|
-
return parsed.hostname;
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
22
|
+
export declare function extractDomain(url: string): string | null;
|
|
32
23
|
/**
|
|
33
24
|
* Normalizes a path by removing slashes and converting to lowercase.
|
|
34
25
|
*
|
|
@@ -45,14 +36,7 @@ export function extractDomain(url: string): string | null {
|
|
|
45
36
|
* // Returns: "about/contact"
|
|
46
37
|
* ```
|
|
47
38
|
*/
|
|
48
|
-
export function normalizePath(path: string): string
|
|
49
|
-
return path
|
|
50
|
-
.trim()
|
|
51
|
-
.toLowerCase()
|
|
52
|
-
.replace(/^\/+|\/+$/g, "")
|
|
53
|
-
.replace(/\/+/g, "/");
|
|
54
|
-
}
|
|
55
|
-
|
|
39
|
+
export declare function normalizePath(path: string): string;
|
|
56
40
|
/**
|
|
57
41
|
* Checks if a string is a full URL with HTTP or HTTPS protocol.
|
|
58
42
|
*
|
|
@@ -70,10 +54,7 @@ export function normalizePath(path: string): string {
|
|
|
70
54
|
* isFullUrl("example.com"); // Returns: false
|
|
71
55
|
* ```
|
|
72
56
|
*/
|
|
73
|
-
export function isFullUrl(str: string): boolean
|
|
74
|
-
return str.startsWith("http://") || str.startsWith("https://");
|
|
75
|
-
}
|
|
76
|
-
|
|
57
|
+
export declare function isFullUrl(str: string): boolean;
|
|
77
58
|
/**
|
|
78
59
|
* Converts text to a URL-friendly slug.
|
|
79
60
|
*
|
|
@@ -97,11 +78,5 @@ export function isFullUrl(str: string): boolean {
|
|
|
97
78
|
* // Returns: "multiple-spaces"
|
|
98
79
|
* ```
|
|
99
80
|
*/
|
|
100
|
-
export function slugify(text: string): string
|
|
101
|
-
|
|
102
|
-
.toLowerCase()
|
|
103
|
-
.trim()
|
|
104
|
-
.replace(/[^\w\s-]/g, "")
|
|
105
|
-
.replace(/[\s_-]+/g, "-")
|
|
106
|
-
.replace(/^-+|-+$/g, "");
|
|
107
|
-
}
|
|
81
|
+
export declare function slugify(text: string): string;
|
|
82
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/lib/url.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOxD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO5C"}
|
package/dist/lib/url.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL Utilities
|
|
3
|
+
*/ /**
|
|
4
|
+
* Extracts the hostname (domain) from a URL string.
|
|
5
|
+
*
|
|
6
|
+
* Parses a full URL and returns just the hostname portion (e.g., "example.com" from
|
|
7
|
+
* "https://example.com/path"). Returns `null` if the URL is malformed or cannot be parsed.
|
|
8
|
+
*
|
|
9
|
+
* @param url - The full URL string to extract the domain from
|
|
10
|
+
* @returns The hostname/domain if valid, or `null` if parsing fails
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const domain = extractDomain("https://www.example.com/path");
|
|
15
|
+
* // Returns: "www.example.com"
|
|
16
|
+
*
|
|
17
|
+
* const invalid = extractDomain("not-a-url");
|
|
18
|
+
* // Returns: null
|
|
19
|
+
* ```
|
|
20
|
+
*/ export function extractDomain(url) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = new URL(url);
|
|
23
|
+
return parsed.hostname;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Normalizes a path by removing slashes and converting to lowercase.
|
|
30
|
+
*
|
|
31
|
+
* Trims whitespace, converts to lowercase, removes leading and trailing slashes,
|
|
32
|
+
* and collapses multiple consecutive slashes into single slashes. Used to create
|
|
33
|
+
* consistent path representations for routing and storage.
|
|
34
|
+
*
|
|
35
|
+
* @param path - The path string to normalize
|
|
36
|
+
* @returns The normalized path string
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const normalized = normalizePath(" /About/Contact// ");
|
|
41
|
+
* // Returns: "about/contact"
|
|
42
|
+
* ```
|
|
43
|
+
*/ export function normalizePath(path) {
|
|
44
|
+
return path.trim().toLowerCase().replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Checks if a string is a full URL with HTTP or HTTPS protocol.
|
|
48
|
+
*
|
|
49
|
+
* Validates whether a string starts with "http://" or "https://", indicating it's
|
|
50
|
+
* a full URL rather than a relative path. Useful for distinguishing between internal
|
|
51
|
+
* paths and external URLs.
|
|
52
|
+
*
|
|
53
|
+
* @param str - The string to check
|
|
54
|
+
* @returns `true` if the string starts with http:// or https://, `false` otherwise
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* isFullUrl("https://example.com"); // Returns: true
|
|
59
|
+
* isFullUrl("/about"); // Returns: false
|
|
60
|
+
* isFullUrl("example.com"); // Returns: false
|
|
61
|
+
* ```
|
|
62
|
+
*/ export function isFullUrl(str) {
|
|
63
|
+
return str.startsWith("http://") || str.startsWith("https://");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Converts text to a URL-friendly slug.
|
|
67
|
+
*
|
|
68
|
+
* Transforms text into a lowercase, hyphen-separated slug by:
|
|
69
|
+
* - Converting to lowercase
|
|
70
|
+
* - Removing special characters (keeping only word characters, spaces, and hyphens)
|
|
71
|
+
* - Replacing whitespace and underscores with hyphens
|
|
72
|
+
* - Removing leading and trailing hyphens
|
|
73
|
+
*
|
|
74
|
+
* Used for generating clean URLs from titles and names.
|
|
75
|
+
*
|
|
76
|
+
* @param text - The text to convert to a slug
|
|
77
|
+
* @returns The slugified string
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const slug = slugify("Hello World! This is a Test.");
|
|
82
|
+
* // Returns: "hello-world-this-is-a-test"
|
|
83
|
+
*
|
|
84
|
+
* const slug = slugify(" Multiple Spaces ");
|
|
85
|
+
* // Returns: "multiple-spaces"
|
|
86
|
+
* ```
|
|
87
|
+
*/ export function slugify(text) {
|
|
88
|
+
return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
89
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Middleware
|
|
3
|
+
*
|
|
4
|
+
* Protects routes by requiring authentication
|
|
5
|
+
*/
|
|
6
|
+
import type { MiddlewareHandler } from "hono";
|
|
7
|
+
import type { Bindings } from "../types.js";
|
|
8
|
+
import type { AppVariables } from "../app.js";
|
|
9
|
+
type Env = {
|
|
10
|
+
Bindings: Bindings;
|
|
11
|
+
Variables: AppVariables;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Middleware that requires authentication.
|
|
15
|
+
* Redirects to signin page if not authenticated.
|
|
16
|
+
*/
|
|
17
|
+
export declare function requireAuth(redirectTo?: string): MiddlewareHandler<Env>;
|
|
18
|
+
/**
|
|
19
|
+
* Middleware for API routes that requires authentication.
|
|
20
|
+
* Returns 401 if not authenticated.
|
|
21
|
+
*/
|
|
22
|
+
export declare function requireAuthApi(): MiddlewareHandler<Env>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D;;;GAGG;AACH,wBAAgB,WAAW,CAAC,UAAU,SAAY,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAkB1E;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAkBvD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Middleware
|
|
3
|
+
*
|
|
4
|
+
* Protects routes by requiring authentication
|
|
5
|
+
*/ /**
|
|
6
|
+
* Middleware that requires authentication.
|
|
7
|
+
* Redirects to signin page if not authenticated.
|
|
8
|
+
*/ export function requireAuth(redirectTo = "/signin") {
|
|
9
|
+
return async (c, next)=>{
|
|
10
|
+
if (!c.var.auth) {
|
|
11
|
+
return c.redirect(redirectTo);
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const session = await c.var.auth.api.getSession({
|
|
15
|
+
headers: c.req.raw.headers
|
|
16
|
+
});
|
|
17
|
+
if (!session?.user) {
|
|
18
|
+
return c.redirect(redirectTo);
|
|
19
|
+
}
|
|
20
|
+
await next();
|
|
21
|
+
} catch {
|
|
22
|
+
return c.redirect(redirectTo);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Middleware for API routes that requires authentication.
|
|
28
|
+
* Returns 401 if not authenticated.
|
|
29
|
+
*/ export function requireAuthApi() {
|
|
30
|
+
return async (c, next)=>{
|
|
31
|
+
if (!c.var.auth) {
|
|
32
|
+
return c.json({
|
|
33
|
+
error: "Authentication not configured"
|
|
34
|
+
}, 500);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const session = await c.var.auth.api.getSession({
|
|
38
|
+
headers: c.req.raw.headers
|
|
39
|
+
});
|
|
40
|
+
if (!session?.user) {
|
|
41
|
+
return c.json({
|
|
42
|
+
error: "Unauthorized"
|
|
43
|
+
}, 401);
|
|
44
|
+
}
|
|
45
|
+
await next();
|
|
46
|
+
} catch {
|
|
47
|
+
return c.json({
|
|
48
|
+
error: "Unauthorized"
|
|
49
|
+
}, 401);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Posts API Routes
|
|
3
|
+
*/
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import type { Bindings } from "../../types.js";
|
|
6
|
+
import type { AppVariables } from "../../app.js";
|
|
7
|
+
type Env = {
|
|
8
|
+
Bindings: Bindings;
|
|
9
|
+
Variables: AppVariables;
|
|
10
|
+
};
|
|
11
|
+
export declare const postsApiRoutes: Hono<Env, import("hono/types").BlankSchema, "/">;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=posts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posts.d.ts","sourceRoot":"","sources":["../../../src/routes/api/posts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAwB,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,cAAc,kDAAkB,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Posts API Routes
|
|
3
|
+
*/ import { Hono } from "hono";
|
|
4
|
+
import * as sqid from "../../lib/sqid.js";
|
|
5
|
+
import { CreatePostSchema, UpdatePostSchema } from "../../lib/schemas.js";
|
|
6
|
+
import { requireAuthApi } from "../../middleware/auth.js";
|
|
7
|
+
export const postsApiRoutes = new Hono();
|
|
8
|
+
// List posts
|
|
9
|
+
postsApiRoutes.get("/", async (c)=>{
|
|
10
|
+
const type = c.req.query("type");
|
|
11
|
+
const visibility = c.req.query("visibility");
|
|
12
|
+
const cursor = c.req.query("cursor");
|
|
13
|
+
const limit = parseInt(c.req.query("limit") ?? "100", 10);
|
|
14
|
+
const posts = await c.var.services.posts.list({
|
|
15
|
+
type,
|
|
16
|
+
visibility: visibility ? [
|
|
17
|
+
visibility
|
|
18
|
+
] : [
|
|
19
|
+
"featured",
|
|
20
|
+
"quiet"
|
|
21
|
+
],
|
|
22
|
+
cursor: cursor ? sqid.decode(cursor) ?? undefined : undefined,
|
|
23
|
+
limit
|
|
24
|
+
});
|
|
25
|
+
return c.json({
|
|
26
|
+
posts: posts.map((p)=>({
|
|
27
|
+
...p,
|
|
28
|
+
sqid: sqid.encode(p.id)
|
|
29
|
+
})),
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Array length check guarantees element exists
|
|
31
|
+
nextCursor: posts.length === limit ? sqid.encode(posts[posts.length - 1].id) : null
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
// Get single post
|
|
35
|
+
postsApiRoutes.get("/:id", async (c)=>{
|
|
36
|
+
const id = sqid.decode(c.req.param("id"));
|
|
37
|
+
if (!id) return c.json({
|
|
38
|
+
error: "Invalid ID"
|
|
39
|
+
}, 400);
|
|
40
|
+
const post = await c.var.services.posts.getById(id);
|
|
41
|
+
if (!post) return c.json({
|
|
42
|
+
error: "Not found"
|
|
43
|
+
}, 404);
|
|
44
|
+
return c.json({
|
|
45
|
+
...post,
|
|
46
|
+
sqid: sqid.encode(post.id)
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
// Create post (requires auth)
|
|
50
|
+
postsApiRoutes.post("/", requireAuthApi(), async (c)=>{
|
|
51
|
+
const rawBody = await c.req.json();
|
|
52
|
+
// Validate request body
|
|
53
|
+
const parseResult = CreatePostSchema.safeParse(rawBody);
|
|
54
|
+
if (!parseResult.success) {
|
|
55
|
+
return c.json({
|
|
56
|
+
error: "Validation failed",
|
|
57
|
+
details: parseResult.error.flatten()
|
|
58
|
+
}, 400);
|
|
59
|
+
}
|
|
60
|
+
const body = parseResult.data;
|
|
61
|
+
const post = await c.var.services.posts.create({
|
|
62
|
+
type: body.type,
|
|
63
|
+
title: body.title,
|
|
64
|
+
content: body.content,
|
|
65
|
+
visibility: body.visibility,
|
|
66
|
+
sourceUrl: body.sourceUrl || undefined,
|
|
67
|
+
sourceName: body.sourceName,
|
|
68
|
+
path: body.path || undefined,
|
|
69
|
+
replyToId: body.replyToId ? sqid.decode(body.replyToId) ?? undefined : undefined,
|
|
70
|
+
publishedAt: body.publishedAt
|
|
71
|
+
});
|
|
72
|
+
return c.json({
|
|
73
|
+
...post,
|
|
74
|
+
sqid: sqid.encode(post.id)
|
|
75
|
+
}, 201);
|
|
76
|
+
});
|
|
77
|
+
// Update post (requires auth)
|
|
78
|
+
postsApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
|
|
79
|
+
const id = sqid.decode(c.req.param("id"));
|
|
80
|
+
if (!id) return c.json({
|
|
81
|
+
error: "Invalid ID"
|
|
82
|
+
}, 400);
|
|
83
|
+
const rawBody = await c.req.json();
|
|
84
|
+
// Validate request body
|
|
85
|
+
const parseResult = UpdatePostSchema.safeParse(rawBody);
|
|
86
|
+
if (!parseResult.success) {
|
|
87
|
+
return c.json({
|
|
88
|
+
error: "Validation failed",
|
|
89
|
+
details: parseResult.error.flatten()
|
|
90
|
+
}, 400);
|
|
91
|
+
}
|
|
92
|
+
const body = parseResult.data;
|
|
93
|
+
const post = await c.var.services.posts.update(id, {
|
|
94
|
+
type: body.type,
|
|
95
|
+
title: body.title,
|
|
96
|
+
content: body.content,
|
|
97
|
+
visibility: body.visibility,
|
|
98
|
+
sourceUrl: body.sourceUrl,
|
|
99
|
+
sourceName: body.sourceName,
|
|
100
|
+
path: body.path,
|
|
101
|
+
publishedAt: body.publishedAt
|
|
102
|
+
});
|
|
103
|
+
if (!post) return c.json({
|
|
104
|
+
error: "Not found"
|
|
105
|
+
}, 404);
|
|
106
|
+
return c.json({
|
|
107
|
+
...post,
|
|
108
|
+
sqid: sqid.encode(post.id)
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
// Delete post (requires auth)
|
|
112
|
+
postsApiRoutes.delete("/:id", requireAuthApi(), async (c)=>{
|
|
113
|
+
const id = sqid.decode(c.req.param("id"));
|
|
114
|
+
if (!id) return c.json({
|
|
115
|
+
error: "Invalid ID"
|
|
116
|
+
}, 400);
|
|
117
|
+
const success = await c.var.services.posts.delete(id);
|
|
118
|
+
if (!success) return c.json({
|
|
119
|
+
error: "Not found"
|
|
120
|
+
}, 404);
|
|
121
|
+
return c.json({
|
|
122
|
+
success: true
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search API Routes
|
|
3
|
+
*/
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import type { Bindings } from "../../types.js";
|
|
6
|
+
import type { AppVariables } from "../../app.js";
|
|
7
|
+
type Env = {
|
|
8
|
+
Bindings: Bindings;
|
|
9
|
+
Variables: AppVariables;
|
|
10
|
+
};
|
|
11
|
+
export declare const searchApiRoutes: Hono<Env, import("hono/types").BlankSchema, "/">;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/routes/api/search.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,eAAe,kDAAkB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search API Routes
|
|
3
|
+
*/ import { Hono } from "hono";
|
|
4
|
+
import * as sqid from "../../lib/sqid.js";
|
|
5
|
+
export const searchApiRoutes = new Hono();
|
|
6
|
+
// Search posts
|
|
7
|
+
searchApiRoutes.get("/", async (c)=>{
|
|
8
|
+
const query = c.req.query("q");
|
|
9
|
+
if (!query || query.trim().length === 0) {
|
|
10
|
+
return c.json({
|
|
11
|
+
error: "Query parameter 'q' is required"
|
|
12
|
+
}, 400);
|
|
13
|
+
}
|
|
14
|
+
if (query.length > 200) {
|
|
15
|
+
return c.json({
|
|
16
|
+
error: "Query too long"
|
|
17
|
+
}, 400);
|
|
18
|
+
}
|
|
19
|
+
const limitParam = c.req.query("limit");
|
|
20
|
+
const limit = limitParam ? Math.min(parseInt(limitParam, 10) || 20, 50) : 20;
|
|
21
|
+
try {
|
|
22
|
+
const results = await c.var.services.search.search(query, {
|
|
23
|
+
limit,
|
|
24
|
+
visibility: [
|
|
25
|
+
"featured",
|
|
26
|
+
"quiet"
|
|
27
|
+
]
|
|
28
|
+
});
|
|
29
|
+
return c.json({
|
|
30
|
+
query,
|
|
31
|
+
results: results.map((r)=>({
|
|
32
|
+
id: sqid.encode(r.post.id),
|
|
33
|
+
type: r.post.type,
|
|
34
|
+
title: r.post.title,
|
|
35
|
+
path: r.post.path,
|
|
36
|
+
snippet: r.snippet,
|
|
37
|
+
publishedAt: r.post.publishedAt,
|
|
38
|
+
url: `/p/${sqid.encode(r.post.id)}`
|
|
39
|
+
})),
|
|
40
|
+
count: results.length
|
|
41
|
+
});
|
|
42
|
+
} catch (err) {
|
|
43
|
+
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
44
|
+
console.error("Search error:", err);
|
|
45
|
+
return c.json({
|
|
46
|
+
error: "Search failed"
|
|
47
|
+
}, 500);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload API Routes
|
|
3
|
+
*
|
|
4
|
+
* Handles file uploads to R2 storage.
|
|
5
|
+
* Supports both JSON and SSE (Datastar) responses.
|
|
6
|
+
*/
|
|
7
|
+
import { Hono } from "hono";
|
|
8
|
+
import type { Bindings } from "../../types.js";
|
|
9
|
+
import type { AppVariables } from "../../app.js";
|
|
10
|
+
type Env = {
|
|
11
|
+
Bindings: Bindings;
|
|
12
|
+
Variables: AppVariables;
|
|
13
|
+
};
|
|
14
|
+
export declare const uploadApiRoutes: Hono<Env, import("hono/types").BlankSchema, "/">;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/routes/api/upload.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,eAAe,kDAAkB,CAAC"}
|