@things-factory/attachment-base 6.1.172 → 6.1.174

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.
@@ -4,15 +4,33 @@ module.exports = {
4
4
  type: 'file',
5
5
  base: 'attachments'
6
6
  }
7
- /* storage configuration for s3-bucket
7
+ /* storage configurations
8
8
  --
9
9
  storage: {
10
- type: 's3',
10
+ type: 'file', // for filesystem backed
11
+ base: 'attachments'
12
+ }
13
+ --
14
+ storage: {
15
+ type: 'database' // for database backed
16
+ }
17
+ --
18
+ storage: {
19
+ type: 's3', // for s3-bucket
11
20
  accessKeyId: 'YOUR ACCESS KEY ID',
12
21
  secretAccessKey: 'YOUR SECRET ACCESS KEY',
13
22
  bucketName: 'YOUR S3 BUCKET NAME',
14
23
  region: 'YOUR S3 REGION'
15
24
  }
16
25
  --
26
+ storage: {
27
+ type: 'azureblob', // for azure-blob-storage
28
+ accountName: '{account-name}',
29
+ accountKey: '{account-key}',
30
+ containerName: '{container-name}',
31
+ url: 'https://{account-name}.blob.core.windows.net/',
32
+ connectionString:
33
+ 'DefaultEndpointsProtocol=https;AccountName={account-name};AccountKey={account-key};EndpointSuffix=core.windows.net'
34
+ }
17
35
  */
18
36
  }
@@ -4,15 +4,33 @@ module.exports = {
4
4
  type: 'file',
5
5
  base: 'attachments'
6
6
  }
7
- /* storage configuration for s3-bucket
7
+ /* storage configurations
8
8
  --
9
9
  storage: {
10
- type: 's3',
10
+ type: 'file', // for filesystem backed
11
+ base: 'attachments'
12
+ }
13
+ --
14
+ storage: {
15
+ type: 'database' // for database backed
16
+ }
17
+ --
18
+ storage: {
19
+ type: 's3', // for s3-bucket
11
20
  accessKeyId: 'YOUR ACCESS KEY ID',
12
21
  secretAccessKey: 'YOUR SECRET ACCESS KEY',
13
22
  bucketName: 'YOUR S3 BUCKET NAME',
14
23
  region: 'YOUR S3 REGION'
15
24
  }
16
25
  --
26
+ storage: {
27
+ type: 'azureblob', // for azure-blob-storage
28
+ accountName: '{account-name}',
29
+ accountKey: '{account-key}',
30
+ containerName: '{container-name}',
31
+ url: 'https://{account-name}.blob.core.windows.net/',
32
+ connectionString:
33
+ 'DefaultEndpointsProtocol=https;AccountName={account-name};AccountKey={account-key};EndpointSuffix=core.windows.net'
34
+ }
17
35
  */
18
36
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  require("./storage-file");
4
4
  require("./storage-s3");
5
5
  require("./storage-database");
6
+ require("./storage-azure-blob");
6
7
  const attachment_const_1 = require("./attachment-const");
7
8
  const multer = require('@koa/multer');
