Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support s3 routes #531

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
process.env.DOTENV_CONFIG_PATH = '.env.development'
require('dotenv/config')
jest.setTimeout(10000)
55 changes: 55 additions & 0 deletions src/entities/Route/routes/filesystem2/s3.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { S3 } from 'aws-sdk'

import { checkS3File } from './s3'

const desc =
!process.env.AWS_ACCESS_KEY ||
!process.env.AWS_ACCESS_SECRET ||
!process.env.AWS_BUCKET_NAME
? describe.skip
: describe

const s3 = new S3({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_ACCESS_SECRET,
region: process.env.AWS_REGION,
})

desc(`checkS3File`, () => {
test(`should return a not found response if doesn't exists`, async () => {
// const result = await checkS3File(s3, {
// Bucket: process.env.AWS_BUCKET_NAME!,
// Key: 'test.txt',
// // Range: '1-4'
// })

const result = await checkS3File(s3, {
Bucket: process.env.AWS_BUCKET_NAME!,
Key: 'test.txt' + Math.random(),
// Range: '1-4'
})

expect(result.status).toBe(404)
expect(result.headers).toEqual({})
expect(result.body).toEqual(Buffer.alloc(0))
// expect(result).toEqual({})
})

test(`should return a request representing the object in the bucket`, async () => {
// const result = await checkS3File(s3, {
// Bucket: process.env.AWS_BUCKET_NAME!,
// Key: 'test.txt',
// // Range: '1-4'
// })

const result = await checkS3File(s3, {
Bucket: process.env.AWS_BUCKET_NAME!,
Key: 'test.txt' + Math.random(),
// Range: '1-4'
})

expect(result.status).toBe(404)
expect(result.headers).toEqual({})
expect(result.body).toEqual(Buffer.alloc(0))
})
})
102 changes: 102 additions & 0 deletions src/entities/Route/routes/filesystem2/s3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as e from 'express'

import logger from '../../../Development/logger'
import RequestError from '../../error'
import handleExpressError from '../../handle/handleExpressError'
import withHttpMetrics from '../../middleware/withHttpMetrics'
import { withLogs } from '../../middleware/withLogs'
import { Response } from './types'

import type { S3 } from 'aws-sdk'

export type S3RouterOptions = {
/**
* Bucket Path to th
* @example `s3://my-bucket/base-path/to/my-dir`
*/
bucket: string

/**
* If file doesn't exists it will continue with the next router
* @default false
*/
continueIfNotFound?: boolean

/**
* Not found file resopnse, if not set a plain/text response will be
* provided instead
*/
notFoundFile?: string

/**
*
* @param res
* @returns
*/
overwrite?: (res: Response) => Promise<Response>
}

export default function createS3Router(s3: S3, options: S3RouterOptions) {
const router = e.Router()
if (!options.bucket) {
throw new Error(`Invalid Bucket: it can't be empty`)
}

let url: URL
try {
url = new URL(options.bucket)
} catch (err) {
throw new Error(`Invalid Bucket: ${options.bucket}`)
}

if (url.protocol !== 's3:') {
throw new Error(
`Invalid Bucket: expected "s3:" protocol but get "${url.protocol}" instead`
)
}

router.use(withLogs())
router.use(withHttpMetrics({ handler: 's3' }))
router.use(async (req, res, next) => {
const basePath = url.pathname.endsWith('/')
? url.pathname.slice(0, -1)
: url.pathname
const params: S3.HeadObjectRequest = {
Key: basePath + req.path,
Bucket: url.host,
}

try {
await s3.headObject(params).promise()
} catch (err) {
// const error = new RequestError()
return handleExpressError(err, req, res)
}

s3.getObject(params).createReadStream().pipe(res)
})

return router
}

export async function checkS3File(s3: S3, params: S3.HeadObjectRequest) {
try {
const data = await s3.headObject(params).promise()
console.log(data)
return {}
} catch (err) {
if (err.code !== 'NotFound') {
logger.error(`Error getting file "s3://${params.Bucket}/${params.Key}"`)
}

return {
status: 404,
headers: {},
body: Buffer.alloc(0),
}
}
}

export async function readS3File(s3: S3, params: S3.HeadObjectRequest) {
return s3.getObject(params).createReadStream()
}