@jant/core 0.3.27 → 0.3.29
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/reset-password.js +22 -0
- package/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +25 -15
- package/src/__tests__/helpers/app.ts +19 -3
- package/src/__tests__/helpers/db.ts +44 -0
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
- package/src/app.tsx +111 -174
- package/src/client.ts +13 -0
- package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
- package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
- package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
- package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
- package/src/db/schema.ts +24 -4
- package/src/i18n/locales/en.po +810 -385
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +733 -522
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +733 -522
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +7 -11
- package/src/index.ts +1 -1
- package/src/lib/__tests__/icons.test.ts +178 -0
- package/src/lib/__tests__/resolve-config.test.ts +184 -0
- package/src/lib/__tests__/schemas.test.ts +12 -6
- package/src/lib/__tests__/theme.test.ts +62 -0
- package/src/lib/__tests__/timezones.test.ts +1 -1
- package/src/lib/__tests__/url.test.ts +12 -0
- package/src/lib/__tests__/view.test.ts +1 -5
- package/src/lib/avatar-upload.ts +18 -10
- package/src/lib/collection-form-bridge.ts +52 -0
- package/src/lib/collections-reorder.ts +28 -0
- package/src/lib/compose-bridge.ts +251 -0
- package/src/lib/errors.ts +116 -0
- package/src/lib/excerpt.ts +1 -1
- package/src/lib/favicon.ts +3 -5
- package/src/lib/html.ts +22 -0
- package/src/lib/icon-catalog.ts +181 -0
- package/src/lib/icons.ts +202 -0
- package/src/lib/navigation.ts +18 -33
- package/src/lib/pagination.ts +3 -2
- package/src/lib/post-form-bridge.ts +136 -0
- package/src/lib/render.tsx +11 -4
- package/src/lib/resolve-config.ts +157 -0
- package/src/lib/schemas.ts +76 -12
- package/src/lib/settings-bridge.ts +139 -0
- package/src/lib/storage.ts +37 -16
- package/src/lib/theme.ts +5 -7
- package/src/lib/timeline.ts +4 -8
- package/src/lib/toast.ts +134 -0
- package/src/lib/upload.ts +71 -0
- package/src/lib/url.ts +9 -1
- package/src/lib/version.ts +16 -0
- package/src/lib/view.ts +9 -10
- package/src/middleware/__tests__/auth.test.ts +6 -28
- package/src/middleware/__tests__/onboarding.test.ts +1 -1
- package/src/middleware/auth.ts +6 -12
- package/src/middleware/config.ts +51 -0
- package/src/middleware/error-handler.ts +56 -0
- package/src/middleware/onboarding.ts +1 -1
- package/src/preset.css +6 -0
- package/src/routes/__tests__/compose.test.ts +104 -17
- package/src/routes/api/__tests__/collections.test.ts +93 -2
- package/src/routes/api/__tests__/posts.test.ts +2 -1
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/collections.ts +64 -68
- package/src/routes/api/nav-items.ts +21 -59
- package/src/routes/api/pages.ts +18 -46
- package/src/routes/api/posts.ts +64 -86
- package/src/routes/api/search.ts +6 -4
- package/src/routes/api/settings.ts +8 -24
- package/src/routes/api/upload.ts +55 -53
- package/src/routes/auth/__tests__/setup.test.ts +118 -0
- package/src/routes/auth/reset.tsx +17 -66
- package/src/routes/auth/setup.tsx +67 -11
- package/src/routes/auth/signin.tsx +44 -8
- package/src/routes/compose.tsx +194 -0
- package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
- package/src/routes/dash/__tests__/pages.test.ts +2 -2
- package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
- package/src/routes/dash/appearance.tsx +173 -0
- package/src/routes/dash/collections.tsx +80 -14
- package/src/routes/dash/index.tsx +12 -14
- package/src/routes/dash/media.tsx +46 -49
- package/src/routes/dash/pages.tsx +85 -37
- package/src/routes/dash/posts.tsx +60 -23
- package/src/routes/dash/redirects.tsx +43 -33
- package/src/routes/dash/settings.tsx +234 -214
- package/src/routes/feed/__tests__/rss.test.ts +7 -3
- package/src/routes/feed/rss.ts +11 -16
- package/src/routes/feed/sitemap.ts +15 -9
- package/src/routes/pages/__tests__/collections.test.ts +9 -8
- package/src/routes/pages/archive.tsx +2 -2
- package/src/routes/pages/collection.tsx +76 -9
- package/src/routes/pages/collections.tsx +3 -1
- package/src/routes/pages/featured.tsx +2 -2
- package/src/routes/pages/home.tsx +3 -3
- package/src/routes/pages/latest.tsx +2 -2
- package/src/routes/pages/page.tsx +2 -2
- package/src/routes/pages/post.tsx +2 -2
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +324 -34
- package/src/services/__tests__/media.test.ts +1 -1
- package/src/services/__tests__/page.test.ts +116 -1
- package/src/services/auth.ts +88 -0
- package/src/services/collection.ts +169 -30
- package/src/services/index.ts +8 -3
- package/src/services/media.ts +39 -12
- package/src/services/navigation.ts +17 -5
- package/src/services/page.ts +24 -4
- package/src/services/post.ts +87 -19
- package/src/services/search.ts +0 -1
- package/src/services/settings.ts +21 -13
- package/src/style.css +3 -0
- package/src/styles/components.css +42 -1
- package/src/styles/tokens.css +4 -0
- package/src/styles/ui.css +902 -73
- package/src/types/app-context.ts +25 -0
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +60 -23
- package/src/types/entities.ts +12 -2
- package/src/types/lingui-react-macro.d.ts +3 -3
- package/src/types/operations.ts +2 -4
- package/src/types/views.ts +1 -3
- package/src/ui/__tests__/font-themes.test.ts +27 -8
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
- package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
- package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
- package/src/ui/components/collection-types.ts +45 -0
- package/src/ui/components/compose-types.ts +75 -0
- package/src/ui/components/jant-collection-form.ts +512 -0
- package/src/ui/components/jant-compose-dialog.ts +494 -0
- package/src/ui/components/jant-compose-editor.ts +799 -0
- package/src/ui/components/jant-post-form.ts +290 -0
- package/src/ui/components/jant-settings-avatar.ts +231 -0
- package/src/ui/components/jant-settings-general.ts +436 -0
- package/src/ui/components/post-form-template.ts +260 -0
- package/src/ui/components/post-form-types.ts +87 -0
- package/src/ui/components/settings-types.ts +62 -0
- package/src/ui/compose/ComposeDialog.tsx +141 -385
- package/src/ui/compose/ComposePrompt.tsx +3 -3
- package/src/ui/dash/PostList.tsx +55 -61
- package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
- package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
- package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
- package/src/ui/dash/collections/CollectionForm.tsx +130 -117
- package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
- package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
- package/src/ui/dash/index.ts +1 -1
- package/src/ui/dash/posts/PostForm.tsx +248 -0
- package/src/ui/dash/settings/AccountContent.tsx +69 -80
- package/src/ui/dash/settings/GeneralContent.tsx +159 -478
- package/src/ui/dash/settings/SettingsNav.tsx +4 -4
- package/src/ui/font-themes.ts +115 -32
- package/src/ui/layouts/BaseLayout.tsx +49 -19
- package/src/ui/layouts/DashLayout.tsx +14 -9
- package/src/ui/layouts/SiteLayout.tsx +38 -23
- package/src/ui/pages/CollectionPage.tsx +12 -2
- package/src/ui/pages/CollectionsPage.tsx +27 -27
- package/src/ui/pages/HomePage.tsx +15 -6
- package/src/ui/pages/SearchPage.tsx +1 -2
- package/src/ui/shared/CollectionsSidebar.tsx +59 -0
- package/src/ui/shared/Pagination.tsx +2 -2
- package/dist/app.js +0 -267
- package/dist/auth.js +0 -39
- package/dist/client.js +0 -13
- package/dist/db/index.js +0 -10
- package/dist/db/schema.js +0 -224
- package/dist/i18n/Trans.js +0 -24
- package/dist/i18n/context.js +0 -58
- package/dist/i18n/detect.js +0 -26
- package/dist/i18n/i18n.js +0 -49
- package/dist/i18n/index.js +0 -44
- package/dist/i18n/locales/en.js +0 -1
- package/dist/i18n/locales/zh-Hans.js +0 -1
- package/dist/i18n/locales/zh-Hant.js +0 -1
- package/dist/i18n/locales.js +0 -13
- package/dist/i18n/middleware.js +0 -30
- package/dist/lib/avatar-upload.js +0 -134
- package/dist/lib/config.js +0 -143
- package/dist/lib/constants.js +0 -50
- package/dist/lib/excerpt.js +0 -76
- package/dist/lib/favicon.js +0 -102
- package/dist/lib/feed.js +0 -123
- package/dist/lib/image-processor.js +0 -187
- package/dist/lib/image.js +0 -97
- package/dist/lib/index.js +0 -7
- package/dist/lib/markdown.js +0 -83
- package/dist/lib/media-helpers.js +0 -49
- package/dist/lib/media-upload.js +0 -104
- package/dist/lib/nav-reorder.js +0 -27
- package/dist/lib/navigation.js +0 -79
- package/dist/lib/pagination.js +0 -44
- package/dist/lib/render.js +0 -53
- package/dist/lib/schemas.js +0 -174
- package/dist/lib/sqid.js +0 -72
- package/dist/lib/sse.js +0 -218
- package/dist/lib/storage.js +0 -164
- package/dist/lib/theme.js +0 -65
- package/dist/lib/time.js +0 -159
- package/dist/lib/timeline.js +0 -95
- package/dist/lib/timezones.js +0 -388
- package/dist/lib/url.js +0 -89
- package/dist/lib/view.js +0 -217
- package/dist/middleware/auth.js +0 -52
- package/dist/middleware/onboarding.js +0 -41
- package/dist/routes/api/collections.js +0 -124
- package/dist/routes/api/nav-items.js +0 -104
- package/dist/routes/api/pages.js +0 -91
- package/dist/routes/api/posts.js +0 -218
- package/dist/routes/api/search.js +0 -48
- package/dist/routes/api/settings.js +0 -68
- package/dist/routes/api/upload.js +0 -246
- package/dist/routes/auth/reset.js +0 -221
- package/dist/routes/auth/setup.js +0 -194
- package/dist/routes/auth/signin.js +0 -176
- package/dist/routes/compose.js +0 -48
- package/dist/routes/dash/collections.js +0 -115
- package/dist/routes/dash/index.js +0 -118
- package/dist/routes/dash/media.js +0 -106
- package/dist/routes/dash/pages.js +0 -294
- package/dist/routes/dash/posts.js +0 -244
- package/dist/routes/dash/redirects.js +0 -257
- package/dist/routes/dash/settings.js +0 -379
- package/dist/routes/feed/rss.js +0 -62
- package/dist/routes/feed/sitemap.js +0 -49
- package/dist/routes/pages/archive.js +0 -62
- package/dist/routes/pages/collection.js +0 -34
- package/dist/routes/pages/collections.js +0 -28
- package/dist/routes/pages/featured.js +0 -36
- package/dist/routes/pages/home.js +0 -64
- package/dist/routes/pages/latest.js +0 -45
- package/dist/routes/pages/page.js +0 -68
- package/dist/routes/pages/post.js +0 -44
- package/dist/routes/pages/search.js +0 -54
- package/dist/services/collection.js +0 -109
- package/dist/services/index.js +0 -24
- package/dist/services/media.js +0 -117
- package/dist/services/navigation.js +0 -91
- package/dist/services/page.js +0 -84
- package/dist/services/post.js +0 -229
- package/dist/services/redirect.js +0 -48
- package/dist/services/search.js +0 -67
- package/dist/services/settings.js +0 -68
- package/dist/types/bindings.js +0 -3
- package/dist/types/config.js +0 -147
- package/dist/types/constants.js +0 -27
- package/dist/types/entities.js +0 -3
- package/dist/types/lingui-react-macro.d.js +0 -9
- package/dist/types/operations.js +0 -3
- package/dist/types/props.js +0 -3
- package/dist/types/sortablejs.d.js +0 -5
- package/dist/types/views.js +0 -5
- package/dist/types.js +0 -11
- package/dist/ui/color-themes.js +0 -268
- package/dist/ui/compose/ComposeDialog.js +0 -467
- package/dist/ui/compose/ComposePrompt.js +0 -55
- package/dist/ui/dash/ActionButtons.js +0 -46
- package/dist/ui/dash/CrudPageHeader.js +0 -22
- package/dist/ui/dash/DangerZone.js +0 -36
- package/dist/ui/dash/FormatBadge.js +0 -27
- package/dist/ui/dash/ListItemRow.js +0 -21
- package/dist/ui/dash/PageForm.js +0 -195
- package/dist/ui/dash/PostForm.js +0 -395
- package/dist/ui/dash/PostList.js +0 -83
- package/dist/ui/dash/StatusBadge.js +0 -46
- package/dist/ui/dash/collections/CollectionForm.js +0 -152
- package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
- package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
- package/dist/ui/dash/index.js +0 -10
- package/dist/ui/dash/media/MediaListContent.js +0 -166
- package/dist/ui/dash/media/ViewMediaContent.js +0 -212
- package/dist/ui/dash/pages/LinkFormContent.js +0 -130
- package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
- package/dist/ui/dash/settings/AccountContent.js +0 -209
- package/dist/ui/dash/settings/AppearanceContent.js +0 -259
- package/dist/ui/dash/settings/GeneralContent.js +0 -536
- package/dist/ui/dash/settings/SettingsNav.js +0 -41
- package/dist/ui/feed/LinkCard.js +0 -72
- package/dist/ui/feed/NoteCard.js +0 -58
- package/dist/ui/feed/QuoteCard.js +0 -63
- package/dist/ui/feed/ThreadPreview.js +0 -48
- package/dist/ui/feed/TimelineFeed.js +0 -41
- package/dist/ui/feed/TimelineItem.js +0 -27
- package/dist/ui/font-themes.js +0 -36
- package/dist/ui/layouts/BaseLayout.js +0 -153
- package/dist/ui/layouts/DashLayout.js +0 -141
- package/dist/ui/layouts/SiteLayout.js +0 -169
- package/dist/ui/pages/ArchivePage.js +0 -143
- package/dist/ui/pages/CollectionPage.js +0 -70
- package/dist/ui/pages/CollectionsPage.js +0 -76
- package/dist/ui/pages/FeaturedPage.js +0 -24
- package/dist/ui/pages/HomePage.js +0 -24
- package/dist/ui/pages/PostPage.js +0 -55
- package/dist/ui/pages/SearchPage.js +0 -122
- package/dist/ui/pages/SinglePage.js +0 -23
- package/dist/ui/shared/EmptyState.js +0 -27
- package/dist/ui/shared/MediaGallery.js +0 -35
- package/dist/ui/shared/Pagination.js +0 -195
- package/dist/ui/shared/ThreadView.js +0 -108
- package/dist/ui/shared/index.js +0 -5
- package/dist/vendor/datastar.js +0 -1606
- package/src/lib/__tests__/config.test.ts +0 -192
- package/src/lib/config.ts +0 -167
- package/src/routes/compose.ts +0 -63
- package/src/ui/dash/PostForm.tsx +0 -360
- package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
|
@@ -29,7 +29,6 @@ describe("CollectionService", () => {
|
|
|
29
29
|
expect(collection.description).toBeNull();
|
|
30
30
|
expect(collection.icon).toBeNull();
|
|
31
31
|
expect(collection.sortOrder).toBe("newest");
|
|
32
|
-
expect(collection.showDivider).toBe(0);
|
|
33
32
|
});
|
|
34
33
|
|
|
35
34
|
it("creates a collection with all fields", async () => {
|
|
@@ -40,7 +39,6 @@ describe("CollectionService", () => {
|
|
|
40
39
|
icon: "laptop",
|
|
41
40
|
sortOrder: "oldest",
|
|
42
41
|
position: 5,
|
|
43
|
-
showDivider: true,
|
|
44
42
|
});
|
|
45
43
|
|
|
46
44
|
expect(collection.slug).toBe("tech");
|
|
@@ -49,7 +47,6 @@ describe("CollectionService", () => {
|
|
|
49
47
|
expect(collection.icon).toBe("laptop");
|
|
50
48
|
expect(collection.sortOrder).toBe("oldest");
|
|
51
49
|
expect(collection.position).toBe(5);
|
|
52
|
-
expect(collection.showDivider).toBe(1);
|
|
53
50
|
});
|
|
54
51
|
|
|
55
52
|
it("sets timestamps", async () => {
|
|
@@ -214,7 +211,7 @@ describe("CollectionService", () => {
|
|
|
214
211
|
expect(updated?.icon).toBeNull();
|
|
215
212
|
});
|
|
216
213
|
|
|
217
|
-
it("updates icon, sortOrder,
|
|
214
|
+
it("updates icon, sortOrder, and position", async () => {
|
|
218
215
|
const collection = await collectionService.create({
|
|
219
216
|
slug: "test",
|
|
220
217
|
title: "Test",
|
|
@@ -224,13 +221,11 @@ describe("CollectionService", () => {
|
|
|
224
221
|
icon: "rocket",
|
|
225
222
|
sortOrder: "rating_desc",
|
|
226
223
|
position: 10,
|
|
227
|
-
showDivider: true,
|
|
228
224
|
});
|
|
229
225
|
|
|
230
226
|
expect(updated?.icon).toBe("rocket");
|
|
231
227
|
expect(updated?.sortOrder).toBe("rating_desc");
|
|
232
228
|
expect(updated?.position).toBe(10);
|
|
233
|
-
expect(updated?.showDivider).toBe(1);
|
|
234
229
|
});
|
|
235
230
|
|
|
236
231
|
it("updates updatedAt timestamp", async () => {
|
|
@@ -266,7 +261,7 @@ describe("CollectionService", () => {
|
|
|
266
261
|
expect(found).toBeNull();
|
|
267
262
|
});
|
|
268
263
|
|
|
269
|
-
it("
|
|
264
|
+
it("removes junction table entries on cascade", async () => {
|
|
270
265
|
const collection = await collectionService.create({
|
|
271
266
|
slug: "test",
|
|
272
267
|
title: "Test",
|
|
@@ -274,15 +269,23 @@ describe("CollectionService", () => {
|
|
|
274
269
|
const post = await postService.create({
|
|
275
270
|
format: "note",
|
|
276
271
|
body: "test post",
|
|
277
|
-
collectionId: collection.id,
|
|
278
272
|
});
|
|
279
273
|
|
|
274
|
+
await collectionService.addPost(collection.id, post.id);
|
|
275
|
+
|
|
276
|
+
// Verify association exists
|
|
277
|
+
const before = await collectionService.getCollectionsByPostId(post.id);
|
|
278
|
+
expect(before).toHaveLength(1);
|
|
279
|
+
|
|
280
280
|
await collectionService.delete(collection.id);
|
|
281
281
|
|
|
282
|
-
// Post
|
|
282
|
+
// Post should still exist
|
|
283
283
|
const found = await postService.getById(post.id);
|
|
284
284
|
expect(found).not.toBeNull();
|
|
285
|
-
|
|
285
|
+
|
|
286
|
+
// Association should be gone (cascade delete)
|
|
287
|
+
const after = await collectionService.getCollectionsByPostId(post.id);
|
|
288
|
+
expect(after).toHaveLength(0);
|
|
286
289
|
});
|
|
287
290
|
|
|
288
291
|
it("returns false for non-existent collection", async () => {
|
|
@@ -358,21 +361,13 @@ describe("CollectionService", () => {
|
|
|
358
361
|
title: "Col 2",
|
|
359
362
|
});
|
|
360
363
|
|
|
361
|
-
await postService.create({
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
await
|
|
367
|
-
|
|
368
|
-
body: "post 2",
|
|
369
|
-
collectionId: col1.id,
|
|
370
|
-
});
|
|
371
|
-
await postService.create({
|
|
372
|
-
format: "note",
|
|
373
|
-
body: "post 3",
|
|
374
|
-
collectionId: col2.id,
|
|
375
|
-
});
|
|
364
|
+
const p1 = await postService.create({ format: "note", body: "post 1" });
|
|
365
|
+
const p2 = await postService.create({ format: "note", body: "post 2" });
|
|
366
|
+
const p3 = await postService.create({ format: "note", body: "post 3" });
|
|
367
|
+
|
|
368
|
+
await collectionService.addPost(col1.id, p1.id);
|
|
369
|
+
await collectionService.addPost(col1.id, p2.id);
|
|
370
|
+
await collectionService.addPost(col2.id, p3.id);
|
|
376
371
|
|
|
377
372
|
const counts = await collectionService.getPostCounts();
|
|
378
373
|
expect(counts.get(col1.id)).toBe(2);
|
|
@@ -385,15 +380,13 @@ describe("CollectionService", () => {
|
|
|
385
380
|
title: "Col",
|
|
386
381
|
});
|
|
387
382
|
|
|
388
|
-
await postService.create({
|
|
383
|
+
const p1 = await postService.create({
|
|
389
384
|
format: "note",
|
|
390
385
|
body: "with collection",
|
|
391
|
-
collectionId: col.id,
|
|
392
|
-
});
|
|
393
|
-
await postService.create({
|
|
394
|
-
format: "note",
|
|
395
|
-
body: "no collection",
|
|
396
386
|
});
|
|
387
|
+
await postService.create({ format: "note", body: "no collection" });
|
|
388
|
+
|
|
389
|
+
await collectionService.addPost(col.id, p1.id);
|
|
397
390
|
|
|
398
391
|
const counts = await collectionService.getPostCounts();
|
|
399
392
|
expect(counts.get(col.id)).toBe(1);
|
|
@@ -409,14 +402,15 @@ describe("CollectionService", () => {
|
|
|
409
402
|
const post = await postService.create({
|
|
410
403
|
format: "note",
|
|
411
404
|
body: "will be deleted",
|
|
412
|
-
collectionId: col.id,
|
|
413
405
|
});
|
|
414
|
-
await postService.create({
|
|
406
|
+
const post2 = await postService.create({
|
|
415
407
|
format: "note",
|
|
416
408
|
body: "still alive",
|
|
417
|
-
collectionId: col.id,
|
|
418
409
|
});
|
|
419
410
|
|
|
411
|
+
await collectionService.addPost(col.id, post.id);
|
|
412
|
+
await collectionService.addPost(col.id, post2.id);
|
|
413
|
+
|
|
420
414
|
// Soft-delete one post
|
|
421
415
|
await postService.delete(post.id);
|
|
422
416
|
|
|
@@ -424,4 +418,300 @@ describe("CollectionService", () => {
|
|
|
424
418
|
expect(counts.get(col.id)).toBe(1);
|
|
425
419
|
});
|
|
426
420
|
});
|
|
421
|
+
|
|
422
|
+
describe("addPost / removePost", () => {
|
|
423
|
+
it("adds a post to a collection", async () => {
|
|
424
|
+
const col = await collectionService.create({
|
|
425
|
+
slug: "test",
|
|
426
|
+
title: "Test",
|
|
427
|
+
});
|
|
428
|
+
const post = await postService.create({
|
|
429
|
+
format: "note",
|
|
430
|
+
body: "test",
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
await collectionService.addPost(col.id, post.id);
|
|
434
|
+
|
|
435
|
+
const collections = await collectionService.getCollectionsByPostId(
|
|
436
|
+
post.id,
|
|
437
|
+
);
|
|
438
|
+
expect(collections).toHaveLength(1);
|
|
439
|
+
expect(collections[0]?.id).toBe(col.id);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("does not duplicate on re-add", async () => {
|
|
443
|
+
const col = await collectionService.create({
|
|
444
|
+
slug: "test",
|
|
445
|
+
title: "Test",
|
|
446
|
+
});
|
|
447
|
+
const post = await postService.create({
|
|
448
|
+
format: "note",
|
|
449
|
+
body: "test",
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
await collectionService.addPost(col.id, post.id);
|
|
453
|
+
await collectionService.addPost(col.id, post.id); // duplicate
|
|
454
|
+
|
|
455
|
+
const postIds = await collectionService.getPostIds(col.id);
|
|
456
|
+
expect(postIds).toHaveLength(1);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("removes a post from a collection", async () => {
|
|
460
|
+
const col = await collectionService.create({
|
|
461
|
+
slug: "test",
|
|
462
|
+
title: "Test",
|
|
463
|
+
});
|
|
464
|
+
const post = await postService.create({
|
|
465
|
+
format: "note",
|
|
466
|
+
body: "test",
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
await collectionService.addPost(col.id, post.id);
|
|
470
|
+
await collectionService.removePost(col.id, post.id);
|
|
471
|
+
|
|
472
|
+
const collections = await collectionService.getCollectionsByPostId(
|
|
473
|
+
post.id,
|
|
474
|
+
);
|
|
475
|
+
expect(collections).toHaveLength(0);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
describe("getCollectionsByPostId", () => {
|
|
480
|
+
it("returns all collections a post belongs to", async () => {
|
|
481
|
+
const col1 = await collectionService.create({
|
|
482
|
+
slug: "col1",
|
|
483
|
+
title: "Col 1",
|
|
484
|
+
position: 0,
|
|
485
|
+
});
|
|
486
|
+
const col2 = await collectionService.create({
|
|
487
|
+
slug: "col2",
|
|
488
|
+
title: "Col 2",
|
|
489
|
+
position: 1,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const post = await postService.create({
|
|
493
|
+
format: "note",
|
|
494
|
+
body: "test",
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
await collectionService.addPost(col1.id, post.id);
|
|
498
|
+
await collectionService.addPost(col2.id, post.id);
|
|
499
|
+
|
|
500
|
+
const collections = await collectionService.getCollectionsByPostId(
|
|
501
|
+
post.id,
|
|
502
|
+
);
|
|
503
|
+
expect(collections).toHaveLength(2);
|
|
504
|
+
expect(collections[0]?.slug).toBe("col1");
|
|
505
|
+
expect(collections[1]?.slug).toBe("col2");
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("returns empty array for post with no collections", async () => {
|
|
509
|
+
const post = await postService.create({
|
|
510
|
+
format: "note",
|
|
511
|
+
body: "test",
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const collections = await collectionService.getCollectionsByPostId(
|
|
515
|
+
post.id,
|
|
516
|
+
);
|
|
517
|
+
expect(collections).toHaveLength(0);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
describe("getPostIds", () => {
|
|
522
|
+
it("returns all post IDs in a collection", async () => {
|
|
523
|
+
const col = await collectionService.create({
|
|
524
|
+
slug: "test",
|
|
525
|
+
title: "Test",
|
|
526
|
+
});
|
|
527
|
+
const p1 = await postService.create({ format: "note", body: "one" });
|
|
528
|
+
const p2 = await postService.create({ format: "note", body: "two" });
|
|
529
|
+
|
|
530
|
+
await collectionService.addPost(col.id, p1.id);
|
|
531
|
+
await collectionService.addPost(col.id, p2.id);
|
|
532
|
+
|
|
533
|
+
const ids = await collectionService.getPostIds(col.id);
|
|
534
|
+
expect(ids).toHaveLength(2);
|
|
535
|
+
expect(ids).toContain(p1.id);
|
|
536
|
+
expect(ids).toContain(p2.id);
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
describe("createDivider", () => {
|
|
541
|
+
it("creates a divider with auto-assigned position", async () => {
|
|
542
|
+
const divider = await collectionService.createDivider();
|
|
543
|
+
|
|
544
|
+
expect(divider.id).toBe(1);
|
|
545
|
+
expect(divider.position).toBe(0);
|
|
546
|
+
expect(divider.createdAt).toBeGreaterThan(0);
|
|
547
|
+
expect(divider.updatedAt).toBeGreaterThan(0);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it("assigns position after existing collections", async () => {
|
|
551
|
+
await collectionService.create({ slug: "a", title: "A" }); // position 0
|
|
552
|
+
await collectionService.create({ slug: "b", title: "B" }); // position 1
|
|
553
|
+
|
|
554
|
+
const divider = await collectionService.createDivider();
|
|
555
|
+
expect(divider.position).toBe(2);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it("assigns position after existing dividers", async () => {
|
|
559
|
+
const d1 = await collectionService.createDivider(); // position 0
|
|
560
|
+
const d2 = await collectionService.createDivider(); // position 1
|
|
561
|
+
|
|
562
|
+
expect(d1.position).toBe(0);
|
|
563
|
+
expect(d2.position).toBe(1);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it("considers both collections and dividers for position", async () => {
|
|
567
|
+
await collectionService.create({ slug: "a", title: "A" }); // position 0
|
|
568
|
+
await collectionService.createDivider(); // position 1
|
|
569
|
+
await collectionService.create({ slug: "b", title: "B" }); // position 2
|
|
570
|
+
|
|
571
|
+
const divider = await collectionService.createDivider();
|
|
572
|
+
expect(divider.position).toBe(3);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
describe("deleteDivider", () => {
|
|
577
|
+
it("deletes a divider by ID", async () => {
|
|
578
|
+
const divider = await collectionService.createDivider();
|
|
579
|
+
|
|
580
|
+
const result = await collectionService.deleteDivider(divider.id);
|
|
581
|
+
expect(result).toBe(true);
|
|
582
|
+
|
|
583
|
+
const list = await collectionService.listDividers();
|
|
584
|
+
expect(list).toHaveLength(0);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("returns false for non-existent divider", async () => {
|
|
588
|
+
const result = await collectionService.deleteDivider(9999);
|
|
589
|
+
expect(result).toBe(false);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe("listDividers", () => {
|
|
594
|
+
it("returns empty array when no dividers exist", async () => {
|
|
595
|
+
const list = await collectionService.listDividers();
|
|
596
|
+
expect(list).toEqual([]);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("returns dividers ordered by position", async () => {
|
|
600
|
+
const d1 = await collectionService.createDivider();
|
|
601
|
+
const d2 = await collectionService.createDivider();
|
|
602
|
+
|
|
603
|
+
const list = await collectionService.listDividers();
|
|
604
|
+
expect(list).toHaveLength(2);
|
|
605
|
+
expect(list[0]?.id).toBe(d1.id);
|
|
606
|
+
expect(list[1]?.id).toBe(d2.id);
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
describe("reorderAll", () => {
|
|
611
|
+
it("handles mixed prefixed IDs correctly", async () => {
|
|
612
|
+
const a = await collectionService.create({ slug: "a", title: "A" });
|
|
613
|
+
const b = await collectionService.create({ slug: "b", title: "B" });
|
|
614
|
+
const d1 = await collectionService.createDivider();
|
|
615
|
+
|
|
616
|
+
// Reorder: divider first, then B, then A
|
|
617
|
+
await collectionService.reorderAll([
|
|
618
|
+
`d-${d1.id}`,
|
|
619
|
+
`c-${b.id}`,
|
|
620
|
+
`c-${a.id}`,
|
|
621
|
+
]);
|
|
622
|
+
|
|
623
|
+
const dividers = await collectionService.listDividers();
|
|
624
|
+
expect(dividers[0]?.position).toBe(0);
|
|
625
|
+
|
|
626
|
+
const colB = await collectionService.getById(b.id);
|
|
627
|
+
const colA = await collectionService.getById(a.id);
|
|
628
|
+
expect(colB?.position).toBe(1);
|
|
629
|
+
expect(colA?.position).toBe(2);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it("handles empty array", async () => {
|
|
633
|
+
await collectionService.reorderAll([]);
|
|
634
|
+
// Should not throw
|
|
635
|
+
const list = await collectionService.list();
|
|
636
|
+
expect(list).toEqual([]);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it("reflects new order in combined list", async () => {
|
|
640
|
+
const a = await collectionService.create({ slug: "a", title: "A" });
|
|
641
|
+
const d1 = await collectionService.createDivider();
|
|
642
|
+
const b = await collectionService.create({ slug: "b", title: "B" });
|
|
643
|
+
|
|
644
|
+
// Put divider between B and A
|
|
645
|
+
await collectionService.reorderAll([
|
|
646
|
+
`c-${b.id}`,
|
|
647
|
+
`d-${d1.id}`,
|
|
648
|
+
`c-${a.id}`,
|
|
649
|
+
]);
|
|
650
|
+
|
|
651
|
+
const cols = await collectionService.list();
|
|
652
|
+
const divs = await collectionService.listDividers();
|
|
653
|
+
|
|
654
|
+
// B at position 0, divider at 1, A at 2
|
|
655
|
+
expect(cols.find((c) => c.id === b.id)?.position).toBe(0);
|
|
656
|
+
expect(divs[0]?.position).toBe(1);
|
|
657
|
+
expect(cols.find((c) => c.id === a.id)?.position).toBe(2);
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
describe("syncPostCollections", () => {
|
|
662
|
+
it("replaces all collection memberships for a post", async () => {
|
|
663
|
+
const col1 = await collectionService.create({
|
|
664
|
+
slug: "col1",
|
|
665
|
+
title: "Col 1",
|
|
666
|
+
});
|
|
667
|
+
const col2 = await collectionService.create({
|
|
668
|
+
slug: "col2",
|
|
669
|
+
title: "Col 2",
|
|
670
|
+
});
|
|
671
|
+
const col3 = await collectionService.create({
|
|
672
|
+
slug: "col3",
|
|
673
|
+
title: "Col 3",
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
const post = await postService.create({
|
|
677
|
+
format: "note",
|
|
678
|
+
body: "test",
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// Initially in col1 and col2
|
|
682
|
+
await collectionService.addPost(col1.id, post.id);
|
|
683
|
+
await collectionService.addPost(col2.id, post.id);
|
|
684
|
+
|
|
685
|
+
// Sync to col2 and col3
|
|
686
|
+
await collectionService.syncPostCollections(post.id, [col2.id, col3.id]);
|
|
687
|
+
|
|
688
|
+
const collections = await collectionService.getCollectionsByPostId(
|
|
689
|
+
post.id,
|
|
690
|
+
);
|
|
691
|
+
const ids = collections.map((c) => c.id);
|
|
692
|
+
expect(ids).toHaveLength(2);
|
|
693
|
+
expect(ids).toContain(col2.id);
|
|
694
|
+
expect(ids).toContain(col3.id);
|
|
695
|
+
expect(ids).not.toContain(col1.id);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it("removes all collections when empty array provided", async () => {
|
|
699
|
+
const col = await collectionService.create({
|
|
700
|
+
slug: "test",
|
|
701
|
+
title: "Test",
|
|
702
|
+
});
|
|
703
|
+
const post = await postService.create({
|
|
704
|
+
format: "note",
|
|
705
|
+
body: "test",
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
await collectionService.addPost(col.id, post.id);
|
|
709
|
+
await collectionService.syncPostCollections(post.id, []);
|
|
710
|
+
|
|
711
|
+
const collections = await collectionService.getCollectionsByPostId(
|
|
712
|
+
post.id,
|
|
713
|
+
);
|
|
714
|
+
expect(collections).toHaveLength(0);
|
|
715
|
+
});
|
|
716
|
+
});
|
|
427
717
|
});
|
|
@@ -92,7 +92,9 @@ describe("PageService", () => {
|
|
|
92
92
|
type: "page",
|
|
93
93
|
label: "Second",
|
|
94
94
|
url: "/second",
|
|
95
|
-
pageId:
|
|
95
|
+
pageId: (
|
|
96
|
+
pages.find((p) => p.slug === "second") as (typeof pages)[number]
|
|
97
|
+
).id,
|
|
96
98
|
});
|
|
97
99
|
|
|
98
100
|
const notInNav = await pageService.listNotInNav();
|
|
@@ -103,4 +105,117 @@ describe("PageService", () => {
|
|
|
103
105
|
expect(slugs).not.toContain("second");
|
|
104
106
|
});
|
|
105
107
|
});
|
|
108
|
+
|
|
109
|
+
describe("update nav item sync", () => {
|
|
110
|
+
it("syncs nav item label when page title changes", async () => {
|
|
111
|
+
const page = await pageService.create({
|
|
112
|
+
slug: "about",
|
|
113
|
+
title: "About",
|
|
114
|
+
});
|
|
115
|
+
await navItemService.create({
|
|
116
|
+
type: "page",
|
|
117
|
+
label: "About",
|
|
118
|
+
url: "/about",
|
|
119
|
+
pageId: page.id,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await pageService.update(page.id, { title: "About Us" });
|
|
123
|
+
|
|
124
|
+
const navs = await navItemService.list();
|
|
125
|
+
expect(navs).toHaveLength(1);
|
|
126
|
+
expect(navs[0]?.label).toBe("About Us");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("syncs nav item url when page slug changes", async () => {
|
|
130
|
+
const page = await pageService.create({
|
|
131
|
+
slug: "about",
|
|
132
|
+
title: "About",
|
|
133
|
+
});
|
|
134
|
+
await navItemService.create({
|
|
135
|
+
type: "page",
|
|
136
|
+
label: "About",
|
|
137
|
+
url: "/about",
|
|
138
|
+
pageId: page.id,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await pageService.update(page.id, { slug: "about-us" });
|
|
142
|
+
|
|
143
|
+
const navs = await navItemService.list();
|
|
144
|
+
expect(navs).toHaveLength(1);
|
|
145
|
+
expect(navs[0]?.url).toBe("/about-us");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("syncs both label and url when title and slug change together", async () => {
|
|
149
|
+
const page = await pageService.create({
|
|
150
|
+
slug: "about",
|
|
151
|
+
title: "About",
|
|
152
|
+
});
|
|
153
|
+
await navItemService.create({
|
|
154
|
+
type: "page",
|
|
155
|
+
label: "About",
|
|
156
|
+
url: "/about",
|
|
157
|
+
pageId: page.id,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await pageService.update(page.id, {
|
|
161
|
+
title: "About Our Company",
|
|
162
|
+
slug: "about-our-company",
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const navs = await navItemService.list();
|
|
166
|
+
expect(navs).toHaveLength(1);
|
|
167
|
+
expect(navs[0]?.label).toBe("About Our Company");
|
|
168
|
+
expect(navs[0]?.url).toBe("/about-our-company");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("does not change nav item label when title is unchanged", async () => {
|
|
172
|
+
const page = await pageService.create({
|
|
173
|
+
slug: "about",
|
|
174
|
+
title: "About",
|
|
175
|
+
});
|
|
176
|
+
await navItemService.create({
|
|
177
|
+
type: "page",
|
|
178
|
+
label: "Custom Label",
|
|
179
|
+
url: "/about",
|
|
180
|
+
pageId: page.id,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Update body only, not title
|
|
184
|
+
await pageService.update(page.id, { body: "New content" });
|
|
185
|
+
|
|
186
|
+
const navs = await navItemService.list();
|
|
187
|
+
expect(navs[0]?.label).toBe("Custom Label");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("does not affect nav items for other pages", async () => {
|
|
191
|
+
const page1 = await pageService.create({
|
|
192
|
+
slug: "about",
|
|
193
|
+
title: "About",
|
|
194
|
+
});
|
|
195
|
+
const page2 = await pageService.create({
|
|
196
|
+
slug: "contact",
|
|
197
|
+
title: "Contact",
|
|
198
|
+
});
|
|
199
|
+
await navItemService.create({
|
|
200
|
+
type: "page",
|
|
201
|
+
label: "About",
|
|
202
|
+
url: "/about",
|
|
203
|
+
pageId: page1.id,
|
|
204
|
+
});
|
|
205
|
+
await navItemService.create({
|
|
206
|
+
type: "page",
|
|
207
|
+
label: "Contact",
|
|
208
|
+
url: "/contact",
|
|
209
|
+
pageId: page2.id,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await pageService.update(page1.id, { title: "About Us" });
|
|
213
|
+
|
|
214
|
+
const navs = await navItemService.list();
|
|
215
|
+
const aboutNav = navs.find((n) => n.pageId === page1.id);
|
|
216
|
+
const contactNav = navs.find((n) => n.pageId === page2.id);
|
|
217
|
+
expect(aboutNav?.label).toBe("About Us");
|
|
218
|
+
expect(contactNav?.label).toBe("Contact");
|
|
219
|
+
});
|
|
220
|
+
});
|
|
106
221
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Service
|
|
3
|
+
*
|
|
4
|
+
* Handles authentication-related business logic:
|
|
5
|
+
* password reset token validation, password updates, and session management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { eq, and } from "drizzle-orm";
|
|
9
|
+
import { hashPassword } from "better-auth/crypto";
|
|
10
|
+
import type { Database } from "../db/index.js";
|
|
11
|
+
import { user, account, session } from "../db/schema.js";
|
|
12
|
+
import type { SettingsService } from "./settings.js";
|
|
13
|
+
import { SETTINGS_KEYS } from "../lib/constants.js";
|
|
14
|
+
import { ValidationError, NotFoundError } from "../lib/errors.js";
|
|
15
|
+
|
|
16
|
+
export interface AuthService {
|
|
17
|
+
/**
|
|
18
|
+
* Validate a password reset token against the stored value.
|
|
19
|
+
*
|
|
20
|
+
* @param token - The reset token from the URL
|
|
21
|
+
* @returns true if the token is valid and not expired
|
|
22
|
+
*/
|
|
23
|
+
validateResetToken(token: string): Promise<boolean>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Reset the admin user's password.
|
|
27
|
+
*
|
|
28
|
+
* Validates the token, hashes the new password, updates the account,
|
|
29
|
+
* clears all sessions, and removes the reset token.
|
|
30
|
+
*
|
|
31
|
+
* @param token - The reset token (re-validated to prevent TOCTOU)
|
|
32
|
+
* @param newPassword - The new plaintext password
|
|
33
|
+
* @throws {ValidationError} if token is invalid or expired
|
|
34
|
+
* @throws {NotFoundError} if no user account exists
|
|
35
|
+
*/
|
|
36
|
+
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createAuthService(
|
|
40
|
+
db: Database,
|
|
41
|
+
settings: SettingsService,
|
|
42
|
+
): AuthService {
|
|
43
|
+
async function validateResetToken(token: string): Promise<boolean> {
|
|
44
|
+
const stored = await settings.get(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
45
|
+
if (!stored) return false;
|
|
46
|
+
|
|
47
|
+
const separatorIndex = stored.lastIndexOf(":");
|
|
48
|
+
const storedToken = stored.substring(0, separatorIndex);
|
|
49
|
+
const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
|
|
50
|
+
const now = Math.floor(Date.now() / 1000);
|
|
51
|
+
|
|
52
|
+
return token === storedToken && now <= expiry;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
validateResetToken,
|
|
57
|
+
|
|
58
|
+
async resetPassword(token, newPassword) {
|
|
59
|
+
const isValid = await validateResetToken(token);
|
|
60
|
+
if (!isValid) {
|
|
61
|
+
throw new ValidationError("Invalid or expired reset token");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const hashedPw = await hashPassword(newPassword);
|
|
65
|
+
|
|
66
|
+
// Get admin user (single-author system)
|
|
67
|
+
const userResult = await db.select({ id: user.id }).from(user).limit(1);
|
|
68
|
+
if (!userResult[0]) {
|
|
69
|
+
throw new NotFoundError("User account");
|
|
70
|
+
}
|
|
71
|
+
const userId = userResult[0].id;
|
|
72
|
+
|
|
73
|
+
// Update password
|
|
74
|
+
await db
|
|
75
|
+
.update(account)
|
|
76
|
+
.set({ password: hashedPw })
|
|
77
|
+
.where(
|
|
78
|
+
and(eq(account.userId, userId), eq(account.providerId, "credential")),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Clear all sessions
|
|
82
|
+
await db.delete(session).where(eq(session.userId, userId));
|
|
83
|
+
|
|
84
|
+
// Remove the reset token
|
|
85
|
+
await settings.remove(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|