8
9
  const upload = multer(); // note you can pass `multer` options here
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA,0BAAuB;AACvB,wBAAqB;AACrB,8BAA2B;AAE3B,yDAA6D;AAE7D,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;AACrC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAA,CAAC,0CAA0C;AAElE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEtC,gFAAgF;AAChF,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IACxE,wBAAwB;IACxB,MAAM,CAAC,GAAG,CAAC,IAAI,kCAAe,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACpE,MAAM,0BAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IAClE,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,IAAI,CAAC,IAAI,kCAAe,EAAE,EAAE,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACvE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAE3B,MAAM,MAAM,GAAiD,MAAM,OAAO,CAAC,GAAG,CAC5E,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,0BAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CACzD,CAAA;QAED,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;QACpB,0BAA0B;QAC1B,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,yBAAyB,CAAC,CAAA;QAChE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import './storage-file'\nimport './storage-s3'\nimport './storage-database'\n\nimport { ATTACHMENT_PATH, STORAGE } from './attachment-const'\n\nconst multer = require('@koa/multer')\nconst upload = multer() // note you can pass `multer` options here\n\nconst { Readable } = require('stream')\n\n// process.on('bootstrap-module-domain-private-route' as any, (app, routes) => {\nprocess.on('bootstrap-module-global-public-route' as any, (app, routes) => {\n // TODO make this secure\n routes.get(`/${ATTACHMENT_PATH}/:attachment`, async (context, next) => {\n await STORAGE.sendFile(context, context.params.attachment, next)\n })\n\n routes.post(`/${ATTACHMENT_PATH}`, upload.any(), async (context, next) => {\n const files = context.files\n\n const result: { id: string; path: string; size: number }[] = await Promise.all(\n files.map(file => STORAGE.uploadFile({ file, context }))\n )\n\n context.status = 200\n // Support < IE 10 browser\n context.res.setHeader('Content-Type', 'text/html;charset=UTF-8')\n context.body = JSON.stringify(result)\n })\n})\n"]}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA,0BAAuB;AACvB,wBAAqB;AACrB,8BAA2B;AAC3B,gCAA6B;AAE7B,yDAA6D;AAE7D,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;AACrC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAA,CAAC,0CAA0C;AAElE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEtC,gFAAgF;AAChF,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IACxE,wBAAwB;IACxB,MAAM,CAAC,GAAG,CAAC,IAAI,kCAAe,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACpE,MAAM,0BAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IAClE,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,IAAI,CAAC,IAAI,kCAAe,EAAE,EAAE,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACvE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAE3B,MAAM,MAAM,GAAiD,MAAM,OAAO,CAAC,GAAG,CAC5E,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,0BAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CACzD,CAAA;QAED,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;QACpB,0BAA0B;QAC1B,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,yBAAyB,CAAC,CAAA;QAChE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import './storage-file'\nimport './storage-s3'\nimport './storage-database'\nimport './storage-azure-blob'\n\nimport { ATTACHMENT_PATH, STORAGE } from './attachment-const'\n\nconst multer = require('@koa/multer')\nconst upload = multer() // note you can pass `multer` options here\n\nconst { Readable } = require('stream')\n\n// process.on('bootstrap-module-domain-private-route' as any, (app, routes) => {\nprocess.on('bootstrap-module-global-public-route' as any, (app, routes) => {\n // TODO make this secure\n routes.get(`/${ATTACHMENT_PATH}/:attachment`, async (context, next) => {\n await STORAGE.sendFile(context, context.params.attachment, next)\n })\n\n routes.post(`/${ATTACHMENT_PATH}`, upload.any(), async (context, next) => {\n const files = context.files\n\n const result: { id: string; path: string; size: number }[] = await Promise.all(\n files.map(file => STORAGE.uploadFile({ file, context }))\n )\n\n context.status = 200\n // Support < IE 10 browser\n context.res.setHeader('Content-Type', 'text/html;charset=UTF-8')\n context.body = JSON.stringify(result)\n })\n})\n"]}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const storage_blob_1 = require("@azure/storage-blob");
4
+ const env_1 = require("@things-factory/env");
5
+ const attachment_const_1 = require("./attachment-const");
6
+ const crypto = require('crypto');
7
+ const mime = require('mime');
8
+ if (attachment_const_1.STORAGE && attachment_const_1.STORAGE.type == 'azureblob') {
9
+ const blobServiceClient = storage_blob_1.BlobServiceClient.fromConnectionString(attachment_const_1.STORAGE.connectionString);
10
+ /* upload file */
11
+ attachment_const_1.STORAGE.uploadFile = async ({ id, file }) => {
12
+ const { createReadStream, filename, mimetype, encoding } = await file;
13
+ const containerClient = blobServiceClient.getContainerClient(attachment_const_1.STORAGE.containerName);
14
+ id = id || crypto.randomUUID();
15
+ const ext = filename.split('.').pop();
16
+ const key = ext ? `${id}.${ext}` : id;
17
+ const blockBlobClient = containerClient.getBlockBlobClient(key);
18
+ const stream = createReadStream();
19
+ const buffer = await streamToBuffer(stream);
20
+ await blockBlobClient.upload(buffer, buffer.length, {
21
+ blobHTTPHeaders: {
22
+ blobContentType: mimetype
23
+ }
24
+ });
25
+ // await blockBlobClient.uploadStream(stream, undefined, undefined, {
26
+ // blobHTTPHeaders: {
27
+ // blobContentType: mimetype
28
+ // }
29
+ // })
30
+ const url = `${attachment_const_1.STORAGE.url}/${attachment_const_1.STORAGE.containerName}/${key}`;
31
+ return {
32
+ id,
33
+ path: key,
34
+ filename,
35
+ size: buffer.length,
36
+ mimetype,
37
+ encoding
38
+ };
39
+ };
40
+ attachment_const_1.STORAGE.deleteFile = async (path) => {
41
+ const containerClient = blobServiceClient.getContainerClient(attachment_const_1.STORAGE.containerName);
42
+ const blockBlobClient = containerClient.getBlockBlobClient(path);
43
+ await blockBlobClient.deleteIfExists();
44
+ };
45
+ /* TODO Streaming to Streaming 으로 구현하라. */
46
+ attachment_const_1.STORAGE.sendFile = async (context, attachment, next) => {
47
+ const containerClient = blobServiceClient.getContainerClient(attachment_const_1.STORAGE.containerName);
48
+ const blockBlobClient = containerClient.getBlockBlobClient(attachment);
49
+ const result = await blockBlobClient.getProperties();
50
+ const response = await blockBlobClient.download(0);
51
+ const body = response.readableStreamBody;
52
+ context.set({
53
+ 'Content-Length': result.contentLength,
54
+ 'Content-Type': mime.getType(attachment),
55
+ 'Last-Modified': result.lastModified.toUTCString(),
56
+ ETag: result.etag,
57
+ 'Cache-Control': 'public, max-age=31556926'
58
+ });
59
+ context.body = body;
60
+ };
61
+ attachment_const_1.STORAGE.readFile = async (attachment, encoding) => {
62
+ const containerClient = blobServiceClient.getContainerClient(attachment_const_1.STORAGE.containerName);
63
+ const blockBlobClient = containerClient.getBlockBlobClient(attachment);
64
+ const response = await blockBlobClient.download(0);
65
+ const body = response.readableStreamBody;
66
+ const buffer = Buffer.from(await streamToBuffer(body));
67
+ switch (encoding) {
68
+ case 'base64':
69
+ return buffer.toString('base64');
70
+ default:
71
+ return buffer;
72
+ }
73
+ };
74
+ attachment_const_1.STORAGE.generateUploadURL = async (type) => {
75
+ const expiresInMinutes = 1;
76
+ const id = crypto.randomUUID();
77
+ return {
78
+ url: `${attachment_const_1.STORAGE.url}/${attachment_const_1.STORAGE.containerName}/${id}`,
79
+ fields: {}
80
+ };
81
+ };
82
+ env_1.logger.info('Azure Blob Storage is Ready.');
83
+ }
84
+ async function streamToBuffer(stream) {
85
+ return new Promise((resolve, reject) => {
86
+ const chunks = [];
87
+ stream.on('data', chunk => chunks.push(chunk));
88
+ stream.on('end', () => resolve(Buffer.concat(chunks)));
89
+ stream.on('error', reject);
90
+ });
91
+ }
92
+ //# sourceMappingURL=storage-azure-blob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-azure-blob.js","sourceRoot":"","sources":["../server/storage-azure-blob.ts"],"names":[],"mappings":"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","sourcesContent":["import { BlobServiceClient, ContainerClient } from '@azure/storage-blob'\nimport { logger } from '@things-factory/env'\n\nimport { STORAGE } from './attachment-const'\n\nconst crypto = require('crypto')\nconst mime = require('mime')\n\nif (STORAGE && STORAGE.type == 'azureblob') {\n const blobServiceClient = BlobServiceClient.fromConnectionString(STORAGE.connectionString)\n\n /* upload file */\n STORAGE.uploadFile = async ({ id, file }) => {\n const { createReadStream, filename, mimetype, encoding } = await file\n\n const containerClient = blobServiceClient.getContainerClient(STORAGE.containerName)\n id = id || crypto.randomUUID()\n const ext = filename.split('.').pop()\n const key = ext ? `${id}.${ext}` : id\n\n const blockBlobClient = containerClient.getBlockBlobClient(key)\n const stream = createReadStream()\n const buffer = await streamToBuffer(stream)\n\n await blockBlobClient.upload(buffer, buffer.length, {\n blobHTTPHeaders: {\n blobContentType: mimetype\n }\n })\n\n // await blockBlobClient.uploadStream(stream, undefined, undefined, {\n // blobHTTPHeaders: {\n // blobContentType: mimetype\n // }\n // })\n\n const url = `${STORAGE.url}/${STORAGE.containerName}/${key}`\n return {\n id,\n path: key,\n filename,\n size: buffer.length,\n mimetype,\n encoding\n }\n }\n\n STORAGE.deleteFile = async (path: string) => {\n const containerClient = blobServiceClient.getContainerClient(STORAGE.containerName)\n const blockBlobClient = containerClient.getBlockBlobClient(path)\n await blockBlobClient.deleteIfExists()\n }\n\n /* TODO Streaming to Streaming 으로 구현하라. */\n STORAGE.sendFile = async (context, attachment, next) => {\n const containerClient = blobServiceClient.getContainerClient(STORAGE.containerName)\n const blockBlobClient = containerClient.getBlockBlobClient(attachment)\n\n const result = await blockBlobClient.getProperties()\n const response = await blockBlobClient.download(0)\n const body = response.readableStreamBody\n\n context.set({\n 'Content-Length': result.contentLength,\n 'Content-Type': mime.getType(attachment),\n 'Last-Modified': result.lastModified.toUTCString(),\n ETag: result.etag,\n 'Cache-Control': 'public, max-age=31556926'\n })\n\n context.body = body\n }\n\n STORAGE.readFile = async (attachment: string, encoding: string) => {\n const containerClient = blobServiceClient.getContainerClient(STORAGE.containerName)\n const blockBlobClient = containerClient.getBlockBlobClient(attachment)\n\n const response = await blockBlobClient.download(0)\n const body = response.readableStreamBody\n\n const buffer = Buffer.from(await streamToBuffer(body))\n\n switch (encoding) {\n case 'base64':\n return buffer.toString('base64')\n default:\n return buffer\n }\n }\n\n STORAGE.generateUploadURL = async (type: string): Promise<{ url: string; fields: { [key: string]: string } }> => {\n const expiresInMinutes = 1\n const id = crypto.randomUUID()\n\n return {\n url: `${STORAGE.url}/${STORAGE.containerName}/${id}`,\n fields: {}\n }\n }\n\n logger.info('Azure Blob Storage is Ready.')\n}\n\nasync function streamToBuffer(stream): Promise<Buffer> {\n return new Promise<Buffer>((resolve, reject) => {\n const chunks = []\n stream.on('data', chunk => chunks.push(chunk))\n stream.on('end', () => resolve(Buffer.concat(chunks)))\n stream.on('error', reject)\n })\n}\n"]}