@jant/core 0.2.12 → 0.2.13
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/bin/jant.js +3 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +112 -85
- package/dist/auth.d.ts +1 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -1
- package/dist/client.js +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/i18n/context.d.ts.map +1 -1
- package/dist/i18n/context.js +0 -3
- package/dist/i18n/detect.d.ts +0 -11
- package/dist/i18n/detect.d.ts.map +1 -1
- package/dist/i18n/detect.js +1 -52
- package/dist/i18n/i18n.d.ts +4 -14
- package/dist/i18n/i18n.d.ts.map +1 -1
- package/dist/i18n/i18n.js +19 -25
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/middleware.d.ts +2 -5
- package/dist/i18n/middleware.d.ts.map +1 -1
- package/dist/i18n/middleware.js +12 -23
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/image.d.ts.map +1 -1
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/sse.d.ts +45 -17
- package/dist/lib/sse.d.ts.map +1 -1
- package/dist/lib/sse.js +77 -37
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/routes/api/posts.js +0 -1
- package/dist/routes/api/upload.js +3 -1
- package/dist/routes/dash/collections.d.ts.map +1 -1
- package/dist/routes/dash/collections.js +134 -142
- package/dist/routes/dash/index.js +25 -26
- package/dist/routes/dash/media.d.ts.map +1 -1
- package/dist/routes/dash/media.js +60 -56
- package/dist/routes/dash/pages.js +64 -66
- package/dist/routes/dash/posts.d.ts.map +1 -1
- package/dist/routes/dash/posts.js +50 -59
- package/dist/routes/dash/redirects.d.ts.map +1 -1
- package/dist/routes/dash/redirects.js +63 -60
- package/dist/routes/dash/settings.d.ts.map +1 -1
- package/dist/routes/dash/settings.js +249 -93
- package/dist/routes/feed/rss.js +6 -4
- package/dist/routes/pages/archive.js +60 -62
- package/dist/routes/pages/collection.js +8 -8
- package/dist/routes/pages/home.js +14 -14
- package/dist/routes/pages/page.js +7 -6
- package/dist/routes/pages/post.js +8 -8
- package/dist/routes/pages/search.js +25 -27
- package/dist/services/collection.d.ts.map +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/media.d.ts.map +1 -1
- package/dist/services/post.d.ts.map +1 -1
- package/dist/services/redirect.d.ts.map +1 -1
- package/dist/services/settings.d.ts.map +1 -1
- package/dist/theme/components/ActionButtons.d.ts +1 -1
- package/dist/theme/components/ActionButtons.d.ts.map +1 -1
- package/dist/theme/components/ActionButtons.js +17 -21
- package/dist/theme/components/CrudPageHeader.d.ts.map +1 -1
- package/dist/theme/components/DangerZone.d.ts.map +1 -1
- package/dist/theme/components/DangerZone.js +12 -15
- package/dist/theme/components/EmptyState.d.ts.map +1 -1
- package/dist/theme/components/PageForm.d.ts.map +1 -1
- package/dist/theme/components/PageForm.js +58 -56
- package/dist/theme/components/Pagination.d.ts.map +1 -1
- package/dist/theme/components/Pagination.js +22 -25
- package/dist/theme/components/PostForm.d.ts +0 -1
- package/dist/theme/components/PostForm.d.ts.map +1 -1
- package/dist/theme/components/PostForm.js +85 -77
- package/dist/theme/components/PostList.d.ts.map +1 -1
- package/dist/theme/components/PostList.js +17 -17
- package/dist/theme/components/ThreadView.d.ts.map +1 -1
- package/dist/theme/components/ThreadView.js +15 -18
- package/dist/theme/components/TypeBadge.d.ts.map +1 -1
- package/dist/theme/components/TypeBadge.js +20 -20
- package/dist/theme/components/VisibilityBadge.d.ts.map +1 -1
- package/dist/theme/components/VisibilityBadge.js +14 -14
- package/dist/theme/components/index.d.ts +1 -1
- package/dist/theme/components/index.d.ts.map +1 -1
- package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
- package/dist/theme/layouts/BaseLayout.js +4 -2
- package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
- package/dist/theme/layouts/DashLayout.js +29 -29
- package/dist/types/lingui-react-macro.d.js +9 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vendor/datastar.js +1606 -0
- package/package.json +5 -2
- package/src/app.tsx +175 -56
- package/src/auth.ts +5 -1
- package/src/client.ts +1 -1
- package/src/db/schema.ts +22 -7
- package/src/i18n/EXAMPLES.md +34 -14
- package/src/i18n/README.md +19 -9
- package/src/i18n/context.tsx +1 -4
- package/src/i18n/detect.ts +1 -67
- package/src/i18n/i18n.ts +15 -19
- package/src/i18n/index.ts +0 -3
- package/src/i18n/middleware.ts +12 -24
- package/src/lib/constants.ts +2 -1
- package/src/lib/image-processor.ts +23 -7
- package/src/lib/image.ts +6 -2
- package/src/lib/schemas.ts +6 -2
- package/src/lib/sse.ts +138 -50
- package/src/middleware/auth.ts +6 -2
- package/src/routes/api/posts.ts +14 -5
- package/src/routes/api/upload.ts +25 -7
- package/src/routes/dash/collections.tsx +162 -70
- package/src/routes/dash/index.tsx +22 -7
- package/src/routes/dash/media.tsx +59 -16
- package/src/routes/dash/pages.tsx +102 -44
- package/src/routes/dash/posts.tsx +87 -54
- package/src/routes/dash/redirects.tsx +74 -26
- package/src/routes/dash/settings.tsx +250 -57
- package/src/routes/feed/rss.ts +6 -4
- package/src/routes/pages/archive.tsx +71 -21
- package/src/routes/pages/collection.tsx +21 -6
- package/src/routes/pages/home.tsx +30 -9
- package/src/routes/pages/page.tsx +14 -5
- package/src/routes/pages/post.tsx +21 -7
- package/src/routes/pages/search.tsx +42 -11
- package/src/services/collection.ts +34 -9
- package/src/services/index.ts +4 -1
- package/src/services/media.ts +15 -3
- package/src/services/post.ts +39 -10
- package/src/services/redirect.ts +4 -1
- package/src/services/settings.ts +14 -3
- package/src/theme/components/ActionButtons.tsx +26 -14
- package/src/theme/components/CrudPageHeader.tsx +6 -1
- package/src/theme/components/DangerZone.tsx +19 -13
- package/src/theme/components/EmptyState.tsx +6 -1
- package/src/theme/components/PageForm.tsx +71 -24
- package/src/theme/components/Pagination.tsx +26 -8
- package/src/theme/components/PostForm.tsx +72 -25
- package/src/theme/components/PostList.tsx +16 -5
- package/src/theme/components/ThreadView.tsx +25 -7
- package/src/theme/components/TypeBadge.tsx +13 -4
- package/src/theme/components/VisibilityBadge.tsx +17 -5
- package/src/theme/components/index.ts +4 -1
- package/src/theme/layouts/BaseLayout.tsx +5 -2
- package/src/theme/layouts/DashLayout.tsx +41 -12
- package/src/types/lingui-react-macro.d.ts +34 -0
- package/src/types.ts +16 -2
- package/src/vendor/datastar.js +9 -0
- package/src/vendor/datastar.js.map +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jant/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "A modern, open-source microblogging platform built on Cloudflare Workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"types": "./dist/theme/index.d.ts",
|
|
16
16
|
"default": "./dist/theme/index.js"
|
|
17
17
|
},
|
|
18
|
+
"./i18n": {
|
|
19
|
+
"types": "./dist/i18n/index.d.ts",
|
|
20
|
+
"default": "./dist/i18n/index.js"
|
|
21
|
+
},
|
|
18
22
|
"./preset.css": "./src/preset.css",
|
|
19
23
|
"./client": "./dist/client.js"
|
|
20
24
|
},
|
|
@@ -29,7 +33,6 @@
|
|
|
29
33
|
},
|
|
30
34
|
"dependencies": {
|
|
31
35
|
"@lingui/core": "^5.9.0",
|
|
32
|
-
"@sudodevnull/datastar": "^0.19.9",
|
|
33
36
|
"better-auth": "^1.4.18",
|
|
34
37
|
"drizzle-orm": "^0.45.1",
|
|
35
38
|
"hono": "^4.11.7",
|
package/src/app.tsx
CHANGED
|
@@ -7,7 +7,8 @@ import type { FC } from "hono/jsx";
|
|
|
7
7
|
import { createDatabase } from "./db/index.js";
|
|
8
8
|
import { createServices, type Services } from "./services/index.js";
|
|
9
9
|
import { createAuth, type Auth } from "./auth.js";
|
|
10
|
-
import { i18nMiddleware
|
|
10
|
+
import { i18nMiddleware } from "./i18n/index.js";
|
|
11
|
+
import { useLingui } from "@lingui/react/macro";
|
|
11
12
|
import type { Bindings, JantConfig } from "./types.js";
|
|
12
13
|
|
|
13
14
|
// Routes - Pages
|
|
@@ -41,6 +42,7 @@ import { requireAuth } from "./middleware/auth.js";
|
|
|
41
42
|
|
|
42
43
|
// Layouts for auth pages
|
|
43
44
|
import { BaseLayout } from "./theme/layouts/index.js";
|
|
45
|
+
import { sse } from "./lib/sse.js";
|
|
44
46
|
|
|
45
47
|
// Extend Hono's context variables
|
|
46
48
|
export interface AppVariables {
|
|
@@ -123,7 +125,7 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
123
125
|
status: "ok",
|
|
124
126
|
auth: c.env.AUTH_SECRET ? "configured" : "missing",
|
|
125
127
|
authSecretLength: c.env.AUTH_SECRET?.length ?? 0,
|
|
126
|
-
})
|
|
128
|
+
}),
|
|
127
129
|
);
|
|
128
130
|
|
|
129
131
|
// better-auth handler
|
|
@@ -138,7 +140,7 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
138
140
|
app.route("/api/posts", postsApiRoutes);
|
|
139
141
|
|
|
140
142
|
// Setup page component
|
|
141
|
-
const SetupContent: FC
|
|
143
|
+
const SetupContent: FC = () => {
|
|
142
144
|
const { t } = useLingui();
|
|
143
145
|
|
|
144
146
|
return (
|
|
@@ -146,7 +148,10 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
146
148
|
<div class="card max-w-md w-full">
|
|
147
149
|
<header>
|
|
148
150
|
<h2>
|
|
149
|
-
{t({
|
|
151
|
+
{t({
|
|
152
|
+
message: "Welcome to Jant",
|
|
153
|
+
comment: "@context: Setup page welcome heading",
|
|
154
|
+
})}
|
|
150
155
|
</h2>
|
|
151
156
|
<p>
|
|
152
157
|
{t({
|
|
@@ -156,15 +161,22 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
156
161
|
</p>
|
|
157
162
|
</header>
|
|
158
163
|
<section>
|
|
159
|
-
|
|
160
|
-
<form
|
|
164
|
+
<div id="setup-message"></div>
|
|
165
|
+
<form
|
|
166
|
+
data-signals="{siteName: '', name: '', email: '', password: ''}"
|
|
167
|
+
data-on:submit__prevent="@post('/setup')"
|
|
168
|
+
class="flex flex-col gap-4"
|
|
169
|
+
>
|
|
161
170
|
<div class="field">
|
|
162
171
|
<label class="label">
|
|
163
|
-
{t({
|
|
172
|
+
{t({
|
|
173
|
+
message: "Site Name",
|
|
174
|
+
comment: "@context: Setup form field - site name",
|
|
175
|
+
})}
|
|
164
176
|
</label>
|
|
165
177
|
<input
|
|
166
178
|
type="text"
|
|
167
|
-
|
|
179
|
+
data-bind="siteName"
|
|
168
180
|
class="input"
|
|
169
181
|
required
|
|
170
182
|
placeholder={t({
|
|
@@ -175,17 +187,29 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
175
187
|
</div>
|
|
176
188
|
<div class="field">
|
|
177
189
|
<label class="label">
|
|
178
|
-
{t({
|
|
190
|
+
{t({
|
|
191
|
+
message: "Your Name",
|
|
192
|
+
comment: "@context: Setup form field - user name",
|
|
193
|
+
})}
|
|
179
194
|
</label>
|
|
180
|
-
<input
|
|
195
|
+
<input
|
|
196
|
+
type="text"
|
|
197
|
+
data-bind="name"
|
|
198
|
+
class="input"
|
|
199
|
+
required
|
|
200
|
+
placeholder="John Doe"
|
|
201
|
+
/>
|
|
181
202
|
</div>
|
|
182
203
|
<div class="field">
|
|
183
204
|
<label class="label">
|
|
184
|
-
{t({
|
|
205
|
+
{t({
|
|
206
|
+
message: "Email",
|
|
207
|
+
comment: "@context: Setup/signin form field - email",
|
|
208
|
+
})}
|
|
185
209
|
</label>
|
|
186
210
|
<input
|
|
187
211
|
type="email"
|
|
188
|
-
|
|
212
|
+
data-bind="email"
|
|
189
213
|
class="input"
|
|
190
214
|
required
|
|
191
215
|
placeholder="you@example.com"
|
|
@@ -198,10 +222,19 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
198
222
|
comment: "@context: Setup/signin form field - password",
|
|
199
223
|
})}
|
|
200
224
|
</label>
|
|
201
|
-
<input
|
|
225
|
+
<input
|
|
226
|
+
type="password"
|
|
227
|
+
data-bind="password"
|
|
228
|
+
class="input"
|
|
229
|
+
required
|
|
230
|
+
minLength={8}
|
|
231
|
+
/>
|
|
202
232
|
</div>
|
|
203
233
|
<button type="submit" class="btn">
|
|
204
|
-
{t({
|
|
234
|
+
{t({
|
|
235
|
+
message: "Complete Setup",
|
|
236
|
+
comment: "@context: Setup form submit button",
|
|
237
|
+
})}
|
|
205
238
|
</button>
|
|
206
239
|
</form>
|
|
207
240
|
</section>
|
|
@@ -215,12 +248,10 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
215
248
|
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
216
249
|
if (isComplete) return c.redirect("/");
|
|
217
250
|
|
|
218
|
-
const error = c.req.query("error");
|
|
219
|
-
|
|
220
251
|
return c.html(
|
|
221
252
|
<BaseLayout title="Setup - Jant" c={c}>
|
|
222
|
-
<SetupContent
|
|
223
|
-
</BaseLayout
|
|
253
|
+
<SetupContent />
|
|
254
|
+
</BaseLayout>,
|
|
224
255
|
);
|
|
225
256
|
});
|
|
226
257
|
|
|
@@ -228,22 +259,36 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
228
259
|
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
229
260
|
if (isComplete) return c.redirect("/");
|
|
230
261
|
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
262
|
+
const body = await c.req.json<{
|
|
263
|
+
siteName: string;
|
|
264
|
+
name: string;
|
|
265
|
+
email: string;
|
|
266
|
+
password: string;
|
|
267
|
+
}>();
|
|
268
|
+
const { siteName, name, email, password } = body;
|
|
236
269
|
|
|
237
270
|
if (!siteName || !name || !email || !password) {
|
|
238
|
-
return c
|
|
271
|
+
return sse(c, async (stream) => {
|
|
272
|
+
await stream.patchElements(
|
|
273
|
+
'<div id="setup-message"><p class="text-destructive text-sm mb-4">All fields are required</p></div>',
|
|
274
|
+
);
|
|
275
|
+
});
|
|
239
276
|
}
|
|
240
277
|
|
|
241
278
|
if (password.length < 8) {
|
|
242
|
-
return c
|
|
279
|
+
return sse(c, async (stream) => {
|
|
280
|
+
await stream.patchElements(
|
|
281
|
+
'<div id="setup-message"><p class="text-destructive text-sm mb-4">Password must be at least 8 characters</p></div>',
|
|
282
|
+
);
|
|
283
|
+
});
|
|
243
284
|
}
|
|
244
285
|
|
|
245
286
|
if (!c.var.auth) {
|
|
246
|
-
return c
|
|
287
|
+
return sse(c, async (stream) => {
|
|
288
|
+
await stream.patchElements(
|
|
289
|
+
'<div id="setup-message"><p class="text-destructive text-sm mb-4">AUTH_SECRET not configured</p></div>',
|
|
290
|
+
);
|
|
291
|
+
});
|
|
247
292
|
}
|
|
248
293
|
|
|
249
294
|
try {
|
|
@@ -252,7 +297,11 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
252
297
|
});
|
|
253
298
|
|
|
254
299
|
if (!signUpResponse || "error" in signUpResponse) {
|
|
255
|
-
return c
|
|
300
|
+
return sse(c, async (stream) => {
|
|
301
|
+
await stream.patchElements(
|
|
302
|
+
'<div id="setup-message"><p class="text-destructive text-sm mb-4">Failed to create account</p></div>',
|
|
303
|
+
);
|
|
304
|
+
});
|
|
256
305
|
}
|
|
257
306
|
|
|
258
307
|
await c.var.services.settings.setMany({
|
|
@@ -261,32 +310,66 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
261
310
|
});
|
|
262
311
|
await c.var.services.settings.completeOnboarding();
|
|
263
312
|
|
|
264
|
-
return c
|
|
313
|
+
return sse(c, async (stream) => {
|
|
314
|
+
await stream.redirect("/signin");
|
|
315
|
+
});
|
|
265
316
|
} catch (err) {
|
|
266
317
|
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
267
318
|
console.error("Setup error:", err);
|
|
268
|
-
return c
|
|
319
|
+
return sse(c, async (stream) => {
|
|
320
|
+
await stream.patchElements(
|
|
321
|
+
'<div id="setup-message"><p class="text-destructive text-sm mb-4">Failed to create account</p></div>',
|
|
322
|
+
);
|
|
323
|
+
});
|
|
269
324
|
}
|
|
270
325
|
});
|
|
271
326
|
|
|
272
327
|
// Signin page component
|
|
273
|
-
const SigninContent: FC<{
|
|
328
|
+
const SigninContent: FC<{
|
|
329
|
+
demoEmail?: string;
|
|
330
|
+
demoPassword?: string;
|
|
331
|
+
}> = ({ demoEmail, demoPassword }) => {
|
|
274
332
|
const { t } = useLingui();
|
|
333
|
+
const signals = JSON.stringify({
|
|
334
|
+
email: demoEmail || "",
|
|
335
|
+
password: demoPassword || "",
|
|
336
|
+
}).replace(/</g, "\\u003c");
|
|
275
337
|
|
|
276
338
|
return (
|
|
277
339
|
<div class="min-h-screen flex items-center justify-center">
|
|
278
340
|
<div class="card max-w-md w-full">
|
|
279
341
|
<header>
|
|
280
|
-
<h2>
|
|
342
|
+
<h2>
|
|
343
|
+
{t({
|
|
344
|
+
message: "Sign In",
|
|
345
|
+
comment: "@context: Sign in page heading",
|
|
346
|
+
})}
|
|
347
|
+
</h2>
|
|
281
348
|
</header>
|
|
282
349
|
<section>
|
|
283
|
-
|
|
284
|
-
|
|
350
|
+
<div id="signin-message"></div>
|
|
351
|
+
{demoEmail && demoPassword && (
|
|
352
|
+
<p class="text-muted-foreground text-sm mb-4">
|
|
353
|
+
{t({
|
|
354
|
+
message: "Demo account pre-filled. Just click Sign In.",
|
|
355
|
+
comment:
|
|
356
|
+
"@context: Hint shown on signin page when demo credentials are pre-filled",
|
|
357
|
+
})}
|
|
358
|
+
</p>
|
|
359
|
+
)}
|
|
360
|
+
<form
|
|
361
|
+
data-signals={signals}
|
|
362
|
+
data-on:submit__prevent="@post('/signin')"
|
|
363
|
+
class="flex flex-col gap-4"
|
|
364
|
+
>
|
|
285
365
|
<div class="field">
|
|
286
366
|
<label class="label">
|
|
287
|
-
{t({
|
|
367
|
+
{t({
|
|
368
|
+
message: "Email",
|
|
369
|
+
comment: "@context: Setup/signin form field - email",
|
|
370
|
+
})}
|
|
288
371
|
</label>
|
|
289
|
-
<input type="email"
|
|
372
|
+
<input type="email" data-bind="email" class="input" required />
|
|
290
373
|
</div>
|
|
291
374
|
<div class="field">
|
|
292
375
|
<label class="label">
|
|
@@ -295,10 +378,18 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
295
378
|
comment: "@context: Setup/signin form field - password",
|
|
296
379
|
})}
|
|
297
380
|
</label>
|
|
298
|
-
<input
|
|
381
|
+
<input
|
|
382
|
+
type="password"
|
|
383
|
+
data-bind="password"
|
|
384
|
+
class="input"
|
|
385
|
+
required
|
|
386
|
+
/>
|
|
299
387
|
</div>
|
|
300
388
|
<button type="submit" class="btn">
|
|
301
|
-
{t({
|
|
389
|
+
{t({
|
|
390
|
+
message: "Sign In",
|
|
391
|
+
comment: "@context: Sign in form submit button",
|
|
392
|
+
})}
|
|
302
393
|
</button>
|
|
303
394
|
</form>
|
|
304
395
|
</section>
|
|
@@ -309,45 +400,70 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
309
400
|
|
|
310
401
|
// Signin page
|
|
311
402
|
app.get("/signin", async (c) => {
|
|
312
|
-
const error = c.req.query("error");
|
|
313
|
-
|
|
314
403
|
return c.html(
|
|
315
404
|
<BaseLayout title="Sign In - Jant" c={c}>
|
|
316
|
-
<SigninContent
|
|
317
|
-
|
|
405
|
+
<SigninContent
|
|
406
|
+
demoEmail={c.env.DEMO_EMAIL}
|
|
407
|
+
demoPassword={c.env.DEMO_PASSWORD}
|
|
408
|
+
/>
|
|
409
|
+
</BaseLayout>,
|
|
318
410
|
);
|
|
319
411
|
});
|
|
320
412
|
|
|
321
413
|
app.post("/signin", async (c) => {
|
|
322
414
|
if (!c.var.auth) {
|
|
323
|
-
return c
|
|
415
|
+
return sse(c, async (stream) => {
|
|
416
|
+
await stream.patchElements(
|
|
417
|
+
'<div id="signin-message"><p class="text-destructive text-sm mb-4">Auth not configured</p></div>',
|
|
418
|
+
);
|
|
419
|
+
});
|
|
324
420
|
}
|
|
325
421
|
|
|
326
|
-
const
|
|
327
|
-
const email
|
|
328
|
-
const password = formData.get("password") as string;
|
|
422
|
+
const body = await c.req.json<{ email: string; password: string }>();
|
|
423
|
+
const { email, password } = body;
|
|
329
424
|
|
|
330
425
|
try {
|
|
331
|
-
const signInRequest = new Request(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
426
|
+
const signInRequest = new Request(
|
|
427
|
+
`${c.env.SITE_URL}/api/auth/sign-in/email`,
|
|
428
|
+
{
|
|
429
|
+
method: "POST",
|
|
430
|
+
headers: { "Content-Type": "application/json" },
|
|
431
|
+
body: JSON.stringify({ email, password }),
|
|
432
|
+
},
|
|
433
|
+
);
|
|
336
434
|
|
|
337
435
|
const response = await c.var.auth.handler(signInRequest);
|
|
338
436
|
|
|
339
437
|
if (!response.ok) {
|
|
340
|
-
return c
|
|
438
|
+
return sse(c, async (stream) => {
|
|
439
|
+
await stream.patchElements(
|
|
440
|
+
'<div id="signin-message"><p class="text-destructive text-sm mb-4">Invalid email or password</p></div>',
|
|
441
|
+
);
|
|
442
|
+
});
|
|
341
443
|
}
|
|
342
444
|
|
|
343
|
-
|
|
344
|
-
|
|
445
|
+
// Forward Set-Cookie headers from auth response
|
|
446
|
+
const cookieHeaders: Record<string, string> = {};
|
|
447
|
+
const setCookie = response.headers.get("set-cookie");
|
|
448
|
+
if (setCookie) {
|
|
449
|
+
cookieHeaders["Set-Cookie"] = setCookie;
|
|
450
|
+
}
|
|
345
451
|
|
|
346
|
-
return
|
|
452
|
+
return sse(
|
|
453
|
+
c,
|
|
454
|
+
async (stream) => {
|
|
455
|
+
await stream.redirect("/dash");
|
|
456
|
+
},
|
|
457
|
+
{ headers: cookieHeaders },
|
|
458
|
+
);
|
|
347
459
|
} catch (err) {
|
|
348
460
|
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
349
461
|
console.error("Signin error:", err);
|
|
350
|
-
return c
|
|
462
|
+
return sse(c, async (stream) => {
|
|
463
|
+
await stream.patchElements(
|
|
464
|
+
'<div id="signin-message"><p class="text-destructive text-sm mb-4">Invalid email or password</p></div>',
|
|
465
|
+
);
|
|
466
|
+
});
|
|
351
467
|
}
|
|
352
468
|
});
|
|
353
469
|
|
|
@@ -397,7 +513,10 @@ export function createApp(config: JantConfig = {}): App {
|
|
|
397
513
|
}
|
|
398
514
|
|
|
399
515
|
const headers = new Headers();
|
|
400
|
-
headers.set(
|
|
516
|
+
headers.set(
|
|
517
|
+
"Content-Type",
|
|
518
|
+
object.httpMetadata?.contentType || media.mimeType,
|
|
519
|
+
);
|
|
401
520
|
headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
402
521
|
|
|
403
522
|
return new Response(object.body, { headers });
|
package/src/auth.ts
CHANGED
|
@@ -7,7 +7,10 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
|
7
7
|
import { drizzle } from "drizzle-orm/d1";
|
|
8
8
|
import * as schema from "./db/schema.js";
|
|
9
9
|
|
|
10
|
-
export function createAuth(
|
|
10
|
+
export function createAuth(
|
|
11
|
+
d1: D1Database,
|
|
12
|
+
options: { secret: string; baseURL: string },
|
|
13
|
+
) {
|
|
11
14
|
const db = drizzle(d1, { schema });
|
|
12
15
|
|
|
13
16
|
return betterAuth({
|
|
@@ -25,6 +28,7 @@ export function createAuth(d1: D1Database, options: { secret: string; baseURL: s
|
|
|
25
28
|
emailAndPassword: {
|
|
26
29
|
enabled: true,
|
|
27
30
|
autoSignIn: true,
|
|
31
|
+
minPasswordLength: 8,
|
|
28
32
|
},
|
|
29
33
|
session: {
|
|
30
34
|
cookieCache: {
|
package/src/client.ts
CHANGED
package/src/db/schema.ts
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* Database schema for Jant
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
sqliteTable,
|
|
9
|
+
text,
|
|
10
|
+
integer,
|
|
11
|
+
primaryKey,
|
|
12
|
+
} from "drizzle-orm/sqlite-core";
|
|
8
13
|
|
|
9
14
|
// =============================================================================
|
|
10
15
|
// Posts
|
|
@@ -12,8 +17,12 @@ import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core"
|
|
|
12
17
|
|
|
13
18
|
export const posts = sqliteTable("posts", {
|
|
14
19
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
15
|
-
type: text("type", {
|
|
16
|
-
|
|
20
|
+
type: text("type", {
|
|
21
|
+
enum: ["note", "article", "link", "quote", "image", "page"],
|
|
22
|
+
}).notNull(),
|
|
23
|
+
visibility: text("visibility", {
|
|
24
|
+
enum: ["featured", "quiet", "unlisted", "draft"],
|
|
25
|
+
})
|
|
17
26
|
.notNull()
|
|
18
27
|
.default("quiet"),
|
|
19
28
|
title: text("title"),
|
|
@@ -77,7 +86,7 @@ export const postCollections = sqliteTable(
|
|
|
77
86
|
.references(() => collections.id),
|
|
78
87
|
addedAt: integer("added_at").notNull(),
|
|
79
88
|
},
|
|
80
|
-
(table) => [primaryKey({ columns: [table.postId, table.collectionId] })]
|
|
89
|
+
(table) => [primaryKey({ columns: [table.postId, table.collectionId] })],
|
|
81
90
|
);
|
|
82
91
|
|
|
83
92
|
// =============================================================================
|
|
@@ -111,7 +120,9 @@ export const user = sqliteTable("user", {
|
|
|
111
120
|
id: text("id").primaryKey(),
|
|
112
121
|
name: text("name").notNull(),
|
|
113
122
|
email: text("email").notNull().unique(),
|
|
114
|
-
emailVerified: integer("email_verified", { mode: "boolean" })
|
|
123
|
+
emailVerified: integer("email_verified", { mode: "boolean" })
|
|
124
|
+
.notNull()
|
|
125
|
+
.default(false),
|
|
115
126
|
image: text("image"),
|
|
116
127
|
role: text("role").default("admin"),
|
|
117
128
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
@@ -141,8 +152,12 @@ export const account = sqliteTable("account", {
|
|
|
141
152
|
accessToken: text("access_token"),
|
|
142
153
|
refreshToken: text("refresh_token"),
|
|
143
154
|
idToken: text("id_token"),
|
|
144
|
-
accessTokenExpiresAt: integer("access_token_expires_at", {
|
|
145
|
-
|
|
155
|
+
accessTokenExpiresAt: integer("access_token_expires_at", {
|
|
156
|
+
mode: "timestamp",
|
|
157
|
+
}),
|
|
158
|
+
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
|
|
159
|
+
mode: "timestamp",
|
|
160
|
+
}),
|
|
146
161
|
scope: text("scope"),
|
|
147
162
|
password: text("password"),
|
|
148
163
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
package/src/i18n/EXAMPLES.md
CHANGED
|
@@ -14,7 +14,7 @@ dashRoute.get("/", async (c) => {
|
|
|
14
14
|
return c.html(
|
|
15
15
|
<I18nProvider c={c}>
|
|
16
16
|
<MyApp />
|
|
17
|
-
</I18nProvider
|
|
17
|
+
</I18nProvider>,
|
|
18
18
|
);
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -22,7 +22,9 @@ dashRoute.get("/", async (c) => {
|
|
|
22
22
|
function MyApp() {
|
|
23
23
|
const { t } = useLingui();
|
|
24
24
|
|
|
25
|
-
return
|
|
25
|
+
return (
|
|
26
|
+
<h1>{t({ message: "Dashboard", comment: "@context: Page title" })}</h1>
|
|
27
|
+
);
|
|
26
28
|
}
|
|
27
29
|
```
|
|
28
30
|
|
|
@@ -53,12 +55,18 @@ dashRoute.get("/", async (c) => {
|
|
|
53
55
|
return c.html(
|
|
54
56
|
<I18nProvider c={c}>
|
|
55
57
|
<Dashboard postCount={posts.length} username="Alice" />
|
|
56
|
-
</I18nProvider
|
|
58
|
+
</I18nProvider>,
|
|
57
59
|
);
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
// Component: use useLingui() hook
|
|
61
|
-
function Dashboard({
|
|
63
|
+
function Dashboard({
|
|
64
|
+
postCount,
|
|
65
|
+
username,
|
|
66
|
+
}: {
|
|
67
|
+
postCount: number;
|
|
68
|
+
username: string;
|
|
69
|
+
}) {
|
|
62
70
|
const { t } = useLingui();
|
|
63
71
|
|
|
64
72
|
return (
|
|
@@ -69,16 +77,22 @@ function Dashboard({ postCount, username }: { postCount: number; username: strin
|
|
|
69
77
|
{/* 2. With variables */}
|
|
70
78
|
<p>
|
|
71
79
|
{t(
|
|
72
|
-
{
|
|
73
|
-
|
|
80
|
+
{
|
|
81
|
+
message: "Welcome back, {name}!",
|
|
82
|
+
comment: "@context: Welcome message",
|
|
83
|
+
},
|
|
84
|
+
{ name: username },
|
|
74
85
|
)}
|
|
75
86
|
</p>
|
|
76
87
|
|
|
77
88
|
{/* 3. With dynamic values */}
|
|
78
89
|
<p>
|
|
79
90
|
{t(
|
|
80
|
-
{
|
|
81
|
-
|
|
91
|
+
{
|
|
92
|
+
message: "You have {count} posts",
|
|
93
|
+
comment: "@context: Post count",
|
|
94
|
+
},
|
|
95
|
+
{ count: postCount },
|
|
82
96
|
)}
|
|
83
97
|
</p>
|
|
84
98
|
|
|
@@ -130,11 +144,14 @@ const { t } = useLingui();
|
|
|
130
144
|
// For dynamic content, use t() with placeholders
|
|
131
145
|
<p>
|
|
132
146
|
{t(
|
|
133
|
-
{
|
|
147
|
+
{
|
|
148
|
+
message: "Visit {linkStart}our website{linkEnd}",
|
|
149
|
+
comment: "@context: Website link",
|
|
150
|
+
},
|
|
134
151
|
{
|
|
135
152
|
linkStart: '<a href="/" class="text-primary">',
|
|
136
153
|
linkEnd: "</a>",
|
|
137
|
-
}
|
|
154
|
+
},
|
|
138
155
|
)}
|
|
139
156
|
</p>;
|
|
140
157
|
```
|
|
@@ -154,7 +171,7 @@ dashRoute.get("/", async (c) => {
|
|
|
154
171
|
return c.html(
|
|
155
172
|
<Layout>
|
|
156
173
|
<MyComponent c={c} /> {/* Must pass c prop */}
|
|
157
|
-
</Layout
|
|
174
|
+
</Layout>,
|
|
158
175
|
);
|
|
159
176
|
});
|
|
160
177
|
|
|
@@ -163,7 +180,7 @@ function MyComponent({ c }: { c: Context }) {
|
|
|
163
180
|
const title = i18n._({ message: "Dashboard", comment: "@context: Title" });
|
|
164
181
|
const greeting = i18n._(
|
|
165
182
|
{ message: "Hello {name}", comment: "@context: Greeting" },
|
|
166
|
-
{ name: "Alice" }
|
|
183
|
+
{ name: "Alice" },
|
|
167
184
|
);
|
|
168
185
|
|
|
169
186
|
return <h1>{title}</h1>;
|
|
@@ -181,14 +198,17 @@ dashRoute.get("/", async (c) => {
|
|
|
181
198
|
<Layout>
|
|
182
199
|
<MyComponent /> {/* No props needed */}
|
|
183
200
|
</Layout>
|
|
184
|
-
</I18nProvider
|
|
201
|
+
</I18nProvider>,
|
|
185
202
|
);
|
|
186
203
|
});
|
|
187
204
|
|
|
188
205
|
function MyComponent() {
|
|
189
206
|
const { t } = useLingui();
|
|
190
207
|
const title = t({ message: "Dashboard", comment: "@context: Title" });
|
|
191
|
-
const greeting = t(
|
|
208
|
+
const greeting = t(
|
|
209
|
+
{ message: "Hello {name}", comment: "@context: Greeting" },
|
|
210
|
+
{ name: "Alice" },
|
|
211
|
+
);
|
|
192
212
|
|
|
193
213
|
return <h1>{title}</h1>;
|
|
194
214
|
}
|