@things-factory/board-service 6.2.15 → 6.2.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/board-service",
3
- "version": "6.2.15",
3
+ "version": "6.2.19",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "client/index.js",
6
6
  "things-factory": true,
@@ -24,11 +24,11 @@
24
24
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create -d ./server/migrations"
25
25
  },
26
26
  "dependencies": {
27
- "@things-factory/auth-base": "^6.2.15",
27
+ "@things-factory/auth-base": "^6.2.19",
28
28
  "@things-factory/env": "^6.2.0",
29
- "@things-factory/fav-base": "^6.2.15",
30
- "@things-factory/font-base": "^6.2.15",
31
- "@things-factory/integration-base": "^6.2.15",
29
+ "@things-factory/fav-base": "^6.2.19",
30
+ "@things-factory/font-base": "^6.2.19",
31
+ "@things-factory/integration-base": "^6.2.19",
32
32
  "@things-factory/operato-license-checker": "^3.1.0",
33
33
  "content-disposition": "^0.5.3",
34
34
  "generic-pool": "^3.8.2"
@@ -37,5 +37,5 @@
37
37
  "@thiagoelg/node-printer": "0.6.2",
38
38
  "puppeteer": "^20.7.3"
39
39
  },
40
- "gitHead": "262fa72c069ac8745dd16acd695d0e87331fbc14"
40
+ "gitHead": "5b5f3c02adca0ecb6344310bfeaf27b04c5dc657"
41
41
  }
@@ -11,6 +11,7 @@ import { labelcommand } from '../controllers/label-command'
11
11
  import { pdf } from '../controllers/pdf'
12
12
  import { screenshot } from '../controllers/screenshot'
13
13
  import { Board } from '../service/board/board'
14
+ import { BoardTemplate } from '../service/board-template/board-template'
14
15
 
15
16
  export const standaloneBoardServiceRouter = new Router()
16
17
 
@@ -216,6 +217,32 @@ standaloneBoardServiceRouter.get('/thumbnail/:id', async (context, next) => {
216
217
  }
217
218
  })
218
219
 
220
+ // for board-template thumbnail
221
+ standaloneBoardServiceRouter.get('/board-template-thumbnail/:id', async (context, next) => {
222
+ const { domain, user } = context.state
223
+
224
+ if (!(await User.hasPrivilege('query', 'board-template', domain, user))) {
225
+ context.status = 403
226
+ return
227
+ }
228
+
229
+ const { id } = context.params
230
+
231
+ const { name, thumbnail } = (await getRepository(BoardTemplate).findOneBy({ domain: { id: domain.id }, id })) || {}
232
+
233
+ if (thumbnail) {
234
+ const index = thumbnail.indexOf(';base64,')
235
+ const base64 = thumbnail.substring(index + 8)
236
+ const buffer = Buffer.from(base64, 'base64')
237
+
238
+ context.type = 'image/png'
239
+ context.set('Content-Disposition', contentDisposition(`thumbnail-${name}.png`))
240
+ context.body = buffer
241
+ } else {
242
+ context.throw(404, 'thumbnail not found')
243
+ }
244
+ })
245
+
219
246
  // for webpage scrap
