@postxl/generators 1.15.0 → 1.16.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/dist/backend-excel-io/excel-io.generator.d.ts +2 -1
- package/dist/backend-excel-io/excel-io.generator.js +2 -0
- package/dist/backend-excel-io/excel-io.generator.js.map +1 -1
- package/dist/backend-excel-io/generators/excel-io-service.generator.js +1 -1
- package/dist/backend-excel-io/template/excel-io.controller.ts +24 -54
- package/dist/backend-upload/index.d.ts +3 -0
- package/dist/backend-upload/index.js +40 -0
- package/dist/backend-upload/index.js.map +1 -0
- package/dist/backend-upload/template/src/index.ts +13 -0
- package/dist/backend-upload/template/src/upload.config.ts +15 -0
- package/dist/backend-upload/template/src/upload.controller.ts +53 -0
- package/dist/backend-upload/template/src/upload.guard.ts +39 -0
- package/dist/backend-upload/template/src/upload.module.ts +26 -0
- package/dist/backend-upload/template/src/upload.service.ts +333 -0
- package/dist/backend-upload/template/src/upload.types.ts +37 -0
- package/dist/backend-upload/template/src/uploaded-file.decorator.ts +12 -0
- package/dist/backend-upload/template/tsconfig.lib.json +10 -0
- package/dist/backend-upload/upload.generator.d.ts +16 -0
- package/dist/backend-upload/upload.generator.js +107 -0
- package/dist/backend-upload/upload.generator.js.map +1 -0
- package/dist/frontend-forms/generators/model/forms.generator.js +191 -0
- package/dist/frontend-forms/generators/model/forms.generator.js.map +1 -1
- package/dist/frontend-tables/generators/model-table.generator.js +16 -2
- package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
- package/dist/generators.js +2 -0
- package/dist/generators.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { DispatcherService } from '@actions/dispatcher.service'
|
|
2
|
+
import { BadRequestException, Injectable, NotFoundException, PayloadTooLargeException } from '@nestjs/common'
|
|
3
|
+
import { S3Service } from '@s3/s3.service'
|
|
4
|
+
import type { FileId, User } from '@types'
|
|
5
|
+
import { ViewService } from '@view/view.service'
|
|
6
|
+
|
|
7
|
+
import type { MultipartFile } from '@fastify/multipart'
|
|
8
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
import { Readable } from 'node:stream'
|
|
11
|
+
|
|
12
|
+
import { UploadConfig } from './upload.config'
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_MAX_SIZE_BYTES,
|
|
15
|
+
type StoredUploadRecord,
|
|
16
|
+
type UploadGuardOptions,
|
|
17
|
+
type UploadedFileDataPayload,
|
|
18
|
+
type UploadStorageMode,
|
|
19
|
+
} from './upload.types'
|
|
20
|
+
|
|
21
|
+
type FileTypeRule = {
|
|
22
|
+
extensions: string[]
|
|
23
|
+
mimeTypes: string[]
|
|
24
|
+
mimePrefixes: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const FILE_TYPE_RULES: Record<string, FileTypeRule> = {
|
|
28
|
+
image: {
|
|
29
|
+
extensions: ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'],
|
|
30
|
+
mimeTypes: [],
|
|
31
|
+
mimePrefixes: ['image/'],
|
|
32
|
+
},
|
|
33
|
+
excel: {
|
|
34
|
+
extensions: ['.xlsx', '.xls'],
|
|
35
|
+
mimeTypes: [
|
|
36
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
37
|
+
'application/vnd.ms-excel',
|
|
38
|
+
'application/octet-stream',
|
|
39
|
+
],
|
|
40
|
+
mimePrefixes: [],
|
|
41
|
+
},
|
|
42
|
+
json: {
|
|
43
|
+
extensions: ['.json'],
|
|
44
|
+
mimeTypes: ['application/json', 'text/json'],
|
|
45
|
+
mimePrefixes: [],
|
|
46
|
+
},
|
|
47
|
+
csv: {
|
|
48
|
+
extensions: ['.csv'],
|
|
49
|
+
mimeTypes: ['text/csv', 'application/csv', 'application/vnd.ms-excel'],
|
|
50
|
+
mimePrefixes: [],
|
|
51
|
+
},
|
|
52
|
+
pdf: {
|
|
53
|
+
extensions: ['.pdf'],
|
|
54
|
+
mimeTypes: ['application/pdf'],
|
|
55
|
+
mimePrefixes: [],
|
|
56
|
+
},
|
|
57
|
+
text: {
|
|
58
|
+
extensions: ['.txt', '.md'],
|
|
59
|
+
mimeTypes: ['text/plain', 'text/markdown'],
|
|
60
|
+
mimePrefixes: ['text/'],
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Injectable()
|
|
65
|
+
export class UploadService {
|
|
66
|
+
private readonly inMemoryFiles = new Map<FileId, Buffer>()
|
|
67
|
+
|
|
68
|
+
constructor(
|
|
69
|
+
private readonly uploadConfig: UploadConfig,
|
|
70
|
+
private readonly dispatcherService: DispatcherService,
|
|
71
|
+
private readonly viewService: ViewService,
|
|
72
|
+
private readonly s3Service: S3Service,
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
public async processUpload({
|
|
76
|
+
multipartFile,
|
|
77
|
+
user,
|
|
78
|
+
options,
|
|
79
|
+
}: {
|
|
80
|
+
multipartFile: MultipartFile
|
|
81
|
+
user: User
|
|
82
|
+
options?: UploadGuardOptions
|
|
83
|
+
}): Promise<UploadedFileDataPayload> {
|
|
84
|
+
const maxBytes = options?.maxFileSizeBytes ?? this.uploadConfig.values.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES
|
|
85
|
+
const filename = multipartFile.filename || 'upload.bin'
|
|
86
|
+
const mimetype = multipartFile.mimetype || 'application/octet-stream'
|
|
87
|
+
const extension = path.extname(filename).toLowerCase()
|
|
88
|
+
|
|
89
|
+
this.validateConstraints({ extension, mimetype, options })
|
|
90
|
+
|
|
91
|
+
const buffer = await this.toBufferWithLimit(multipartFile.file, maxBytes)
|
|
92
|
+
if (buffer.length === 0) {
|
|
93
|
+
throw new BadRequestException('Uploaded file is empty')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const storageMode = this.resolveStorageMode(options)
|
|
97
|
+
const createdFile = await this.dispatcherService.dispatch({
|
|
98
|
+
action: {
|
|
99
|
+
scope: 'file',
|
|
100
|
+
type: 'create',
|
|
101
|
+
payload: {
|
|
102
|
+
name: filename,
|
|
103
|
+
mimetype,
|
|
104
|
+
size: buffer.length,
|
|
105
|
+
url: '',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
user,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const stored = await this.storeUpload({ fileId: createdFile.id, filename, extension, buffer, storageMode })
|
|
113
|
+
|
|
114
|
+
const updatedFile = await this.dispatcherService.dispatch({
|
|
115
|
+
action: {
|
|
116
|
+
scope: 'file',
|
|
117
|
+
type: 'update',
|
|
118
|
+
payload: {
|
|
119
|
+
id: createdFile.id,
|
|
120
|
+
name: filename,
|
|
121
|
+
mimetype,
|
|
122
|
+
size: buffer.length,
|
|
123
|
+
url: stored.location,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
user,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
file: updatedFile,
|
|
131
|
+
buffer,
|
|
132
|
+
filename,
|
|
133
|
+
mimetype,
|
|
134
|
+
size: buffer.length,
|
|
135
|
+
fields: multipartFile.fields,
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
await this.deleteFailedUploadRecord({ fileId: createdFile.id, user })
|
|
139
|
+
throw error
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public async getStoredFileBuffer({ fileId, user }: { fileId: FileId; user: User }): Promise<Buffer> {
|
|
144
|
+
const file = await this.viewService.files.get({ id: fileId, user })
|
|
145
|
+
if (!file) {
|
|
146
|
+
throw new NotFoundException(`File ${fileId} not found`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return this.readStoredFile({ fileId, location: file.url })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public async getStoredFileRecord({ fileId, user }: { fileId: FileId; user: User }) {
|
|
153
|
+
const file = await this.viewService.files.get({ id: fileId, user })
|
|
154
|
+
if (!file) {
|
|
155
|
+
throw new NotFoundException(`File ${fileId} not found`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return file
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public async getBufferFromFileRecord({ fileId, location }: { fileId: FileId; location: string }): Promise<Buffer> {
|
|
162
|
+
return this.readStoredFile({ fileId, location })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public getRootUser(): User {
|
|
166
|
+
return this.viewService.users.data.rootUser
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private resolveStorageMode(options?: UploadGuardOptions): UploadStorageMode {
|
|
170
|
+
if (this.uploadConfig.values.dryRun) {
|
|
171
|
+
return 'none'
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return options?.storageMode ?? this.uploadConfig.values.storage
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async storeUpload({
|
|
178
|
+
fileId,
|
|
179
|
+
filename,
|
|
180
|
+
extension,
|
|
181
|
+
buffer,
|
|
182
|
+
storageMode,
|
|
183
|
+
}: {
|
|
184
|
+
fileId: FileId
|
|
185
|
+
filename: string
|
|
186
|
+
extension: string
|
|
187
|
+
buffer: Buffer
|
|
188
|
+
storageMode: UploadStorageMode
|
|
189
|
+
}): Promise<StoredUploadRecord> {
|
|
190
|
+
if (storageMode === 'none') {
|
|
191
|
+
this.inMemoryFiles.set(fileId, buffer)
|
|
192
|
+
return {
|
|
193
|
+
fileId,
|
|
194
|
+
location: `memory://${fileId}`,
|
|
195
|
+
storageMode,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (storageMode === 'local') {
|
|
200
|
+
const sanitizedName = filename.replaceAll(/[^A-Za-z0-9._-]/g, '_')
|
|
201
|
+
const storageDir = path.resolve(this.uploadConfig.values.localDirectory)
|
|
202
|
+
const targetPath = path.join(storageDir, `${fileId}-${sanitizedName}`)
|
|
203
|
+
|
|
204
|
+
await mkdir(storageDir, { recursive: true })
|
|
205
|
+
await writeFile(targetPath, buffer)
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
fileId,
|
|
209
|
+
location: `file://${targetPath}`,
|
|
210
|
+
storageMode,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const key = await this.s3Service.send(
|
|
215
|
+
Readable.from(buffer),
|
|
216
|
+
String(fileId),
|
|
217
|
+
extension ? extension.slice(1) : undefined,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
fileId,
|
|
222
|
+
location: `s3://${key}`,
|
|
223
|
+
storageMode,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private async readStoredFile({ fileId, location }: { fileId: FileId; location: string }): Promise<Buffer> {
|
|
228
|
+
if (location.startsWith('memory://')) {
|
|
229
|
+
const cached = this.inMemoryFiles.get(fileId)
|
|
230
|
+
if (!cached) {
|
|
231
|
+
throw new NotFoundException(`No in-memory binary found for ${fileId}`)
|
|
232
|
+
}
|
|
233
|
+
return cached
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (location.startsWith('file://')) {
|
|
237
|
+
const filePath = location.slice('file://'.length)
|
|
238
|
+
return readFile(filePath)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (location.startsWith('s3://')) {
|
|
242
|
+
const key = location.slice('s3://'.length)
|
|
243
|
+
const body = await this.s3Service.getFile(key)
|
|
244
|
+
if (!body) {
|
|
245
|
+
throw new NotFoundException(`Stored object ${key} not found in S3`)
|
|
246
|
+
}
|
|
247
|
+
return Buffer.from(body)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
throw new BadRequestException(`Unsupported upload location format: ${location}`)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private validateConstraints({
|
|
254
|
+
extension,
|
|
255
|
+
mimetype,
|
|
256
|
+
options,
|
|
257
|
+
}: {
|
|
258
|
+
extension: string
|
|
259
|
+
mimetype: string
|
|
260
|
+
options?: UploadGuardOptions
|
|
261
|
+
}): void {
|
|
262
|
+
if (options?.allowedMimeTypes && options.allowedMimeTypes.length > 0) {
|
|
263
|
+
if (!options.allowedMimeTypes.includes(mimetype)) {
|
|
264
|
+
throw new BadRequestException(`Unsupported mimetype: ${mimetype}`)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (options?.allowedFileExtensions && options.allowedFileExtensions.length > 0) {
|
|
269
|
+
const allowedExtensions = options.allowedFileExtensions.map((ext) => ext.toLowerCase())
|
|
270
|
+
if (!allowedExtensions.includes(extension)) {
|
|
271
|
+
throw new BadRequestException(`Unsupported file extension: ${extension || '(none)'}`)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (options?.allowedFileTypes && options.allowedFileTypes.length > 0) {
|
|
276
|
+
const allowed = options.allowedFileTypes.some((type) => {
|
|
277
|
+
const rule = FILE_TYPE_RULES[type]
|
|
278
|
+
return (
|
|
279
|
+
rule.extensions.includes(extension) ||
|
|
280
|
+
rule.mimeTypes.includes(mimetype) ||
|
|
281
|
+
rule.mimePrefixes.some((prefix) => mimetype.startsWith(prefix))
|
|
282
|
+
)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
if (!allowed) {
|
|
286
|
+
throw new BadRequestException(
|
|
287
|
+
`File type is not allowed for mimetype ${mimetype} and extension ${extension || '(none)'}`,
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private async toBufferWithLimit(stream: AsyncIterable<unknown>, maxBytes: number): Promise<Buffer> {
|
|
294
|
+
const chunks: Buffer[] = []
|
|
295
|
+
let size = 0
|
|
296
|
+
|
|
297
|
+
for await (const chunk of stream) {
|
|
298
|
+
const buffer = toBuffer(chunk)
|
|
299
|
+
size += buffer.length
|
|
300
|
+
if (size > maxBytes) {
|
|
301
|
+
throw new PayloadTooLargeException(`Uploaded file exceeds ${Math.floor(maxBytes / (1024 * 1024))} MB limit`)
|
|
302
|
+
}
|
|
303
|
+
chunks.push(buffer)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return Buffer.concat(chunks)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private async deleteFailedUploadRecord({ fileId, user }: { fileId: FileId; user: User }): Promise<void> {
|
|
310
|
+
try {
|
|
311
|
+
await this.dispatcherService.dispatch({
|
|
312
|
+
action: {
|
|
313
|
+
scope: 'file',
|
|
314
|
+
type: 'delete',
|
|
315
|
+
payload: fileId,
|
|
316
|
+
},
|
|
317
|
+
user,
|
|
318
|
+
})
|
|
319
|
+
} catch {
|
|
320
|
+
// Best-effort rollback to avoid orphan file records when storage fails.
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function toBuffer(chunk: unknown): Buffer {
|
|
326
|
+
if (Buffer.isBuffer(chunk)) {
|
|
327
|
+
return chunk
|
|
328
|
+
}
|
|
329
|
+
if (chunk instanceof Uint8Array) {
|
|
330
|
+
return Buffer.from(chunk)
|
|
331
|
+
}
|
|
332
|
+
return Buffer.from(String(chunk))
|
|
333
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { MultipartFile } from '@fastify/multipart'
|
|
2
|
+
import type { FastifyRequestWithViewer } from '@authentication/auth.guard'
|
|
3
|
+
|
|
4
|
+
import type { FileId, FileViewModel } from '@types'
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_MAX_SIZE_BYTES = 30 * 1024 * 1024
|
|
7
|
+
|
|
8
|
+
export type UploadStorageMode = 'none' | 'local' | 's3'
|
|
9
|
+
|
|
10
|
+
export type UploadAllowedFileType = 'image' | 'excel' | 'json' | 'csv' | 'pdf' | 'text'
|
|
11
|
+
|
|
12
|
+
export type UploadGuardOptions = {
|
|
13
|
+
maxFileSizeBytes?: number
|
|
14
|
+
allowedMimeTypes?: string[]
|
|
15
|
+
allowedFileExtensions?: string[]
|
|
16
|
+
allowedFileTypes?: UploadAllowedFileType[]
|
|
17
|
+
storageMode?: UploadStorageMode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type UploadedFileDataPayload = {
|
|
21
|
+
file: FileViewModel
|
|
22
|
+
buffer: Buffer
|
|
23
|
+
filename: string
|
|
24
|
+
mimetype: string
|
|
25
|
+
size: number
|
|
26
|
+
fields: MultipartFile['fields']
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type UploadRequest = FastifyRequestWithViewer & {
|
|
30
|
+
uploadedFileData?: UploadedFileDataPayload
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type StoredUploadRecord = {
|
|
34
|
+
fileId: FileId
|
|
35
|
+
location: string
|
|
36
|
+
storageMode: UploadStorageMode
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
|
|
2
|
+
|
|
3
|
+
import type { UploadRequest } from './upload.types'
|
|
4
|
+
|
|
5
|
+
export const UploadedFileData = createParamDecorator((_data: unknown, ctx: ExecutionContext) => {
|
|
6
|
+
const req = ctx.switchToHttp().getRequest<UploadRequest>()
|
|
7
|
+
if (!req.uploadedFileData) {
|
|
8
|
+
throw new Error('Decorator @UploadedFileData must be used with @UploadGuard')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return req.uploadedFileData
|
|
12
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as Generator from '@postxl/generator';
|
|
2
|
+
import { WithActions } from '../backend-actions';
|
|
3
|
+
import { WithBackend } from '../backend-core';
|
|
4
|
+
import { WithView } from '../backend-view';
|
|
5
|
+
import { WithTypes } from '../types';
|
|
6
|
+
type ContextRequirements = WithTypes<WithActions<WithView<WithBackend<Generator.Context>>>>;
|
|
7
|
+
export type ContextResult = WithUpload<ContextRequirements>;
|
|
8
|
+
export type WithUpload<Context> = Generator.ExtendContext<Context, {
|
|
9
|
+
upload: UploadContext;
|
|
10
|
+
}>;
|
|
11
|
+
type UploadContext = {
|
|
12
|
+
module: Generator.ImportableClass;
|
|
13
|
+
};
|
|
14
|
+
export declare const generatorId: string & import("zod").$brand<"PXL.GeneratorInterfaceId">;
|
|
15
|
+
export declare const generator: Generator.GeneratorInterface;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generator = exports.generatorId = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const Generator = __importStar(require("@postxl/generator"));
|
|
39
|
+
const generator_1 = require("@postxl/generator");
|
|
40
|
+
const backend_actions_1 = require("../backend-actions");
|
|
41
|
+
const backend_core_1 = require("../backend-core");
|
|
42
|
+
const backend_s3_1 = require("../backend-s3");
|
|
43
|
+
const backend_view_1 = require("../backend-view");
|
|
44
|
+
const types_1 = require("../types");
|
|
45
|
+
exports.generatorId = Generator.toGeneratorInterfaceId('backend-upload');
|
|
46
|
+
exports.generator = {
|
|
47
|
+
id: exports.generatorId,
|
|
48
|
+
requires: [
|
|
49
|
+
backend_core_1.backendGeneratorId,
|
|
50
|
+
backend_actions_1.backendActionsGeneratorId,
|
|
51
|
+
backend_view_1.backendViewGeneratorId,
|
|
52
|
+
backend_s3_1.backendS3GeneratorId,
|
|
53
|
+
types_1.typesGeneratorId,
|
|
54
|
+
],
|
|
55
|
+
register: (context) => {
|
|
56
|
+
const module = {
|
|
57
|
+
name: Generator.toClassName('UploadModule'),
|
|
58
|
+
location: Generator.toBackendModuleLocation('@upload/upload.module'),
|
|
59
|
+
};
|
|
60
|
+
const uploadModule = {
|
|
61
|
+
name: Generator.toBackendModuleName('upload'),
|
|
62
|
+
moduleClass: module,
|
|
63
|
+
apiModuleRegistration: {
|
|
64
|
+
code: (0, generator_1.ts)('UploadModule.forRoot(config.upload)'),
|
|
65
|
+
},
|
|
66
|
+
envConfig: {
|
|
67
|
+
dotEnvExample: `
|
|
68
|
+
# Upload storage mode: none | local | s3
|
|
69
|
+
UPLOAD_STORAGE=none
|
|
70
|
+
# Path for local uploads if UPLOAD_STORAGE=local
|
|
71
|
+
UPLOAD_LOCAL_DIRECTORY="./tmp/uploads"
|
|
72
|
+
# If true, upload binaries are not persisted to local disk/S3
|
|
73
|
+
UPLOAD_DRY_RUN=true
|
|
74
|
+
# Default max upload size in bytes (30 MB)
|
|
75
|
+
UPLOAD_MAX_SIZE_BYTES=31457280`,
|
|
76
|
+
decoder: (0, generator_1.ts)(`
|
|
77
|
+
UPLOAD_STORAGE: z.enum(['none', 'local', 's3']).optional().default('none'),
|
|
78
|
+
UPLOAD_LOCAL_DIRECTORY: z.string().optional().default('./tmp/uploads'),
|
|
79
|
+
UPLOAD_DRY_RUN: zEnvBoolean.optional().default(true),
|
|
80
|
+
UPLOAD_MAX_SIZE_BYTES: z.coerce.number().int().positive().optional().default(30 * 1024 * 1024),
|
|
81
|
+
`),
|
|
82
|
+
transformer: (0, generator_1.ts)(`
|
|
83
|
+
upload: {
|
|
84
|
+
storage: val.UPLOAD_STORAGE,
|
|
85
|
+
localDirectory: val.UPLOAD_LOCAL_DIRECTORY,
|
|
86
|
+
dryRun: val.UPLOAD_DRY_RUN,
|
|
87
|
+
maxSizeBytes: val.UPLOAD_MAX_SIZE_BYTES,
|
|
88
|
+
}`),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
context.backend.modules.push(uploadModule);
|
|
92
|
+
context.backend.packageJson.dependencies.push({ packageName: '@fastify/multipart', version: '9.2.1' });
|
|
93
|
+
return {
|
|
94
|
+
...context,
|
|
95
|
+
upload: { module },
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
generate: async (context) => {
|
|
99
|
+
const vfs = new Generator.VirtualFileSystem();
|
|
100
|
+
await vfs.loadFolder({
|
|
101
|
+
diskPath: path.resolve(__dirname, './template'),
|
|
102
|
+
});
|
|
103
|
+
context.vfs.insertFromVfs({ targetPath: '/backend/libs/upload', vfs });
|
|
104
|
+
return context;
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=upload.generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.generator.js","sourceRoot":"","sources":["../../src/backend-upload/upload.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAiC;AAEjC,6DAA8C;AAC9C,iDAAsC;AAEtC,wDAA2E;AAC3E,kDAA+E;AAC/E,8CAAoD;AACpD,kDAAkE;AAClE,oCAAsD;AAUzC,QAAA,WAAW,GAAG,SAAS,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAA;AAEhE,QAAA,SAAS,GAAiC;IACrD,EAAE,EAAE,mBAAW;IACf,QAAQ,EAAE;QACR,iCAAkB;QAClB,2CAAyB;QACzB,qCAAsB;QACtB,iCAAoB;QACpB,wBAAgB;KACjB;IAED,QAAQ,EAAE,CAAsC,OAAgB,EAAiB,EAAE;QACjF,MAAM,MAAM,GAA8B;YACxC,IAAI,EAAE,SAAS,CAAC,WAAW,CAAC,cAAc,CAAC;YAC3C,QAAQ,EAAE,SAAS,CAAC,uBAAuB,CAAC,uBAAuB,CAAC;SACrE,CAAA;QAED,MAAM,YAAY,GAAiB;YACjC,IAAI,EAAE,SAAS,CAAC,mBAAmB,CAAC,QAAQ,CAAC;YAC7C,WAAW,EAAE,MAAM;YACnB,qBAAqB,EAAE;gBACrB,IAAI,EAAE,IAAA,cAAE,EAAC,qCAAqC,CAAC;aAChD;YACD,SAAS,EAAE;gBACT,aAAa,EAAE;;;;;;;;yCAQkB;gBACjC,OAAO,EAAE,IAAA,cAAE,EAAC;;;;;SAKX,CAAC;gBACF,WAAW,EAAE,IAAA,cAAE,EAAC;;;;;;YAMZ,CAAC;aACN;SACF,CAAA;QAED,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAC1C,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;QAEtG,OAAO;YACL,GAAG,OAAO;YACV,MAAM,EAAE,EAAE,MAAM,EAAE;SACnB,CAAA;IACH,CAAC;IAED,QAAQ,EAAE,KAAK,EAAiC,OAAgB,EAAoB,EAAE;QACpF,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,iBAAiB,EAAE,CAAA;QAE7C,MAAM,GAAG,CAAC,UAAU,CAAC;YACnB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC;SAChD,CAAC,CAAA;QAEF,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAA;QACtE,OAAO,OAAO,CAAA;IAChB,CAAC;CACF,CAAA"}
|