@igstack/app-catalog-backend-core 6.11.0 → 6.12.1
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/generated/prisma/client.mjs.map +1 -1
- package/dist/generated/prisma/internal/class.d.mts +12 -0
- package/dist/generated/prisma/internal/class.d.mts.map +1 -1
- package/dist/generated/prisma/internal/class.mjs +4 -4
- package/dist/generated/prisma/internal/class.mjs.map +1 -1
- package/dist/generated/prisma/internal/prismaNamespace.d.mts +90 -3
- package/dist/generated/prisma/internal/prismaNamespace.d.mts.map +1 -1
- package/dist/generated/prisma/models/DbCommunityTool.d.mts +289 -4
- package/dist/generated/prisma/models/DbCommunityTool.d.mts.map +1 -1
- package/dist/generated/prisma/models/DbCommunityToolOpen.d.mts +1171 -0
- package/dist/generated/prisma/models/DbCommunityToolOpen.d.mts.map +1 -0
- package/dist/generated/prisma/models.d.mts +2 -1
- package/dist/modules/communityTools/capture.d.mts +8 -1
- package/dist/modules/communityTools/capture.d.mts.map +1 -1
- package/dist/modules/communityTools/capture.mjs +40 -14
- package/dist/modules/communityTools/capture.mjs.map +1 -1
- package/dist/modules/communityTools/healthJob.mjs +3 -2
- package/dist/modules/communityTools/healthJob.mjs.map +1 -1
- package/dist/modules/communityTools/router.d.mts +14 -1
- package/dist/modules/communityTools/router.mjs +7 -2
- package/dist/modules/communityTools/router.mjs.map +1 -1
- package/dist/modules/communityTools/service.mjs +39 -82
- package/dist/modules/communityTools/service.mjs.map +1 -1
- package/dist/modules/communityTools/toView.mjs +7 -2
- package/dist/modules/communityTools/toView.mjs.map +1 -1
- package/dist/modules/communityTools/writeService.mjs +1 -0
- package/dist/modules/communityTools/writeService.mjs.map +1 -1
- package/dist/server/controller.d.mts +9 -0
- package/dist/server/controller.d.mts.map +1 -1
- package/package.json +4 -4
- package/prisma/schema.prisma +25 -2
- package/src/__tests__/communityTools.test.ts +106 -42
- package/src/__tests__/communityToolsCapture.test.ts +34 -0
- package/src/__tests__/communityToolsHealth.test.ts +62 -0
- package/src/__tests__/communityToolsWrite.test.ts +6 -1
- package/src/generated/prisma/browser.ts +6 -1
- package/src/generated/prisma/client.ts +6 -1
- package/src/generated/prisma/internal/class.ts +14 -4
- package/src/generated/prisma/internal/prismaNamespace.ts +90 -1
- package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +14 -0
- package/src/generated/prisma/models/DbCommunityTool.ts +299 -2
- package/src/generated/prisma/models/DbCommunityToolOpen.ts +1383 -0
- package/src/generated/prisma/models/DbCommunityToolView.ts +1 -1
- package/src/generated/prisma/models.ts +1 -0
- package/src/modules/communityTools/capture.ts +54 -18
- package/src/modules/communityTools/healthJob.ts +7 -1
- package/src/modules/communityTools/router.ts +26 -1
- package/src/modules/communityTools/service.ts +52 -92
- package/src/modules/communityTools/toView.ts +9 -0
- package/src/modules/communityTools/writeService.ts +1 -0
|
@@ -30,10 +30,9 @@ describe('Community tools service', () => {
|
|
|
30
30
|
findMany: vi.fn(),
|
|
31
31
|
findUnique: vi.fn(),
|
|
32
32
|
},
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
dbCommunityToolOpen: {
|
|
34
|
+
create: vi.fn(),
|
|
35
35
|
count: vi.fn(),
|
|
36
|
-
findMany: vi.fn(),
|
|
37
36
|
},
|
|
38
37
|
dbCommunityToolPin: {
|
|
39
38
|
findMany: vi.fn(),
|
|
@@ -87,61 +86,99 @@ describe('Community tools service', () => {
|
|
|
87
86
|
})
|
|
88
87
|
})
|
|
89
88
|
|
|
90
|
-
describe('
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
describe('recordOpen', () => {
|
|
90
|
+
const actor = {
|
|
91
|
+
hash: 'test-hash',
|
|
92
|
+
publicId: 'test-public',
|
|
93
|
+
alias: 'Test User',
|
|
94
|
+
kind: 'visitor' as const,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The open table in memory: what was written is what is counted. */
|
|
98
|
+
function opensTable() {
|
|
99
|
+
const rows: { toolId: string; actorHash: string; at: Date }[] = []
|
|
100
|
+
type Where = {
|
|
101
|
+
toolId: string
|
|
102
|
+
actorHash?: string
|
|
103
|
+
at?: { gte?: Date; gt?: Date }
|
|
97
104
|
}
|
|
105
|
+
const matches = (r: (typeof rows)[number], w: Where) =>
|
|
106
|
+
r.toolId === w.toolId &&
|
|
107
|
+
(w.actorHash === undefined || r.actorHash === w.actorHash) &&
|
|
108
|
+
(!w.at?.gte || r.at >= w.at.gte) &&
|
|
109
|
+
(!w.at?.gt || r.at > w.at.gt)
|
|
110
|
+
mockDb.dbCommunityToolOpen.create = vi.fn(
|
|
111
|
+
({ data }: { data: (typeof rows)[number] }) => {
|
|
112
|
+
rows.push(data)
|
|
113
|
+
return Promise.resolve(data)
|
|
114
|
+
},
|
|
115
|
+
) as never
|
|
116
|
+
mockDb.dbCommunityToolOpen.count = vi.fn(({ where }: { where: Where }) =>
|
|
117
|
+
Promise.resolve(rows.filter((r) => matches(r, where)).length),
|
|
118
|
+
) as never
|
|
119
|
+
return rows
|
|
120
|
+
}
|
|
98
121
|
|
|
122
|
+
beforeEach(() => {
|
|
99
123
|
mockDb.dbCommunityTool.findUnique = vi
|
|
100
124
|
.fn()
|
|
101
125
|
.mockResolvedValue({ id: 'tool-1' })
|
|
102
|
-
|
|
126
|
+
})
|
|
103
127
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const results = [1, 5, 1, 5] // views30d, viewsAll for each recordView call
|
|
108
|
-
return Promise.resolve(results[countCallIndex++])
|
|
109
|
-
})
|
|
128
|
+
it('counts every open, two on the same day as two', async () => {
|
|
129
|
+
const rows = opensTable()
|
|
130
|
+
const at = new Date('2024-01-15T10:00:00Z')
|
|
110
131
|
|
|
111
|
-
|
|
112
|
-
const
|
|
132
|
+
await service.recordOpen('test-slug', 'card', actor, null, at)
|
|
133
|
+
const second = await service.recordOpen(
|
|
113
134
|
'test-slug',
|
|
135
|
+
'site',
|
|
114
136
|
actor,
|
|
115
137
|
null,
|
|
116
|
-
new Date(
|
|
138
|
+
new Date(at.getTime() + 5 * 60_000),
|
|
117
139
|
)
|
|
118
140
|
|
|
119
|
-
|
|
120
|
-
|
|
141
|
+
expect(second).toEqual({ views30d: 2, viewsAll: 2 })
|
|
142
|
+
expect(rows.map((r) => (r as unknown as { kind: string }).kind)).toEqual([
|
|
143
|
+
'card',
|
|
144
|
+
'site',
|
|
145
|
+
])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('counts the last 30 days in views30d, every open in viewsAll', async () => {
|
|
149
|
+
const rows = opensTable()
|
|
150
|
+
rows.push({
|
|
151
|
+
toolId: 'tool-1',
|
|
152
|
+
actorHash: 'someone',
|
|
153
|
+
at: new Date('2023-12-01T00:00:00Z'),
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const counts = await service.recordOpen(
|
|
121
157
|
'test-slug',
|
|
158
|
+
'card',
|
|
122
159
|
actor,
|
|
123
160
|
null,
|
|
124
|
-
new Date('2024-01-
|
|
161
|
+
new Date('2024-01-15T10:00:00Z'),
|
|
125
162
|
)
|
|
126
163
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
164
|
+
expect(counts).toEqual({ views30d: 1, viewsAll: 2 })
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('ignores a visitor opening one tool more than 30 times a minute', async () => {
|
|
168
|
+
opensTable()
|
|
169
|
+
const at = new Date('2024-01-15T10:00:00Z')
|
|
170
|
+
let counts = { views30d: 0, viewsAll: 0 }
|
|
171
|
+
for (let i = 0; i < 35; i++) {
|
|
172
|
+
counts = await service.recordOpen(
|
|
173
|
+
'test-slug',
|
|
174
|
+
'site',
|
|
175
|
+
actor,
|
|
176
|
+
null,
|
|
177
|
+
new Date(at.getTime() + i * 1000),
|
|
178
|
+
)
|
|
179
|
+
}
|
|
142
180
|
|
|
143
|
-
expect(
|
|
144
|
-
expect(result2).toEqual({ views30d: 1, viewsAll: 5 })
|
|
181
|
+
expect(counts.viewsAll).toBe(30)
|
|
145
182
|
})
|
|
146
183
|
})
|
|
147
184
|
|
|
@@ -173,10 +210,12 @@ describe('Community tools service', () => {
|
|
|
173
210
|
failingSince: null,
|
|
174
211
|
archivedAt: null,
|
|
175
212
|
archivedBy: null,
|
|
213
|
+
unreachableSince: null,
|
|
176
214
|
archivedReason: null,
|
|
177
215
|
owners: [],
|
|
178
216
|
namedOwners: [],
|
|
179
217
|
likes: [] as { userId: string; userName: string }[],
|
|
218
|
+
_count: { feedback: 0 },
|
|
180
219
|
tags: [],
|
|
181
220
|
},
|
|
182
221
|
},
|
|
@@ -202,10 +241,12 @@ describe('Community tools service', () => {
|
|
|
202
241
|
failingSince: null,
|
|
203
242
|
archivedAt: null,
|
|
204
243
|
archivedBy: null,
|
|
244
|
+
unreachableSince: null,
|
|
205
245
|
archivedReason: null,
|
|
206
246
|
owners: [],
|
|
207
247
|
namedOwners: [],
|
|
208
248
|
likes: [] as { userId: string; userName: string }[],
|
|
249
|
+
_count: { feedback: 0 },
|
|
209
250
|
tags: [],
|
|
210
251
|
},
|
|
211
252
|
},
|
|
@@ -231,17 +272,19 @@ describe('Community tools service', () => {
|
|
|
231
272
|
failingSince: null,
|
|
232
273
|
archivedAt: null,
|
|
233
274
|
archivedBy: null,
|
|
275
|
+
unreachableSince: null,
|
|
234
276
|
archivedReason: null,
|
|
235
277
|
owners: [],
|
|
236
278
|
namedOwners: [],
|
|
237
279
|
likes: [] as { userId: string; userName: string }[],
|
|
280
|
+
_count: { feedback: 0 },
|
|
238
281
|
tags: [],
|
|
239
282
|
},
|
|
240
283
|
},
|
|
241
284
|
])
|
|
242
285
|
|
|
243
286
|
mockDb.dbPerson.findMany = vi.fn().mockResolvedValue([])
|
|
244
|
-
mockDb.
|
|
287
|
+
mockDb.dbCommunityToolOpen.count = vi.fn().mockResolvedValue(0)
|
|
245
288
|
|
|
246
289
|
const now = new Date('2024-01-15T12:00:00Z')
|
|
247
290
|
const result = await service.listPins(
|
|
@@ -407,10 +450,12 @@ describe('Community tools service', () => {
|
|
|
407
450
|
failingSince: null,
|
|
408
451
|
archivedAt: null,
|
|
409
452
|
archivedBy: null,
|
|
453
|
+
unreachableSince: null,
|
|
410
454
|
archivedReason: null,
|
|
411
455
|
owners: [],
|
|
412
456
|
namedOwners: [],
|
|
413
457
|
likes: [] as { userId: string; userName: string }[],
|
|
458
|
+
_count: { feedback: 0 },
|
|
414
459
|
tags: [],
|
|
415
460
|
},
|
|
416
461
|
{
|
|
@@ -432,16 +477,18 @@ describe('Community tools service', () => {
|
|
|
432
477
|
failingSince: new Date('2024-01-10T00:00:00Z'),
|
|
433
478
|
archivedAt: null,
|
|
434
479
|
archivedBy: null,
|
|
480
|
+
unreachableSince: null,
|
|
435
481
|
archivedReason: null,
|
|
436
482
|
owners: [],
|
|
437
483
|
namedOwners: [],
|
|
438
484
|
likes: [] as { userId: string; userName: string }[],
|
|
485
|
+
_count: { feedback: 0 },
|
|
439
486
|
tags: [],
|
|
440
487
|
},
|
|
441
488
|
])
|
|
442
489
|
|
|
443
490
|
mockDb.dbPerson.findMany = vi.fn().mockResolvedValue([])
|
|
444
|
-
mockDb.
|
|
491
|
+
mockDb.dbCommunityToolOpen.count = vi.fn().mockResolvedValue(0)
|
|
445
492
|
|
|
446
493
|
const result = await service.listTools(null, false, now, NO_CAPTURE)
|
|
447
494
|
|
|
@@ -481,6 +528,7 @@ describe('Community tools service', () => {
|
|
|
481
528
|
archivedAt: null,
|
|
482
529
|
archivedReason: null,
|
|
483
530
|
archivedBy: null,
|
|
531
|
+
unreachableSince: null,
|
|
484
532
|
screenshotAssetId: null,
|
|
485
533
|
faviconAssetId: null,
|
|
486
534
|
capturedAt: null,
|
|
@@ -488,6 +536,7 @@ describe('Community tools service', () => {
|
|
|
488
536
|
owners: [],
|
|
489
537
|
namedOwners: [],
|
|
490
538
|
likes: [] as { userId: string; userName: string }[],
|
|
539
|
+
_count: { feedback: 0 },
|
|
491
540
|
tags: [],
|
|
492
541
|
...over,
|
|
493
542
|
}) as Parameters<typeof toView>[0]
|
|
@@ -542,6 +591,19 @@ describe('Community tools service', () => {
|
|
|
542
591
|
expect(anonymous.likedByMe).toBe(false)
|
|
543
592
|
})
|
|
544
593
|
|
|
594
|
+
it('counts the comments the query counted', async () => {
|
|
595
|
+
const view = await toView(
|
|
596
|
+
row({ _count: { feedback: 3 } }),
|
|
597
|
+
new Map(),
|
|
598
|
+
{ views30d: 0, viewsAll: 0 },
|
|
599
|
+
null,
|
|
600
|
+
false,
|
|
601
|
+
new Date('2024-01-15T12:00:00Z'),
|
|
602
|
+
CAPTURES,
|
|
603
|
+
)
|
|
604
|
+
expect(view.comments).toBe(3)
|
|
605
|
+
})
|
|
606
|
+
|
|
545
607
|
it('lists an owner outside the directory by name, with no slug', async () => {
|
|
546
608
|
const view = await toView(
|
|
547
609
|
row({
|
|
@@ -593,6 +655,7 @@ describe('Community tools service', () => {
|
|
|
593
655
|
failingSince: null,
|
|
594
656
|
archivedAt: null,
|
|
595
657
|
archivedBy: null,
|
|
658
|
+
unreachableSince: null,
|
|
596
659
|
},
|
|
597
660
|
{ ...CAPTURES, capturer: launchFails },
|
|
598
661
|
now,
|
|
@@ -636,6 +699,7 @@ describe('Community tools service', () => {
|
|
|
636
699
|
failingSince: null,
|
|
637
700
|
archivedAt: null,
|
|
638
701
|
archivedBy: null,
|
|
702
|
+
unreachableSince: null,
|
|
639
703
|
},
|
|
640
704
|
{ ...CAPTURES, capturer: { capture } },
|
|
641
705
|
new Date('2024-01-15T12:00:00Z'),
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
allowRequest,
|
|
6
6
|
capturePage,
|
|
7
7
|
chromiumFenceArgs,
|
|
8
|
+
failureOf,
|
|
8
9
|
fenceRoute,
|
|
9
10
|
followWithinHosts,
|
|
10
11
|
oneAtATime,
|
|
@@ -55,6 +56,39 @@ describe('Community tools capture', () => {
|
|
|
55
56
|
expect(page.screenshot).not.toHaveBeenCalled()
|
|
56
57
|
})
|
|
57
58
|
|
|
59
|
+
it('tells an error status and an unknown name from no answer at all', async () => {
|
|
60
|
+
const { page } = fakePage({})
|
|
61
|
+
page.goto.mockResolvedValueOnce({
|
|
62
|
+
ok: () => false,
|
|
63
|
+
status: () => 500,
|
|
64
|
+
headers: () => ({}),
|
|
65
|
+
})
|
|
66
|
+
expect(await capturePage(page, URL_OK, HOSTS)).toEqual({
|
|
67
|
+
ok: false,
|
|
68
|
+
error: 'HTTP 500',
|
|
69
|
+
})
|
|
70
|
+
page.goto.mockResolvedValueOnce(null)
|
|
71
|
+
expect(await capturePage(page, URL_OK, HOSTS)).toEqual({
|
|
72
|
+
ok: false,
|
|
73
|
+
error: 'No response',
|
|
74
|
+
unreachable: true,
|
|
75
|
+
})
|
|
76
|
+
expect(
|
|
77
|
+
failureOf(new Error('getaddrinfo ENOTFOUND gone.pages.example.com')),
|
|
78
|
+
).toEqual({
|
|
79
|
+
ok: false,
|
|
80
|
+
error: 'getaddrinfo ENOTFOUND gone.pages.example.com',
|
|
81
|
+
})
|
|
82
|
+
expect(failureOf(new Error('connect ETIMEDOUT 10.0.0.1:443'))).toEqual({
|
|
83
|
+
ok: false,
|
|
84
|
+
error: 'connect ETIMEDOUT 10.0.0.1:443',
|
|
85
|
+
unreachable: true,
|
|
86
|
+
})
|
|
87
|
+
expect(failureOf(new Error('connect ECONNREFUSED'))).toMatchObject({
|
|
88
|
+
unreachable: true,
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
58
92
|
it('skips the favicon fetch for an off-list href', async () => {
|
|
59
93
|
const { page } = fakePage({
|
|
60
94
|
iconHref: 'https://intranet.example.org/x.ico',
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
import * as dbClient from '../db/client'
|
|
4
4
|
import type { PrismaClient } from '../generated/prisma/client'
|
|
5
|
+
import { failureOf } from '../modules/communityTools/capture'
|
|
5
6
|
import type { ScreenshotCapturer } from '../modules/communityTools/capture'
|
|
6
7
|
import {
|
|
7
8
|
runHealthCheckOnce,
|
|
@@ -20,6 +21,7 @@ function tool(id: string, fields: Record<string, unknown> = {}) {
|
|
|
20
21
|
failingSince: null,
|
|
21
22
|
archivedAt: null,
|
|
22
23
|
archivedBy: null,
|
|
24
|
+
unreachableSince: null,
|
|
23
25
|
...fields,
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -82,6 +84,66 @@ describe('Community tools health job', () => {
|
|
|
82
84
|
})
|
|
83
85
|
})
|
|
84
86
|
|
|
87
|
+
it('a timeout is not a failure; a 500 and an unknown name are', async () => {
|
|
88
|
+
const since = new Date(NOW.getTime() - 5 * DAY)
|
|
89
|
+
db.dbCommunityTool.findMany.mockResolvedValue([
|
|
90
|
+
tool('timeout', { failingSince: since }),
|
|
91
|
+
tool('http-500'),
|
|
92
|
+
tool('nxdomain'),
|
|
93
|
+
])
|
|
94
|
+
const results: Record<string, unknown> = {
|
|
95
|
+
'https://timeout.pages.example.com/': failureOf(
|
|
96
|
+
new Error('connect ETIMEDOUT 10.0.0.1:443'),
|
|
97
|
+
),
|
|
98
|
+
'https://http-500.pages.example.com/': { ok: false, error: 'HTTP 500' },
|
|
99
|
+
'https://nxdomain.pages.example.com/': failureOf(
|
|
100
|
+
new Error('getaddrinfo ENOTFOUND nxdomain.pages.example.com'),
|
|
101
|
+
),
|
|
102
|
+
}
|
|
103
|
+
const capturer: ScreenshotCapturer = {
|
|
104
|
+
capture: vi.fn((url: string) => Promise.resolve(results[url] as never)),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await runHealthCheckOnce({ ...opts, capturer }, NOW)
|
|
108
|
+
|
|
109
|
+
expect(db.dbCommunityTool.update.mock.calls.map((c) => c[0])).toEqual([
|
|
110
|
+
// The streak is left as it was, and nothing is archived.
|
|
111
|
+
{
|
|
112
|
+
where: { id: 'timeout' },
|
|
113
|
+
data: { lastCheckAt: NOW, unreachableSince: NOW },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
where: { id: 'http-500' },
|
|
117
|
+
data: { lastCheckAt: NOW, failingSince: NOW },
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
where: { id: 'nxdomain' },
|
|
121
|
+
data: { lastCheckAt: NOW, failingSince: NOW },
|
|
122
|
+
},
|
|
123
|
+
])
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('a month of timeouts never archives', async () => {
|
|
127
|
+
db.dbCommunityTool.findMany.mockResolvedValue([
|
|
128
|
+
tool('timeout', {
|
|
129
|
+
failingSince: new Date(NOW.getTime() - 40 * DAY),
|
|
130
|
+
unreachableSince: new Date(NOW.getTime() - 35 * DAY),
|
|
131
|
+
}),
|
|
132
|
+
])
|
|
133
|
+
const capturer: ScreenshotCapturer = {
|
|
134
|
+
capture: vi
|
|
135
|
+
.fn()
|
|
136
|
+
.mockResolvedValue(failureOf(new Error('Timeout 20000ms exceeded'))),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await runHealthCheckOnce({ ...opts, capturer }, NOW)
|
|
140
|
+
|
|
141
|
+
expect(db.dbCommunityTool.update).toHaveBeenCalledWith({
|
|
142
|
+
where: { id: 'timeout' },
|
|
143
|
+
data: { lastCheckAt: NOW },
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
85
147
|
it('health run continues with the next tool after an error', async () => {
|
|
86
148
|
db.dbCommunityTool.findMany.mockResolvedValue([tool('a'), tool('b')])
|
|
87
149
|
const flaky: ScreenshotCapturer = {
|
|
@@ -50,6 +50,7 @@ const dbTool = {
|
|
|
50
50
|
archivedAt: null,
|
|
51
51
|
archivedReason: null,
|
|
52
52
|
archivedBy: null,
|
|
53
|
+
unreachableSince: null,
|
|
53
54
|
screenshotAssetId: null,
|
|
54
55
|
faviconAssetId: null,
|
|
55
56
|
capturedAt: null,
|
|
@@ -57,6 +58,7 @@ const dbTool = {
|
|
|
57
58
|
owners: [{ toolId: 'tool-1', personSlug: 'ann@example.com' }],
|
|
58
59
|
namedOwners: [] as { toolId: string; name: string }[],
|
|
59
60
|
likes: [] as { userId: string; userName: string }[],
|
|
61
|
+
_count: { feedback: 0 },
|
|
60
62
|
tags: [],
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -86,7 +88,7 @@ describe('Community tools write service', () => {
|
|
|
86
88
|
dbCommunityToolOwner: {
|
|
87
89
|
findMany: vi.fn().mockResolvedValue(dbTool.owners),
|
|
88
90
|
},
|
|
89
|
-
|
|
91
|
+
dbCommunityToolOpen: { count: vi.fn().mockResolvedValue(0) },
|
|
90
92
|
dbPerson: {
|
|
91
93
|
findMany: vi
|
|
92
94
|
.fn()
|
|
@@ -354,6 +356,7 @@ describe('Community tools write service', () => {
|
|
|
354
356
|
failingSince: NOW,
|
|
355
357
|
archivedAt: NOW,
|
|
356
358
|
archivedBy: 'auto',
|
|
359
|
+
unreachableSince: null,
|
|
357
360
|
archivedReason: 'Not working for 14 days',
|
|
358
361
|
})
|
|
359
362
|
const capture = vi.fn().mockResolvedValue({ ok: false, error: 'x' })
|
|
@@ -371,6 +374,7 @@ describe('Community tools write service', () => {
|
|
|
371
374
|
lastCheckAt: null,
|
|
372
375
|
archivedAt: null,
|
|
373
376
|
archivedBy: null,
|
|
377
|
+
unreachableSince: null,
|
|
374
378
|
archivedReason: null,
|
|
375
379
|
})
|
|
376
380
|
await vi.waitFor(() => expect(capture).toHaveBeenCalled())
|
|
@@ -382,6 +386,7 @@ describe('Community tools write service', () => {
|
|
|
382
386
|
failingSince: NOW,
|
|
383
387
|
archivedAt: NOW,
|
|
384
388
|
archivedBy: 'Ann Owner',
|
|
389
|
+
unreachableSince: null,
|
|
385
390
|
})
|
|
386
391
|
|
|
387
392
|
await write.editTool(
|
|
@@ -132,9 +132,14 @@ export type DbCommunityToolNamedOwner = Prisma.DbCommunityToolNamedOwnerModel
|
|
|
132
132
|
* Tags attached to community tools (app associations and topics).
|
|
133
133
|
*/
|
|
134
134
|
export type DbCommunityToolTag = Prisma.DbCommunityToolTagModel
|
|
135
|
+
/**
|
|
136
|
+
* Model DbCommunityToolOpen
|
|
137
|
+
* One card open or click through to the site.
|
|
138
|
+
*/
|
|
139
|
+
export type DbCommunityToolOpen = Prisma.DbCommunityToolOpenModel
|
|
135
140
|
/**
|
|
136
141
|
* Model DbCommunityToolView
|
|
137
|
-
*
|
|
142
|
+
* Superseded by DbCommunityToolOpen and no longer written; kept so the schema stays additive.
|
|
138
143
|
*/
|
|
139
144
|
export type DbCommunityToolView = Prisma.DbCommunityToolViewModel
|
|
140
145
|
/**
|
|
@@ -156,9 +156,14 @@ export type DbCommunityToolNamedOwner = Prisma.DbCommunityToolNamedOwnerModel
|
|
|
156
156
|
* Tags attached to community tools (app associations and topics).
|
|
157
157
|
*/
|
|
158
158
|
export type DbCommunityToolTag = Prisma.DbCommunityToolTagModel
|
|
159
|
+
/**
|
|
160
|
+
* Model DbCommunityToolOpen
|
|
161
|
+
* One card open or click through to the site.
|
|
162
|
+
*/
|
|
163
|
+
export type DbCommunityToolOpen = Prisma.DbCommunityToolOpenModel
|
|
159
164
|
/**
|
|
160
165
|
* Model DbCommunityToolView
|
|
161
|
-
*
|
|
166
|
+
* Superseded by DbCommunityToolOpen and no longer written; kept so the schema stays additive.
|
|
162
167
|
*/
|
|
163
168
|
export type DbCommunityToolView = Prisma.DbCommunityToolViewModel
|
|
164
169
|
/**
|