220
247
  standaloneBoardServiceRouter.get('/screenshot/:id', async (context, next) => {
221
248
  const { domain, user } = context.state
@@ -55,10 +55,12 @@ export class BoardMutation {
55
55
  }
56
56
 
57
57
  const newBoard: Board = {
58
- ...board,
59
- thumbnail: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* empty thumbnail */
58
+ ...board
60
59
  }
61
60
 
61
+ newBoard.thumbnail ||=
62
+ 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* empty thumbnail */
63
+
62
64
  if (board.groupId) {
63
65
  newBoard.group = await groupRepository.findOneBy({
64
66
  id: board.groupId
@@ -239,7 +241,7 @@ export class BoardMutation {
239
241
 
240
242
  @Directive('@transaction')
241
243
  @Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
242
- @Mutation(returns => Board, { description: 'To release Board information' })
244
+ @Mutation(returns => Board, { description: 'To release a Board' })
243
245
  async releaseBoard(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Board> {
244
246
  const { domain, user, notify, tx } = context.state
245
247
  const repository = tx.getRepository(Board)
@@ -1,13 +1,15 @@
1
1
  import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
2
2
 
3
- import { thumbnail } from '../../controllers/thumbnail'
3
+ import { getRedirectSubdomainPath } from '@things-factory/shell'
4
4
  import { BoardTemplate } from './board-template'
5
+ import { Board } from '../board/board'
5
6
  import { NewBoardTemplate, BoardTemplatePatch } from './board-template-type'
7
+ import { thumbnail } from '../../controllers/thumbnail'
6
8
 
7
9
  @Resolver(BoardTemplate)
8
10
  export class BoardTemplateMutation {
9
11
  @Directive('@transaction')
10
- @Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
12
+ @Directive('@privilege(category: "board-template", privilege: "mutation", domainOwnerGranted: true)')
11
13
  @Mutation(returns => BoardTemplate, { description: 'To create new BoardTemplate' })
12
14
  async createBoardTemplate(
13
15
  @Arg('boardTemplate') boardTemplate: NewBoardTemplate,
@@ -49,7 +51,7 @@ export class BoardTemplateMutation {
49
51
  }
50
52
 
51
53
  @Directive('@transaction')
52
- @Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
54
+ @Directive('@privilege(category: "board-template", privilege: "mutation", domainOwnerGranted: true)')
53
55
  @Mutation(returns => BoardTemplate, { description: 'To modify BoardTemplate information' })
54
56
  async updateBoardTemplate(
55
57
  @Arg('id') id: string,
@@ -84,7 +86,7 @@ export class BoardTemplateMutation {
84
86
  }
85
87
 
86
88
  @Directive('@transaction')
87
- @Directive('@privilege(category: "board", privilege: "mutation", domainOwnerGranted: true)')
89
+ @Directive('@privilege(category: "board-template", privilege: "mutation", domainOwnerGranted: true)')
88
90
  @Mutation(returns => Boolean, { description: 'To delete BoardTemplate' })
89
91
  async deleteBoardTemplate(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
90
92
  const { domain, tx } = context.state
@@ -93,4 +95,67 @@ export class BoardTemplateMutation {
93
95
 
94
96
  return true
95
97
  }
98
+
99
+ @Directive('@transaction')
100
+ @Directive('@privilege(category: "board-template", privilege: "mutation", domainOwnerGranted: true)')
101
+ @Mutation(returns => BoardTemplate, { description: 'To register a board as a board template with the given ID' })
102
+ async registerBoardAsTemplate(
103
+ @Arg('id', { description: 'board Id to be regiestered' }) id: string,
104
+ @Arg('name', { description: 'name of board template to be regiestered' }) name: string,
105
+ @Arg('description', { description: 'description of board template to be regiestered' }) description: string,
106
+ @Arg('visibility', { description: 'visibility of board template to be regiestered' }) visibility: string,
107
+ @Ctx() context: ResolverContext
108
+ ): Promise<BoardTemplate> {
109
+ const { domain, user, notify, tx } = context.state
110
+
111
+ const boardTemplateRepository = tx.getRepository(BoardTemplate)
112
+
113
+ const board = await tx.getRepository(Board).findOne({
114
+ where: { domain: { id: domain.id }, id }
115
+ })
116
+
117
+ if (!board) {
118
+ throw `Board given id(${id}) is not found`
119
+ }
120
+
121
+ name ||= board.name
122
+
123
+ const { model, thumbnail } = board
124
+
125
+ const boardTemplate = await boardTemplateRepository.findOne({
126
+ where: { domain: { id: domain.id }, name }
127
+ })
128
+
129
+ const registered = boardTemplate
130
+ ? await boardTemplateRepository.save({
131
+ ...boardTemplate,
132
+ name,
133
+ description: description || boardTemplate.description,
134
+ visibility: visibility || boardTemplate.visibility,
135
+ model,
136
+ thumbnail,
137
+ updater: user
138
+ })
139
+ : await boardTemplateRepository.save({
140
+ domain: { id: domain.id },
141
+ name,
142
+ description: description || board.description,
143
+ model,
144
+ thumbnail,
145
+ visibility: visibility || 'private',
146
+ updater: user,
147
+ creator: user
148
+ })
149
+
150
+ notify &&
151
+ notify({
152
+ mode: 'in-app',
153
+ title: `BoardTemplate '${registered.name}' registered`,
154
+ body: `BoardTemplate '${registered.name}' registered by ${user.name}\n${registered.description}`,
155
+ image: getRedirectSubdomainPath(context, domain.subdomain, `/board-template-thumbnail/${registered.id}`),
156
+ url: getRedirectSubdomainPath(context, domain.subdomain, `/board-template-viewer/${registered.id}`)
157
+ })
158
+
159
+ return registered
160
+ }
96
161
  }
@@ -1,4 +1,4 @@
1
- import { In } from 'typeorm'
1
+ import { Brackets, In } from 'typeorm'
2
2
  import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'
3
3
  import { Attachment } from '@things-factory/attachment-base'
4
4
  import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
@@ -9,23 +9,35 @@ import { BoardTemplateList } from './board-template-type'
9
9
  @Resolver(BoardTemplate)
10
10
  export class BoardTemplateQuery {
11
11
  @Query(returns => BoardTemplate!, { nullable: true, description: 'To fetch a BoardTemplate' })
12
- @Directive('@privilege(category: "board", privilege: "query", domainOwnerGranted: true)')
12
+ @Directive('@privilege(category: "board-template", privilege: "query", domainOwnerGranted: true)')
13
13
  async boardTemplate(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<BoardTemplate> {
14
- const { domain } = context.state
15
-
16
- return await getRepository(BoardTemplate).findOne({
17
- where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id }
18
- })
19
- }
20
-
21
- @Query(returns => BoardTemplate, { nullable: true, description: 'To fetch a BoardTemplate by name' })
22
- @Directive('@privilege(category: "board", privilege: "query", domainOwnerGranted: true)')
23
- async boardTemplateByName(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<BoardTemplate> {
24
- const { domain } = context.state
14
+ const { domain, user } = context.state
25
15
 
26
- return await getRepository(BoardTemplate).findOne({
27
- where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, name }
28
- })
16
+ const qb = await getRepository(BoardTemplate)
17
+ .createQueryBuilder('BoardTemplate')
18
+ .where('id=:id', { id })
19
+ .andWhere(
20
+ new Brackets(qb => {
21
+ qb.where({ visibility: 'public' })
22
+ .orWhere({
23
+ visibility: 'domain',
24
+ domain: { id: In([domain.id, domain.parentId]) }
25
+ })
26
+ .orWhere({
27
+ visibility: 'private',
28
+ creator: { id: user.id }
29
+ })
30
+ })
31
+ )
32
+
33
+ return qb.getOne()
34
+
35
+ // return await getRepository(BoardTemplate).findOne({
36
+ // where: {
37
+ // domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },
38
+ // id
39
+ // }
40
+ // })
29
41
  }
30
42
 
31
43
  @Query(returns => BoardTemplateList, { description: 'To fetch BoardTemplates created by me' })
@@ -33,12 +45,11 @@ export class BoardTemplateQuery {
33
45
  @Args() params: ListParam,
34
46
  @Ctx() context: ResolverContext
35
47
  ): Promise<BoardTemplateList> {
36
- const { domain, user } = context.state
48
+ const { user } = context.state
37
49
 
38
50
  const queryBuilder = getQueryBuilderFromListParams({
39
51
  repository: getRepository(BoardTemplate),
40
52
  params,
41
- domain,
42
53
  alias: 'template',
43
54
  searchables: ['name', 'description']
44
55
  }).andWhere('template.creator = :user', { user: user.id })
@@ -49,33 +60,45 @@ export class BoardTemplateQuery {
49
60
  }
50
61
 
51
62
  @Query(returns => BoardTemplateList, { description: 'To fetch multiple BoardTemplates' })
52
- @Directive('@privilege(category: "board", privilege: "query", domainOwnerGranted: true)')
63
+ @Directive('@privilege(category: "board-template", privilege: "query", domainOwnerGranted: true)')
53
64
  async boardTemplates(@Args() params: ListParam, @Ctx() context: ResolverContext): Promise<BoardTemplateList> {
54
- const { domain } = context.state
65
+ const { domain, user } = context.state
66
+ /*
67
+ 리스트에 포함되는 보드템플릿들
68
+ - visibility가 public 인 경우
69
+ - visibility가 domain이며 컨텍스트의 도메인과 같은 경우
70
+ - visibility가 private 이며, creator가 나인 경우
71
+ */
55
72
 
56
73
  const queryBuilder = getQueryBuilderFromListParams({
57
- domain,
58
74
  params,
59
75
  repository: await getRepository(BoardTemplate),
60
76
  searchables: ['name', 'description']
61
- })
77
+ }).andWhere(
78
+ new Brackets(qb => {
79
+ qb.where({ visibility: 'public' })
80
+ .orWhere({
81
+ visibility: 'domain',
82
+ domain: { id: In([domain.id, domain.parentId]) }
83
+ })
84
+ .orWhere({
85
+ visibility: 'private',
86
+ creator: { id: user.id }
87
+ })
88
+ })
89
+ )
62
90
 
63
91
  const [items, total] = await queryBuilder.getManyAndCount()
64
92
 
65
93
  return { items, total }
66
94
  }
67
95
 
68
- @FieldResolver(type => String)
69
- async thumbnail(@Root() boardTemplate: BoardTemplate): Promise<string | undefined> {
70
- const attachment: Attachment = await getRepository(Attachment).findOne({
71
- where: {
72
- domain: { id: boardTemplate.domainId },
73
- refType: BoardTemplate.name,
74
- refBy: boardTemplate.id
75
- }
76
- })
77
-
78
- return attachment?.fullpath
96
+ @FieldResolver(type => Boolean)
97
+ async mine(@Root() boardTemplate: BoardTemplate, @Ctx() context: ResolverContext): Promise<boolean> {
98
+ const { creatorId } = boardTemplate
99
+ const { user } = context.state
100
+
101
+ return creatorId == user.id
79
102
  }
80
103
 
81
104
  @FieldResolver(type => Domain)
@@ -1,27 +1,22 @@
1
- import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'
2
- import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
3
- import { ObjectType, Field, InputType, Int, ID, registerEnumType } from 'type-graphql'
4
-
5
- import { ObjectRef, ScalarObject } from '@things-factory/shell'
6
-
7
- import { BoardTemplate, BoardTemplateStatus } from './board-template'
1
+ import { ObjectType, Field, InputType, Int, ID } from 'type-graphql'
2
+ import { BoardTemplate } from './board-template'
8
3
 
9
4
  @InputType()
10
5
  export class NewBoardTemplate {
11
6
  @Field()
12
7
  name: string
13
8
 
14
- @Field({ nullable: true })
15
- description?: string
16
-
17
- @Field(type => BoardTemplateStatus, { nullable: true })
18
- state?: BoardTemplateStatus
9
+ @Field()
10
+ description: string
19
11
 
20
12
  @Field()
21
13
  model: string
22
14
 
15
+ @Field()
16
+ visibility: 'private' | 'public'
17
+
23
18
  @Field({ nullable: true })
24
- thumbnail?: string
19
+ thumbnail: string
25
20
  }
26
21
 
27
22
  @InputType()
@@ -35,12 +30,12 @@ export class BoardTemplatePatch {
35
30
  @Field({ nullable: true })
36
31
  description?: string
37
32
 
38
- @Field(type => BoardTemplateStatus, { nullable: true })
39
- state?: BoardTemplateStatus
40
-
41
- @Field()
33
+ @Field({ nullable: true })
42
34
  model: string
43
35
 
36
+ @Field({ nullable: true })
37
+ visibility?: 'private' | 'public'
38
+
44
39
  @Field({ nullable: true })
45
40
  thumbnail?: string
46
41
 
@@ -6,12 +6,11 @@ import {
6
6
  Column,
7
7
  RelationId,
8
8
  ManyToOne,
9
- PrimaryGeneratedColumn,
10
- VersionColumn
9
+ PrimaryGeneratedColumn
11
10
  } from 'typeorm'
12
11
  import { ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql'
13
12
 
14
- import { Domain } from '@things-factory/shell'
13
+ import { Domain, ScalarObject } from '@things-factory/shell'
15
14
  import { User } from '@things-factory/auth-base'
16
15
  import { config } from '@things-factory/env'
17
16
 
@@ -38,10 +37,6 @@ export class BoardTemplate {
38
37
  @Field(type => ID)
39
38
  readonly id: string
40
39
 
41
- @VersionColumn()
42
- @Field({ nullable: true })
43
- version?: number = 1
44
-
45
40
  @ManyToOne(type => Domain)
46
41
  @Field({ nullable: true })
47
42
  domain?: Domain
@@ -57,9 +52,13 @@ export class BoardTemplate {
57
52
  @Field({ nullable: true })
58
53
  description?: string
59
54
 
60
- @Column({ nullable: true })
55
+ @Column('simple-json', { nullable: true, default: null })
56
+ @Field(type => ScalarObject, { nullable: true })
57
+ tags?: string[]
58
+
59
+ @Column({ nullable: true, default: 'private' })
61
60
  @Field({ nullable: true })
62
- state?: BoardTemplateStatus
61
+ visibility?: string // 'private' | 'public' | 'domain'
63
62
 
64
63
  @Column({
65
64
  nullable: true,