-
-
Notifications
You must be signed in to change notification settings - Fork 227
Extend schema to allow for presigned URLs for uploading files #108
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
Open
singingwolfboy
wants to merge
13
commits into
graphile:main
Choose a base branch
from
singingwolfboy:upload-via-signed-url-extend-schema
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
1bb7b59
Extend schema to allow for presigned URLs for uploading files
singingwolfboy e96c5e1
respond to review comments
singingwolfboy c9378b3
update schema.graphql
singingwolfboy 0309090
use typescript enum
singingwolfboy f28ebc2
prettier
singingwolfboy 76807dc
missed a backtick
singingwolfboy 62c7e83
Add dependency on aws-sdk to server/package.json
singingwolfboy 8626b2f
respond to review comments
singingwolfboy 8442139
remove unneeded query field from payload
singingwolfboy 8b73401
process.env.AWS_BUCKET_UPLOAD
singingwolfboy 7d3ff8b
resolve conflict
singingwolfboy 75475d4
uuid 8
singingwolfboy d08e934
match aws-sdk versions
singingwolfboy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 |
---|---|---|
|
@@ -6,6 +6,7 @@ const packageJson = require("../../../package.json"); | |
export const fromEmail = | ||
'"PostGraphile Starter" <[email protected]>'; | ||
export const awsRegion = "us-east-1"; | ||
export const uploadBucket = process.env.AWS_BUCKET_UPLOAD; | ||
export const projectName = packageJson.name.replace(/[-_]/g, " "); | ||
export const companyName = projectName; // For copyright ownership | ||
export const emailLegalText = | ||
|
This file contains hidden or 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 hidden or 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 hidden or 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,197 @@ | ||
import { awsRegion, uploadBucket } from "@app/config"; | ||
import * as aws from "aws-sdk"; | ||
import { gql, makeExtendSchemaPlugin } from "graphile-utils"; | ||
import { Pool } from "pg"; | ||
import { v4 as uuidv4 } from "uuid"; | ||
|
||
import { OurGraphQLContext } from "../middleware/installPostGraphile"; | ||
|
||
enum AllowedUploadContentType { | ||
IMAGE_APNG = "image/apng", | ||
IMAGE_BMP = "image/bmp", | ||
IMAGE_GIF = "image/gif", | ||
IMAGE_JPEG = "image/jpeg", | ||
IMAGE_PNG = "image/png", | ||
IMAGE_SVG_XML = "image/svg+xml", | ||
IMAGE_TIFF = "image/tiff", | ||
IMAGE_WEBP = "image/webp", | ||
} | ||
|
||
interface CreateUploadUrlInput { | ||
clientMutationId?: string; | ||
contentType: AllowedUploadContentType; | ||
} | ||
|
||
/** The minimal set of information that this plugin needs to know about users. */ | ||
interface User { | ||
id: string; | ||
isVerified: boolean; | ||
} | ||
|
||
async function getCurrentUser(pool: Pool): Promise<User | null> { | ||
await pool.query("SAVEPOINT"); | ||
try { | ||
const { | ||
rows: [row], | ||
} = await pool.query( | ||
"select id, is_verified from app_public.users where id = app_public.current_user_id()" | ||
); | ||
if (!row) { | ||
return null; | ||
} | ||
return { | ||
id: row.id, | ||
isVerified: row.is_verified, | ||
}; | ||
} catch (err) { | ||
await pool.query("ROLLBACK TO SAVEPOINT"); | ||
throw err; | ||
} finally { | ||
await pool.query("RELEASE SAVEPOINT"); | ||
} | ||
} | ||
|
||
const CreateUploadUrlPlugin = makeExtendSchemaPlugin(() => ({ | ||
typeDefs: gql` | ||
""" | ||
The set of content types that we allow users to upload. | ||
""" | ||
enum AllowedUploadContentType { | ||
""" | ||
image/apng | ||
""" | ||
IMAGE_APNG | ||
""" | ||
image/bmp | ||
""" | ||
IMAGE_BMP | ||
""" | ||
image/gif | ||
""" | ||
IMAGE_GIF | ||
""" | ||
image/jpeg | ||
""" | ||
IMAGE_JPEG | ||
""" | ||
image/png | ||
""" | ||
IMAGE_PNG | ||
""" | ||
image/svg+xml | ||
""" | ||
IMAGE_SVG_XML | ||
""" | ||
image/tiff | ||
""" | ||
IMAGE_TIFF | ||
""" | ||
image/webp | ||
""" | ||
IMAGE_WEBP | ||
} | ||
|
||
""" | ||
All input for the \`createUploadUrl\` mutation. | ||
""" | ||
input CreateUploadUrlInput @scope(isMutationInput: true) { | ||
""" | ||
An arbitrary string value with no semantic meaning. Will be included in the | ||
payload verbatim. May be used to track mutations by the client. | ||
""" | ||
clientMutationId: String | ||
|
||
""" | ||
You must provide the content type (or MIME type) of the content you intend | ||
to upload. For further information about content types, see | ||
https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types | ||
""" | ||
contentType: AllowedUploadContentType! | ||
} | ||
|
||
""" | ||
The output of our \`createUploadUrl\` mutation. | ||
""" | ||
type CreateUploadUrlPayload @scope(isMutationPayload: true) { | ||
""" | ||
The exact same \`clientMutationId\` that was provided in the mutation input, | ||
unchanged and unused. May be used by a client to track mutations. | ||
""" | ||
clientMutationId: String | ||
|
||
""" | ||
Upload content to this signed URL. | ||
""" | ||
uploadUrl: String! | ||
} | ||
|
||
extend type Mutation { | ||
""" | ||
Get a signed URL for uploading files. It will expire in 5 minutes. | ||
""" | ||
createUploadUrl( | ||
""" | ||
The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. | ||
""" | ||
input: CreateUploadUrlInput! | ||
): CreateUploadUrlPayload | ||
} | ||
`, | ||
resolvers: { | ||
Mutation: { | ||
async createUploadUrl( | ||
_query, | ||
args: { input: CreateUploadUrlInput }, | ||
context: OurGraphQLContext, | ||
_resolveInfo | ||
) { | ||
if (!uploadBucket) { | ||
const err = new Error( | ||
"Server misconfigured: missing `AWS_BUCKET_UPLOAD` envvar" | ||
); | ||
// @ts-ignore | ||
err.code = "MSCFG"; | ||
throw err; | ||
} | ||
|
||
const user = await getCurrentUser(context.rootPgPool); | ||
|
||
if (!user) { | ||
const err = new Error("Login required"); | ||
// @ts-ignore | ||
err.code = "LOGIN"; | ||
throw err; | ||
} | ||
|
||
if (!user.isVerified) { | ||
const err = new Error("Only verified users may upload files"); | ||
// @ts-ignore | ||
err.code = "DNIED"; | ||
throw err; | ||
} | ||
|
||
const { input } = args; | ||
const contentType: string = AllowedUploadContentType[input.contentType]; | ||
const s3 = new aws.S3({ | ||
region: awsRegion, | ||
signatureVersion: "v4", | ||
}); | ||
const params = { | ||
Bucket: uploadBucket, | ||
ContentType: contentType, | ||
// randomly generated filename, nested under username directory | ||
Key: `${user.id}/${uuidv4()}`, | ||
Expires: 300, // signed URL will expire in 5 minutes | ||
ACL: "public-read", // uploaded file will be publicly readable | ||
}; | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const signedUrl = await s3.getSignedUrlPromise("putObject", params); | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return { | ||
clientMutationId: input.clientMutationId, | ||
uploadUrl: signedUrl, | ||
}; | ||
}, | ||
}, | ||
}, | ||
})); | ||
|
||
export default CreateUploadUrlPlugin; |
This file contains hidden or 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 hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.