@things-factory/attachment-base 8.0.0-beta.8 → 8.0.0
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/index.js +0 -7
- package/dist-server/index.js.map +1 -1
- package/dist-server/storage-database.js +1 -3
- package/dist-server/storage-database.js.map +1 -1
- package/dist-server/storage-file.js +1 -3
- package/dist-server/storage-file.js.map +1 -1
- package/dist-server/storage-s3.js +1 -3
- package/dist-server/storage-s3.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/attachment-const.ts +5 -0
- package/server/awb-storage-s3.ts +44 -0
- package/server/index.ts +15 -0
- package/server/routes.ts +35 -0
- package/server/service/attachment/attachment-mutation.ts +343 -0
- package/server/service/attachment/attachment-query.ts +78 -0
- package/server/service/attachment/attachment-types.ts +76 -0
- package/server/service/attachment/attachment.ts +129 -0
- package/server/service/attachment/index.ts +6 -0
- package/server/service/index.ts +19 -0
- package/server/storage-azure-blob.ts +111 -0
- package/server/storage-database.ts +78 -0
- package/server/storage-file.ts +78 -0
- package/server/storage-s3.ts +139 -0
- package/server/util/index.ts +1 -0
- package/server/util/upload-awb.ts +11 -0
- package/dist-server/nfc-normalize.d.ts +0 -1
- package/dist-server/nfc-normalize.js +0 -24
- package/dist-server/nfc-normalize.js.map +0 -1
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@things-factory/attachment-base",
|
3
|
-
"version": "8.0.0
|
3
|
+
"version": "8.0.0",
|
4
4
|
"main": "dist-server/index.js",
|
5
5
|
"browser": "client/index.js",
|
6
6
|
"things-factory": true,
|
@@ -29,11 +29,11 @@
|
|
29
29
|
"@aws-sdk/s3-presigned-post": "^3.46.0",
|
30
30
|
"@azure/storage-blob": "^12.18.0",
|
31
31
|
"@koa/multer": "^3.0.0",
|
32
|
-
"@things-factory/auth-base": "^8.0.0
|
33
|
-
"@things-factory/env": "^8.0.0
|
34
|
-
"@things-factory/shell": "^8.0.0
|
32
|
+
"@things-factory/auth-base": "^8.0.0",
|
33
|
+
"@things-factory/env": "^8.0.0",
|
34
|
+
"@things-factory/shell": "^8.0.0",
|
35
35
|
"mime": "^3.0.0",
|
36
36
|
"multer": "^1.4.5-lts.1"
|
37
37
|
},
|
38
|
-
"gitHead": "
|
38
|
+
"gitHead": "07ef27d272dd9a067a9648ac7013748510556a18"
|
39
39
|
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3'
|
2
|
+
import { Upload } from '@aws-sdk/lib-storage'
|
3
|
+
import { logger } from '@things-factory/env'
|
4
|
+
|
5
|
+
import { AWBSTORAGE } from './attachment-const'
|
6
|
+
|
7
|
+
if (AWBSTORAGE && AWBSTORAGE.type == 's3') {
|
8
|
+
const client = new S3Client({
|
9
|
+
credentials: {
|
10
|
+
accessKeyId: AWBSTORAGE.accessKeyId,
|
11
|
+
secretAccessKey: AWBSTORAGE.secretAccessKey
|
12
|
+
},
|
13
|
+
region: AWBSTORAGE.region
|
14
|
+
})
|
15
|
+
|
16
|
+
/* upload file */
|
17
|
+
AWBSTORAGE.uploadFile = async ({ stream, filename }) => {
|
18
|
+
const upload = new Upload({
|
19
|
+
client,
|
20
|
+
params: {
|
21
|
+
Bucket: AWBSTORAGE.bucketName,
|
22
|
+
Key: `${filename}.pdf`,
|
23
|
+
Body: stream,
|
24
|
+
ContentType: 'application/pdf'
|
25
|
+
}
|
26
|
+
})
|
27
|
+
|
28
|
+
let result
|
29
|
+
let url
|
30
|
+
try {
|
31
|
+
result = (await upload.done()) as any
|
32
|
+
url = `https://${AWBSTORAGE.bucketName}.s3.${AWBSTORAGE.region}.amazonaws.com/${filename}.pdf`
|
33
|
+
} catch (e) {
|
34
|
+
console.log(e)
|
35
|
+
}
|
36
|
+
|
37
|
+
return {
|
38
|
+
result,
|
39
|
+
url
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
logger.info('operato-awb: S3 Bucket Storage is Ready.')
|
44
|
+
}
|
package/server/index.ts
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
export * from './attachment-const'
|
2
|
+
export * from './service'
|
3
|
+
export {
|
4
|
+
createAttachment,
|
5
|
+
createAttachments,
|
6
|
+
deleteAttachment,
|
7
|
+
deleteAttachmentsByRef,
|
8
|
+
multipleUpload,
|
9
|
+
singleUpload
|
10
|
+
} from './service/attachment/attachment-mutation'
|
11
|
+
|
12
|
+
import './routes'
|
13
|
+
|
14
|
+
export * from './attachment-const'
|
15
|
+
export * from './util'
|
package/server/routes.ts
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
import './storage-file'
|
2
|
+
import './storage-s3'
|
3
|
+
import './storage-database'
|
4
|
+
import './storage-azure-blob'
|
5
|
+
|
6
|
+
import { ATTACHMENT_PATH, STORAGE } from './attachment-const'
|
7
|
+
|
8
|
+
const multer = require('@koa/multer')
|
9
|
+
const upload = multer() // note you can pass `multer` options here
|
10
|
+
|
11
|
+
const { Readable } = require('stream')
|
12
|
+
|
13
|
+
// process.on('bootstrap-module-domain-private-route' as any, (app, routes) => {
|
14
|
+
process.on('bootstrap-module-global-public-route' as any, (app, routes) => {
|
15
|
+
// TODO make this secure
|
16
|
+
routes.get(`/${ATTACHMENT_PATH}/:attachment`, async (context, next) => {
|
17
|
+
context.set('Cache-Control', 'public, max-age=31536000') // 캐시 기간 설정 : 1년
|
18
|
+
context.set('Expires', new Date(Date.now() + 31536000000).toUTCString()) // 캐시 만료일 설정
|
19
|
+
|
20
|
+
await STORAGE.sendFile(context, context.params.attachment, next)
|
21
|
+
})
|
22
|
+
|
23
|
+
routes.post(`/${ATTACHMENT_PATH}`, upload.any(), async (context, next) => {
|
24
|
+
const files = context.files
|
25
|
+
|
26
|
+
const result: { id: string; path: string; size: number }[] = await Promise.all(
|
27
|
+
files.map(file => STORAGE.uploadFile({ file, context }))
|
28
|
+
)
|
29
|
+
|
30
|
+
context.status = 200
|
31
|
+
// Support < IE 10 browser
|
32
|
+
context.set('Content-Type', 'text/html;charset=UTF-8')
|
33
|
+
context.body = JSON.stringify(result)
|
34
|
+
})
|
35
|
+
})
|
@@ -0,0 +1,343 @@
|
|
1
|
+
import { FileUpload } from 'graphql-upload/GraphQLUpload.js'
|
2
|
+
import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
|
3
|
+
import promisesAll from 'promises-all'
|
4
|
+
|
5
|
+
import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'
|
6
|
+
import { In } from 'typeorm'
|
7
|
+
|
8
|
+
import { config, logger } from '@things-factory/env'
|
9
|
+
import { getRepository } from '@things-factory/shell'
|
10
|
+
|
11
|
+
import { STORAGE } from '../../attachment-const'
|
12
|
+
import { Attachment } from './attachment'
|
13
|
+
import { AttachmentPatch, NewAttachment, UploadURL } from './attachment-types'
|
14
|
+
|
15
|
+
const allowedMimeTypes = config.get('fileUpload/mimeTypes', [])
|
16
|
+
|
17
|
+
@Resolver(Attachment)
|
18
|
+
export class AttachmentMutation {
|
19
|
+
@Directive('@transaction')
|
20
|
+
@Mutation(returns => Attachment)
|
21
|
+
async createAttachment(
|
22
|
+
@Arg('attachment', type => NewAttachment) attachment: NewAttachment,
|
23
|
+
@Ctx() context: ResolverContext
|
24
|
+
): Promise<Attachment> {
|
25
|
+
return await createAttachment(null, { attachment }, context)
|
26
|
+
}
|
27
|
+
|
28
|
+
@Directive('@transaction')
|
29
|
+
@Mutation(returns => [Attachment])
|
30
|
+
async createAttachments(
|
31
|
+
@Arg('attachments', type => [NewAttachment]) attachments: NewAttachment[],
|
32
|
+
@Ctx() context: ResolverContext
|
33
|
+
): Promise<Attachment[]> {
|
34
|
+
return await createAttachments(null, { attachments }, context)
|
35
|
+
}
|
36
|
+
|
37
|
+
@Directive('@transaction')
|
38
|
+
@Mutation(returns => Attachment)
|
39
|
+
async updateAttachment(
|
40
|
+
@Arg('id') id: string,
|
41
|
+
@Arg('patch', type => AttachmentPatch) patch: AttachmentPatch,
|
42
|
+
@Ctx() context: ResolverContext
|
43
|
+
): Promise<Attachment> {
|
44
|
+
const attachment = await getRepository(Attachment).findOne({
|
45
|
+
where: {
|
46
|
+
domain: { id: context.state.domain.id },
|
47
|
+
id
|
48
|
+
}
|
49
|
+
})
|
50
|
+
|
51
|
+
return await getRepository(Attachment).save({
|
52
|
+
...attachment,
|
53
|
+
...patch,
|
54
|
+
updater: context.state.user
|
55
|
+
})
|
56
|
+
}
|
57
|
+
|
58
|
+
@Directive('@transaction')
|
59
|
+
@Mutation(returns => Boolean)
|
60
|
+
async deleteAttachment(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
61
|
+
return await deleteAttachment(null, { id }, context)
|
62
|
+
}
|
63
|
+
|
64
|
+
@Directive('@transaction')
|
65
|
+
@Mutation(returns => Boolean)
|
66
|
+
async deleteAttachmentsByRef(
|
67
|
+
@Arg('refBys', type => [String]) refBys: string[],
|
68
|
+
@Arg('refType', type => String, { nullable: true }) refType: string,
|
69
|
+
@Ctx() context: ResolverContext
|
70
|
+
): Promise<boolean> {
|
71
|
+
return await deleteAttachmentsByRef(null, { refBys, refType }, context)
|
72
|
+
}
|
73
|
+
|
74
|
+
@Directive('@transaction')
|
75
|
+
@Mutation(returns => Attachment)
|
76
|
+
async singleUpload(
|
77
|
+
@Arg('file', type => GraphQLUpload) file: FileUpload,
|
78
|
+
@Ctx() context: ResolverContext
|
79
|
+
): Promise<Attachment> {
|
80
|
+
return await singleUpload(null, { file }, context)
|
81
|
+
}
|
82
|
+
|
83
|
+
@Directive('@transaction')
|
84
|
+
@Mutation(returns => [Attachment])
|
85
|
+
async multipleUpload(
|
86
|
+
@Arg('files', type => [GraphQLUpload]) files: FileUpload[],
|
87
|
+
@Ctx() context: ResolverContext
|
88
|
+
): Promise<Attachment[]> {
|
89
|
+
return await multipleUpload(null, { files }, context)
|
90
|
+
}
|
91
|
+
|
92
|
+
@Mutation(returns => UploadURL)
|
93
|
+
async generateUploadURL(
|
94
|
+
@Arg('type', type => String) type: string,
|
95
|
+
@Ctx() context: ResolverContext
|
96
|
+
): Promise<{ url: string; fields: { [key: string]: string } }> {
|
97
|
+
return await generateUploadURL(null, { type }, context)
|
98
|
+
}
|
99
|
+
|
100
|
+
@Directive('@transaction')
|
101
|
+
@Mutation(returns => [Attachment], { description: 'To import some Attachments' })
|
102
|
+
async importAttachments(
|
103
|
+
@Arg('file', () => GraphQLUpload) file: FileUpload,
|
104
|
+
@Ctx() context: ResolverContext
|
105
|
+
): Promise<Attachment[]> {
|
106
|
+
return await importAttachments(file, context)
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
export async function createAttachment(_: any, { attachment }, context: ResolverContext): Promise<Attachment> {
|
111
|
+
const { file, category, refType = '', refBy, description } = attachment
|
112
|
+
const { mimetype } = (await file) as FileUpload
|
113
|
+
|
114
|
+
if (allowedMimeTypes instanceof Array && allowedMimeTypes.length > 0 && !allowedMimeTypes.includes('*/*')) {
|
115
|
+
const isAllowed = allowedMimeTypes.some(type => {
|
116
|
+
const [typeMain, typeSub] = type.split('/')
|
117
|
+
const [mimeMain, mimeSub] = mimetype.split('/')
|
118
|
+
return (
|
119
|
+
(typeMain === mimeMain && (typeSub === '*' || typeSub === mimeSub)) || (typeMain === '*' && typeSub === '*')
|
120
|
+
)
|
121
|
+
})
|
122
|
+
|
123
|
+
if (!isAllowed) {
|
124
|
+
throw Error(context.t(`error.not allowed file type for upload`, { mimetype }))
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
const { id, path, size, filename, encoding, contents } = await STORAGE.uploadFile({ file, context })
|
129
|
+
const { domain, user, tx } = context.state
|
130
|
+
|
131
|
+
const repository = tx ? tx.getRepository(Attachment) : getRepository(Attachment)
|
132
|
+
|
133
|
+
return await repository.save({
|
134
|
+
domain,
|
135
|
+
creator: user,
|
136
|
+
updater: user,
|
137
|
+
id,
|
138
|
+
description,
|
139
|
+
name: filename,
|
140
|
+
mimetype,
|
141
|
+
encoding,
|
142
|
+
refType,
|
143
|
+
refBy,
|
144
|
+
category: category || mimetype.split('/').shift(),
|
145
|
+
size: size as any,
|
146
|
+
path,
|
147
|
+
contents
|
148
|
+
})
|
149
|
+
}
|
150
|
+
|
151
|
+
export async function createAttachments(_: any, { attachments }, context: ResolverContext): Promise<Attachment[]> {
|
152
|
+
const { resolve, reject } = await promisesAll.all(
|
153
|
+
attachments.map(attachment => createAttachment(_, { attachment }, context))
|
154
|
+
)
|
155
|
+
|
156
|
+
if (reject.length) {
|
157
|
+
reject.forEach(({ name, message }) => logger.error(`${name}: ${message}`))
|
158
|
+
|
159
|
+
return reject
|
160
|
+
}
|
161
|
+
|
162
|
+
return resolve
|
163
|
+
}
|
164
|
+
|
165
|
+
export async function deleteAttachment(_: any, { id }, context: ResolverContext): Promise<boolean> {
|
166
|
+
const { domain, tx } = context.state
|
167
|
+
|
168
|
+
const repository = tx ? tx.getRepository(Attachment) : getRepository(Attachment)
|
169
|
+
const attachment = await repository.findOne({
|
170
|
+
where: { domain: { id: domain.id }, id }
|
171
|
+
})
|
172
|
+
|
173
|
+
if (attachment) {
|
174
|
+
await repository.delete({ id: attachment.id })
|
175
|
+
await STORAGE.deleteFile(attachment.path)
|
176
|
+
return true
|
177
|
+
} else {
|
178
|
+
return false
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
interface DeleteAttachmentsObject {
|
183
|
+
refBys: Array<string>
|
184
|
+
refType?: string
|
185
|
+
}
|
186
|
+
export async function deleteAttachmentsByRef(
|
187
|
+
_: any,
|
188
|
+
{ refBys, refType = null }: DeleteAttachmentsObject,
|
189
|
+
context: ResolverContext
|
190
|
+
): Promise<boolean> {
|
191
|
+
const { domain, tx } = context.state
|
192
|
+
const repository = tx ? tx.getRepository(Attachment) : getRepository(Attachment)
|
193
|
+
const inquryWhereClause: any = { domain: { id: domain.id }, refBy: In(refBys) }
|
194
|
+
const deleteWhereClause: any = { refBy: In(refBys) }
|
195
|
+
|
196
|
+
// refType이 존재하면 where 절에 추가
|
197
|
+
if (refType) {
|
198
|
+
inquryWhereClause.refType = refType;
|
199
|
+
deleteWhereClause.refType = refType;
|
200
|
+
}
|
201
|
+
|
202
|
+
const attachments = await repository.find({
|
203
|
+
where: inquryWhereClause
|
204
|
+
})
|
205
|
+
|
206
|
+
//remove attachment from repo
|
207
|
+
await repository.delete(deleteWhereClause)
|
208
|
+
|
209
|
+
//remove files from attachments folder
|
210
|
+
if (attachments.length) {
|
211
|
+
await Promise.all(
|
212
|
+
attachments.map(async attachment => {
|
213
|
+
await STORAGE.deleteFile(attachment.path)
|
214
|
+
})
|
215
|
+
)
|
216
|
+
|
217
|
+
return true
|
218
|
+
} else {
|
219
|
+
return false
|
220
|
+
}
|
221
|
+
}
|
222
|
+
|
223
|
+
export async function generateUploadURL(
|
224
|
+
_: any,
|
225
|
+
{ type },
|
226
|
+
context: ResolverContext
|
227
|
+
): Promise<{ url: string; fields: { [key: string]: string } }> {
|
228
|
+
return await STORAGE.generateUploadURL(type)
|
229
|
+
}
|
230
|
+
|
231
|
+
export async function singleUpload(_: any, { file }, context: ResolverContext): Promise<Attachment> {
|
232
|
+
return await createAttachment(null, { attachment: { file } }, context)
|
233
|
+
}
|
234
|
+
|
235
|
+
export async function multipleUpload(_: any, { files }, context: ResolverContext): Promise<Attachment[]> {
|
236
|
+
return await createAttachments(null, { attachments: { files } }, context)
|
237
|
+
}
|
238
|
+
|
239
|
+
async function parseJSONFile(uploadedFile: FileUpload): Promise<any> {
|
240
|
+
var { createReadStream } = await uploadedFile
|
241
|
+
|
242
|
+
return new Promise((resolve, reject) => {
|
243
|
+
const chunks: Uint8Array[] = []
|
244
|
+
|
245
|
+
createReadStream()
|
246
|
+
.on('data', (chunk: Uint8Array) => {
|
247
|
+
chunks.push(chunk)
|
248
|
+
})
|
249
|
+
.on('end', () => {
|
250
|
+
try {
|
251
|
+
const fileContents = Buffer.concat(chunks).toString('utf-8')
|
252
|
+
const jsonData = JSON.parse(fileContents)
|
253
|
+
resolve(jsonData)
|
254
|
+
} catch (error) {
|
255
|
+
reject(error)
|
256
|
+
}
|
257
|
+
})
|
258
|
+
.on('error', (error: Error) => {
|
259
|
+
reject(error)
|
260
|
+
})
|
261
|
+
})
|
262
|
+
}
|
263
|
+
|
264
|
+
function dataURLToFileUpload(dataURL: string, filename: string, mimeType: string): FileUpload {
|
265
|
+
const indexOfComma = dataURL.indexOf(',')
|
266
|
+
if (indexOfComma === -1) {
|
267
|
+
throw new Error('Invalid Data URL')
|
268
|
+
}
|
269
|
+
|
270
|
+
const base64Data = dataURL.slice(indexOfComma + 1)
|
271
|
+
const buffer = Buffer.from(base64Data, 'base64')
|
272
|
+
|
273
|
+
return {
|
274
|
+
filename,
|
275
|
+
mimetype: mimeType,
|
276
|
+
encoding: 'base64',
|
277
|
+
createReadStream: () => {
|
278
|
+
const stream = require('stream')
|
279
|
+
const readable = new stream.Readable()
|
280
|
+
readable.push(buffer)
|
281
|
+
readable.push(null)
|
282
|
+
return readable
|
283
|
+
}
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
export async function importAttachments(upload: FileUpload, context: ResolverContext): Promise<Attachment[]> {
|
288
|
+
const { domain, user, notify, tx } = context.state
|
289
|
+
|
290
|
+
const repository = tx.getRepository(Attachment)
|
291
|
+
|
292
|
+
const attachments = []
|
293
|
+
|
294
|
+
const parsed = await parseJSONFile(upload)
|
295
|
+
|
296
|
+
for (const id in parsed) {
|
297
|
+
var { name, description, category, mimetype, encoding, contents } = parsed[id]
|
298
|
+
if (!name || !contents || !mimetype) {
|
299
|
+
throw 'Malformed attachments import file'
|
300
|
+
}
|
301
|
+
|
302
|
+
var sameIdAttachment = await repository.findOneBy({ id })
|
303
|
+
|
304
|
+
if (sameIdAttachment) {
|
305
|
+
if (sameIdAttachment.domainId != domain.id) {
|
306
|
+
throw `Attachment with id/name(${id}/${name}) is already taken in another domain`
|
307
|
+
}
|
308
|
+
|
309
|
+
// 동일 아이디 첨부파일이 있다면, 스킵한다.
|
310
|
+
continue
|
311
|
+
}
|
312
|
+
|
313
|
+
const file = dataURLToFileUpload(contents, name, mimetype)
|
314
|
+
|
315
|
+
var { path, size, contents } = await STORAGE.uploadFile({ id, file, context })
|
316
|
+
|
317
|
+
attachments.push(
|
318
|
+
await repository.save({
|
319
|
+
domain,
|
320
|
+
creator: user,
|
321
|
+
updater: user,
|
322
|
+
id,
|
323
|
+
description,
|
324
|
+
name,
|
325
|
+
mimetype,
|
326
|
+
encoding,
|
327
|
+
category: category || mimetype.split('/').shift(),
|
328
|
+
size: size as any,
|
329
|
+
path,
|
330
|
+
contents
|
331
|
+
})
|
332
|
+
)
|
333
|
+
}
|
334
|
+
|
335
|
+
notify &&
|
336
|
+
notify({
|
337
|
+
mode: 'in-app',
|
338
|
+
title: `${attachments.length} Attachment(s) are imported`,
|
339
|
+
body: `${attachments.length} Attachment(s) are imported by ${user.name}`
|
340
|
+
})
|
341
|
+
|
342
|
+
return attachments
|
343
|
+
}
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import { In } from 'typeorm'
|
2
|
+
import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'
|
3
|
+
|
4
|
+
import { User } from '@things-factory/auth-base'
|
5
|
+
import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
|
6
|
+
|
7
|
+
import { AttachmentList } from '../'
|
8
|
+
import { Attachment } from './attachment'
|
9
|
+
|
10
|
+
@Resolver(Attachment)
|
11
|
+
export class AttachmentQuery {
|
12
|
+
@Directive('@privilege(category: "attachment", privilege: "query", domainOwnerGranted: true)')
|
13
|
+
@Query(returns => AttachmentList)
|
14
|
+
async attachments(
|
15
|
+
@Ctx() context: ResolverContext,
|
16
|
+
@Args(type => ListParam) params: ListParam
|
17
|
+
): Promise<AttachmentList> {
|
18
|
+
const { domain } = context.state
|
19
|
+
|
20
|
+
const queryBuilder = getQueryBuilderFromListParams({
|
21
|
+
repository: await getRepository(Attachment),
|
22
|
+
params,
|
23
|
+
domain,
|
24
|
+
alias: 'attachment',
|
25
|
+
searchables: ['name', 'description', 'tags']
|
26
|
+
})
|
27
|
+
|
28
|
+
const [items, total] = await queryBuilder.getManyAndCount()
|
29
|
+
|
30
|
+
return { items, total }
|
31
|
+
}
|
32
|
+
|
33
|
+
@Directive('@privilege(category: "attachment", privilege: "query", domainOwnerGranted: true)')
|
34
|
+
@Query(returns => Attachment)
|
35
|
+
async attachment(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Attachment> {
|
36
|
+
const { domain } = context.state
|
37
|
+
|
38
|
+
return await getRepository(Attachment).findOne({
|
39
|
+
select: [
|
40
|
+
'domain',
|
41
|
+
'id',
|
42
|
+
'name',
|
43
|
+
'path',
|
44
|
+
'size',
|
45
|
+
'mimetype',
|
46
|
+
'encoding',
|
47
|
+
'category',
|
48
|
+
'updatedAt',
|
49
|
+
'updater',
|
50
|
+
'createdAt',
|
51
|
+
'creator'
|
52
|
+
],
|
53
|
+
where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id },
|
54
|
+
relations: ['domain', 'creator', 'updater']
|
55
|
+
})
|
56
|
+
}
|
57
|
+
|
58
|
+
@FieldResolver(type => Domain)
|
59
|
+
async domain(@Root() attachment: Attachment) {
|
60
|
+
return await getRepository(Domain).findOneBy({
|
61
|
+
id: attachment.domainId
|
62
|
+
})
|
63
|
+
}
|
64
|
+
|
65
|
+
@FieldResolver(type => User)
|
66
|
+
async updater(@Root() attachment: Attachment): Promise<User> {
|
67
|
+
return await getRepository(User).findOneBy({
|
68
|
+
id: attachment.updaterId
|
69
|
+
})
|
70
|
+
}
|
71
|
+
|
72
|
+
@FieldResolver(type => User)
|
73
|
+
async creator(@Root() attachment: Attachment): Promise<User> {
|
74
|
+
return await getRepository(User).findOneBy({
|
75
|
+
id: attachment.creatorId
|
76
|
+
})
|
77
|
+
}
|
78
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'
|
2
|
+
import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
|
3
|
+
import { Field, InputType, Int, ObjectType } from 'type-graphql'
|
4
|
+
|
5
|
+
import { ScalarAny, ScalarObject } from '@things-factory/shell'
|
6
|
+
|
7
|
+
import { Attachment } from './attachment'
|
8
|
+
|
9
|
+
@ObjectType()
|
10
|
+
export class AttachmentList {
|
11
|
+
@Field(type => [Attachment])
|
12
|
+
items: Attachment[]
|
13
|
+
|
14
|
+
@Field(type => Int)
|
15
|
+
total: number
|
16
|
+
}
|
17
|
+
|
18
|
+
@ObjectType()
|
19
|
+
export class UploadURL {
|
20
|
+
@Field(type => String)
|
21
|
+
url: string
|
22
|
+
|
23
|
+
@Field(type => ScalarAny)
|
24
|
+
fields: any
|
25
|
+
}
|
26
|
+
|
27
|
+
@InputType()
|
28
|
+
export class NewAttachment {
|
29
|
+
@Field({ nullable: true })
|
30
|
+
category: string
|
31
|
+
|
32
|
+
@Field(type => GraphQLUpload)
|
33
|
+
file: FileUpload
|
34
|
+
|
35
|
+
@Field({ nullable: true })
|
36
|
+
description: string
|
37
|
+
|
38
|
+
@Field({ nullable: true })
|
39
|
+
refType: string
|
40
|
+
|
41
|
+
@Field({ nullable: true })
|
42
|
+
refBy: string
|
43
|
+
|
44
|
+
@Field(type => ScalarObject, { nullable: true })
|
45
|
+
tags?: string[]
|
46
|
+
}
|
47
|
+
|
48
|
+
@InputType()
|
49
|
+
export class AttachmentPatch {
|
50
|
+
@Field({ nullable: true })
|
51
|
+
name: string
|
52
|
+
|
53
|
+
@Field({ nullable: true })
|
54
|
+
description: string
|
55
|
+
|
56
|
+
@Field({ nullable: true })
|
57
|
+
mimetype: string
|
58
|
+
|
59
|
+
@Field({ nullable: true })
|
60
|
+
encoding: string
|
61
|
+
|
62
|
+
@Field({ nullable: true })
|
63
|
+
category: string
|
64
|
+
|
65
|
+
@Field(type => GraphQLUpload, { nullable: true })
|
66
|
+
file: FileUpload
|
67
|
+
|
68
|
+
@Field({ nullable: true })
|
69
|
+
refType: string
|
70
|
+
|
71
|
+
@Field({ nullable: true })
|
72
|
+
refBy: string
|
73
|
+
|
74
|
+
@Field(type => ScalarObject, { nullable: true })
|
75
|
+
tags?: string[]
|
76
|
+
}
|