add route to get pre-signed s3 upload url

This commit is contained in:
MarconLP 2023-04-11 21:15:42 +02:00
parent 8eceee520f
commit 6e69c1b3e3
No known key found for this signature in database
GPG key ID: F4CAFFDFA3451D5E
9 changed files with 2556 additions and 12 deletions

View file

@ -18,5 +18,15 @@ const config = {
locales: ["en"], locales: ["en"],
defaultLocale: "en", defaultLocale: "en",
}, },
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '*.backblazeb2.com',
port: '',
pathname: '/**',
},
],
},
}; };
export default config; export default config;

2469
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,8 @@
"start": "next start" "start": "next start"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.310.0",
"@aws-sdk/s3-request-presigner": "^3.310.0",
"@next-auth/prisma-adapter": "^1.0.5", "@next-auth/prisma-adapter": "^1.0.5",
"@prisma/client": "^4.11.0", "@prisma/client": "^4.11.0",
"@tanstack/react-query": "^4.28.0", "@tanstack/react-query": "^4.28.0",

View file

@ -21,6 +21,8 @@ model Video {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
title String title String
video_url String video_url String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
} }
// Necessary for Next auth // Necessary for Next auth
@ -58,6 +60,7 @@ model User {
image String? image String?
accounts Account[] accounts Account[]
sessions Session[] sessions Session[]
videos Video[]
} }
model VerificationToken { model VerificationToken {

View file

@ -23,6 +23,11 @@ const server = z.object({
GOOGLE_CLIENT_SECRET: z.string(), GOOGLE_CLIENT_SECRET: z.string(),
GITHUB_ID: z.string(), GITHUB_ID: z.string(),
GITHUB_SECRET: z.string(), GITHUB_SECRET: z.string(),
AWS_ENDPOINT: z.string(),
AWS_REGION: z.string(),
AWS_KEY_ID: z.string(),
AWS_ACCESS_KEY: z.string(),
AWS_BUCKET_NAME: z.string()
}); });
/** /**
@ -49,6 +54,11 @@ const processEnv = {
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
GITHUB_ID: process.env.GITHUB_ID, GITHUB_ID: process.env.GITHUB_ID,
GITHUB_SECRET: process.env.GITHUB_SECRET, GITHUB_SECRET: process.env.GITHUB_SECRET,
AWS_ENDPOINT: process.env.AWS_ENDPOINT,
AWS_REGION: process.env.AWS_REGION,
AWS_KEY_ID: process.env.AWS_KEY_ID,
AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY,
AWS_BUCKET_NAME: process.env.AWS_BUCKET_NAME
}; };
// Don't touch the part below // Don't touch the part below

View file

@ -3,8 +3,25 @@ import Head from "next/head";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import Link from "next/link"; import Link from "next/link";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import Image from "next/image";
const VideoList: NextPage = () => { const VideoList: NextPage = () => {
const router = useRouter();
const { status } = useSession();
const { data: videos } = api.video.getAll.useQuery();
const getUploadUrl = api.video.getUploadUrl.useMutation();
if (status === "unauthenticated") {
void router.push("/sign-in");
}
const onUpload = () => {
const data = getUploadUrl.mutate({ key: "something random" });
console.log(data);
};
return ( return (
<> <>
<Head> <Head>
@ -16,7 +33,10 @@ const VideoList: NextPage = () => {
<div className="flex min-h-[80px] w-full items-center justify-between border-b border-solid border-b-[#E7E9EB] bg-white px-6"> <div className="flex min-h-[80px] w-full items-center justify-between border-b border-solid border-b-[#E7E9EB] bg-white px-6">
<span>Screenity</span> <span>Screenity</span>
<div> <div>
<span className="cursor-pointer rounded border border-[#0000001a] px-2 py-2 text-sm text-[#292d34] hover:bg-[#fafbfc]"> <span
onClick={onUpload}
className="cursor-pointer rounded border border-[#0000001a] px-2 py-2 text-sm text-[#292d34] hover:bg-[#fafbfc]"
>
New video New video
</span> </span>
</div> </div>
@ -135,10 +155,12 @@ const VideoCard = ({ title, id, timestamp }: VideoCardProps) => {
return ( return (
<Link href={`/share/${id}`}> <Link href={`/share/${id}`}>
<div className="h-[240px] w-[250px] cursor-pointer overflow-hidden rounded-lg border border-[#6c668533] text-sm font-normal"> <div className="h-[240px] w-[250px] cursor-pointer overflow-hidden rounded-lg border border-[#6c668533] text-sm font-normal">
<figure> <figure className="relative">
<img <Image
src="https://daisyui.com/images/stock/photo-1606107557195-0e29a4b5b4aa.jpg" src="https://f003.backblazeb2.com/file/test-bucket-dev/green+vs+blue+bbbles.jpg"
alt="Shoes" alt="video thumbnail"
fill={true}
className="!relative object-contain"
/> />
</figure> </figure>
<div className="m-4 flex flex-col"> <div className="m-4 flex flex-col">

View file

@ -1,16 +1,16 @@
import { z } from "zod"; import { z } from "zod";
import { import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
createTRPCRouter, import { PutObjectCommand } from "@aws-sdk/client-s3";
publicProcedure, import { env } from "~/env.mjs";
protectedProcedure, import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
} from "~/server/api/trpc";
export const videoRouter = createTRPCRouter({ export const videoRouter = createTRPCRouter({
getAll: publicProcedure.query(({ ctx }) => { getAll: protectedProcedure.query(({ ctx }) => {
console.log(ctx.session);
return ctx.prisma.video.findMany(); return ctx.prisma.video.findMany();
}), }),
get: publicProcedure get: protectedProcedure
.input(z.object({ videoId: z.string() })) .input(z.object({ videoId: z.string() }))
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const video = await ctx.prisma.video.findUnique({ const video = await ctx.prisma.video.findUnique({
@ -20,4 +20,19 @@ export const videoRouter = createTRPCRouter({
}); });
return video; return video;
}), }),
getUploadUrl: protectedProcedure
.input(z.object({ key: z.string() }))
.mutation(async ({ ctx, input }) => {
const { key } = input;
const { s3 } = ctx;
const putObjectCommand = new PutObjectCommand({
Bucket: env.AWS_BUCKET_NAME,
Key: key,
});
const signedUrl = await getSignedUrl(s3, putObjectCommand);
return signedUrl;
}),
}); });

View file

@ -38,6 +38,7 @@ const createInnerTRPCContext = (opts: CreateContextOptions) => {
return { return {
session: opts.session, session: opts.session,
prisma, prisma,
s3,
}; };
}; };
@ -68,6 +69,7 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
import { initTRPC, TRPCError } from "@trpc/server"; import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson"; import superjson from "superjson";
import { ZodError } from "zod"; import { ZodError } from "zod";
import { s3 } from "~/server/aws/s3";
const t = initTRPC.context<typeof createTRPCContext>().create({ const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson, transformer: superjson,

11
src/server/aws/s3.ts Normal file
View file

@ -0,0 +1,11 @@
import { S3 } from "@aws-sdk/client-s3";
import { env } from "~/env.mjs";
export const s3 = new S3({
endpoint: env.AWS_ENDPOINT,
region: env.AWS_REGION,
credentials: {
accessKeyId: env.AWS_KEY_ID,
secretAccessKey: env.AWS_ACCESS_KEY,
},
});