-
Notifications
You must be signed in to change notification settings - Fork 51
/
google-storage-strategy.ts
127 lines (116 loc) · 4.02 KB
/
google-storage-strategy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { AssetStorageStrategy } from '@vendure/core';
import { Storage } from '@google-cloud/storage';
import { Request } from 'express';
import { Stream } from 'stream';
import * as tmp from 'tmp';
import * as fs from 'fs';
import { GoogleStorageConfig } from './google-storage-config';
import sharp from 'sharp';
export class GoogleStorageStrategy implements AssetStorageStrategy {
storage: Storage;
urlPrefix = 'https://storage.googleapis.com';
bucketName: string;
readonly useAssetServerForAdminUi: boolean;
constructor(private config: GoogleStorageConfig) {
this.bucketName = config.bucketName;
this.useAssetServerForAdminUi =
config.useAssetServerForAdminUi === undefined
? true
: config.useAssetServerForAdminUi;
if (!config.thumbnails) {
config.thumbnails = {
height: 300,
width: 300,
};
}
this.storage = new Storage(config.storageOptions ?? {});
}
toAbsoluteUrl(request: Request | undefined, identifier: string): string {
// Vendure v3 has an extra 'default' property before we can access the apiType
const apiType =
(request as any)?.vendureRequestContext?.default?._apiType ||
(request as any)?.vendureRequestContext?._apiType;
if (this.useAssetServerForAdminUi && apiType === 'admin') {
// go via assetServer if admin
return `${request!.protocol}://${request!.get(
'host'
)}/assets/${identifier}`;
}
return `${this.urlPrefix}/${this.bucketName}/${identifier}`;
}
async deleteFile(identifier: string): Promise<void> {
await this.storage.bucket(this.bucketName).file(identifier).delete();
}
async fileExists(fileName: string): Promise<boolean> {
const [exists] = await this.storage
.bucket(this.bucketName)
.file(fileName)
.exists();
return exists;
}
async readFileToBuffer(identifier: string): Promise<Buffer> {
if (identifier?.startsWith('/')) {
identifier = identifier.replace('/', '');
}
const tmpFile = tmp.fileSync();
await this.storage
.bucket(this.bucketName)
.file(identifier)
.download({ destination: tmpFile.name });
return fs.readFileSync(tmpFile.name);
}
async readFileToStream(identifier: string): Promise<Stream> {
if (identifier?.startsWith('/')) {
identifier = identifier.replace('/', '');
}
return this.storage
.bucket(this.bucketName)
.file(identifier)
.createReadStream();
}
async writeFileFromBuffer(fileName: string, data: Buffer): Promise<string> {
const tmpFile = tmp.fileSync();
fs.writeFileSync(tmpFile.name, data);
await this.storage.bucket(this.bucketName).upload(tmpFile.name, {
destination: fileName,
});
if (fileName.startsWith('preview/')) {
// For each preview, we also generate a thumbnail version
await this.writeThumbnail(fileName, tmpFile.name);
}
return fileName;
}
async writeFileFromStream(fileName: string, data: Stream): Promise<string> {
const blob = this.storage.bucket(this.bucketName).file(fileName);
const uploadStream = blob.createWriteStream();
await Promise.all([
this.streamToPromise(data.pipe(uploadStream)),
this.streamToPromise(uploadStream),
]);
return fileName;
}
streamToPromise(stream: Stream): Promise<void> {
return new Promise(function (resolve, reject) {
stream.on('end', resolve);
stream.on('finish', resolve);
stream.on('close', resolve);
stream.on('error', reject);
});
}
/**
* Transforms local file to thumbnail (jpg) and uploads to Storage
*/
async writeThumbnail(fileName: string, localFilePath: string): Promise<void> {
const tmpFile = tmp.fileSync({ postfix: '.jpg' });
await sharp(localFilePath)
.resize({
width: this.config.thumbnails!.width,
height: this.config.thumbnails!.height,
})
.flatten({ background: '#ffffff' })
.toFile(tmpFile.name);
await this.storage.bucket(this.bucketName).upload(tmpFile.name, {
destination: `${fileName}_thumbnail.jpg`,
});
}
}