@igstack/app-catalog-backend-core 6.12.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 +35 -81
- package/dist/modules/communityTools/service.mjs.map +1 -1
- package/dist/modules/communityTools/toView.mjs +3 -1
- 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 +87 -42
- package/src/__tests__/communityToolsCapture.test.ts +34 -0
- package/src/__tests__/communityToolsHealth.test.ts +62 -0
- package/src/__tests__/communityToolsWrite.test.ts +5 -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 +48 -91
- package/src/modules/communityTools/toView.ts +2 -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,6 +210,7 @@ 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: [],
|
|
@@ -203,6 +241,7 @@ describe('Community tools service', () => {
|
|
|
203
241
|
failingSince: null,
|
|
204
242
|
archivedAt: null,
|
|
205
243
|
archivedBy: null,
|
|
244
|
+
unreachableSince: null,
|
|
206
245
|
archivedReason: null,
|
|
207
246
|
owners: [],
|
|
208
247
|
namedOwners: [],
|
|
@@ -233,6 +272,7 @@ describe('Community tools service', () => {
|
|
|
233
272
|
failingSince: null,
|
|
234
273
|
archivedAt: null,
|
|
235
274
|
archivedBy: null,
|
|
275
|
+
unreachableSince: null,
|
|
236
276
|
archivedReason: null,
|
|
237
277
|
owners: [],
|
|
238
278
|
namedOwners: [],
|
|
@@ -244,7 +284,7 @@ describe('Community tools service', () => {
|
|
|
244
284
|
])
|
|
245
285
|
|
|
246
286
|
mockDb.dbPerson.findMany = vi.fn().mockResolvedValue([])
|
|
247
|
-
mockDb.
|
|
287
|
+
mockDb.dbCommunityToolOpen.count = vi.fn().mockResolvedValue(0)
|
|
248
288
|
|
|
249
289
|
const now = new Date('2024-01-15T12:00:00Z')
|
|
250
290
|
const result = await service.listPins(
|
|
@@ -410,6 +450,7 @@ describe('Community tools service', () => {
|
|
|
410
450
|
failingSince: null,
|
|
411
451
|
archivedAt: null,
|
|
412
452
|
archivedBy: null,
|
|
453
|
+
unreachableSince: null,
|
|
413
454
|
archivedReason: null,
|
|
414
455
|
owners: [],
|
|
415
456
|
namedOwners: [],
|
|
@@ -436,6 +477,7 @@ describe('Community tools service', () => {
|
|
|
436
477
|
failingSince: new Date('2024-01-10T00:00:00Z'),
|
|
437
478
|
archivedAt: null,
|
|
438
479
|
archivedBy: null,
|
|
480
|
+
unreachableSince: null,
|
|
439
481
|
archivedReason: null,
|
|
440
482
|
owners: [],
|
|
441
483
|
namedOwners: [],
|
|
@@ -446,7 +488,7 @@ describe('Community tools service', () => {
|
|
|
446
488
|
])
|
|
447
489
|
|
|
448
490
|
mockDb.dbPerson.findMany = vi.fn().mockResolvedValue([])
|
|
449
|
-
mockDb.
|
|
491
|
+
mockDb.dbCommunityToolOpen.count = vi.fn().mockResolvedValue(0)
|
|
450
492
|
|
|
451
493
|
const result = await service.listTools(null, false, now, NO_CAPTURE)
|
|
452
494
|
|
|
@@ -486,6 +528,7 @@ describe('Community tools service', () => {
|
|
|
486
528
|
archivedAt: null,
|
|
487
529
|
archivedReason: null,
|
|
488
530
|
archivedBy: null,
|
|
531
|
+
unreachableSince: null,
|
|
489
532
|
screenshotAssetId: null,
|
|
490
533
|
faviconAssetId: null,
|
|
491
534
|
capturedAt: null,
|
|
@@ -612,6 +655,7 @@ describe('Community tools service', () => {
|
|
|
612
655
|
failingSince: null,
|
|
613
656
|
archivedAt: null,
|
|
614
657
|
archivedBy: null,
|
|
658
|
+
unreachableSince: null,
|
|
615
659
|
},
|
|
616
660
|
{ ...CAPTURES, capturer: launchFails },
|
|
617
661
|
now,
|
|
@@ -655,6 +699,7 @@ describe('Community tools service', () => {
|
|
|
655
699
|
failingSince: null,
|
|
656
700
|
archivedAt: null,
|
|
657
701
|
archivedBy: null,
|
|
702
|
+
unreachableSince: null,
|
|
658
703
|
},
|
|
659
704
|
{ ...CAPTURES, capturer: { capture } },
|
|
660
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,
|
|
@@ -87,7 +88,7 @@ describe('Community tools write service', () => {
|
|
|
87
88
|
dbCommunityToolOwner: {
|
|
88
89
|
findMany: vi.fn().mockResolvedValue(dbTool.owners),
|
|
89
90
|
},
|
|
90
|
-
|
|
91
|
+
dbCommunityToolOpen: { count: vi.fn().mockResolvedValue(0) },
|
|
91
92
|
dbPerson: {
|
|
92
93
|
findMany: vi
|
|
93
94
|
.fn()
|
|
@@ -355,6 +356,7 @@ describe('Community tools write service', () => {
|
|
|
355
356
|
failingSince: NOW,
|
|
356
357
|
archivedAt: NOW,
|
|
357
358
|
archivedBy: 'auto',
|
|
359
|
+
unreachableSince: null,
|
|
358
360
|
archivedReason: 'Not working for 14 days',
|
|
359
361
|
})
|
|
360
362
|
const capture = vi.fn().mockResolvedValue({ ok: false, error: 'x' })
|
|
@@ -372,6 +374,7 @@ describe('Community tools write service', () => {
|
|
|
372
374
|
lastCheckAt: null,
|
|
373
375
|
archivedAt: null,
|
|
374
376
|
archivedBy: null,
|
|
377
|
+
unreachableSince: null,
|
|
375
378
|
archivedReason: null,
|
|
376
379
|
})
|
|
377
380
|
await vi.waitFor(() => expect(capture).toHaveBeenCalled())
|
|
@@ -383,6 +386,7 @@ describe('Community tools write service', () => {
|
|
|
383
386
|
failingSince: NOW,
|
|
384
387
|
archivedAt: NOW,
|
|
385
388
|
archivedBy: 'Ann Owner',
|
|
389
|
+
unreachableSince: null,
|
|
386
390
|
})
|
|
387
391
|
|
|
388
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
|
/**
|