-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
9,655 additions
and
6,091 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,3 +36,5 @@ yarn-error.log* | |
# Misc | ||
.DS_Store | ||
*.pem | ||
|
||
uploads/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,13 @@ | ||
DATABASE_URL="postgres://postgres:guidebook@localhost:5432/guidebook" | ||
JWT_SECRET= | ||
JWT_REFRESH_SECRET= | ||
|
||
# AWS | ||
AWS_ENDPOINT=https://s3.eu-central-1.amazonaws.com | ||
AWS_REGION=eu-central-1 | ||
AWS_ACCESS_KEY_ID=2ff0700865cc5c7192db13dbb37434d5 | ||
AWS_SECRET_ACCESS_KEY=23924d388c6a5720a32399e80ec191fb | ||
AWS_BUCKET_NAME=test-bucket | ||
|
||
# LOCAL UPLOADS | ||
LOCAL_UPLOAD_FOLDER=uploads |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
examples/common_nestjs_remix/apps/api/src/common/configuration/aws.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { registerAs } from "@nestjs/config"; | ||
import { Static, Type } from "@sinclair/typebox"; | ||
import { Value } from "@sinclair/typebox/value"; | ||
|
||
const schema = Type.Object({ | ||
region: Type.String(), | ||
accessKeyId: Type.String(), | ||
secretAccessKey: Type.String(), | ||
bucketName: Type.String(), | ||
endpoint: Type.String(), | ||
}); | ||
|
||
type AWSConfig = Static<typeof schema>; | ||
|
||
export default registerAs("aws", (): AWSConfig => { | ||
const values = { | ||
region: process.env.AWS_REGION, | ||
accessKeyId: process.env.AWS_ACCESS_KEY_ID, | ||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, | ||
bucketName: process.env.AWS_BUCKET_NAME, | ||
endpoint: process.env.AWS_ENDPOINT, | ||
}; | ||
|
||
return Value.Decode(schema, values); | ||
}); |
17 changes: 17 additions & 0 deletions
17
examples/common_nestjs_remix/apps/api/src/common/configuration/local_file.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { registerAs } from "@nestjs/config"; | ||
import { Static, Type } from "@sinclair/typebox"; | ||
import { Value } from "@sinclair/typebox/value"; | ||
|
||
const schema = Type.Object({ | ||
uploadDir: Type.Optional(Type.String()), | ||
}); | ||
|
||
type LocalFileConfig = Static<typeof schema>; | ||
|
||
export default registerAs("localFile", (): LocalFileConfig => { | ||
const values = { | ||
uploadDir: process.env.LOCAL_UPLOAD_FOLDER, | ||
}; | ||
|
||
return Value.Decode(schema, values); | ||
}); |
7 changes: 7 additions & 0 deletions
7
examples/common_nestjs_remix/apps/api/src/common/schemas/common-file.schema.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { files } from "src/storage/schema"; | ||
import { createSelectSchema } from "drizzle-typebox"; | ||
import { Static } from "@sinclair/typebox"; | ||
|
||
export const commonFileSchema = createSelectSchema(files); | ||
|
||
export type CommonFile = Static<typeof commonFileSchema>; |
45 changes: 45 additions & 0 deletions
45
examples/common_nestjs_remix/apps/api/src/files/__tests__/s3-file.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { DatabasePg } from "src/common"; | ||
import { TestContext, createUnitTest } from "test/create-unit-test"; | ||
import { truncateAllTables } from "test/helpers/test-helpers"; | ||
import { S3FileService } from "../s3-file.service"; | ||
import { files } from "../../storage/schema"; | ||
import fs from "node:fs"; | ||
import { FileService } from "../file.service"; | ||
|
||
describe("S3FileService", () => { | ||
let testContext: TestContext; | ||
let s3FileService: S3FileService; | ||
let db: DatabasePg; | ||
|
||
beforeAll(async () => { | ||
testContext = await createUnitTest(); | ||
s3FileService = testContext.module.get(FileService); | ||
db = testContext.db; | ||
}, 70000); | ||
|
||
afterAll(async () => { | ||
await testContext.teardown(); | ||
}); | ||
|
||
afterEach(async () => { | ||
await truncateAllTables(db); | ||
}); | ||
|
||
describe("upload file", () => { | ||
it("should upload a file", async () => { | ||
const buffer = fs.readFileSync(`${__dirname}/test.txt`); | ||
const file = { | ||
buffer, | ||
originalname: "test.txt", | ||
mimetype: "text/plain", | ||
} as Express.Multer.File; | ||
|
||
const result = await s3FileService.uploadFile(file); | ||
|
||
const [savedFile] = await db.select().from(files).limit(1); | ||
console.log("savedFile", savedFile); | ||
|
||
expect(result.id).toBe(expect.any(String)); | ||
}); | ||
}); | ||
}); |
Empty file.
51 changes: 51 additions & 0 deletions
51
examples/common_nestjs_remix/apps/api/src/files/api/file.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { | ||
Controller, | ||
Delete, | ||
Get, | ||
Param, | ||
ParseFilePipeBuilder, | ||
Post, | ||
UploadedFile, | ||
UseInterceptors, | ||
} from "@nestjs/common"; | ||
import { BaseResponse, UUIDSchema } from "../../common"; | ||
import { Validate } from "nestjs-typebox"; | ||
import { FileInterceptor } from "@nestjs/platform-express"; | ||
import { FileService } from "../file.service"; | ||
|
||
@Controller("files") | ||
export class FileController { | ||
constructor(private readonly fileService: FileService) {} | ||
|
||
@Post("upload") | ||
@UseInterceptors(FileInterceptor("file")) | ||
async uploadFile( | ||
@UploadedFile( | ||
new ParseFilePipeBuilder() | ||
.addFileTypeValidator({ fileType: ".(png|jpeg|jpg)" }) | ||
.addMaxSizeValidator({ maxSize: 10 * 1024 * 1024 }) | ||
.build(), | ||
) | ||
file: Express.Multer.File, | ||
) { | ||
const result = await this.fileService.uploadFile(file); | ||
|
||
return new BaseResponse(result); | ||
} | ||
|
||
@Get("download/:id") | ||
@Validate({ | ||
request: [{ type: "param", name: "id", schema: UUIDSchema }], | ||
}) | ||
async downloadFile(@Param("id") id: string) { | ||
return new BaseResponse(await this.fileService.getFileUrl(id)); | ||
} | ||
|
||
@Delete(":id") | ||
@Validate({ | ||
request: [{ type: "param", name: "id", schema: UUIDSchema }], | ||
}) | ||
async deleteFile(@Param("id") id: string) { | ||
await this.fileService.deleteFile(id); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
examples/common_nestjs_remix/apps/api/src/files/file.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { CommonFile } from "../common/schemas/common-file.schema"; | ||
|
||
export abstract class FileService { | ||
abstract uploadFile(file: Express.Multer.File): Promise<CommonFile>; | ||
|
||
abstract deleteFile(id: string): Promise<void>; | ||
|
||
abstract getFileUrl(id: string): Promise<{ url: string }>; | ||
} |
15 changes: 15 additions & 0 deletions
15
examples/common_nestjs_remix/apps/api/src/files/files.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Module } from "@nestjs/common"; | ||
import { FileService } from "./file.service"; | ||
import { LocalFileService } from "./local-file.service"; | ||
import { FileController } from "./api/file.controller"; | ||
|
||
@Module({ | ||
providers: [ | ||
{ | ||
provide: FileService, | ||
useClass: LocalFileService, | ||
}, | ||
], | ||
controllers: [FileController], | ||
}) | ||
export class FilesModule {} |
94 changes: 94 additions & 0 deletions
94
examples/common_nestjs_remix/apps/api/src/files/local-file.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { | ||
Inject, | ||
Injectable, | ||
InternalServerErrorException, | ||
NotFoundException, | ||
} from "@nestjs/common"; | ||
import { FileService } from "./file.service"; | ||
import { ConfigService } from "@nestjs/config"; | ||
import { DatabasePg } from "../common"; | ||
import { v7 as uuid } from "uuid"; | ||
import { files } from "../storage/schema"; | ||
import { CommonFile } from "../common/schemas/common-file.schema"; | ||
import { eq } from "drizzle-orm"; | ||
import * as fs from "node:fs"; | ||
import * as path from "node:path"; | ||
|
||
@Injectable() | ||
export class LocalFileService extends FileService { | ||
uploadsDir = path.join( | ||
__dirname, | ||
"..", | ||
"..", | ||
"..", | ||
"..", | ||
"..", | ||
this.configService.getOrThrow<string>("localFile.uploadDir"), | ||
); | ||
constructor( | ||
private configService: ConfigService, | ||
@Inject("DB") private readonly db: DatabasePg, | ||
) { | ||
super(); | ||
} | ||
|
||
async uploadFile(file: Express.Multer.File) { | ||
try { | ||
const key = `${uuid()}-${file.originalname}`; | ||
fs.writeFile(path.join(this.uploadsDir, key), file.buffer, (err) => { | ||
if (err) { | ||
throw new InternalServerErrorException("Failed to upload file"); | ||
} | ||
}); | ||
|
||
const [savedFile] = await this.db | ||
.insert(files) | ||
.values({ | ||
url: key, | ||
filename: file.originalname, | ||
mimetype: file.mimetype, | ||
size: file.size, | ||
}) | ||
.returning(); | ||
|
||
return savedFile as CommonFile; | ||
} catch (error) { | ||
throw new InternalServerErrorException("Failed to upload file"); | ||
} | ||
} | ||
|
||
async getFileUrl(id: string): Promise<{ url: string }> { | ||
try { | ||
const file = await this.db.query.files.findFirst({ | ||
where: eq(files.id, id), | ||
}); | ||
|
||
if (!file) { | ||
throw new NotFoundException("File not found"); | ||
} | ||
|
||
const url = `https://storage.guidebook.localhost/${file.url}`; | ||
return { url: url }; | ||
} catch (error) { | ||
throw new InternalServerErrorException("Failed to get file"); | ||
} | ||
} | ||
|
||
async deleteFile(id: string): Promise<void> { | ||
try { | ||
const file = await this.db.query.files.findFirst({ | ||
where: eq(files.id, id), | ||
}); | ||
|
||
if (!file) { | ||
throw new NotFoundException("File not found"); | ||
} | ||
|
||
fs.unlinkSync(path.join(this.uploadsDir, file.url)); | ||
|
||
await this.db.delete(files).where(eq(files.id, id)).execute(); | ||
} catch (error) { | ||
throw new InternalServerErrorException("Failed to delete file"); | ||
} | ||
} | ||
} |
Oops, something went wrong.