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
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DeleteObjectCommand,
|
|
3
|
-
GetObjectCommand,
|
|
4
|
-
PutObjectCommand,
|
|
5
|
-
S3Client,
|
|
6
|
-
type S3ClientConfig,
|
|
7
|
-
} from "@aws-sdk/client-s3";
|
|
8
|
-
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
9
|
-
import fs from "fs/promises";
|
|
10
|
-
import path from "path";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 파일 저장소의 공통 인터페이스
|
|
14
|
-
*/
|
|
15
|
-
export interface Driver {
|
|
16
|
-
put(
|
|
17
|
-
key: string,
|
|
18
|
-
contents: Buffer,
|
|
19
|
-
options?: { contentType?: string; visibility?: "public" | "private" },
|
|
20
|
-
): Promise<void>;
|
|
21
|
-
|
|
22
|
-
del(key: string): Promise<void>;
|
|
23
|
-
|
|
24
|
-
getUrl(key: string): string;
|
|
25
|
-
|
|
26
|
-
getSignedUrl(key: string, expiresIn?: number): Promise<string>;
|
|
27
|
-
|
|
28
|
-
destroy(): void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type FSDriverConfig = {
|
|
32
|
-
location: string;
|
|
33
|
-
urlPrefix: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 로컬 파일시스템
|
|
38
|
-
*/
|
|
39
|
-
export class FSDriver implements Driver {
|
|
40
|
-
constructor(private config: FSDriverConfig) {}
|
|
41
|
-
|
|
42
|
-
async put(key: string, contents: Buffer): Promise<void> {
|
|
43
|
-
const filePath = path.join(this.config.location, key);
|
|
44
|
-
const dir = path.dirname(filePath);
|
|
45
|
-
|
|
46
|
-
await fs.mkdir(dir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
await fs.writeFile(filePath, contents);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async del(key: string) {
|
|
52
|
-
await fs.rm(path.join(this.config.location, key));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
getUrl(key: string): string {
|
|
56
|
-
return `${this.config.urlPrefix}/${key}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 로컬 파일시스템은 signed URL을 지원하지 않으므로 일반 URL 반환
|
|
60
|
-
async getSignedUrl(key: string, _expiresIn?: number): Promise<string> {
|
|
61
|
-
return this.getUrl(key);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
destroy() {
|
|
65
|
-
// 아무것도 하지 않음
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export type S3DriverConfig = S3ClientConfig & {
|
|
70
|
-
bucket: string;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export class S3Driver implements Driver {
|
|
74
|
-
s3: S3Client;
|
|
75
|
-
|
|
76
|
-
constructor(private config: S3DriverConfig) {
|
|
77
|
-
this.s3 = new S3Client(config);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async put(
|
|
81
|
-
key: string,
|
|
82
|
-
contents: Buffer,
|
|
83
|
-
options?: { contentType?: string; visibility?: "public" | "private" },
|
|
84
|
-
): Promise<void> {
|
|
85
|
-
await this.s3.send(
|
|
86
|
-
new PutObjectCommand({
|
|
87
|
-
Bucket: this.config.bucket,
|
|
88
|
-
Key: key,
|
|
89
|
-
Body: contents,
|
|
90
|
-
ContentType: options?.contentType,
|
|
91
|
-
ACL: this.getAcl(options?.visibility),
|
|
92
|
-
}),
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async del(key: string): Promise<void> {
|
|
97
|
-
await this.s3.send(
|
|
98
|
-
new DeleteObjectCommand({
|
|
99
|
-
Bucket: this.config.bucket,
|
|
100
|
-
Key: key,
|
|
101
|
-
}),
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
getUrl(key: string): string {
|
|
106
|
-
return `https://${this.config.bucket}.s3.${this.config.region}.amazonaws.com/${key}`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async getSignedUrl(key: string, expiresIn?: number): Promise<string> {
|
|
110
|
-
const command = new GetObjectCommand({
|
|
111
|
-
Bucket: this.config.bucket,
|
|
112
|
-
Key: key,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return getSignedUrl(this.s3, command, {
|
|
116
|
-
expiresIn: expiresIn ?? 60 * 60 * 24 * 7,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private getAcl(visibility?: "public" | "private") {
|
|
121
|
-
if (visibility === "public") {
|
|
122
|
-
return "public-read";
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return visibility;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
destroy() {
|
|
129
|
-
this.s3.destroy();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import type { MultipartFile } from "@fastify/multipart";
|
|
2
|
-
import { createHash } from "crypto";
|
|
3
|
-
import mime from "mime-types";
|
|
4
|
-
import type { Driver } from "./driver";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @fastify/multipart의 MultipartFile 래퍼
|
|
8
|
-
*/
|
|
9
|
-
export class FileStorage {
|
|
10
|
-
private _file: MultipartFile;
|
|
11
|
-
private _buffer?: Buffer;
|
|
12
|
-
private _driver: Driver;
|
|
13
|
-
|
|
14
|
-
constructor(file: MultipartFile, driver: Driver) {
|
|
15
|
-
this._file = file;
|
|
16
|
-
this._driver = driver;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 사용자 컴퓨터의 원본 파일명
|
|
21
|
-
*/
|
|
22
|
-
get clientName(): string {
|
|
23
|
-
return this._file.filename;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 파일명 (clientName의 별칭)
|
|
28
|
-
*/
|
|
29
|
-
get filename(): string {
|
|
30
|
-
return this._file.filename;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* HTML input 필드명
|
|
35
|
-
*/
|
|
36
|
-
get fieldName(): string {
|
|
37
|
-
return this._file.fieldname;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 파일 크기 (바이트)
|
|
42
|
-
*/
|
|
43
|
-
get size(): number {
|
|
44
|
-
return this._file.file.bytesRead;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 파일 확장자 (점 제외)
|
|
49
|
-
*/
|
|
50
|
-
get extname(): string | false {
|
|
51
|
-
return mime.extension(this._file.mimetype);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get mimetype(): string {
|
|
55
|
-
return this._file.mimetype;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get encoding(): string {
|
|
59
|
-
return this._file.encoding;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async toBuffer(): Promise<Buffer> {
|
|
63
|
-
if (!this._buffer) {
|
|
64
|
-
this._buffer = await this._file.toBuffer();
|
|
65
|
-
}
|
|
66
|
-
return this._buffer;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async md5(): Promise<string> {
|
|
70
|
-
const buffer = await this.toBuffer();
|
|
71
|
-
return createHash("md5").update(buffer).digest("hex");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 파일을 저장소에 저장
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* ```typescript
|
|
79
|
-
* const { file } = Sonamu.getUploadContext();
|
|
80
|
-
* const url = await file.saveToDisk('uploads/avatar.png');
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
async saveToDisk(
|
|
84
|
-
key: string,
|
|
85
|
-
options?: { contentType?: string; visibility?: "public" | "private" },
|
|
86
|
-
): Promise<string> {
|
|
87
|
-
const buffer = await this.toBuffer();
|
|
88
|
-
|
|
89
|
-
await this._driver.put(key, buffer, {
|
|
90
|
-
contentType: options?.contentType ?? this.mimetype,
|
|
91
|
-
visibility: options?.visibility,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
return this._driver.getSignedUrl(key);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
get raw(): MultipartFile {
|
|
98
|
-
return this._file;
|
|
99
|
-
}
|
|
100
|
-
}
|