@things-factory/board-service 8.0.0 → 9.0.0-beta.3
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-server/controllers/headless-pdf-to-image.js +7 -2
- package/dist-server/controllers/headless-pdf-to-image.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/views/internal-board-full-feature-view.html +1 -1
- package/server/constants/error-code.ts +0 -2
- package/server/controllers/analyzer/analyze-integration.ts +0 -142
- package/server/controllers/fonts.ts +0 -83
- package/server/controllers/headless-model.ts +0 -53
- package/server/controllers/headless-pdf-to-image.ts +0 -103
- package/server/controllers/headless-playlist.ts +0 -71
- package/server/controllers/headless-pool-for-board.ts +0 -71
- package/server/controllers/headless-pool-for-label.ts +0 -141
- package/server/controllers/index.ts +0 -11
- package/server/controllers/label-command.ts +0 -62
- package/server/controllers/pdf.ts +0 -132
- package/server/controllers/screenshot.ts +0 -127
- package/server/controllers/thumbnail.ts +0 -18
- package/server/errors/index.ts +0 -1
- package/server/errors/license-error.ts +0 -21
- package/server/index.ts +0 -36
- package/server/migrations/1556862253000-SeedGroup.ts +0 -51
- package/server/migrations/index.ts +0 -9
- package/server/routers/internal-board-view-router.ts +0 -33
- package/server/routers/standalone-board-service-router.ts +0 -326
- package/server/routes.ts +0 -25
- package/server/service/analysis/analysis-query.ts +0 -13
- package/server/service/analysis/index.ts +0 -3
- package/server/service/board/board-history.ts +0 -137
- package/server/service/board/board-mutation.ts +0 -446
- package/server/service/board/board-query.ts +0 -180
- package/server/service/board/board-subscription.ts +0 -43
- package/server/service/board/board-type.ts +0 -58
- package/server/service/board/board.ts +0 -125
- package/server/service/board/event-subscriber.ts +0 -68
- package/server/service/board/index.ts +0 -10
- package/server/service/board-favorite/board-favorite-query.ts +0 -53
- package/server/service/board-favorite/board-favorite-type.ts +0 -18
- package/server/service/board-favorite/index.ts +0 -4
- package/server/service/board-template/board-template-mutation.ts +0 -161
- package/server/service/board-template/board-template-query.ts +0 -121
- package/server/service/board-template/board-template-type.ts +0 -53
- package/server/service/board-template/board-template.ts +0 -114
- package/server/service/board-template/index.ts +0 -7
- package/server/service/group/group-mutation.ts +0 -82
- package/server/service/group/group-query.ts +0 -58
- package/server/service/group/group-type.ts +0 -30
- package/server/service/group/group.ts +0 -69
- package/server/service/group/index.ts +0 -6
- package/server/service/index.ts +0 -56
- package/server/service/permission/domain-permission-subscriber.ts +0 -27
- package/server/service/permission/index.ts +0 -3
- package/server/service/play-group/event-subscriber.ts +0 -58
- package/server/service/play-group/index.ts +0 -9
- package/server/service/play-group/play-group-mutation.ts +0 -148
- package/server/service/play-group/play-group-query.ts +0 -92
- package/server/service/play-group/play-group-subscription.ts +0 -43
- package/server/service/play-group/play-group-type.ts +0 -30
- package/server/service/play-group/play-group.ts +0 -74
- package/server/service/theme/index.ts +0 -7
- package/server/service/theme/theme-mutation.ts +0 -128
- package/server/service/theme/theme-query.ts +0 -48
- package/server/service/theme/theme-type.ts +0 -55
- package/server/service/theme/theme.ts +0 -97
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
import { Arg, Ctx, Mutation, Resolver, Directive } from 'type-graphql'
|
|
2
|
-
import { EntityManager, In } from 'typeorm'
|
|
3
|
-
import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'
|
|
4
|
-
import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
|
|
5
|
-
import { Domain, getDataSource, getRedirectSubdomainPath, getRepository } from '@things-factory/shell'
|
|
6
|
-
|
|
7
|
-
import { thumbnail } from '../../controllers/thumbnail'
|
|
8
|
-
import { Group } from '../group/group'
|
|
9
|
-
import { Board } from './board'
|
|
10
|
-
import { BoardHistory } from './board-history'
|
|
11
|
-
import { BoardPatch, NewBoard } from './board-type'
|
|
12
|
-
|
|
13
|
-
async function parseJSONFile(uploadedFile: FileUpload): Promise<any> {
|
|
14
|
-
var { createReadStream } = await uploadedFile
|
|
15
|
-
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
const chunks: Uint8Array[] = []
|
|
18
|
-
|
|
19
|
-
createReadStream()
|
|
20
|
-
.on('data', (chunk: Uint8Array) => {
|
|
21
|
-
chunks.push(chunk)
|
|
22
|
-
})
|
|
23
|
-
.on('end', () => {
|
|
24
|
-
try {
|
|
25
|
-
const fileContents = Buffer.concat(chunks).toString('utf-8')
|
|
26
|
-
const jsonData = JSON.parse(fileContents)
|
|
27
|
-
resolve(jsonData)
|
|
28
|
-
} catch (error) {
|
|
29
|
-
reject(error)
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
.on('error', (error: Error) => {
|
|
33
|
-
reject(error)
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
@Resolver(Board)
|
|
39
|
-
export class BoardMutation {
|
|
40
|
-
@Directive('@transaction')
|
|
41
|
-
@Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
|
|
42
|
-
@Mutation(returns => Board, { description: 'To create new Board' })
|
|
43
|
-
async createBoard(@Arg('board') board: NewBoard, @Ctx() context: ResolverContext): Promise<Board> {
|
|
44
|
-
const { domain, user, notify, tx } = context.state
|
|
45
|
-
const repository = tx.getRepository(Board)
|
|
46
|
-
const groupRepository = tx.getRepository(Group)
|
|
47
|
-
|
|
48
|
-
const oldBoard: Board = await repository.findOneBy({
|
|
49
|
-
name: board.name,
|
|
50
|
-
domain: { id: domain.id }
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
if (oldBoard) {
|
|
54
|
-
throw new Error(context.t('error.board name is already taken', { name: board.name }))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const newBoard: Board = {
|
|
58
|
-
...board
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
newBoard.thumbnail ||=
|
|
62
|
-
'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* empty thumbnail */
|
|
63
|
-
|
|
64
|
-
if (board.groupId) {
|
|
65
|
-
newBoard.group = await groupRepository.findOneBy({
|
|
66
|
-
id: board.groupId
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const created = await repository.save({
|
|
71
|
-
domain,
|
|
72
|
-
...newBoard,
|
|
73
|
-
state: 'draft',
|
|
74
|
-
creator: user,
|
|
75
|
-
updater: user
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
notify &&
|
|
79
|
-
notify({
|
|
80
|
-
mode: 'in-app',
|
|
81
|
-
title: `Board '${created.name}' created`,
|
|
82
|
-
body: `Board '${created.name}' created by ${user.name}\n${created.description}`,
|
|
83
|
-
url: getRedirectSubdomainPath(context, domain.subdomain, `/board-viewer/${created.id}`)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
return created
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@Directive('@transaction')
|
|
90
|
-
@Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
|
|
91
|
-
@Mutation(returns => Board, { description: 'To clone a Board from existing Board' })
|
|
92
|
-
async cloneBoard(
|
|
93
|
-
@Arg('id') id: string,
|
|
94
|
-
@Arg('patch') patch: BoardPatch,
|
|
95
|
-
@Arg('targetSubdomain') targetSubdomain: string,
|
|
96
|
-
@Arg('targetGroupId', { nullable: true }) targetGroupId: string,
|
|
97
|
-
@Ctx() context: ResolverContext
|
|
98
|
-
): Promise<Board> {
|
|
99
|
-
const { domain, user, notify, tx } = context.state
|
|
100
|
-
const { t } = context
|
|
101
|
-
const repository = tx.getRepository(Board)
|
|
102
|
-
|
|
103
|
-
const board = await repository.findOneBy({ domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id })
|
|
104
|
-
|
|
105
|
-
if (!patch.name || (patch.name == board.name && targetSubdomain == domain.subdomain)) {
|
|
106
|
-
throw t('error.name must be unique from the original board', { name: patch.name })
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (targetSubdomain != domain.subdomain) {
|
|
110
|
-
if ((await repository.count({ where: { domain: { subdomain: targetSubdomain }, name: patch.name } })) > 0) {
|
|
111
|
-
throw t('error.name must be unique from the original board', { name: patch.name })
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
var targetDomain = domain
|
|
116
|
-
if (targetDomain && domain.subdomain != targetSubdomain) {
|
|
117
|
-
targetDomain = await tx.getRepository(Domain).findOneBy({ subdomain: targetSubdomain })
|
|
118
|
-
if (!targetDomain) {
|
|
119
|
-
throw `given subdomain(${targetSubdomain}) not found`
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
var targetGroup = null
|
|
124
|
-
if (targetGroupId) {
|
|
125
|
-
targetGroup = await tx.getRepository(Group).findOneBy({ domain: { id: targetDomain.id }, id: targetGroupId })
|
|
126
|
-
if (!targetGroup) {
|
|
127
|
-
throw `given group(${targetGroupId}) in domain(${targetSubdomain}) not found`
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const { id: excluded, ...clone } = board
|
|
132
|
-
|
|
133
|
-
const cloned = await repository.save({
|
|
134
|
-
domain: targetDomain,
|
|
135
|
-
...clone,
|
|
136
|
-
...patch,
|
|
137
|
-
group: targetGroup,
|
|
138
|
-
version: 0,
|
|
139
|
-
state: 'draft',
|
|
140
|
-
updater: user,
|
|
141
|
-
creator: user
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
notify &&
|
|
145
|
-
notify({
|
|
146
|
-
mode: 'in-app',
|
|
147
|
-
title: `Board '${cloned.name}' cloned`,
|
|
148
|
-
body: `Board '${cloned.name}' cloned by ${user.name}\n${cloned.description}`,
|
|
149
|
-
image: getRedirectSubdomainPath(context, targetSubdomain, `/thumbnail/${cloned.id}`),
|
|
150
|
-
url: getRedirectSubdomainPath(context, targetSubdomain, `/board-viewer/${cloned.id}`)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
return cloned
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
@Directive('@transaction')
|
|
157
|
-
@Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
|
|
158
|
-
@Mutation(returns => Board, { description: 'To modify Board information' })
|
|
159
|
-
async updateBoard(
|
|
160
|
-
@Arg('id') id: string,
|
|
161
|
-
@Arg('patch') patch: BoardPatch,
|
|
162
|
-
@Ctx() context: ResolverContext
|
|
163
|
-
): Promise<Board> {
|
|
164
|
-
const { domain, user, notify, tx } = context.state
|
|
165
|
-
const repository = tx.getRepository(Board)
|
|
166
|
-
|
|
167
|
-
const board = await repository.findOne({
|
|
168
|
-
where: { domain: { id: domain.id }, id },
|
|
169
|
-
relations: ['creator']
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
if (patch.model) {
|
|
173
|
-
const thumbnailPromise = thumbnail({
|
|
174
|
-
model: patch.model,
|
|
175
|
-
context
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
const thumbnailBase64 = await Promise.race([
|
|
180
|
-
thumbnailPromise,
|
|
181
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('5 seconds timeout')), 5000))
|
|
182
|
-
])
|
|
183
|
-
|
|
184
|
-
patch.thumbnail = 'data:image/png;base64,' + thumbnailBase64.toString('base64')
|
|
185
|
-
} catch (e) {
|
|
186
|
-
console.warn(`Failed to get thumbnail for '${board.name}' in first 5 seconds`)
|
|
187
|
-
// 5초 안에 썸네일이 생성되지 않았으므로 모델만 저장합니다.
|
|
188
|
-
patch.thumbnail =
|
|
189
|
-
'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* empty thumbnail */
|
|
190
|
-
|
|
191
|
-
Promise.race([
|
|
192
|
-
thumbnailPromise,
|
|
193
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Thumbnail extended timeout')), 5000))
|
|
194
|
-
])
|
|
195
|
-
.then(async thumbnailBase64 => {
|
|
196
|
-
/* use new resource manager */
|
|
197
|
-
await getDataSource().transaction(async (tx: EntityManager) => {
|
|
198
|
-
await tx.getRepository(Board).save({
|
|
199
|
-
id: updated.id,
|
|
200
|
-
thumbnail: 'data:image/png;base64,' + thumbnailBase64.toString('base64')
|
|
201
|
-
})
|
|
202
|
-
})
|
|
203
|
-
})
|
|
204
|
-
.catch(error => {
|
|
205
|
-
console.error(`Failed to save thumbnail for '${board.name}' even after extended time:`, error)
|
|
206
|
-
})
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const { groupId, ...patched } = patch
|
|
211
|
-
|
|
212
|
-
if (groupId !== undefined) {
|
|
213
|
-
const groupRepository = tx.getRepository(Group)
|
|
214
|
-
board.group = groupId
|
|
215
|
-
? (await groupRepository.findOneBy({
|
|
216
|
-
domain: { id: domain.id },
|
|
217
|
-
id: groupId
|
|
218
|
-
})) || null
|
|
219
|
-
: null
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const updated = await repository.save({
|
|
223
|
-
domain,
|
|
224
|
-
...board,
|
|
225
|
-
...patched,
|
|
226
|
-
state: 'draft',
|
|
227
|
-
updater: user
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
notify &&
|
|
231
|
-
notify({
|
|
232
|
-
mode: 'in-app',
|
|
233
|
-
title: `Board '${updated.name}' updated`,
|
|
234
|
-
body: `Board '${updated.name}' updated by ${user.name}\n${updated.description}`,
|
|
235
|
-
image: getRedirectSubdomainPath(context, domain.subdomain, `/thumbnail/${updated.id}`),
|
|
236
|
-
url: getRedirectSubdomainPath(context, domain.subdomain, `/board-viewer/${updated.id}`)
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
return updated
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
@Directive('@transaction')
|
|
243
|
-
@Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
|
|
244
|
-
@Mutation(returns => Board, { description: 'To release a Board' })
|
|
245
|
-
async releaseBoard(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Board> {
|
|
246
|
-
const { domain, user, notify, tx } = context.state
|
|
247
|
-
const repository = tx.getRepository(Board)
|
|
248
|
-
|
|
249
|
-
const board = await repository.findOne({
|
|
250
|
-
where: { domain: { id: domain.id }, id },
|
|
251
|
-
relations: ['creator']
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
if (!board) {
|
|
255
|
-
throw `Board given id(${id}) is not found`
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (board.state == 'released') {
|
|
259
|
-
throw `Board given id(${id}) is already released`
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const updated = await repository.save({
|
|
263
|
-
domain,
|
|
264
|
-
...board,
|
|
265
|
-
version: (board.version || 0) + 1,
|
|
266
|
-
state: 'released',
|
|
267
|
-
updater: user
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
notify &&
|
|
271
|
-
notify({
|
|
272
|
-
mode: 'in-app',
|
|
273
|
-
title: `Board '${updated.name}' released`,
|
|
274
|
-
body: `Board '${updated.name}' released by ${user.name}\n${updated.description}`,
|
|
275
|
-
image: getRedirectSubdomainPath(context, domain.subdomain, `/thumbnail/${updated.id}`),
|
|
276
|
-
url: getRedirectSubdomainPath(context, domain.subdomain, `/board-viewer/${updated.id}`)
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
return updated
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
@Directive('@transaction')
|
|
283
|
-
@Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
|
|
284
|
-
@Mutation(returns => Board, { description: 'To revert Board version' })
|
|
285
|
-
async revertBoardVersion(
|
|
286
|
-
@Arg('id') id: string,
|
|
287
|
-
@Arg('version') version: number,
|
|
288
|
-
@Ctx() context: ResolverContext
|
|
289
|
-
): Promise<Board> {
|
|
290
|
-
const { domain, user, notify, tx } = context.state
|
|
291
|
-
const repository = tx.getRepository(Board)
|
|
292
|
-
|
|
293
|
-
const board = await repository.findOne({
|
|
294
|
-
where: { domain: { id: domain.id }, id },
|
|
295
|
-
relations: ['creator']
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
if (!board) {
|
|
299
|
-
throw `Board with id(${id}) is not found`
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const historyRepository = tx.getRepository(BoardHistory)
|
|
303
|
-
|
|
304
|
-
const boardHistory = await historyRepository.findOne({
|
|
305
|
-
where: { domain: { id: domain.id }, originalId: id, version },
|
|
306
|
-
order: { version: 'DESC' }
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
if (!boardHistory) {
|
|
310
|
-
throw `Board with id:version(${id}:${version}) is not found`
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const updated = await repository.save({
|
|
314
|
-
domain,
|
|
315
|
-
...board,
|
|
316
|
-
model: boardHistory.model,
|
|
317
|
-
thumbnail: boardHistory.thumbnail,
|
|
318
|
-
state: 'draft',
|
|
319
|
-
updater: user
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
notify &&
|
|
323
|
-
notify({
|
|
324
|
-
mode: 'in-app',
|
|
325
|
-
title: `Board '${updated.name}' updated`,
|
|
326
|
-
body: `Board '${updated.name}' updated by ${user.name}\n${updated.description}`,
|
|
327
|
-
image: getRedirectSubdomainPath(context, domain.subdomain, `/thumbnail/${updated.id}`),
|
|
328
|
-
url: getRedirectSubdomainPath(context, domain.subdomain, `/board-viewer/${updated.id}`)
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
return updated
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
@Directive('@transaction')
|
|
335
|
-
@Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
|
|
336
|
-
@Mutation(returns => Boolean, { description: 'To delete Board' })
|
|
337
|
-
async deleteBoard(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
|
338
|
-
const { domain, user, notify, tx } = context.state
|
|
339
|
-
const repository = tx.getRepository(Board)
|
|
340
|
-
const board = await repository.findOneBy({ domain: { id: domain.id }, id })
|
|
341
|
-
|
|
342
|
-
const deleted = await repository.softDelete(id)
|
|
343
|
-
|
|
344
|
-
notify &&
|
|
345
|
-
notify({
|
|
346
|
-
mode: 'in-app',
|
|
347
|
-
title: `Board '${board.name}' deleted`,
|
|
348
|
-
body: `Board '${board.name}' deleted by ${user.name}\n${board.description}`
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
return true
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
@Directive('@transaction')
|
|
355
|
-
@Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
|
|
356
|
-
@Mutation(returns => [Board], { description: 'To import some Boards' })
|
|
357
|
-
async importBoards(
|
|
358
|
-
@Arg('groupId') groupId: string,
|
|
359
|
-
@Arg('files', () => [GraphQLUpload]) files: FileUpload[],
|
|
360
|
-
@Arg('overwrite') overwrite: boolean,
|
|
361
|
-
@Ctx() context: ResolverContext
|
|
362
|
-
): Promise<Board[]> {
|
|
363
|
-
const { domain, user, notify, tx } = context.state
|
|
364
|
-
const groupRepository = tx.getRepository(Group)
|
|
365
|
-
const boardRepository = tx.getRepository(Board)
|
|
366
|
-
const group = await groupRepository.findOneBy({ domain: { id: domain.id }, id: groupId })
|
|
367
|
-
|
|
368
|
-
if (!group) {
|
|
369
|
-
throw `Group with id(${groupId}) is not found`
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const boards = []
|
|
373
|
-
|
|
374
|
-
for (const file of files) {
|
|
375
|
-
const { id, name, description, model, thumbnail } = await parseJSONFile(file)
|
|
376
|
-
|
|
377
|
-
var sameNameBoard = await boardRepository.findOneBy({ domain: { id: domain.id }, name })
|
|
378
|
-
var sameIdBoard = await boardRepository.findOneBy({ id })
|
|
379
|
-
|
|
380
|
-
if (overwrite) {
|
|
381
|
-
var board = {} as any
|
|
382
|
-
|
|
383
|
-
if (sameIdBoard) {
|
|
384
|
-
if (overwrite && sameIdBoard.domainId != domain.id) {
|
|
385
|
-
throw `Board with id(${id}) is already taken in another domain`
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
board = {
|
|
389
|
-
...sameIdBoard,
|
|
390
|
-
name
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (sameNameBoard && sameIdBoard.id != sameNameBoard.id) {
|
|
394
|
-
/* 이름 충돌 회피 */
|
|
395
|
-
board.name = `${board.name}(${Date.now()})`
|
|
396
|
-
}
|
|
397
|
-
} else {
|
|
398
|
-
board = {
|
|
399
|
-
id,
|
|
400
|
-
name: sameNameBoard ? `${name}(${Date.now()})` : name,
|
|
401
|
-
version: 0,
|
|
402
|
-
creator: user
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
} else {
|
|
406
|
-
board = {
|
|
407
|
-
name,
|
|
408
|
-
version: 0,
|
|
409
|
-
creator: user
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/* ID가 없으면, 사용해도 됨 */
|
|
413
|
-
if (!sameIdBoard) {
|
|
414
|
-
board.id = id
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/* 이름 충돌 회피 */
|
|
418
|
-
if (sameNameBoard) {
|
|
419
|
-
board.name = `${board.name}(${Date.now()})`
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
boards.push(
|
|
424
|
-
await boardRepository.save({
|
|
425
|
-
...board,
|
|
426
|
-
domain,
|
|
427
|
-
description,
|
|
428
|
-
model: typeof model != 'string' ? JSON.stringify(model) : model,
|
|
429
|
-
thumbnail,
|
|
430
|
-
group,
|
|
431
|
-
state: 'draft',
|
|
432
|
-
updater: user
|
|
433
|
-
})
|
|
434
|
-
)
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
notify &&
|
|
438
|
-
notify({
|
|
439
|
-
mode: 'in-app',
|
|
440
|
-
title: `${boards.length} Board(s) are imported`,
|
|
441
|
-
body: `${boards.length} Board(s) are imported into group ${group.name} by ${user.name}`
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
return boards
|
|
445
|
-
}
|
|
446
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { In } from 'typeorm'
|
|
2
|
-
import { Arg, Args, Ctx, FieldResolver, Query, Resolver, Root, Directive } from 'type-graphql'
|
|
3
|
-
|
|
4
|
-
import { User } from '@things-factory/auth-base'
|
|
5
|
-
import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
|
|
6
|
-
import { checkTarget, checkDomain, getPermission } from '@things-factory/operato-license-checker'
|
|
7
|
-
|
|
8
|
-
import { Group } from '../group/group'
|
|
9
|
-
import { PlayGroup } from '../play-group/play-group'
|
|
10
|
-
import { Board } from './board'
|
|
11
|
-
import { BoardList } from './board-type'
|
|
12
|
-
import { BoardHistory } from './board-history'
|
|
13
|
-
|
|
14
|
-
import { LicenseError } from '../../errors/license-error'
|
|
15
|
-
@Resolver(Board)
|
|
16
|
-
export class BoardQuery {
|
|
17
|
-
@Directive('@privilege(category: "board", privilege: "query", domainOwnerGranted: true)')
|
|
18
|
-
@Query(returns => Board, { description: 'To fetch a board' })
|
|
19
|
-
async board(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Board> {
|
|
20
|
-
const { domain } = context.state
|
|
21
|
-
|
|
22
|
-
var board = await getRepository(Board).findOne({
|
|
23
|
-
where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id }
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
if (domain) {
|
|
27
|
-
// 1. check domainSecret is over limit quantity or not
|
|
28
|
-
var count = await getRepository(Domain)
|
|
29
|
-
.manager.createQueryBuilder()
|
|
30
|
-
.select('id')
|
|
31
|
-
.from(Domain, 'domain')
|
|
32
|
-
.getCount()
|
|
33
|
-
|
|
34
|
-
if (!checkDomain(count)) {
|
|
35
|
-
throw new LicenseError({
|
|
36
|
-
errorCode: context.t(LicenseError.ERROR_CODES.OVER_LIMIT)
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (board) {
|
|
42
|
-
// 1. check boardSecret is over limit quantity or not
|
|
43
|
-
var count = await getRepository(Board)
|
|
44
|
-
.manager.createQueryBuilder()
|
|
45
|
-
.select('id')
|
|
46
|
-
.from(Board, 'board')
|
|
47
|
-
.where('"domain_id" = :domainId', { domainId: domain.id })
|
|
48
|
-
.getCount()
|
|
49
|
-
|
|
50
|
-
if (!checkTarget(count)) {
|
|
51
|
-
throw new LicenseError({
|
|
52
|
-
errorCode: context.t(LicenseError.ERROR_CODES.OVER_LIMIT)
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return board
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@Query(returns => Board, { nullable: true, description: 'To fetch a Board Model by name' })
|
|
61
|
-
@Directive('@privilege(category: "board", privilege: "query", domainOwnerGranted: true)')
|
|
62
|
-
async boardByName(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<Board> {
|
|
63
|
-
const { domain } = context.state
|
|
64
|
-
|
|
65
|
-
return await getRepository(Board).findOne({
|
|
66
|
-
where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, name }
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@Query(returns => BoardList, { description: 'To fetch Boards created by me' })
|
|
71
|
-
async boardsCreatedByMe(
|
|
72
|
-
@Args(type => ListParam) params: ListParam,
|
|
73
|
-
@Ctx() context: ResolverContext
|
|
74
|
-
): Promise<BoardList> {
|
|
75
|
-
const { domain, user } = context.state
|
|
76
|
-
|
|
77
|
-
const queryBuilder = getQueryBuilderFromListParams({
|
|
78
|
-
repository: getRepository(Board),
|
|
79
|
-
params,
|
|
80
|
-
domain,
|
|
81
|
-
alias: 'board',
|
|
82
|
-
searchables: ['name', 'description']
|
|
83
|
-
}).andWhere('board.creator = :user', { user: user.id })
|
|
84
|
-
|
|
85
|
-
const [items, total] = await queryBuilder.getManyAndCount()
|
|
86
|
-
|
|
87
|
-
return { items, total }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
@Query(returns => [BoardHistory], { description: 'To fetch a Board Versions' })
|
|
91
|
-
@Directive('@privilege(category: "board", privilege: "query", domainOwnerGranted: true)')
|
|
92
|
-
async boardVersions(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<BoardHistory[]> {
|
|
93
|
-
const { domain } = context.state
|
|
94
|
-
|
|
95
|
-
return await getRepository(BoardHistory).find({
|
|
96
|
-
where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, originalId: id },
|
|
97
|
-
relations: ['updater'],
|
|
98
|
-
order: { version: 'DESC' },
|
|
99
|
-
take: 10
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@Query(returns => BoardHistory, { description: 'To fetch the latest Board published' })
|
|
104
|
-
@Directive('@privilege(category: "board", privilege: "query", domainOwnerGranted: true)')
|
|
105
|
-
async boardPublished(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<BoardHistory> {
|
|
106
|
-
const { domain } = context.state
|
|
107
|
-
|
|
108
|
-
return await getRepository(BoardHistory).findOne({
|
|
109
|
-
where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, originalId: id },
|
|
110
|
-
order: { version: 'DESC' }
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
@Query(returns => BoardList, { description: 'To fetch multiple Boards' })
|
|
115
|
-
@Directive('@privilege(category: "board", privilege: "query", domainOwnerGranted: true)')
|
|
116
|
-
async boards(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<BoardList> {
|
|
117
|
-
const { domain } = context.state
|
|
118
|
-
|
|
119
|
-
const queryBuilder = getQueryBuilderFromListParams({
|
|
120
|
-
repository: getRepository(Board),
|
|
121
|
-
params,
|
|
122
|
-
domain,
|
|
123
|
-
searchables: ['name', 'description']
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
const [items, total] = await queryBuilder.getManyAndCount()
|
|
127
|
-
|
|
128
|
-
return { items, total }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
@FieldResolver(type => Group)
|
|
132
|
-
async group(@Root() board: Board) {
|
|
133
|
-
return (
|
|
134
|
-
board.groupId &&
|
|
135
|
-
(await getRepository(Group).findOneBy({
|
|
136
|
-
id: board.groupId
|
|
137
|
-
}))
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
@FieldResolver(type => [PlayGroup])
|
|
142
|
-
async playGroups(@Root() board: Board) {
|
|
143
|
-
return (
|
|
144
|
-
await getRepository(Board).findOne({
|
|
145
|
-
where: { id: board.id },
|
|
146
|
-
relations: ['playGroups']
|
|
147
|
-
})
|
|
148
|
-
)?.playGroups
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
@FieldResolver(type => Domain)
|
|
152
|
-
async domain(@Root() board: Board) {
|
|
153
|
-
return (
|
|
154
|
-
board.domainId &&
|
|
155
|
-
(await getRepository(Domain).findOneBy({
|
|
156
|
-
id: board.domainId
|
|
157
|
-
}))
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
@FieldResolver(type => User)
|
|
162
|
-
async updater(@Root() board: Board): Promise<User> {
|
|
163
|
-
return (
|
|
164
|
-
board.updaterId &&
|
|
165
|
-
(await getRepository(User).findOneBy({
|
|
166
|
-
id: board.updaterId
|
|
167
|
-
}))
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@FieldResolver(type => User)
|
|
172
|
-
async creator(@Root() board: Board): Promise<User> {
|
|
173
|
-
return (
|
|
174
|
-
board.creatorId &&
|
|
175
|
-
(await getRepository(User).findOneBy({
|
|
176
|
-
id: board.creatorId
|
|
177
|
-
}))
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Resolver, Subscription, Root, Arg } from 'type-graphql'
|
|
2
|
-
import { filter, pipe } from 'graphql-yoga'
|
|
3
|
-
import { pubsub } from '@things-factory/shell'
|
|
4
|
-
import { Board } from './board'
|
|
5
|
-
|
|
6
|
-
@Resolver(Board)
|
|
7
|
-
export class BoardSubscription {
|
|
8
|
-
@Subscription({
|
|
9
|
-
subscribe: ({ args, context, info }) => {
|
|
10
|
-
const { domain, user } = context.state
|
|
11
|
-
const { id } = args
|
|
12
|
-
const subdomain = domain?.subdomain
|
|
13
|
-
|
|
14
|
-
if (!domain) {
|
|
15
|
-
throw new Error('domain required')
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (!user.domains?.find(d => d.subdomain === subdomain) && !process.superUserGranted(domain, user)) {
|
|
19
|
-
throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return pipe(
|
|
23
|
-
pubsub.subscribe('board'),
|
|
24
|
-
filter((payload: { board: Board }) => {
|
|
25
|
-
const { domainId, id: boardId } = payload.board
|
|
26
|
-
|
|
27
|
-
if (domainId !== domain.id) {
|
|
28
|
-
return false
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (id !== boardId) {
|
|
32
|
-
return false
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return true
|
|
36
|
-
})
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
board(@Root() payload: { board: Board }, @Arg('id') id: string): Board {
|
|
41
|
-
return payload.board
|
|
42
|
-
}
|
|
43
|
-
}
|