Skip to content

Commit 414096a

Browse files
authored
Merge pull request #19 from supabase/feat/public-buckets
support public buckets
2 parents 94db90b + 408dacf commit 414096a

13 files changed

Lines changed: 275 additions & 8 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE storage.buckets ADD COLUMN "public" boolean default false;

src/routes/bucket/createBucket.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const createBucketBodySchema = {
99
properties: {
1010
name: { type: 'string', example: 'avatars' },
1111
id: { type: 'string', example: 'avatars' },
12+
public: { type: 'boolean', example: false },
1213
},
1314
required: ['name'],
1415
} as const
@@ -51,9 +52,12 @@ export default async function routes(fastify: FastifyInstance) {
5152

5253
const { name: bucketName } = request.body
5354

54-
// IMPORTANT: by default set the id as the name of the bucket
55+
// by default set the id as the name of the bucket
5556
const id = request.body.id ?? bucketName
5657

58+
// by default buckets are not public
59+
const isPublic = request.body.public ?? false
60+
5761
if (!isValidKey(id) || !isValidKey(bucketName)) {
5862
return response
5963
.status(400)
@@ -68,6 +72,7 @@ export default async function routes(fastify: FastifyInstance) {
6872
id,
6973
name: bucketName,
7074
owner,
75+
public: isPublic,
7176
},
7277
],
7378
{

src/routes/bucket/getAllBuckets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default async function routes(fastify: FastifyInstance) {
3939
const postgrest = getPostgrestClient(jwt)
4040
const { data: results, error, status } = await postgrest
4141
.from<Bucket>('buckets')
42-
.select('id, name, owner, created_at, updated_at')
42+
.select('id, name, public, owner, created_at, updated_at')
4343

4444
if (error) {
4545
request.log.error({ error }, 'error bucket')

src/routes/bucket/getBucket.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default async function routes(fastify: FastifyInstance) {
3838
const postgrest = getPostgrestClient(jwt)
3939
const { data: results, error, status } = await postgrest
4040
.from<Bucket>('buckets')
41-
.select('id, name, owner, created_at, updated_at')
41+
.select('id, name, owner, public, created_at, updated_at')
4242
.eq('id', bucketId)
4343
.single()
4444

src/routes/bucket/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import deleteBucket from './deleteBucket'
44
import emptyBucket from './emptyBucket'
55
import getAllBuckets from './getAllBuckets'
66
import getBucket from './getBucket'
7+
import updateBucket from './updateBucket'
78

89
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
910
export default async function routes(fastify: FastifyInstance) {
@@ -12,4 +13,5 @@ export default async function routes(fastify: FastifyInstance) {
1213
emptyBucket(fastify)
1314
getAllBuckets(fastify)
1415
getBucket(fastify)
16+
updateBucket(fastify)
1517
}

src/routes/bucket/updateBucket.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { FastifyInstance } from 'fastify'
2+
import { FromSchema } from 'json-schema-to-ts'
3+
import { AuthenticatedRequest, Bucket } from '../../types/types'
4+
import { getPostgrestClient, transformPostgrestError } from '../../utils'
5+
import { createDefaultSchema, createResponse } from '../../utils/generic-routes'
6+
7+
const updateBucketBodySchema = {
8+
type: 'object',
9+
properties: {
10+
public: { type: 'boolean', example: false },
11+
},
12+
} as const
13+
const updateBucketParamsSchema = {
14+
type: 'object',
15+
properties: {
16+
bucketId: { type: 'string', example: 'avatars' },
17+
},
18+
required: ['bucketId'],
19+
} as const
20+
21+
const successResponseSchema = {
22+
type: 'object',
23+
properties: {
24+
message: { type: 'string', example: 'Successfully updated' },
25+
},
26+
required: ['message'],
27+
}
28+
interface updateBucketRequestInterface extends AuthenticatedRequest {
29+
Body: FromSchema<typeof updateBucketBodySchema>
30+
Params: FromSchema<typeof updateBucketParamsSchema>
31+
}
32+
33+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
34+
export default async function routes(fastify: FastifyInstance) {
35+
const summary = 'Update properties of a bucket'
36+
const schema = createDefaultSchema(successResponseSchema, {
37+
body: updateBucketBodySchema,
38+
summary,
39+
tags: ['bucket'],
40+
})
41+
fastify.put<updateBucketRequestInterface>(
42+
'/:bucketId',
43+
{
44+
schema,
45+
},
46+
async (request, response) => {
47+
const authHeader = request.headers.authorization
48+
const jwt = authHeader.substring('Bearer '.length)
49+
const postgrest = getPostgrestClient(jwt)
50+
const { bucketId } = request.params
51+
52+
const { public: isPublic } = request.body
53+
54+
const { error, status } = await postgrest
55+
.from<Bucket>('buckets')
56+
.update({
57+
public: isPublic,
58+
})
59+
.match({ id: bucketId })
60+
.single()
61+
62+
if (error) {
63+
request.log.error({ error }, 'error updating bucket')
64+
return response.status(400).send(transformPostgrestError(error, status))
65+
}
66+
return response.status(200).send(createResponse('Successfully updated'))
67+
}
68+
)
69+
}

src/routes/object/createObject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const successResponseSchema = {
2222
properties: {
2323
Key: {
2424
type: 'string',
25-
example: 'projectref/avatars/folder/cat.png',
25+
example: 'avatars/folder/cat.png',
2626
},
2727
},
2828
required: ['Key'],
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { FastifyInstance } from 'fastify'
2+
import { FromSchema } from 'json-schema-to-ts'
3+
import { Bucket } from '../../types/types'
4+
import { getPostgrestClient, transformPostgrestError } from '../../utils'
5+
import { getConfig } from '../../utils/config'
6+
import { getObject, initClient } from '../../utils/s3'
7+
8+
const { region, projectRef, globalS3Bucket, globalS3Endpoint, serviceKey } = getConfig()
9+
const client = initClient(region, globalS3Endpoint)
10+
11+
const getPublicObjectParamsSchema = {
12+
type: 'object',
13+
properties: {
14+
bucketName: { type: 'string', example: 'avatars' },
15+
'*': { type: 'string', example: 'folder/cat.png' },
16+
},
17+
required: ['bucketName', '*'],
18+
} as const
19+
interface getObjectRequestInterface {
20+
Params: FromSchema<typeof getPublicObjectParamsSchema>
21+
}
22+
23+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
24+
export default async function routes(fastify: FastifyInstance) {
25+
const summary = 'Retrieve a public object'
26+
fastify.get<getObjectRequestInterface>(
27+
'/public/:bucketName/*',
28+
{
29+
// @todo add success response schema here
30+
schema: {
31+
params: getPublicObjectParamsSchema,
32+
summary,
33+
response: { '4xx': { $ref: 'errorSchema#' } },
34+
tags: ['object'],
35+
},
36+
},
37+
async (request, response) => {
38+
const { bucketName } = request.params
39+
const objectName = request.params['*']
40+
41+
const superUserPostgrest = getPostgrestClient(serviceKey)
42+
const { error, status } = await superUserPostgrest
43+
.from<Bucket>('buckets')
44+
.select('id, public')
45+
.eq('id', bucketName)
46+
.eq('public', true)
47+
.single()
48+
49+
if (error) {
50+
request.log.error({ error }, 'error finding public bucket')
51+
return response.status(400).send(transformPostgrestError(error, status))
52+
}
53+
54+
const s3Key = `${projectRef}/${bucketName}/${objectName}`
55+
request.log.info(s3Key)
56+
try {
57+
const data = await getObject(client, globalS3Bucket, s3Key)
58+
return response
59+
.status(data.$metadata.httpStatusCode ?? 200)
60+
.header('Content-Type', data.ContentType)
61+
.header('Cache-Control', data.CacheControl)
62+
.header('ETag', data.ETag)
63+
.header('Last-Modified', data.LastModified)
64+
.send(data.Body)
65+
} catch (err) {
66+
if (err.$metadata?.httpStatusCode === 404) {
67+
return response.status(404).send()
68+
} else {
69+
return response.status(400).send({
70+
message: err.message,
71+
statusCode: err.$metadata?.httpStatusCode,
72+
error: err.message,
73+
})
74+
}
75+
}
76+
}
77+
)
78+
}

src/routes/object/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import createObject from './createObject'
44
import deleteObject from './deleteObject'
55
import deleteObjects from './deleteObjects'
66
import getObject from './getObject'
7+
import getPublicObject from './getPublicObject'
78
import getSignedObject from './getSignedObject'
89
import getSignedURL from './getSignedURL'
910
import listObjects from './listObjects'
@@ -18,6 +19,7 @@ export default async function routes(fastify: FastifyInstance) {
1819
deleteObjects(fastify)
1920
getObject(fastify)
2021
getSignedObject(fastify)
22+
getPublicObject(fastify)
2123
getSignedURL(fastify)
2224
moveObject(fastify)
2325
updateObject(fastify)

src/schemas/bucket.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const bucketSchema = {
55
id: { type: 'string' },
66
name: { type: 'string' },
77
owner: { type: 'string' },
8+
public: { type: 'boolean' },
89
created_at: { type: 'string' },
910
updated_at: { type: 'string' },
1011
},

0 commit comments

Comments
 (0)