sonamu 0.7.18 → 0.7.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/dist/api/config.d.ts +19 -2
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/context.d.ts +3 -3
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +1 -1
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +4 -8
- package/dist/api/index.d.ts +0 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -3
- package/dist/api/sonamu.d.ts +5 -3
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +10 -8
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/storage/drivers.d.ts +14 -0
- package/dist/storage/drivers.d.ts.map +1 -0
- package/dist/storage/drivers.js +11 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/storage-manager.d.ts +21 -0
- package/dist/storage/storage-manager.d.ts.map +1 -0
- package/dist/storage/storage-manager.js +33 -0
- package/dist/storage/types.d.ts +12 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +5 -0
- package/dist/storage/uploaded-file.d.ts +35 -0
- package/dist/storage/uploaded-file.d.ts.map +1 -0
- package/dist/storage/uploaded-file.js +58 -0
- package/dist/template/implementations/services.template.js +5 -5
- package/package.json +7 -2
- package/src/api/config.ts +19 -2
- package/src/api/context.ts +3 -3
- package/src/api/decorators.ts +3 -8
- package/src/api/index.ts +0 -2
- package/src/api/sonamu.ts +12 -9
- package/src/index.ts +0 -1
- package/src/storage/drivers.ts +15 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/storage-manager.ts +39 -0
- package/src/storage/types.ts +12 -0
- package/src/storage/uploaded-file.ts +81 -0
- package/src/template/implementations/service.template.ts.txt +328 -0
- package/src/template/implementations/services.template.ts +4 -4
- package/dist/file-storage/driver.d.ts +0 -48
- package/dist/file-storage/driver.d.ts.map +0 -1
- package/dist/file-storage/driver.js +0 -79
- package/dist/file-storage/file-storage.d.ts +0 -50
- package/dist/file-storage/file-storage.d.ts.map +0 -1
- package/dist/file-storage/file-storage.js +0 -75
- package/src/file-storage/driver.ts +0 -131
- package/src/file-storage/file-storage.ts +0 -100
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import inflection from "inflection";
|
|
3
|
+
import { diff, group, sort, unique } from "radashi";
|
|
4
|
+
import {
|
|
5
|
+
apiParamToTsCode,
|
|
6
|
+
apiParamToTsCodeAsObject,
|
|
7
|
+
apiParamTypeToTsType,
|
|
8
|
+
unwrapPromiseOnce,
|
|
9
|
+
} from "../../api/code-converters";
|
|
10
|
+
import type { ExtendedApi } from "../../api/decorators";
|
|
11
|
+
import { Sonamu } from "../../api/sonamu";
|
|
12
|
+
import type { EntityNamesRecord } from "../../entity/entity-manager";
|
|
13
|
+
import { Naite } from "../../naite/naite";
|
|
14
|
+
import type { TemplateOptions } from "../../types/types";
|
|
15
|
+
import { type ApiParam, ApiParamType } from "../../types/types";
|
|
16
|
+
import { assertDefined } from "../../utils/utils";
|
|
17
|
+
import { Template } from "../template";
|
|
18
|
+
import { zodTypeToTsTypeDef } from "../zod-converter";
|
|
19
|
+
|
|
20
|
+
export class Template__service extends Template {
|
|
21
|
+
constructor() {
|
|
22
|
+
super("service");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getTargetAndPath(names: EntityNamesRecord) {
|
|
26
|
+
return {
|
|
27
|
+
target: ":target/src/services",
|
|
28
|
+
path: `${names.fs}/${names.fs}.service.ts`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
render({ namesRecord }: TemplateOptions["service"]) {
|
|
33
|
+
Naite.t("render", { namesRecord });
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
syncer: { apis },
|
|
37
|
+
} = Sonamu;
|
|
38
|
+
|
|
39
|
+
const apisForThisModel = apis.filter(
|
|
40
|
+
(api) =>
|
|
41
|
+
api.modelName === `${namesRecord.capital}Model` ||
|
|
42
|
+
api.modelName === `${namesRecord.capital}Frame`,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// 서비스 TypeSource
|
|
46
|
+
const { lines, importKeys } = this.getTypeSource(apisForThisModel);
|
|
47
|
+
|
|
48
|
+
// AxiosProgressEvent 있는지 확인
|
|
49
|
+
const hasAxiosProgressEvent = apis.find((api) =>
|
|
50
|
+
(api.options.clients ?? []).includes("axios-multipart"),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
...this.getTargetAndPath(namesRecord),
|
|
55
|
+
body: lines.join("\n"),
|
|
56
|
+
importKeys: importKeys.filter((key) => ["ListResult"].includes(key) === false),
|
|
57
|
+
customHeaders: [
|
|
58
|
+
`import { z } from 'zod';`,
|
|
59
|
+
`import qs from "qs";`,
|
|
60
|
+
`import useSWR, { type SWRResponse } from "swr";`,
|
|
61
|
+
`import { fetch, ListResult, SWRError, SwrOptions, handleConditional, swrPostFetcher, EventHandlers, SSEStreamOptions, useSSEStream } from '../sonamu.shared';`,
|
|
62
|
+
...(hasAxiosProgressEvent ? [`import { type AxiosProgressEvent } from 'axios';`] : []),
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getTypeSource(apis: ExtendedApi[]): {
|
|
68
|
+
lines: string[];
|
|
69
|
+
importKeys: string[];
|
|
70
|
+
} {
|
|
71
|
+
const importKeys: string[] = [];
|
|
72
|
+
|
|
73
|
+
// 제네릭에서 선언한 타입, importKeys에서 제외 필요
|
|
74
|
+
let typeParamNames: string[] = [];
|
|
75
|
+
|
|
76
|
+
const groups = group(apis, (api) => api.modelName);
|
|
77
|
+
const body = Object.keys(groups)
|
|
78
|
+
.map((modelName) => {
|
|
79
|
+
const methods = groups[modelName];
|
|
80
|
+
assert(methods);
|
|
81
|
+
const methodCodes = methods
|
|
82
|
+
.map((api) => {
|
|
83
|
+
// 컨텍스트 제외된 파라미터 리스트
|
|
84
|
+
const paramsWithoutContext = api.parameters.filter(
|
|
85
|
+
(param) =>
|
|
86
|
+
!ApiParamType.isContext(param.type) &&
|
|
87
|
+
!ApiParamType.isRefKnex(param.type) &&
|
|
88
|
+
!(param.optional === true && param.name.startsWith("_")), // _로 시작하는 파라미터는 제외
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// 파라미터 타입 정의
|
|
92
|
+
const typeParametersAsTsType = api.typeParameters
|
|
93
|
+
.map((typeParam) => {
|
|
94
|
+
return apiParamTypeToTsType(typeParam, importKeys);
|
|
95
|
+
})
|
|
96
|
+
.join(", ");
|
|
97
|
+
const typeParamsDef = typeParametersAsTsType ? `<${typeParametersAsTsType}>` : "";
|
|
98
|
+
typeParamNames = typeParamNames.concat(
|
|
99
|
+
api.typeParameters.map((typeParam) => typeParam.id),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// 파라미터 정의
|
|
103
|
+
const paramsDef = apiParamToTsCode(paramsWithoutContext, importKeys);
|
|
104
|
+
|
|
105
|
+
// 파라미터 정의 (객체 형태)
|
|
106
|
+
const paramsDefAsObject = apiParamToTsCodeAsObject(paramsWithoutContext, importKeys);
|
|
107
|
+
|
|
108
|
+
// 리턴 타입 정의
|
|
109
|
+
const returnTypeDef = apiParamTypeToTsType(
|
|
110
|
+
assertDefined(unwrapPromiseOnce(api.returnType)),
|
|
111
|
+
importKeys,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// 페이로드 데이터 정의
|
|
115
|
+
const payloadDef = `{ ${paramsWithoutContext.map((param) => param.name).join(", ")} }`;
|
|
116
|
+
|
|
117
|
+
// 기본 URL
|
|
118
|
+
const apiBaseUrl = `${Sonamu.config.api.route.prefix}${api.path}`;
|
|
119
|
+
|
|
120
|
+
const clients = api.options.clients ?? [];
|
|
121
|
+
return [
|
|
122
|
+
// 클라이언트별로 생성
|
|
123
|
+
...sort(clients, (client) => (client === "swr" ? 0 : 1)).map((client) => {
|
|
124
|
+
switch (client) {
|
|
125
|
+
case "axios":
|
|
126
|
+
return this.renderAxios(
|
|
127
|
+
api,
|
|
128
|
+
apiBaseUrl,
|
|
129
|
+
typeParamsDef,
|
|
130
|
+
paramsDef,
|
|
131
|
+
returnTypeDef,
|
|
132
|
+
payloadDef,
|
|
133
|
+
);
|
|
134
|
+
case "axios-multipart":
|
|
135
|
+
return this.renderAxiosMultipart(
|
|
136
|
+
api,
|
|
137
|
+
apiBaseUrl,
|
|
138
|
+
typeParamsDef,
|
|
139
|
+
paramsDef,
|
|
140
|
+
returnTypeDef,
|
|
141
|
+
paramsWithoutContext,
|
|
142
|
+
);
|
|
143
|
+
case "swr":
|
|
144
|
+
return this.renderSwr(
|
|
145
|
+
api,
|
|
146
|
+
apiBaseUrl,
|
|
147
|
+
typeParamsDef,
|
|
148
|
+
paramsDef,
|
|
149
|
+
returnTypeDef,
|
|
150
|
+
payloadDef,
|
|
151
|
+
);
|
|
152
|
+
case "window-fetch":
|
|
153
|
+
return this.renderWindowFetch(
|
|
154
|
+
api,
|
|
155
|
+
apiBaseUrl,
|
|
156
|
+
typeParamsDef,
|
|
157
|
+
paramsDef,
|
|
158
|
+
payloadDef,
|
|
159
|
+
);
|
|
160
|
+
default:
|
|
161
|
+
return `// Not supported ${inflection.camelize(client, true)} yet.`;
|
|
162
|
+
}
|
|
163
|
+
}),
|
|
164
|
+
// 스트리밍인 경우
|
|
165
|
+
...(api.streamOptions ? [this.renderStream(api, apiBaseUrl, paramsDefAsObject)] : []),
|
|
166
|
+
].join("\n");
|
|
167
|
+
})
|
|
168
|
+
.join("\n\n");
|
|
169
|
+
|
|
170
|
+
return `export namespace ${modelName.replace(/Model$/, "Service").replace(/Frame$/, "Service")} {
|
|
171
|
+
${methodCodes}
|
|
172
|
+
}`;
|
|
173
|
+
})
|
|
174
|
+
.join("\n\n");
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
lines: [body],
|
|
178
|
+
importKeys: diff(unique(importKeys), typeParamNames),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
renderAxios(
|
|
183
|
+
api: ExtendedApi,
|
|
184
|
+
apiBaseUrl: string,
|
|
185
|
+
typeParamsDef: string,
|
|
186
|
+
paramsDef: string,
|
|
187
|
+
returnTypeDef: string,
|
|
188
|
+
payloadDef: string,
|
|
189
|
+
) {
|
|
190
|
+
const methodNameAxios = api.options.resourceName
|
|
191
|
+
? `get${inflection.camelize(api.options.resourceName)}`
|
|
192
|
+
: api.methodName;
|
|
193
|
+
|
|
194
|
+
if (api.options.httpMethod === "GET") {
|
|
195
|
+
return `
|
|
196
|
+
export async function ${methodNameAxios}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
|
|
197
|
+
return fetch({
|
|
198
|
+
method: "GET",
|
|
199
|
+
url: \`${apiBaseUrl}?\${qs.stringify(${payloadDef})}\`,
|
|
200
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
`.trim();
|
|
204
|
+
} else {
|
|
205
|
+
return `
|
|
206
|
+
export async function ${methodNameAxios}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
|
|
207
|
+
return fetch({
|
|
208
|
+
method: '${api.options.httpMethod}',
|
|
209
|
+
url: \`${apiBaseUrl}\`,
|
|
210
|
+
data: ${payloadDef},
|
|
211
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
`.trim();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
renderAxiosMultipart(
|
|
219
|
+
api: ExtendedApi,
|
|
220
|
+
apiBaseUrl: string,
|
|
221
|
+
typeParamsDef: string,
|
|
222
|
+
paramsDef: string,
|
|
223
|
+
returnTypeDef: string,
|
|
224
|
+
paramsWithoutContext: ApiParam[],
|
|
225
|
+
) {
|
|
226
|
+
const isMultiple = api.uploadOptions?.mode === "multiple";
|
|
227
|
+
const fileParamName = isMultiple ? "files" : "file";
|
|
228
|
+
const fileParamType = isMultiple ? "File[]" : "File";
|
|
229
|
+
|
|
230
|
+
const formDataDef = isMultiple
|
|
231
|
+
? [
|
|
232
|
+
`${fileParamName}.forEach(f => { formData.append("${fileParamName}", f) } ); `,
|
|
233
|
+
...paramsWithoutContext.map(
|
|
234
|
+
(param) => `formData.append('${param.name}', String(${param.name}));`,
|
|
235
|
+
),
|
|
236
|
+
].join("\n")
|
|
237
|
+
: [
|
|
238
|
+
`formData.append("${fileParamName}", ${fileParamName});`,
|
|
239
|
+
...paramsWithoutContext.map(
|
|
240
|
+
(param) => `formData.append('${param.name}', String(${param.name}));`,
|
|
241
|
+
),
|
|
242
|
+
].join("\n");
|
|
243
|
+
|
|
244
|
+
const paramsDefComma = paramsDef !== "" ? ", " : "";
|
|
245
|
+
return `
|
|
246
|
+
export async function ${api.methodName}${typeParamsDef}(
|
|
247
|
+
${paramsDef}${paramsDefComma}
|
|
248
|
+
${fileParamName}: ${fileParamType},
|
|
249
|
+
onUploadProgress?: (pe:AxiosProgressEvent) => void
|
|
250
|
+
): Promise<${returnTypeDef}> {
|
|
251
|
+
const formData = new FormData();
|
|
252
|
+
${formDataDef}
|
|
253
|
+
return fetch({
|
|
254
|
+
method: 'POST',
|
|
255
|
+
url: \`${apiBaseUrl}\`,
|
|
256
|
+
headers: {
|
|
257
|
+
"Content-Type": "multipart/form-data",
|
|
258
|
+
},
|
|
259
|
+
onUploadProgress,
|
|
260
|
+
data: formData,
|
|
261
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
`.trim();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
renderSwr(
|
|
268
|
+
api: ExtendedApi,
|
|
269
|
+
apiBaseUrl: string,
|
|
270
|
+
typeParamsDef: string,
|
|
271
|
+
paramsDef: string,
|
|
272
|
+
returnTypeDef: string,
|
|
273
|
+
payloadDef: string,
|
|
274
|
+
) {
|
|
275
|
+
const methodNameSwr = api.options.resourceName
|
|
276
|
+
? `use${inflection.camelize(api.options.resourceName)}`
|
|
277
|
+
: `use${inflection.camelize(api.methodName)}`;
|
|
278
|
+
return ` export function ${inflection.camelize(methodNameSwr, true)}${typeParamsDef}(${[
|
|
279
|
+
paramsDef,
|
|
280
|
+
"swrOptions?: SwrOptions",
|
|
281
|
+
]
|
|
282
|
+
.filter((p) => p !== "")
|
|
283
|
+
.join(",")}, ): SWRResponse<${returnTypeDef}, SWRError> {
|
|
284
|
+
return useSWR(handleConditional([
|
|
285
|
+
\`${apiBaseUrl}\`,
|
|
286
|
+
${payloadDef},
|
|
287
|
+
], swrOptions?.conditional)${api.options.httpMethod === "POST" ? ", swrPostFetcher" : ""}${
|
|
288
|
+
api.options.timeout ? `, { loadingTimeout: ${api.options.timeout} }` : ""
|
|
289
|
+
});
|
|
290
|
+
}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
renderWindowFetch(
|
|
294
|
+
api: ExtendedApi,
|
|
295
|
+
apiBaseUrl: string,
|
|
296
|
+
typeParamsDef: string,
|
|
297
|
+
paramsDef: string,
|
|
298
|
+
payloadDef: string,
|
|
299
|
+
) {
|
|
300
|
+
return `
|
|
301
|
+
export async function ${api.methodName}${typeParamsDef}(${paramsDef}): Promise<Response> {
|
|
302
|
+
return window.fetch(\`${apiBaseUrl}?\${qs.stringify(${payloadDef})}\`${
|
|
303
|
+
api.options.timeout ? `, { signal: AbortSignal.timeout(${api.options.timeout}) }` : ""
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
`.trim();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
renderStream(api: ExtendedApi, apiBaseUrl: string, paramsDefAsObject: string) {
|
|
310
|
+
if (!api.streamOptions) {
|
|
311
|
+
return "// streamOptions not found";
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const methodNameStream = api.options.resourceName
|
|
315
|
+
? `use${inflection.camelize(api.options.resourceName)}`
|
|
316
|
+
: `use${inflection.camelize(api.methodName)}`;
|
|
317
|
+
const methodNameStreamCamelized = inflection.camelize(methodNameStream, true);
|
|
318
|
+
|
|
319
|
+
const eventsTypeDef = zodTypeToTsTypeDef(api.streamOptions.events);
|
|
320
|
+
|
|
321
|
+
return ` export function ${methodNameStreamCamelized}(
|
|
322
|
+
params: ${paramsDefAsObject},
|
|
323
|
+
handlers: EventHandlers<${eventsTypeDef} & { end?: () => void }>,
|
|
324
|
+
options: SSEStreamOptions) {
|
|
325
|
+
return useSSEStream<${eventsTypeDef}>(\`${apiBaseUrl}\`, params, handlers, options);
|
|
326
|
+
}`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -55,7 +55,6 @@ export class Template__services extends Template {
|
|
|
55
55
|
!(param.optional === true && param.name.startsWith("_")),
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
-
const paramsDef = apiParamToTsCode(paramsWithoutContext, importKeys);
|
|
59
58
|
const apiBaseUrl = `${Sonamu.config.api.route.prefix}${api.path}`;
|
|
60
59
|
|
|
61
60
|
const methodNameStream = api.options.resourceName
|
|
@@ -65,19 +64,20 @@ export class Template__services extends Template {
|
|
|
65
64
|
|
|
66
65
|
const eventsTypeDef = zodTypeToTsTypeDef(api.streamOptions.events);
|
|
67
66
|
|
|
67
|
+
// 파라미터를 객체 형태로 정의 (타입과 실제 값 모두에 사용)
|
|
68
68
|
const paramsDefAsObject =
|
|
69
69
|
paramsWithoutContext.length > 0
|
|
70
|
-
? `{ ${paramsWithoutContext.map((p) => p.name).join(", ")} }`
|
|
70
|
+
? `{ ${paramsWithoutContext.map((p) => `${p.name}: ${apiParamTypeToTsType(p.type, importKeys)}`).join(", ")} }`
|
|
71
71
|
: "{}";
|
|
72
72
|
|
|
73
73
|
functions.push(
|
|
74
74
|
`
|
|
75
75
|
export function ${methodNameStreamCamelized}(
|
|
76
|
-
params: ${
|
|
76
|
+
params: ${paramsDefAsObject},
|
|
77
77
|
handlers: EventHandlers<${eventsTypeDef} & { end?: () => void }>,
|
|
78
78
|
options: SSEStreamOptions
|
|
79
79
|
) {
|
|
80
|
-
return useSSEStream<${eventsTypeDef}>(\`${apiBaseUrl}\`,
|
|
80
|
+
return useSSEStream<${eventsTypeDef}>(\`${apiBaseUrl}\`, params, handlers, options);
|
|
81
81
|
}
|
|
82
82
|
`.trim(),
|
|
83
83
|
);
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { S3Client, type S3ClientConfig } from "@aws-sdk/client-s3";
|
|
2
|
-
/**
|
|
3
|
-
* 파일 저장소의 공통 인터페이스
|
|
4
|
-
*/
|
|
5
|
-
export interface Driver {
|
|
6
|
-
put(key: string, contents: Buffer, options?: {
|
|
7
|
-
contentType?: string;
|
|
8
|
-
visibility?: "public" | "private";
|
|
9
|
-
}): Promise<void>;
|
|
10
|
-
del(key: string): Promise<void>;
|
|
11
|
-
getUrl(key: string): string;
|
|
12
|
-
getSignedUrl(key: string, expiresIn?: number): Promise<string>;
|
|
13
|
-
destroy(): void;
|
|
14
|
-
}
|
|
15
|
-
export type FSDriverConfig = {
|
|
16
|
-
location: string;
|
|
17
|
-
urlPrefix: string;
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* 로컬 파일시스템
|
|
21
|
-
*/
|
|
22
|
-
export declare class FSDriver implements Driver {
|
|
23
|
-
private config;
|
|
24
|
-
constructor(config: FSDriverConfig);
|
|
25
|
-
put(key: string, contents: Buffer): Promise<void>;
|
|
26
|
-
del(key: string): Promise<void>;
|
|
27
|
-
getUrl(key: string): string;
|
|
28
|
-
getSignedUrl(key: string, _expiresIn?: number): Promise<string>;
|
|
29
|
-
destroy(): void;
|
|
30
|
-
}
|
|
31
|
-
export type S3DriverConfig = S3ClientConfig & {
|
|
32
|
-
bucket: string;
|
|
33
|
-
};
|
|
34
|
-
export declare class S3Driver implements Driver {
|
|
35
|
-
private config;
|
|
36
|
-
s3: S3Client;
|
|
37
|
-
constructor(config: S3DriverConfig);
|
|
38
|
-
put(key: string, contents: Buffer, options?: {
|
|
39
|
-
contentType?: string;
|
|
40
|
-
visibility?: "public" | "private";
|
|
41
|
-
}): Promise<void>;
|
|
42
|
-
del(key: string): Promise<void>;
|
|
43
|
-
getUrl(key: string): string;
|
|
44
|
-
getSignedUrl(key: string, expiresIn?: number): Promise<string>;
|
|
45
|
-
private getAcl;
|
|
46
|
-
destroy(): void;
|
|
47
|
-
}
|
|
48
|
-
//# sourceMappingURL=driver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/file-storage/driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,QAAQ,EACR,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAK5B;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,CACD,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;KAAE,GACpE,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5B,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/D,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,qBAAa,QAAS,YAAW,MAAM;IACzB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAEpC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASjD,GAAG,CAAC,GAAG,EAAE,MAAM;IAIrB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAKrB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIrE,OAAO;CAGR;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG;IAC5C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,qBAAa,QAAS,YAAW,MAAM;IAGzB,OAAO,CAAC,MAAM;IAF1B,EAAE,EAAE,QAAQ,CAAC;gBAEO,MAAM,EAAE,cAAc;IAIpC,GAAG,CACP,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;KAAE,GACpE,OAAO,CAAC,IAAI,CAAC;IAYV,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASrC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAIrB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWpE,OAAO,CAAC,MAAM;IAQd,OAAO;CAGR"}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
2
|
-
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import path from "path";
|
|
5
|
-
/**
|
|
6
|
-
* 로컬 파일시스템
|
|
7
|
-
*/ export class FSDriver {
|
|
8
|
-
config;
|
|
9
|
-
constructor(config){
|
|
10
|
-
this.config = config;
|
|
11
|
-
}
|
|
12
|
-
async put(key, contents) {
|
|
13
|
-
const filePath = path.join(this.config.location, key);
|
|
14
|
-
const dir = path.dirname(filePath);
|
|
15
|
-
await fs.mkdir(dir, {
|
|
16
|
-
recursive: true
|
|
17
|
-
});
|
|
18
|
-
await fs.writeFile(filePath, contents);
|
|
19
|
-
}
|
|
20
|
-
async del(key) {
|
|
21
|
-
await fs.rm(path.join(this.config.location, key));
|
|
22
|
-
}
|
|
23
|
-
getUrl(key) {
|
|
24
|
-
return `${this.config.urlPrefix}/${key}`;
|
|
25
|
-
}
|
|
26
|
-
// 로컬 파일시스템은 signed URL을 지원하지 않으므로 일반 URL 반환
|
|
27
|
-
async getSignedUrl(key, _expiresIn) {
|
|
28
|
-
return this.getUrl(key);
|
|
29
|
-
}
|
|
30
|
-
destroy() {
|
|
31
|
-
// 아무것도 하지 않음
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export class S3Driver {
|
|
35
|
-
config;
|
|
36
|
-
s3;
|
|
37
|
-
constructor(config){
|
|
38
|
-
this.config = config;
|
|
39
|
-
this.s3 = new S3Client(config);
|
|
40
|
-
}
|
|
41
|
-
async put(key, contents, options) {
|
|
42
|
-
await this.s3.send(new PutObjectCommand({
|
|
43
|
-
Bucket: this.config.bucket,
|
|
44
|
-
Key: key,
|
|
45
|
-
Body: contents,
|
|
46
|
-
ContentType: options?.contentType,
|
|
47
|
-
ACL: this.getAcl(options?.visibility)
|
|
48
|
-
}));
|
|
49
|
-
}
|
|
50
|
-
async del(key) {
|
|
51
|
-
await this.s3.send(new DeleteObjectCommand({
|
|
52
|
-
Bucket: this.config.bucket,
|
|
53
|
-
Key: key
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
getUrl(key) {
|
|
57
|
-
return `https://${this.config.bucket}.s3.${this.config.region}.amazonaws.com/${key}`;
|
|
58
|
-
}
|
|
59
|
-
async getSignedUrl(key, expiresIn) {
|
|
60
|
-
const command = new GetObjectCommand({
|
|
61
|
-
Bucket: this.config.bucket,
|
|
62
|
-
Key: key
|
|
63
|
-
});
|
|
64
|
-
return getSignedUrl(this.s3, command, {
|
|
65
|
-
expiresIn: expiresIn ?? 60 * 60 * 24 * 7
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
getAcl(visibility) {
|
|
69
|
-
if (visibility === "public") {
|
|
70
|
-
return "public-read";
|
|
71
|
-
}
|
|
72
|
-
return visibility;
|
|
73
|
-
}
|
|
74
|
-
destroy() {
|
|
75
|
-
this.s3.destroy();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9maWxlLXN0b3JhZ2UvZHJpdmVyLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIERlbGV0ZU9iamVjdENvbW1hbmQsXG4gIEdldE9iamVjdENvbW1hbmQsXG4gIFB1dE9iamVjdENvbW1hbmQsXG4gIFMzQ2xpZW50LFxuICB0eXBlIFMzQ2xpZW50Q29uZmlnLFxufSBmcm9tIFwiQGF3cy1zZGsvY2xpZW50LXMzXCI7XG5pbXBvcnQgeyBnZXRTaWduZWRVcmwgfSBmcm9tIFwiQGF3cy1zZGsvczMtcmVxdWVzdC1wcmVzaWduZXJcIjtcbmltcG9ydCBmcyBmcm9tIFwiZnMvcHJvbWlzZXNcIjtcbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XG5cbi8qKlxuICog7YyM7J28IOyggOyepeyGjOydmCDqs7XthrUg7J247YSw7Y6Y7J207IqkXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRHJpdmVyIHtcbiAgcHV0KFxuICAgIGtleTogc3RyaW5nLFxuICAgIGNvbnRlbnRzOiBCdWZmZXIsXG4gICAgb3B0aW9ucz86IHsgY29udGVudFR5cGU/OiBzdHJpbmc7IHZpc2liaWxpdHk/OiBcInB1YmxpY1wiIHwgXCJwcml2YXRlXCIgfSxcbiAgKTogUHJvbWlzZTx2b2lkPjtcblxuICBkZWwoa2V5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+O1xuXG4gIGdldFVybChrZXk6IHN0cmluZyk6IHN0cmluZztcblxuICBnZXRTaWduZWRVcmwoa2V5OiBzdHJpbmcsIGV4cGlyZXNJbj86IG51bWJlcik6IFByb21pc2U8c3RyaW5nPjtcblxuICBkZXN0cm95KCk6IHZvaWQ7XG59XG5cbmV4cG9ydCB0eXBlIEZTRHJpdmVyQ29uZmlnID0ge1xuICBsb2NhdGlvbjogc3RyaW5nO1xuICB1cmxQcmVmaXg6IHN0cmluZztcbn07XG5cbi8qKlxuICog66Gc7LusIO2MjOydvOyLnOyKpO2FnFxuICovXG5leHBvcnQgY2xhc3MgRlNEcml2ZXIgaW1wbGVtZW50cyBEcml2ZXIge1xuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGNvbmZpZzogRlNEcml2ZXJDb25maWcpIHt9XG5cbiAgYXN5bmMgcHV0KGtleTogc3RyaW5nLCBjb250ZW50czogQnVmZmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgZmlsZVBhdGggPSBwYXRoLmpvaW4odGhpcy5jb25maWcubG9jYXRpb24sIGtleSk7XG4gICAgY29uc3QgZGlyID0gcGF0aC5kaXJuYW1lKGZpbGVQYXRoKTtcblxuICAgIGF3YWl0IGZzLm1rZGlyKGRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG5cbiAgICBhd2FpdCBmcy53cml0ZUZpbGUoZmlsZVBhdGgsIGNvbnRlbnRzKTtcbiAgfVxuXG4gIGFzeW5jIGRlbChrZXk6IHN0cmluZykge1xuICAgIGF3YWl0IGZzLnJtKHBhdGguam9pbih0aGlzLmNvbmZpZy5sb2NhdGlvbiwga2V5KSk7XG4gIH1cblxuICBnZXRVcmwoa2V5OiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiBgJHt0aGlzLmNvbmZpZy51cmxQcmVmaXh9LyR7a2V5fWA7XG4gIH1cblxuICAvLyDroZzsu6wg7YyM7J287Iuc7Iqk7YWc7J2AIHNpZ25lZCBVUkzsnYQg7KeA7JuQ7ZWY7KeAIOyViuycvOuvgOuhnCDsnbzrsJggVVJMIOuwmO2ZmFxuICBhc3luYyBnZXRTaWduZWRVcmwoa2V5OiBzdHJpbmcsIF9leHBpcmVzSW4/OiBudW1iZXIpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIHJldHVybiB0aGlzLmdldFVybChrZXkpO1xuICB9XG5cbiAgZGVzdHJveSgpIHtcbiAgICAvLyDslYTrrLTqsoPrj4Qg7ZWY7KeAIOyViuydjFxuICB9XG59XG5cbmV4cG9ydCB0eXBlIFMzRHJpdmVyQ29uZmlnID0gUzNDbGllbnRDb25maWcgJiB7XG4gIGJ1Y2tldDogc3RyaW5nO1xufTtcblxuZXhwb3J0IGNsYXNzIFMzRHJpdmVyIGltcGxlbWVudHMgRHJpdmVyIHtcbiAgczM6IFMzQ2xpZW50O1xuXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgY29uZmlnOiBTM0RyaXZlckNvbmZpZykge1xuICAgIHRoaXMuczMgPSBuZXcgUzNDbGllbnQoY29uZmlnKTtcbiAgfVxuXG4gIGFzeW5jIHB1dChcbiAgICBrZXk6IHN0cmluZyxcbiAgICBjb250ZW50czogQnVmZmVyLFxuICAgIG9wdGlvbnM/OiB7IGNvbnRlbnRUeXBlPzogc3RyaW5nOyB2aXNpYmlsaXR5PzogXCJwdWJsaWNcIiB8IFwicHJpdmF0ZVwiIH0sXG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IHRoaXMuczMuc2VuZChcbiAgICAgIG5ldyBQdXRPYmplY3RDb21tYW5kKHtcbiAgICAgICAgQnVja2V0OiB0aGlzLmNvbmZpZy5idWNrZXQsXG4gICAgICAgIEtleToga2V5LFxuICAgICAgICBCb2R5OiBjb250ZW50cyxcbiAgICAgICAgQ29udGVudFR5cGU6IG9wdGlvbnM/LmNvbnRlbnRUeXBlLFxuICAgICAgICBBQ0w6IHRoaXMuZ2V0QWNsKG9wdGlvbnM/LnZpc2liaWxpdHkpLFxuICAgICAgfSksXG4gICAgKTtcbiAgfVxuXG4gIGFzeW5jIGRlbChrZXk6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IHRoaXMuczMuc2VuZChcbiAgICAgIG5ldyBEZWxldGVPYmplY3RDb21tYW5kKHtcbiAgICAgICAgQnVja2V0OiB0aGlzLmNvbmZpZy5idWNrZXQsXG4gICAgICAgIEtleToga2V5LFxuICAgICAgfSksXG4gICAgKTtcbiAgfVxuXG4gIGdldFVybChrZXk6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGBodHRwczovLyR7dGhpcy5jb25maWcuYnVja2V0fS5zMy4ke3RoaXMuY29uZmlnLnJlZ2lvbn0uYW1hem9uYXdzLmNvbS8ke2tleX1gO1xuICB9XG5cbiAgYXN5bmMgZ2V0U2lnbmVkVXJsKGtleTogc3RyaW5nLCBleHBpcmVzSW4/OiBudW1iZXIpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IGNvbW1hbmQgPSBuZXcgR2V0T2JqZWN0Q29tbWFuZCh7XG4gICAgICBCdWNrZXQ6IHRoaXMuY29uZmlnLmJ1Y2tldCxcbiAgICAgIEtleToga2V5LFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIGdldFNpZ25lZFVybCh0aGlzLnMzLCBjb21tYW5kLCB7XG4gICAgICBleHBpcmVzSW46IGV4cGlyZXNJbiA/PyA2MCAqIDYwICogMjQgKiA3LFxuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXRBY2wodmlzaWJpbGl0eT86IFwicHVibGljXCIgfCBcInByaXZhdGVcIikge1xuICAgIGlmICh2aXNpYmlsaXR5ID09PSBcInB1YmxpY1wiKSB7XG4gICAgICByZXR1cm4gXCJwdWJsaWMtcmVhZFwiO1xuICAgIH1cblxuICAgIHJldHVybiB2aXNpYmlsaXR5O1xuICB9XG5cbiAgZGVzdHJveSgpIHtcbiAgICB0aGlzLnMzLmRlc3Ryb3koKTtcbiAgfVxufVxuIl0sIm5hbWVzIjpbIkRlbGV0ZU9iamVjdENvbW1hbmQiLCJHZXRPYmplY3RDb21tYW5kIiwiUHV0T2JqZWN0Q29tbWFuZCIsIlMzQ2xpZW50IiwiZ2V0U2lnbmVkVXJsIiwiZnMiLCJwYXRoIiwiRlNEcml2ZXIiLCJjb25maWciLCJwdXQiLCJrZXkiLCJjb250ZW50cyIsImZpbGVQYXRoIiwiam9pbiIsImxvY2F0aW9uIiwiZGlyIiwiZGlybmFtZSIsIm1rZGlyIiwicmVjdXJzaXZlIiwid3JpdGVGaWxlIiwiZGVsIiwicm0iLCJnZXRVcmwiLCJ1cmxQcmVmaXgiLCJfZXhwaXJlc0luIiwiZGVzdHJveSIsIlMzRHJpdmVyIiwiczMiLCJvcHRpb25zIiwic2VuZCIsIkJ1Y2tldCIsImJ1Y2tldCIsIktleSIsIkJvZHkiLCJDb250ZW50VHlwZSIsImNvbnRlbnRUeXBlIiwiQUNMIiwiZ2V0QWNsIiwidmlzaWJpbGl0eSIsInJlZ2lvbiIsImV4cGlyZXNJbiIsImNvbW1hbmQiXSwibWFwcGluZ3MiOiJBQUFBLFNBQ0VBLG1CQUFtQixF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
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { MultipartFile } from "@fastify/multipart";
|
|
2
|
-
import type { Driver } from "./driver";
|
|
3
|
-
/**
|
|
4
|
-
* @fastify/multipart의 MultipartFile 래퍼
|
|
5
|
-
*/
|
|
6
|
-
export declare class FileStorage {
|
|
7
|
-
private _file;
|
|
8
|
-
private _buffer?;
|
|
9
|
-
private _driver;
|
|
10
|
-
constructor(file: MultipartFile, driver: Driver);
|
|
11
|
-
/**
|
|
12
|
-
* 사용자 컴퓨터의 원본 파일명
|
|
13
|
-
*/
|
|
14
|
-
get clientName(): string;
|
|
15
|
-
/**
|
|
16
|
-
* 파일명 (clientName의 별칭)
|
|
17
|
-
*/
|
|
18
|
-
get filename(): string;
|
|
19
|
-
/**
|
|
20
|
-
* HTML input 필드명
|
|
21
|
-
*/
|
|
22
|
-
get fieldName(): string;
|
|
23
|
-
/**
|
|
24
|
-
* 파일 크기 (바이트)
|
|
25
|
-
*/
|
|
26
|
-
get size(): number;
|
|
27
|
-
/**
|
|
28
|
-
* 파일 확장자 (점 제외)
|
|
29
|
-
*/
|
|
30
|
-
get extname(): string | false;
|
|
31
|
-
get mimetype(): string;
|
|
32
|
-
get encoding(): string;
|
|
33
|
-
toBuffer(): Promise<Buffer>;
|
|
34
|
-
md5(): Promise<string>;
|
|
35
|
-
/**
|
|
36
|
-
* 파일을 저장소에 저장
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```typescript
|
|
40
|
-
* const { file } = Sonamu.getUploadContext();
|
|
41
|
-
* const url = await file.saveToDisk('uploads/avatar.png');
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
saveToDisk(key: string, options?: {
|
|
45
|
-
contentType?: string;
|
|
46
|
-
visibility?: "public" | "private";
|
|
47
|
-
}): Promise<string>;
|
|
48
|
-
get raw(): MultipartFile;
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=file-storage.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../../src/file-storage/file-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM;IAK/C;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,MAAM,GAAG,KAAK,CAE5B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAEK,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAO3B,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;IAK5B;;;;;;;;OAQG;IACG,UAAU,CACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;KAAE,GACpE,OAAO,CAAC,MAAM,CAAC;IAWlB,IAAI,GAAG,IAAI,aAAa,CAEvB;CACF"}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
|
-
import mime from "mime-types";
|
|
3
|
-
/**
|
|
4
|
-
* @fastify/multipart의 MultipartFile 래퍼
|
|
5
|
-
*/ export class FileStorage {
|
|
6
|
-
_file;
|
|
7
|
-
_buffer;
|
|
8
|
-
_driver;
|
|
9
|
-
constructor(file, driver){
|
|
10
|
-
this._file = file;
|
|
11
|
-
this._driver = driver;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* 사용자 컴퓨터의 원본 파일명
|
|
15
|
-
*/ get clientName() {
|
|
16
|
-
return this._file.filename;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* 파일명 (clientName의 별칭)
|
|
20
|
-
*/ get filename() {
|
|
21
|
-
return this._file.filename;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* HTML input 필드명
|
|
25
|
-
*/ get fieldName() {
|
|
26
|
-
return this._file.fieldname;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* 파일 크기 (바이트)
|
|
30
|
-
*/ get size() {
|
|
31
|
-
return this._file.file.bytesRead;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* 파일 확장자 (점 제외)
|
|
35
|
-
*/ get extname() {
|
|
36
|
-
return mime.extension(this._file.mimetype);
|
|
37
|
-
}
|
|
38
|
-
get mimetype() {
|
|
39
|
-
return this._file.mimetype;
|
|
40
|
-
}
|
|
41
|
-
get encoding() {
|
|
42
|
-
return this._file.encoding;
|
|
43
|
-
}
|
|
44
|
-
async toBuffer() {
|
|
45
|
-
if (!this._buffer) {
|
|
46
|
-
this._buffer = await this._file.toBuffer();
|
|
47
|
-
}
|
|
48
|
-
return this._buffer;
|
|
49
|
-
}
|
|
50
|
-
async md5() {
|
|
51
|
-
const buffer = await this.toBuffer();
|
|
52
|
-
return createHash("md5").update(buffer).digest("hex");
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* 파일을 저장소에 저장
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* ```typescript
|
|
59
|
-
* const { file } = Sonamu.getUploadContext();
|
|
60
|
-
* const url = await file.saveToDisk('uploads/avatar.png');
|
|
61
|
-
* ```
|
|
62
|
-
*/ async saveToDisk(key, options) {
|
|
63
|
-
const buffer = await this.toBuffer();
|
|
64
|
-
await this._driver.put(key, buffer, {
|
|
65
|
-
contentType: options?.contentType ?? this.mimetype,
|
|
66
|
-
visibility: options?.visibility
|
|
67
|
-
});
|
|
68
|
-
return this._driver.getSignedUrl(key);
|
|
69
|
-
}
|
|
70
|
-
get raw() {
|
|
71
|
-
return this._file;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9maWxlLXN0b3JhZ2UvZmlsZS1zdG9yYWdlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgTXVsdGlwYXJ0RmlsZSB9IGZyb20gXCJAZmFzdGlmeS9tdWx0aXBhcnRcIjtcbmltcG9ydCB7IGNyZWF0ZUhhc2ggfSBmcm9tIFwiY3J5cHRvXCI7XG5pbXBvcnQgbWltZSBmcm9tIFwibWltZS10eXBlc1wiO1xuaW1wb3J0IHR5cGUgeyBEcml2ZXIgfSBmcm9tIFwiLi9kcml2ZXJcIjtcblxuLyoqXG4gKiBAZmFzdGlmeS9tdWx0aXBhcnTsnZggTXVsdGlwYXJ0RmlsZSDrnpjtjbxcbiAqL1xuZXhwb3J0IGNsYXNzIEZpbGVTdG9yYWdlIHtcbiAgcHJpdmF0ZSBfZmlsZTogTXVsdGlwYXJ0RmlsZTtcbiAgcHJpdmF0ZSBfYnVmZmVyPzogQnVmZmVyO1xuICBwcml2YXRlIF9kcml2ZXI6IERyaXZlcjtcblxuICBjb25zdHJ1Y3RvcihmaWxlOiBNdWx0aXBhcnRGaWxlLCBkcml2ZXI6IERyaXZlcikge1xuICAgIHRoaXMuX2ZpbGUgPSBmaWxlO1xuICAgIHRoaXMuX2RyaXZlciA9IGRyaXZlcjtcbiAgfVxuXG4gIC8qKlxuICAgKiDsgqzsmqnsnpAg7Lu07ZOo7YSw7J2YIOybkOuzuCDtjIzsnbzrqoVcbiAgICovXG4gIGdldCBjbGllbnROYW1lKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHRoaXMuX2ZpbGUuZmlsZW5hbWU7XG4gIH1cblxuICAvKipcbiAgICog7YyM7J2866qFIChjbGllbnROYW1l7J2YIOuzhOy5rSlcbiAgICovXG4gIGdldCBmaWxlbmFtZSgpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLl9maWxlLmZpbGVuYW1lO1xuICB9XG5cbiAgLyoqXG4gICAqIEhUTUwgaW5wdXQg7ZWE65Oc66qFXG4gICAqL1xuICBnZXQgZmllbGROYW1lKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHRoaXMuX2ZpbGUuZmllbGRuYW1lO1xuICB9XG5cbiAgLyoqXG4gICAqIO2MjOydvCDtgazquLAgKOuwlOydtO2KuClcbiAgICovXG4gIGdldCBzaXplKCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMuX2ZpbGUuZmlsZS5ieXRlc1JlYWQ7XG4gIH1cblxuICAvKipcbiAgICog7YyM7J28IO2ZleyepeyekCAo7KCQIOygnOyZuClcbiAgICovXG4gIGdldCBleHRuYW1lKCk6IHN0cmluZyB8IGZhbHNlIHtcbiAgICByZXR1cm4gbWltZS5leHRlbnNpb24odGhpcy5fZmlsZS5taW1ldHlwZSk7XG4gIH1cblxuICBnZXQgbWltZXR5cGUoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gdGhpcy5fZmlsZS5taW1ldHlwZTtcbiAgfVxuXG4gIGdldCBlbmNvZGluZygpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLl9maWxlLmVuY29kaW5nO1xuICB9XG5cbiAgYXN5bmMgdG9CdWZmZXIoKTogUHJvbWlzZTxCdWZmZXI+IHtcbiAgICBpZiAoIXRoaXMuX2J1ZmZlcikge1xuICAgICAgdGhpcy5fYnVmZmVyID0gYXdhaXQgdGhpcy5fZmlsZS50b0J1ZmZlcigpO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fYnVmZmVyO1xuICB9XG5cbiAgYXN5bmMgbWQ1KCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgYnVmZmVyID0gYXdhaXQgdGhpcy50b0J1ZmZlcigpO1xuICAgIHJldHVybiBjcmVhdGVIYXNoKFwibWQ1XCIpLnVwZGF0ZShidWZmZXIpLmRpZ2VzdChcImhleFwiKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDtjIzsnbzsnYQg7KCA7J6l7IaM7JeQIOyggOyepVxuICAgKlxuICAgKiBAZXhhbXBsZVxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIGNvbnN0IHsgZmlsZSB9ID0gU29uYW11LmdldFVwbG9hZENvbnRleHQoKTtcbiAgICogY29uc3QgdXJsID0gYXdhaXQgZmlsZS5zYXZlVG9EaXNrKCd1cGxvYWRzL2F2YXRhci5wbmcnKTtcbiAgICogYGBgXG4gICAqL1xuICBhc3luYyBzYXZlVG9EaXNrKFxuICAgIGtleTogc3RyaW5nLFxuICAgIG9wdGlvbnM/OiB7IGNvbnRlbnRUeXBlPzogc3RyaW5nOyB2aXNpYmlsaXR5PzogXCJwdWJsaWNcIiB8IFwicHJpdmF0ZVwiIH0sXG4gICk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgYnVmZmVyID0gYXdhaXQgdGhpcy50b0J1ZmZlcigpO1xuXG4gICAgYXdhaXQgdGhpcy5fZHJpdmVyLnB1dChrZXksIGJ1ZmZlciwge1xuICAgICAgY29udGVudFR5cGU6IG9wdGlvbnM/LmNvbnRlbnRUeXBlID8/IHRoaXMubWltZXR5cGUsXG4gICAgICB2aXNpYmlsaXR5OiBvcHRpb25zPy52aXNpYmlsaXR5LFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHRoaXMuX2RyaXZlci5nZXRTaWduZWRVcmwoa2V5KTtcbiAgfVxuXG4gIGdldCByYXcoKTogTXVsdGlwYXJ0RmlsZSB7XG4gICAgcmV0dXJuIHRoaXMuX2ZpbGU7XG4gIH1cbn1cbiJdLCJuYW1lcyI6WyJjcmVhdGVIYXNoIiwibWltZSIsIkZpbGVTdG9yYWdlIiwiX2ZpbGUiLCJfYnVmZmVyIiwiX2RyaXZlciIsImZpbGUiLCJkcml2ZXIiLCJjbGllbnROYW1lIiwiZmlsZW5hbWUiLCJmaWVsZE5hbWUiLCJmaWVsZG5hbWUiLCJzaXplIiwiYnl0ZXNSZWFkIiwiZXh0bmFtZSIsImV4dGVuc2lvbiIsIm1pbWV0eXBlIiwiZW5jb2RpbmciLCJ0b0J1ZmZlciIsIm1kNSIsImJ1ZmZlciIsInVwZGF0ZSIsImRpZ2VzdCIsInNhdmVUb0Rpc2siLCJrZXkiLCJvcHRpb25zIiwicHV0IiwiY29udGVudFR5cGUiLCJ2aXNpYmlsaXR5IiwiZ2V0U2lnbmVkVXJsIiwicmF3Il0sIm1hcHBpbmdzIjoiQUFDQSxTQUFTQSxVQUFVLFFBQVEsU0FBUztBQUNwQyxPQUFPQyxVQUFVLGFBQWE7QUFHOUI7O0NBRUMsR0FDRCxPQUFPLE1BQU1DO0lBQ0hDLE1BQXFCO0lBQ3JCQyxRQUFpQjtJQUNqQkMsUUFBZ0I7SUFFeEIsWUFBWUMsSUFBbUIsRUFBRUMsTUFBYyxDQUFFO1FBQy9DLElBQUksQ0FBQ0osS0FBSyxHQUFHRztRQUNiLElBQUksQ0FBQ0QsT0FBTyxHQUFHRTtJQUNqQjtJQUVBOztHQUVDLEdBQ0QsSUFBSUMsYUFBcUI7UUFDdkIsT0FBTyxJQUFJLENBQUNMLEtBQUssQ0FBQ00sUUFBUTtJQUM1QjtJQUVBOztHQUVDLEdBQ0QsSUFBSUEsV0FBbUI7UUFDckIsT0FBTyxJQUFJLENBQUNOLEtBQUssQ0FBQ00sUUFBUTtJQUM1QjtJQUVBOztHQUVDLEdBQ0QsSUFBSUMsWUFBb0I7UUFDdEIsT0FBTyxJQUFJLENBQUNQLEtBQUssQ0FBQ1EsU0FBUztJQUM3QjtJQUVBOztHQUVDLEdBQ0QsSUFBSUMsT0FBZTtRQUNqQixPQUFPLElBQUksQ0FBQ1QsS0FBSyxDQUFDRyxJQUFJLENBQUNPLFNBQVM7SUFDbEM7SUFFQTs7R0FFQyxHQUNELElBQUlDLFVBQTBCO1FBQzVCLE9BQU9iLEtBQUtjLFNBQVMsQ0FBQyxJQUFJLENBQUNaLEtBQUssQ0FBQ2EsUUFBUTtJQUMzQztJQUVBLElBQUlBLFdBQW1CO1FBQ3JCLE9BQU8sSUFBSSxDQUFDYixLQUFLLENBQUNhLFFBQVE7SUFDNUI7SUFFQSxJQUFJQyxXQUFtQjtRQUNyQixPQUFPLElBQUksQ0FBQ2QsS0FBSyxDQUFDYyxRQUFRO0lBQzVCO0lBRUEsTUFBTUMsV0FBNEI7UUFDaEMsSUFBSSxDQUFDLElBQUksQ0FBQ2QsT0FBTyxFQUFFO1lBQ2pCLElBQUksQ0FBQ0EsT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDRCxLQUFLLENBQUNlLFFBQVE7UUFDMUM7UUFDQSxPQUFPLElBQUksQ0FBQ2QsT0FBTztJQUNyQjtJQUVBLE1BQU1lLE1BQXVCO1FBQzNCLE1BQU1DLFNBQVMsTUFBTSxJQUFJLENBQUNGLFFBQVE7UUFDbEMsT0FBT2xCLFdBQVcsT0FBT3FCLE1BQU0sQ0FBQ0QsUUFBUUUsTUFBTSxDQUFDO0lBQ2pEO0lBRUE7Ozs7Ozs7O0dBUUMsR0FDRCxNQUFNQyxXQUNKQyxHQUFXLEVBQ1hDLE9BQXFFLEVBQ3BEO1FBQ2pCLE1BQU1MLFNBQVMsTUFBTSxJQUFJLENBQUNGLFFBQVE7UUFFbEMsTUFBTSxJQUFJLENBQUNiLE9BQU8sQ0FBQ3FCLEdBQUcsQ0FBQ0YsS0FBS0osUUFBUTtZQUNsQ08sYUFBYUYsU0FBU0UsZUFBZSxJQUFJLENBQUNYLFFBQVE7WUFDbERZLFlBQVlILFNBQVNHO1FBQ3ZCO1FBRUEsT0FBTyxJQUFJLENBQUN2QixPQUFPLENBQUN3QixZQUFZLENBQUNMO0lBQ25DO0lBRUEsSUFBSU0sTUFBcUI7UUFDdkIsT0FBTyxJQUFJLENBQUMzQixLQUFLO0lBQ25CO0FBQ0YifQ==
|