Skip to content

Commit 6bc4b0c

Browse files
committed
WIP filestorage
1 parent ca36930 commit 6bc4b0c

File tree

16 files changed

+2068
-726
lines changed

16 files changed

+2068
-726
lines changed

Makefile

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
IMGNAME?=ghcr.io/libertech-fr/teaket:latest
22
APPNAME?=teaket
3+
MINIO_ACCESS_KEY?=AKIAIOSFODNN7EXAMPLE
4+
MINIO_SECRET_KEY?=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
35

46
.DEFAULT_GOAL := help
57
help:
@@ -21,15 +23,33 @@ dbs: ## Start databases
2123
@docker volume create $(APPNAME)-redis
2224
@docker run -d --rm \
2325
--name $(APPNAME)-redis \
26+
-v $(APPNAME)-redis:/data \
2427
--network dev \
2528
-p 6379:6379 \
26-
redis
27-
@sleep 1
29+
redis || true
30+
@docker volume create $(APPNAME)-minio
31+
@docker run -d --rm \
32+
-p 9000:9000 \
33+
-p 9090:9090 \
34+
--name $(APPNAME)-minio \
35+
--network dev \
36+
-v $(APPNAME)-minio:/data \
37+
-e "MINIO_ACCESS_KEY=$(MINIO_ACCESS_KEY)" \
38+
-e "MINIO_SECRET_KEY=$(MINIO_SECRET_KEY)" \
39+
minio/minio server /data --console-address ":9090" || true
40+
@docker run --rm -it \
41+
--network dev \
42+
-e MINIO_BUCKET="teaket" \
43+
--entrypoint sh minio/mc -c "\
44+
mc config host add myminio http://teaket-minio:9000 \$(MINIO_ACCESS_KEY) \$(MINIO_SECRET_KEY) && \
45+
(mc mb myminio/teaket || true) \
46+
" || true
2847
@docker exec -it teaket-mongodb mongo --eval "rs.initiate({_id: 'rs0', members: [{_id: 0, host: '127.0.0.1:27017'}]})" || true
2948

3049
stop-dbs: ## Stop databases
3150
@docker stop $(APPNAME)-redis || true
3251
@docker stop $(APPNAME)-mongodb || true
52+
@docker stop $(APPNAME)-minio || true
3353

