-
Notifications
You must be signed in to change notification settings - Fork 2
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
1 parent
2fe18bb
commit 7cce5f9
Showing
17 changed files
with
333 additions
and
165 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
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,118 @@ | ||
import assert from "assert"; | ||
import { uploadPrivateFile, uploadPublicFile } from "./service/uploadFile"; | ||
import { buildFilePath } from "./service/utils"; | ||
import { getFileFromS3 } from "./service/getFile"; | ||
import { z } from "zod"; | ||
import { ValidatedRequestHandler } from "../../shared/middleware/validate"; | ||
import { ServerError } from "../../errors"; | ||
|
||
assert(process.env.AWS_S3_BUCKET); | ||
assert(process.env.AWS_S3_REGION); | ||
assert(process.env.AWS_ACCESS_KEY); | ||
assert(process.env.AWS_SECRET_KEY); | ||
|
||
interface UploadFileResponse { | ||
fileType: string | null; | ||
fileUrl: string; | ||
} | ||
|
||
export const uploadFileSchema = z.object({ | ||
body: z.object({ | ||
filename: z.string().trim().min(1), | ||
}), | ||
}); | ||
|
||
export type UploadController = ValidatedRequestHandler< | ||
typeof uploadFileSchema, | ||
UploadFileResponse | ||
>; | ||
|
||
export const privateUploadController: UploadController = async ( | ||
req, | ||
res, | ||
next, | ||
) => { | ||
try { | ||
if (!req.file) throw Error("Missing file"); | ||
const { filename } = res.locals.parsedReq.body; | ||
const fileResponse = await uploadPrivateFile(req.file, filename); | ||
res.json(fileResponse); | ||
} catch (error) { | ||
return next( | ||
new ServerError({ message: `Failed to upload private file: ${error}` }), | ||
); | ||
} | ||
}; | ||
|
||
export const publicUploadController: UploadController = async ( | ||
req, | ||
res, | ||
next, | ||
) => { | ||
try { | ||
if (!req.file) throw Error("Missing file"); | ||
const { filename } = res.locals.parsedReq.body; | ||
const fileResponse = await uploadPublicFile(req.file, filename); | ||
res.json(fileResponse); | ||
} catch (error) { | ||
return next( | ||
new ServerError({ | ||
message: `Failed to upload public file: ${(error as Error).message}`, | ||
}), | ||
); | ||
} | ||
}; | ||
|
||
export const downloadFileSchema = z.object({ | ||
params: z.object({ | ||
fileKey: z.string(), | ||
fileName: z.string(), | ||
}), | ||
}); | ||
|
||
export type DownloadController = ValidatedRequestHandler< | ||
typeof downloadFileSchema, | ||
Buffer | undefined | ||
>; | ||
|
||
export const publicDownloadController: DownloadController = async ( | ||
_req, | ||
res, | ||
next, | ||
) => { | ||
const { fileKey, fileName } = res.locals.parsedReq.params; | ||
const filePath = buildFilePath(fileKey, fileName); | ||
|
||
try { | ||
const { body, headers, isPrivate } = await getFileFromS3(filePath); | ||
|
||
if (isPrivate) throw Error("Bad request"); | ||
|
||
res.set(headers); | ||
res.send(body); | ||
} catch (error) { | ||
return next( | ||
new ServerError({ message: `Failed to download public file: ${error}` }), | ||
); | ||
} | ||
}; | ||
|
||
export const privateDownloadController: DownloadController = async ( | ||
_req, | ||
res, | ||
next, | ||
) => { | ||
const { fileKey, fileName } = res.locals.parsedReq.params; | ||
const filePath = buildFilePath(fileKey, fileName); | ||
|
||
try { | ||
const { body, headers } = await getFileFromS3(filePath); | ||
|
||
res.set(headers); | ||
res.send(body); | ||
} catch (error) { | ||
return next( | ||
new ServerError({ message: `Failed to download private file: ${error}` }), | ||
); | ||
} | ||
}; |
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,99 @@ | ||
openapi: 3.1.0 | ||
info: | ||
title: Plan✕ API | ||
version: 0.1.0 | ||
tags: | ||
- name: file | ||
description: Endpoints for uploading and downloading files | ||
components: | ||
parameters: | ||
fileKey: | ||
in: path | ||
name: fileKey | ||
type: string | ||
required: true | ||
fileName: | ||
in: path | ||
name: fileName | ||
type: string | ||
required: true | ||
schemas: | ||
UploadFile: | ||
type: object | ||
properties: | ||
filename: | ||
type: string | ||
required: true | ||
file: | ||
type: string | ||
format: binary | ||
responses: | ||
UploadFile: | ||
type: object | ||
properties: | ||
fileType: | ||
oneOf: | ||
- type: string | ||
- type: "null" | ||
fileUrl: | ||
type: string | ||
DownloadFile: | ||
description: Successful response | ||
content: | ||
application/octet-stream: | ||
schema: | ||
type: string | ||
format: binary | ||
paths: | ||
/file/private/upload: | ||
post: | ||
tags: ["file"] | ||
security: | ||
- bearerAuth: [] | ||
requestBody: | ||
content: | ||
multipart/form-data: | ||
schema: | ||
$ref: "#/components/schemas/UploadFile" | ||
responses: | ||
"200": | ||
$ref: "#/components/responses/UploadFile" | ||
"500": | ||
$ref: "#/components/responses/ErrorMessage" | ||
/file/public/upload: | ||
post: | ||
tags: ["file"] | ||
requestBody: | ||
content: | ||
multipart/form-data: | ||
schema: | ||
$ref: "#/components/schemas/UploadFile" | ||
responses: | ||
"200": | ||
$ref: "#/components/responses/UploadFile" | ||
"500": | ||
$ref: "#/components/responses/ErrorMessage" | ||
/file/public/{fileKey}/{fileName}: | ||
get: | ||
tags: ["file"] | ||
parameters: | ||
- $ref: "#/components/parameters/fileKey" | ||
- $ref: "#/components/parameters/fileName" | ||
responses: | ||
"200": | ||
$ref: "#/components/responses/DownloadFile" | ||
"500": | ||
$ref: "#/components/responses/ErrorMessage" | ||
/file/private/{fileKey}/{fileName}: | ||
get: | ||
tags: ["file"] | ||
parameters: | ||
- $ref: "#/components/parameters/fileKey" | ||
- $ref: "#/components/parameters/fileName" | ||
security: | ||
- fileAPIKeyAuth: [] | ||
responses: | ||
"200": | ||
$ref: "#/components/responses/DownloadFile" | ||
"500": | ||
$ref: "#/components/responses/ErrorMessage" |
Oops, something went wrong.