@lenne.tech/nest-server 8.6.12 → 8.6.15
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/core/common/interfaces/server-options.interface.d.ts +1 -1
- package/dist/core/common/services/module.service.js +0 -1
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/common/types/maybe-promise.type.d.ts +1 -0
- package/dist/core/common/types/maybe-promise.type.js +3 -0
- package/dist/core/common/types/maybe-promise.type.js.map +1 -0
- package/dist/core/modules/file/{file-info.output.d.ts → core-file-info.model.d.ts} +4 -3
- package/dist/core/modules/file/{file-info.output.js → core-file-info.model.js} +35 -20
- package/dist/core/modules/file/core-file-info.model.js.map +1 -0
- package/dist/core/modules/file/core-file.controller.d.ts +7 -0
- package/dist/core/modules/file/core-file.controller.js +52 -0
- package/dist/core/modules/file/core-file.controller.js.map +1 -0
- package/dist/core/modules/file/core-file.resolver.d.ts +10 -0
- package/dist/core/modules/file/core-file.resolver.js +70 -0
- package/dist/core/modules/file/core-file.resolver.js.map +1 -0
- package/dist/core/modules/file/core-file.service.d.ts +20 -15
- package/dist/core/modules/file/core-file.service.js +58 -22
- package/dist/core/modules/file/core-file.service.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/server/modules/file/file-info.model.d.ts +26 -0
- package/dist/server/modules/file/file-info.model.js +21 -0
- package/dist/server/modules/file/file-info.model.js.map +1 -0
- package/dist/server/modules/file/file.controller.d.ts +2 -3
- package/dist/server/modules/file/file.controller.js +4 -26
- package/dist/server/modules/file/file.controller.js.map +1 -1
- package/dist/server/modules/file/file.module.d.ts +2 -0
- package/dist/server/modules/file/file.module.js +27 -0
- package/dist/server/modules/file/file.module.js.map +1 -0
- package/dist/server/modules/file/file.resolver.d.ts +3 -4
- package/dist/server/modules/file/file.resolver.js +4 -5
- package/dist/server/modules/file/file.resolver.js.map +1 -1
- package/dist/server/modules/file/file.service.js +1 -1
- package/dist/server/modules/file/file.service.js.map +1 -1
- package/dist/server/server.module.js +4 -6
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/common/interfaces/server-options.interface.ts +1 -1
- package/src/core/common/services/module.service.ts +0 -1
- package/src/core/common/types/maybe-promise.type.ts +4 -0
- package/src/core/modules/file/core-file-info.model.ts +40 -0
- package/src/core/modules/file/core-file.controller.ts +34 -0
- package/src/core/modules/file/core-file.resolver.ts +56 -0
- package/src/core/modules/file/core-file.service.ts +80 -27
- package/src/index.ts +4 -1
- package/src/server/modules/file/file-info.model.ts +15 -0
- package/src/server/modules/file/file.controller.ts +7 -36
- package/src/server/modules/file/file.module.ts +17 -0
- package/src/server/modules/file/file.resolver.ts +1 -4
- package/src/server/modules/file/file.service.ts +4 -1
- package/src/server/server.module.ts +6 -8
- package/dist/core/modules/file/file-info.output.js.map +0 -1
- package/src/core/modules/file/file-info.output.ts +0 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "8.6.
|
|
3
|
+
"version": "8.6.15",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApolloDriverConfig } from '@nestjs/apollo';
|
|
2
2
|
import { GqlModuleAsyncOptions } from '@nestjs/graphql';
|
|
3
3
|
import { JwtModuleOptions } from '@nestjs/jwt';
|
|
4
|
-
import { MongooseModuleOptions } from '@nestjs/mongoose
|
|
4
|
+
import { MongooseModuleOptions } from '@nestjs/mongoose';
|
|
5
5
|
import { ServeStaticOptions } from '@nestjs/platform-express/interfaces/serve-static-options.interface';
|
|
6
6
|
import * as SMTPTransport from 'nodemailer/lib/smtp-transport';
|
|
7
7
|
import { MailjetOptions } from './mailjet-options.interface';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Field, ObjectType } from '@nestjs/graphql';
|
|
2
|
+
import { Prop } from '@nestjs/mongoose';
|
|
3
|
+
import { Types } from 'mongoose';
|
|
4
|
+
import { CoreModel } from '../../common/models/core-model.model';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* File info
|
|
8
|
+
*/
|
|
9
|
+
@ObjectType({ description: 'Information about file' })
|
|
10
|
+
export class CoreFileInfo extends CoreModel {
|
|
11
|
+
_id: Types.ObjectId;
|
|
12
|
+
|
|
13
|
+
@Field(() => String, { description: 'ID of the file' })
|
|
14
|
+
id: string = undefined;
|
|
15
|
+
|
|
16
|
+
@Field(() => Number, {
|
|
17
|
+
description:
|
|
18
|
+
'The size of each chunk in bytes. GridFS divides the document into chunks of size chunkSize, ' +
|
|
19
|
+
'except for the last, which is only as large as needed. The default size is 255 kilobytes (kB)',
|
|
20
|
+
nullable: true,
|
|
21
|
+
})
|
|
22
|
+
@Prop({ type: Number, required: false })
|
|
23
|
+
chunkSize: number = undefined;
|
|
24
|
+
|
|
25
|
+
@Field(() => String, { description: 'Content type', nullable: true })
|
|
26
|
+
@Prop({ type: String, required: false })
|
|
27
|
+
contentType?: string = undefined;
|
|
28
|
+
|
|
29
|
+
@Field(() => String, { description: 'Name of the file', nullable: true })
|
|
30
|
+
@Prop({ type: String, required: false })
|
|
31
|
+
filename?: string = undefined;
|
|
32
|
+
|
|
33
|
+
@Field(() => Number, { description: 'The size of the document in bytes', nullable: true })
|
|
34
|
+
@Prop({ type: Number, required: false })
|
|
35
|
+
length: number = undefined;
|
|
36
|
+
|
|
37
|
+
@Field(() => Date, { description: 'The date the file was first stored', nullable: true })
|
|
38
|
+
@Prop({ type: Date, required: false })
|
|
39
|
+
uploadDate: Date = undefined;
|
|
40
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BadRequestException, Controller, Get, NotFoundException, Param, Res } from '@nestjs/common';
|
|
2
|
+
import { User } from '../../../server/modules/user/user.model';
|
|
3
|
+
import { RESTUser } from '../../common/decorators/rest-user.decorator';
|
|
4
|
+
import { CoreFileService } from './core-file.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* File controller
|
|
8
|
+
*/
|
|
9
|
+
@Controller('files')
|
|
10
|
+
export abstract class CoreFileController {
|
|
11
|
+
/**
|
|
12
|
+
* Include services
|
|
13
|
+
*/
|
|
14
|
+
protected constructor(protected fileService: CoreFileService) {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Download file
|
|
18
|
+
*/
|
|
19
|
+
@Get(':filename')
|
|
20
|
+
async getFile(@Param('filename') filename: string, @Res() res, @RESTUser() user: User) {
|
|
21
|
+
if (!filename) {
|
|
22
|
+
throw new BadRequestException('Missing filename for download');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const file = await this.fileService.getFileInfoByName(filename);
|
|
26
|
+
if (!file) {
|
|
27
|
+
throw new NotFoundException('File not found');
|
|
28
|
+
}
|
|
29
|
+
const filestream = await this.fileService.getFileStream(file.id);
|
|
30
|
+
res.header('Content-Type', file.contentType);
|
|
31
|
+
res.header('Content-Disposition', 'attachment; filename=' + file.filename);
|
|
32
|
+
return filestream.pipe(res);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|
2
|
+
import * as GraphQLUpload from 'graphql-upload/GraphQLUpload.js';
|
|
3
|
+
import { CoreFileInfo } from './core-file-info.model';
|
|
4
|
+
import { CoreFileService } from './core-file.service';
|
|
5
|
+
import { FileUpload } from './interfaces/file-upload.interface';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* File resolver
|
|
9
|
+
*/
|
|
10
|
+
@Resolver()
|
|
11
|
+
export class CoreFileResolver {
|
|
12
|
+
/**
|
|
13
|
+
* Integrate services
|
|
14
|
+
*/
|
|
15
|
+
constructor(protected readonly fileService: CoreFileService) {}
|
|
16
|
+
|
|
17
|
+
// ===========================================================================
|
|
18
|
+
// Queries
|
|
19
|
+
// ===========================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get file info
|
|
23
|
+
*/
|
|
24
|
+
@Query(() => CoreFileInfo, { nullable: true })
|
|
25
|
+
async getFileInfo(@Args({ name: 'filename', type: () => String }) filename: string): Promise<any> {
|
|
26
|
+
return await this.fileService.getFileInfoByName(filename);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ===========================================================================
|
|
30
|
+
// Mutations
|
|
31
|
+
// ===========================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Delete file
|
|
35
|
+
*/
|
|
36
|
+
@Mutation(() => CoreFileInfo)
|
|
37
|
+
async deleteFile(@Args({ name: 'filename', type: () => String }) filename: string): Promise<any> {
|
|
38
|
+
return await this.fileService.deleteFileByName(filename);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Upload file
|
|
43
|
+
*/
|
|
44
|
+
@Mutation(() => CoreFileInfo)
|
|
45
|
+
async uploadFile(@Args({ name: 'file', type: () => GraphQLUpload }) file: FileUpload): Promise<any> {
|
|
46
|
+
return await this.fileService.createFile(file);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Upload files
|
|
51
|
+
*/
|
|
52
|
+
@Mutation(() => [CoreFileInfo])
|
|
53
|
+
async uploadFiles(@Args({ name: 'files', type: () => [GraphQLUpload] }) files: FileUpload[]): Promise<any> {
|
|
54
|
+
return await this.fileService.createFiles(files);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -7,10 +7,16 @@ import { getObjectIds, getStringIds } from '../../common/helpers/db.helper';
|
|
|
7
7
|
import { convertFilterArgsToQuery } from '../../common/helpers/filter.helper';
|
|
8
8
|
import { check } from '../../common/helpers/input.helper';
|
|
9
9
|
import { prepareOutput } from '../../common/helpers/service.helper';
|
|
10
|
-
import {
|
|
10
|
+
import { MaybePromise } from '../../common/types/maybe-promise.type';
|
|
11
|
+
import { CoreFileInfo } from './core-file-info.model';
|
|
11
12
|
import { FileServiceOptions } from './interfaces/file-service-options.interface';
|
|
12
13
|
import { FileUpload } from './interfaces/file-upload.interface';
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Type for checking input
|
|
17
|
+
*/
|
|
18
|
+
export type FileInputCheckType = 'file' | 'files' | 'filterArgs' | 'id' | 'filename';
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Abstract core file service
|
|
16
22
|
*/
|
|
@@ -20,16 +26,19 @@ export abstract class CoreFileService {
|
|
|
20
26
|
/**
|
|
21
27
|
* Include MongoDB connection and create File bucket
|
|
22
28
|
*/
|
|
23
|
-
protected constructor(protected readonly connection: Connection,
|
|
24
|
-
this.files = createBucket({
|
|
29
|
+
protected constructor(protected readonly connection: Connection, bucketName = 'fs') {
|
|
30
|
+
this.files = createBucket({ bucketName, connection });
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
34
|
* Save file in DB
|
|
29
35
|
*/
|
|
30
|
-
createFile(file: FileUpload
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
async createFile(file: MaybePromise<FileUpload>, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
|
|
37
|
+
if (!(await this.checkRights(file, { ...serviceOptions, checkInputType: 'file' }))) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return await new Promise(async (resolve, reject) => {
|
|
41
|
+
const { filename, mimetype, encoding, createReadStream } = await file;
|
|
33
42
|
const readStream = createReadStream();
|
|
34
43
|
const options: MongoGridFSOptions = { filename, contentType: mimetype };
|
|
35
44
|
this.files.writeFile(options, readStream, (error, fileInfo) => {
|
|
@@ -41,8 +50,11 @@ export abstract class CoreFileService {
|
|
|
41
50
|
/**
|
|
42
51
|
* Save files in DB
|
|
43
52
|
*/
|
|
44
|
-
async createFiles(files: FileUpload[], serviceOptions?: FileServiceOptions): Promise<
|
|
45
|
-
|
|
53
|
+
async createFiles(files: MaybePromise<FileUpload>[], serviceOptions?: FileServiceOptions): Promise<CoreFileInfo[]> {
|
|
54
|
+
if (!(await this.checkRights(files, { ...serviceOptions, checkInputType: 'files' }))) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const promises: Promise<CoreFileInfo>[] = [];
|
|
46
58
|
for (const file of files) {
|
|
47
59
|
promises.push(this.createFile(file, serviceOptions));
|
|
48
60
|
}
|
|
@@ -52,8 +64,11 @@ export abstract class CoreFileService {
|
|
|
52
64
|
/**
|
|
53
65
|
* Get file infos via filter
|
|
54
66
|
*/
|
|
55
|
-
findFileInfo(filterArgs?: FilterArgs, serviceOptions?: FileServiceOptions): Promise<
|
|
56
|
-
|
|
67
|
+
async findFileInfo(filterArgs?: FilterArgs, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo[]> {
|
|
68
|
+
if (!(await this.checkRights(filterArgs, { ...serviceOptions, checkInputType: 'filterArgs' }))) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return await new Promise((resolve, reject) => {
|
|
57
72
|
const filterQuery = convertFilterArgsToQuery(filterArgs);
|
|
58
73
|
const cursor = this.files.find(filterQuery[0], filterQuery[1]);
|
|
59
74
|
if (!cursor) {
|
|
@@ -68,8 +83,11 @@ export abstract class CoreFileService {
|
|
|
68
83
|
/**
|
|
69
84
|
* Get info about file via file ID
|
|
70
85
|
*/
|
|
71
|
-
getFileInfo(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<
|
|
72
|
-
|
|
86
|
+
async getFileInfo(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
|
|
87
|
+
if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return await new Promise((resolve, reject) => {
|
|
73
91
|
this.files.findById(getObjectIds(id), (error, fileInfo) => {
|
|
74
92
|
error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
|
|
75
93
|
});
|
|
@@ -79,8 +97,11 @@ export abstract class CoreFileService {
|
|
|
79
97
|
/**
|
|
80
98
|
* Get info about file via filename
|
|
81
99
|
*/
|
|
82
|
-
getFileInfoByName(filename: string, serviceOptions?: FileServiceOptions): Promise<
|
|
83
|
-
|
|
100
|
+
async getFileInfoByName(filename: string, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
|
|
101
|
+
if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return await new Promise((resolve, reject) => {
|
|
84
105
|
this.files.findOne({ filename }, (error, fileInfo) => {
|
|
85
106
|
error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
|
|
86
107
|
});
|
|
@@ -90,22 +111,34 @@ export abstract class CoreFileService {
|
|
|
90
111
|
/**
|
|
91
112
|
* Get file stream (for big files) via file ID
|
|
92
113
|
*/
|
|
93
|
-
getFileStream(id: string | Types.ObjectId,
|
|
94
|
-
|
|
114
|
+
async getFileStream(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions) {
|
|
115
|
+
if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return this.files.openDownloadStream(getObjectIds(id));
|
|
95
119
|
}
|
|
96
120
|
|
|
97
121
|
/**
|
|
98
122
|
* Get file stream (for big files) via filename
|
|
99
123
|
*/
|
|
100
|
-
getFileStreamByName(
|
|
124
|
+
async getFileStreamByName(
|
|
125
|
+
filename: string,
|
|
126
|
+
serviceOptions?: FileServiceOptions
|
|
127
|
+
): Promise<GridFSBucketReadStreamOptions> {
|
|
128
|
+
if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
101
131
|
return this.files.readFile({ filename });
|
|
102
132
|
}
|
|
103
133
|
|
|
104
134
|
/**
|
|
105
135
|
* Get file buffer (for small files) via file ID
|
|
106
136
|
*/
|
|
107
|
-
getBuffer(id: string | Types.ObjectId): Promise<Buffer> {
|
|
108
|
-
|
|
137
|
+
async getBuffer(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<Buffer> {
|
|
138
|
+
if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return await new Promise((resolve, reject) => {
|
|
109
142
|
this.files.readFile({ _id: getObjectIds(id) }, (error, buffer) => {
|
|
110
143
|
error ? reject(error) : resolve(buffer);
|
|
111
144
|
});
|
|
@@ -115,8 +148,11 @@ export abstract class CoreFileService {
|
|
|
115
148
|
/**
|
|
116
149
|
* Get file buffer (for small files) via file ID
|
|
117
150
|
*/
|
|
118
|
-
getBufferByName(filename: string): Promise<Buffer> {
|
|
119
|
-
|
|
151
|
+
async getBufferByName(filename: string, serviceOptions?: FileServiceOptions): Promise<Buffer> {
|
|
152
|
+
if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return await new Promise((resolve, reject) => {
|
|
120
156
|
this.files.readFile({ filename }, (error, buffer) => {
|
|
121
157
|
error ? reject(error) : resolve(buffer);
|
|
122
158
|
});
|
|
@@ -126,8 +162,11 @@ export abstract class CoreFileService {
|
|
|
126
162
|
/**
|
|
127
163
|
* Delete file reference of avatar
|
|
128
164
|
*/
|
|
129
|
-
deleteFile(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<
|
|
130
|
-
|
|
165
|
+
async deleteFile(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
|
|
166
|
+
if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return await new Promise((resolve, reject) => {
|
|
131
170
|
return this.files.unlink(getObjectIds(id), (error, fileInfo) => {
|
|
132
171
|
error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
|
|
133
172
|
});
|
|
@@ -137,7 +176,10 @@ export abstract class CoreFileService {
|
|
|
137
176
|
/**
|
|
138
177
|
* Delete file reference of avatar
|
|
139
178
|
*/
|
|
140
|
-
async deleteFileByName(filename: string, serviceOptions?: FileServiceOptions): Promise<
|
|
179
|
+
async deleteFileByName(filename: string, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
|
|
180
|
+
if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
141
183
|
const fileInfo = await this.getFileInfoByName(filename);
|
|
142
184
|
if (!fileInfo) {
|
|
143
185
|
throw new NotFoundException('File not found with filename ' + filename);
|
|
@@ -149,22 +191,33 @@ export abstract class CoreFileService {
|
|
|
149
191
|
// Helper methods
|
|
150
192
|
// ===================================================================================================================
|
|
151
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Check rights before processing file handling
|
|
196
|
+
* Can throw an exception if the rights do not fit
|
|
197
|
+
*/
|
|
198
|
+
protected checkRights(
|
|
199
|
+
input: any,
|
|
200
|
+
options?: FileServiceOptions & { checkInputType: FileInputCheckType }
|
|
201
|
+
): MaybePromise<boolean> {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
152
205
|
/**
|
|
153
206
|
* Prepare output before return
|
|
154
207
|
*/
|
|
155
|
-
protected async prepareOutput(fileInfo:
|
|
208
|
+
protected async prepareOutput(fileInfo: CoreFileInfo | CoreFileInfo[], options?: FileServiceOptions) {
|
|
156
209
|
if (!fileInfo) {
|
|
157
210
|
return fileInfo;
|
|
158
211
|
}
|
|
159
212
|
this.setId(fileInfo);
|
|
160
|
-
fileInfo = await prepareOutput(fileInfo, { targetModel:
|
|
213
|
+
fileInfo = await prepareOutput(fileInfo, { targetModel: CoreFileInfo });
|
|
161
214
|
return check(fileInfo, options?.currentUser, { roles: options?.roles });
|
|
162
215
|
}
|
|
163
216
|
|
|
164
217
|
/**
|
|
165
218
|
* Set file info ID via _id
|
|
166
219
|
*/
|
|
167
|
-
protected setId(fileInfo:
|
|
220
|
+
protected setId(fileInfo: CoreFileInfo | CoreFileInfo[]) {
|
|
168
221
|
if (Array.isArray(fileInfo)) {
|
|
169
222
|
fileInfo.forEach((item) => {
|
|
170
223
|
if (typeof item === 'object') {
|
package/src/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ export * from './core/common/services/template.service';
|
|
|
56
56
|
export * from './core/common/types/core-model-constructor.type';
|
|
57
57
|
export * from './core/common/types/field-selection.type';
|
|
58
58
|
export * from './core/common/types/ids.type';
|
|
59
|
+
export * from './core/common/types/maybe-promise.type';
|
|
59
60
|
export * from './core/common/types/plain-input.type';
|
|
60
61
|
export * from './core/common/types/require-only-one.type';
|
|
61
62
|
export * from './core/common/types/required-at-least-one.type';
|
|
@@ -84,8 +85,10 @@ export * from './core/modules/auth/jwt.strategy';
|
|
|
84
85
|
|
|
85
86
|
export * from './core/modules/file/interfaces/file-service-options.interface';
|
|
86
87
|
export * from './core/modules/file/interfaces/file-upload.interface';
|
|
88
|
+
export * from './core/modules/file/core-file.controller';
|
|
89
|
+
export * from './core/modules/file/core-file.resolver';
|
|
87
90
|
export * from './core/modules/file/core-file.service';
|
|
88
|
-
export * from './core/modules/file/file-info.
|
|
91
|
+
export * from './core/modules/file/core-file-info.model';
|
|
89
92
|
|
|
90
93
|
// =====================================================================================================================
|
|
91
94
|
// Core - Modules - User
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ObjectType } from '@nestjs/graphql';
|
|
2
|
+
import { Schema as MongooseSchema, SchemaFactory } from '@nestjs/mongoose';
|
|
3
|
+
import { CoreFileInfo } from '../../../core/modules/file/core-file-info.model';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* File info model
|
|
7
|
+
*/
|
|
8
|
+
@ObjectType({ description: 'Information about file' })
|
|
9
|
+
@MongooseSchema({ collection: 'fs.files' })
|
|
10
|
+
export class FileInfo extends CoreFileInfo {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* File info schema
|
|
14
|
+
*/
|
|
15
|
+
export const FileInfoSchema = SchemaFactory.createForClass(FileInfo);
|
|
@@ -1,34 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BadRequestException,
|
|
3
|
-
Body,
|
|
4
|
-
Controller,
|
|
5
|
-
Get,
|
|
6
|
-
NotFoundException,
|
|
7
|
-
Param,
|
|
8
|
-
Post,
|
|
9
|
-
Res,
|
|
10
|
-
UploadedFiles,
|
|
11
|
-
UseInterceptors,
|
|
12
|
-
} from '@nestjs/common';
|
|
1
|
+
import { Body, Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
|
|
13
2
|
import { FilesInterceptor } from '@nestjs/platform-express';
|
|
14
3
|
import { diskStorage } from 'multer';
|
|
15
4
|
import envConfig from '../../../config.env';
|
|
16
|
-
import { RESTUser } from '../../../core/common/decorators/rest-user.decorator';
|
|
17
5
|
import { Roles } from '../../../core/common/decorators/roles.decorator';
|
|
18
6
|
import { RoleEnum } from '../../../core/common/enums/role.enum';
|
|
19
7
|
import { multerRandomFileName } from '../../../core/common/helpers/file.helper';
|
|
20
|
-
import {
|
|
8
|
+
import { CoreFileController } from '../../../core/modules/file/core-file.controller';
|
|
21
9
|
import { FileService } from './file.service';
|
|
22
10
|
|
|
23
11
|
/**
|
|
24
12
|
* File controller
|
|
25
13
|
*/
|
|
14
|
+
@Roles(RoleEnum.S_USER)
|
|
26
15
|
@Controller('files')
|
|
27
|
-
export class FileController {
|
|
16
|
+
export class FileController extends CoreFileController {
|
|
28
17
|
/**
|
|
29
18
|
* Include services
|
|
30
19
|
*/
|
|
31
|
-
constructor(protected fileService: FileService) {
|
|
20
|
+
constructor(protected fileService: FileService) {
|
|
21
|
+
super(fileService);
|
|
22
|
+
}
|
|
32
23
|
|
|
33
24
|
/**
|
|
34
25
|
* Upload files via REST as an alternative to uploading via GraphQL (see file.resolver.ts)
|
|
@@ -53,24 +44,4 @@ export class FileController {
|
|
|
53
44
|
uploadFiles(@UploadedFiles() files, @Body() fields: any) {
|
|
54
45
|
console.log('Saved file info', JSON.stringify({ files, fields }, null, 2));
|
|
55
46
|
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Download file
|
|
59
|
-
*/
|
|
60
|
-
@Roles(RoleEnum.ADMIN)
|
|
61
|
-
@Get(':filename')
|
|
62
|
-
async getFile(@Param('filename') filename: string, @Res() res, @RESTUser() user: User) {
|
|
63
|
-
if (!filename) {
|
|
64
|
-
throw new BadRequestException('Missing filename for download');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const file = await this.fileService.getFileInfoByName(filename);
|
|
68
|
-
if (!file) {
|
|
69
|
-
throw new NotFoundException('File not found');
|
|
70
|
-
}
|
|
71
|
-
const filestream = await this.fileService.getFileStream(file.id);
|
|
72
|
-
res.header('Content-Type', file.contentType);
|
|
73
|
-
res.header('Content-Disposition', 'attachment; filename=' + file.filename);
|
|
74
|
-
return filestream.pipe(res);
|
|
75
|
-
}
|
|
76
47
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { MongooseModule } from '@nestjs/mongoose';
|
|
3
|
+
import { FileInfo, FileInfoSchema } from './file-info.model';
|
|
4
|
+
import { FileController } from './file.controller';
|
|
5
|
+
import { FileResolver } from './file.resolver';
|
|
6
|
+
import { FileService } from './file.service';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* File module
|
|
10
|
+
*/
|
|
11
|
+
@Module({
|
|
12
|
+
imports: [MongooseModule.forFeature([{ name: FileInfo.name, schema: FileInfoSchema }])],
|
|
13
|
+
controllers: [FileController],
|
|
14
|
+
providers: [FileService, FileResolver],
|
|
15
|
+
exports: [MongooseModule, FileService],
|
|
16
|
+
})
|
|
17
|
+
export class FileModule {}
|
|
@@ -3,8 +3,8 @@ import { createWriteStream } from 'fs';
|
|
|
3
3
|
import * as GraphQLUpload from 'graphql-upload/GraphQLUpload.js';
|
|
4
4
|
import { Roles } from '../../../core/common/decorators/roles.decorator';
|
|
5
5
|
import { RoleEnum } from '../../../core/common/enums/role.enum';
|
|
6
|
-
import { FileInfo } from '../../../core/modules/file/file-info.output';
|
|
7
6
|
import { FileUpload } from '../../../core/modules/file/interfaces/file-upload.interface';
|
|
7
|
+
import { FileInfo } from './file-info.model';
|
|
8
8
|
import { FileService } from './file.service';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -49,9 +49,6 @@ export class FileResolver {
|
|
|
49
49
|
@Roles(RoleEnum.ADMIN)
|
|
50
50
|
@Mutation(() => FileInfo)
|
|
51
51
|
async uploadFile(@Args({ name: 'file', type: () => GraphQLUpload }) file: FileUpload) {
|
|
52
|
-
const { filename, mimetype, encoding, createReadStream } = file;
|
|
53
|
-
|
|
54
|
-
// Save file in DB
|
|
55
52
|
return await this.fileService.createFile(file);
|
|
56
53
|
}
|
|
57
54
|
|
|
@@ -3,9 +3,12 @@ import { InjectConnection } from '@nestjs/mongoose';
|
|
|
3
3
|
import { Connection } from 'mongoose';
|
|
4
4
|
import { CoreFileService } from '../../../core/modules/file/core-file.service';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* File service
|
|
8
|
+
*/
|
|
6
9
|
@Injectable()
|
|
7
10
|
export class FileService extends CoreFileService {
|
|
8
11
|
constructor(@InjectConnection() protected readonly connection: Connection) {
|
|
9
|
-
super(connection, '
|
|
12
|
+
super(connection, 'fs');
|
|
10
13
|
}
|
|
11
14
|
}
|
|
@@ -3,9 +3,7 @@ import envConfig from '../config.env';
|
|
|
3
3
|
import { CoreModule } from '../core.module';
|
|
4
4
|
import { CoreAuthService } from '../core/modules/auth/services/core-auth.service';
|
|
5
5
|
import { AuthModule } from './modules/auth/auth.module';
|
|
6
|
-
import {
|
|
7
|
-
import { FileResolver } from './modules/file/file.resolver';
|
|
8
|
-
import { FileService } from './modules/file/file.service';
|
|
6
|
+
import { FileModule } from './modules/file/file.module';
|
|
9
7
|
import { ServerController } from './server.controller';
|
|
10
8
|
|
|
11
9
|
/**
|
|
@@ -23,15 +21,15 @@ import { ServerController } from './server.controller';
|
|
|
23
21
|
// Include AuthModule for authorization handling,
|
|
24
22
|
// which will also include UserModule
|
|
25
23
|
AuthModule.forRoot(envConfig.jwt),
|
|
24
|
+
|
|
25
|
+
// Include FileModule for file handling
|
|
26
|
+
FileModule,
|
|
26
27
|
],
|
|
27
28
|
|
|
28
29
|
// Include REST controllers
|
|
29
|
-
controllers: [
|
|
30
|
-
|
|
31
|
-
// Include resolvers, services and other providers
|
|
32
|
-
providers: [FileService, FileResolver],
|
|
30
|
+
controllers: [ServerController],
|
|
33
31
|
|
|
34
32
|
// Export modules for reuse in other modules
|
|
35
|
-
exports: [CoreModule, AuthModule],
|
|
33
|
+
exports: [CoreModule, AuthModule, FileModule],
|
|
36
34
|
})
|
|
37
35
|
export class ServerModule {}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-info.output.js","sourceRoot":"","sources":["../../../../src/core/modules/file/file-info.output.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6CAAoD;AAEpD,2EAAiE;AAMjE,IAAa,QAAQ,GAArB,MAAa,QAAS,SAAQ,4BAAS;IAAvC;;QAIE,OAAE,GAAW,SAAS,CAAC;QAGvB,WAAM,GAAW,SAAS,CAAC;QAG3B,cAAS,GAAW,SAAS,CAAC;QAG9B,aAAQ,GAAY,SAAS,CAAC;QAG9B,gBAAW,GAAY,SAAS,CAAC;IACnC,CAAC;CAAA,CAAA;AAbC;IADC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;;oCACzC;AAGvB;IADC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,6BAA6B,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;wCACzD;AAG3B;IADC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,mBAAmB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CAC5C;AAG9B;IADC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,kBAAkB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CAC3C;AAG9B;IADC,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6CACpC;AAhBtB,QAAQ;IADpB,IAAA,oBAAU,EAAC,EAAE,WAAW,EAAE,mCAAmC,EAAE,CAAC;GACpD,QAAQ,CAiBpB;AAjBY,4BAAQ"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Field, ObjectType } from '@nestjs/graphql';
|
|
2
|
-
import { Types } from 'mongoose';
|
|
3
|
-
import { CoreModel } from '../../common/models/core-model.model';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* File info (output)
|
|
7
|
-
*/
|
|
8
|
-
@ObjectType({ description: 'Information about attachment file' })
|
|
9
|
-
export class FileInfo extends CoreModel {
|
|
10
|
-
_id: Types.ObjectId;
|
|
11
|
-
|
|
12
|
-
@Field(() => String, { description: 'ID of the file in bytes' })
|
|
13
|
-
id: string = undefined;
|
|
14
|
-
|
|
15
|
-
@Field(() => Number, { description: 'Length of the file in bytes', nullable: true })
|
|
16
|
-
length: number = undefined;
|
|
17
|
-
|
|
18
|
-
@Field(() => Number, { description: 'Size of the chunk', nullable: true })
|
|
19
|
-
chunkSize: number = undefined;
|
|
20
|
-
|
|
21
|
-
@Field(() => String, { description: 'Name of the file', nullable: true })
|
|
22
|
-
filename?: string = undefined;
|
|
23
|
-
|
|
24
|
-
@Field(() => String, { description: 'Content type', nullable: true })
|
|
25
|
-
contentType?: string = undefined;
|
|
26
|
-
}
|