3454
buildseeds: ## Build populate image
3555
docker build -t seeding -f ./populate/Dockerfile ./populate

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"radash": "^11.0.0"
3030
},
3131
"devDependencies": {
32-
"@types/node": "^20.5.7",
32+
"@types/node": "^18.16.0",
3333
"@typescript-eslint/eslint-plugin": "^6.5.0",
3434
"@typescript-eslint/parser": "^6.5.0",
3535
"eslint": "^8.48.0",

service/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
"@nestjs/passport": "^10.0.1",
3030
"@nestjs/platform-express": "^9.0.0",
3131
"@nestjs/swagger": "^7.1.10",
32+
"@streamkits/nestjs_module_factorydrive": "^0.0.7",
33+
"@streamkits/nestjs_module_factorydrive-s3": "^0.0.7",
3234
"@streamkits/nestjs_module_scrud": "^0.0.16",
3335
"@typegoose/auto-increment": "^3.4.0",
3436
"argon2": "^0.31.1",
@@ -57,7 +59,8 @@
5759
"@types/cookie-parser": "^1.4.3",
5860
"@types/express": "^4.17.13",
5961
"@types/jest": "29.5.0",
60-
"@types/node": "18.15.11",
62+
"@types/multer": "^1.4.7",
63+
"@types/node": "18.16.0",
6164
"@types/passport": "^1.0.12",
6265
"@types/passport-jwt": "^3.0.9",
6366
"@types/passport-local": "^1.0.35",

service/src/_common/abstracts/abstract.service.schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
229229
.findByIdAndUpdate<Query<T | null, T, any, T>>({ _id }, {
230230
...update,
231231
metadata: {
232-
lastUpdatedBy: this.request.user || 'anonymous',
232+
lastUpdatedBy: this.request?.user || 'anonymous',
233233
lastUpdatedAt: new Date(),
234234
},
235235
}, {

service/src/app.module.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { AcceptLanguageResolver, CookieResolver, HeaderResolver, I18nModule, Que
1818
import { join } from 'path'
1919
import { ExtensionsModule } from '~/extensions/extensions.module'
2020
import { EventEmitterModule } from '@nestjs/event-emitter'
21+
import { FactorydriveModule, FactorydriveService } from '@streamkits/nestjs_module_factorydrive'
22+
import { AwsS3Storage } from '@streamkits/nestjs_module_factorydrive-s3'
2123

2224
@Module({
2325
imports: [
@@ -53,6 +55,22 @@ import { EventEmitterModule } from '@nestjs/event-emitter'
5355
},
5456
}),
5557
}),
58+
FactorydriveModule.forRootAsync({
59+
imports: [ConfigModule],
60+
inject: [ConfigService],
61+
useFactory: async (config: ConfigService) => ({
62+
...config.get('factorydrive.options'),
63+
}),
64+
}),
65+
// S3Module.forRootAsync({
66+
// imports: [ConfigModule],
67+
// inject: [ConfigService],
68+
// useFactory: (config: ConfigService) => ({
69+
// config: {
70+
// ...config.get<S3ClientConfig>('s3.options'),
71+
// },
72+
// }),
73+
// }),
5674
EventEmitterModule.forRoot({
5775
wildcard: true,
5876
delimiter: '.',
@@ -104,4 +122,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter'
104122
],
105123
})
106124
export class AppModule {
125+
public constructor(storage: FactorydriveService) {
126+
storage.registerDriver('s3', AwsS3Storage)
127+
}
107128
}

service/src/config.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { IAuthModuleOptions } from '@nestjs/passport'
44
import { SwaggerCustomOptions } from '@nestjs/swagger'
55
import { HelmetOptions } from 'helmet'
66
import { RedisOptions } from 'ioredis'
7+
import * as process from 'process'
8+
import { StorageManagerConfig } from '@streamkits/nestjs_module_factorydrive'
79

810
export interface MongoosePlugin {
911
package: string
@@ -31,6 +33,12 @@ export interface ConfigInstance {
3133
// oidc: {
3234
// options: BuildOpenIdClientOptions
3335
// }
36+
factorydrive: {
37+
options: StorageManagerConfig
38+
}
39+
// s3: {
40+
// options: S3ClientConfig
41+
// }
3442
i18n: {
3543
fallbackLanguage: string
3644
}
@@ -103,6 +111,27 @@ export default (): ConfigInstance => ({
103111
// },
104112
// },
105113
// },
114+
factorydrive: {
115+
options: {
116+
default: 'local',
117+
disks: {
118+
local: {
119+
driver: 'local',
120+
config: {
121+
root: process.cwd() + '/storage',
122+
},
123+
},
124+
// s3: {
125+
// driver: 's3',
126+
// config: {
127+
// key: 'AKIAIOSFODNN7EXAMPLE',
128+
// secret: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
129+
// endpoint: 'http://teaket-minio:9000/',
130+
// },
131+
// },
132+
},
133+
},
134+
},
106135
i18n: {
107136
fallbackLanguage: 'en',
108137
},

service/src/core/core.module.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { PreferencesModule } from './preferences/preferences.module'
1111
import { FilestorageModule } from './filestorage/filestorage.module'
1212
import { EntitiesModule } from '~/core/entities/entities.module'
1313
import { IdentitiesModule } from '~/core/identities/identities.module'
14-
import { ApiTags } from '@nestjs/swagger'
1514

1615
@Module({
1716
imports: [

service/src/core/filestorage/_dto/filestorage.dto.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ApiProperty, PartialType } from '@nestjs/swagger'
22
import { CustomFieldsDto } from '~/_common/abstracts/dto/custom-fields.dto'
3-
import { IsMongoId, IsString, IsEnum, IsOptional, IsObject, IsBoolean, IsNotEmpty } from 'class-validator'
3+
import { IsMongoId, IsString, IsEnum, IsOptional, IsObject, IsBoolean, IsNotEmpty, IsMimeType } from 'class-validator'
44
import { FsType, FsTypeList } from '~/core/filestorage/_enum/fs-type.enum'
55

66
export class FilestorageCreateDto extends CustomFieldsDto {
@@ -24,6 +24,11 @@ export class FilestorageCreateDto extends CustomFieldsDto {
2424
@ApiProperty()
2525
comments?: string
2626

27+
@IsOptional()
28+
@IsMimeType()
29+
@ApiProperty()
30+
mime?: string
31+
2732
@IsOptional()
2833
@IsBoolean()
2934
@ApiProperty()

service/src/core/filestorage/_schemas/filestorage.schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ export class Filestorage extends AbstractSchema {
1414
})
1515
public type: FsType
1616

17-
// text/html, application/pdf
1817
@Prop({
1918
type: String,
19+
default: 'application/octet-stream',
2020
})
21-
public mime?: string
21+
public mime: string
2222

2323
@Prop({
2424
required: true,

service/src/core/filestorage/filestorage.controller.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
import { Body, Controller, Delete, Get, HttpStatus, Param, Patch, Post, Res } from '@nestjs/common'
1+
import {
2+
Body,
3+
Controller,
4+
Delete,
5+
FileTypeValidator,
6+
Get,
7+
HttpStatus,
8+
MaxFileSizeValidator,
9+
Param,
10+
ParseFilePipe,
11+
Patch,
12+
Post,
13+
Res,
14+
UploadedFile,
15+
UseInterceptors,
16+
} from '@nestjs/common'
217
import { FilestorageCreateDto, FilestorageDto, FilestorageUpdateDto } from './_dto/filestorage.dto'
318
import { FilestorageService } from './filestorage.service'
419
import { AbstractController } from '~/_common/abstracts/abstract.controller'
@@ -19,7 +34,10 @@ import { PickProjectionHelper } from '~/_common/helpers/pick-projection.helper'
1934
import { ApiReadResponseDecorator } from '~/_common/decorators/api-read-response.decorator'
2035
import { ApiUpdateDecorator } from '~/_common/decorators/api-update.decorator'
2136
import { ApiDeletedResponseDecorator } from '~/_common/decorators/api-deleted-response.decorator'
37+
import { Public } from '~/_common/decorators/public.decorator'
38+
import { FileInterceptor } from '@nestjs/platform-express'
2239

40+
@Public()
2341
@ApiTags('core')
2442
@Controller('filestorage')
2543
export class FilestorageController extends AbstractController {
@@ -35,9 +53,16 @@ export class FilestorageController extends AbstractController {
3553
}
3654

3755
@Post()
56+
@UseInterceptors(FileInterceptor('file'))
3857
@ApiCreateDecorator(FilestorageCreateDto, FilestorageDto)
39-
public async create(@Res() res: Response, @Body() body: FilestorageCreateDto): Promise<Response> {
40-
const data = await this._service.create(body)
58+
public async create(
59+
@Res() res: Response,
60+
@Body() body: FilestorageCreateDto,
61+
@UploadedFile(
62+
new ParseFilePipe({ fileIsRequired: false }),
63+
) file?: Express.Multer.File,
64+
): Promise<Response> {
65+
const data = await this._service.create({ ...body, file })
4166
return res.status(HttpStatus.CREATED).json({
4267
statusCode: HttpStatus.CREATED,
4368
data,
@@ -66,6 +91,15 @@ export class FilestorageController extends AbstractController {
6691
})
6792
}
6893

94+
@Get(':_id([0-9a-fA-F]{24})/raw')
95+
@ApiParam({ name: '_id', type: String })
96+
@ApiReadResponseDecorator(FilestorageDto)
97+
public async readRawData(@Param('_id', ObjectIdValidationPipe) _id: Types.ObjectId, @Res() res: Response): Promise<void> {
98+
const [data, stream] = await this._service.findByIdWithRawData(_id)
99+
res.setHeader('Content-Type', data.mime || 'application/octet-stream')
100+
stream.pipe(res)
101+
}
102+
69103
@Patch(':_id([0-9a-fA-F]{24})')
70104
@ApiParam({ name: '_id', type: String })
71105
@ApiUpdateDecorator(FilestorageUpdateDto, FilestorageDto)

0 commit comments

Comments
 (0)