@things-factory/attachment-base 8.0.38 → 9.0.0-9.0.0-beta.59.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/attachment-base",
3
- "version": "8.0.38",
3
+ "version": "9.0.0-9.0.0-beta.59.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.38",
33
- "@things-factory/env": "^8.0.37",
34
- "@things-factory/shell": "^8.0.38",
32
+ "@things-factory/auth-base": "^9.0.0-9.0.0-beta.59.0",
33
+ "@things-factory/env": "^9.0.0-9.0.0-beta.59.0",
34
+ "@things-factory/shell": "^9.0.0-9.0.0-beta.59.0",
35
35
  "mime": "^3.0.0",
36
36
  "multer": "^1.4.5-lts.1"
37
37
  },
38
- "gitHead": "613db8b1fa9fd156294f113518348247ae61a2db"
38
+ "gitHead": "cf6ee84b991f469a4e71198b0e6314b45e9e10b8"
39
39
  }
@@ -1,5 +0,0 @@
1
- import { config } from '@things-factory/env'
2
-
3
- export var STORAGE: any = config.get('storage')
4
- export var AWBSTORAGE: any = config.get('awbFileStorage')
5
- export const ATTACHMENT_PATH: string = config.get('attachmentPath', 'attachment')
@@ -1,44 +0,0 @@
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 DELETED
@@ -1,15 +0,0 @@
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 DELETED
@@ -1,35 +0,0 @@
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
- })
@@ -1,343 +0,0 @@
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
- }
@@ -1,78 +0,0 @@
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
- }
@@ -1,76 +0,0 @@
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
- }