[resumes][fix] process file transfer over next.js

pull/321/head
Keane Chan 3 years ago
parent 498b0b0a28
commit 6a58342b38
No known key found for this signature in database
GPG Key ID: 32718398E1E9F87C

@ -22,8 +22,10 @@
"@trpc/next": "^9.27.2", "@trpc/next": "^9.27.2",
"@trpc/react": "^9.27.2", "@trpc/react": "^9.27.2",
"@trpc/server": "^9.27.2", "@trpc/server": "^9.27.2",
"axios": "^1.1.2",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"formidable": "^2.0.1",
"next": "12.3.1", "next": "12.3.1",
"next-auth": "~4.10.3", "next-auth": "~4.10.3",
"react": "18.2.0", "react": "18.2.0",
@ -38,6 +40,7 @@
"devDependencies": { "devDependencies": {
"@tih/tailwind-config": "*", "@tih/tailwind-config": "*",
"@tih/tsconfig": "*", "@tih/tsconfig": "*",
"@types/formidable": "^2.0.5",
"@types/node": "^18.0.0", "@types/node": "^18.0.0",
"@types/react": "^18.0.21", "@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",

@ -12,6 +12,8 @@ export const serverSchema = z.object({
NEXTAUTH_SECRET: z.string(), NEXTAUTH_SECRET: z.string(),
NEXTAUTH_URL: z.string().url(), NEXTAUTH_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'test', 'production']), NODE_ENV: z.enum(['development', 'test', 'production']),
SUPABASE_ANON_KEY: z.string(),
SUPABASE_URL: z.string().url(),
}); });
/** /**
@ -20,8 +22,7 @@ export const serverSchema = z.object({
* To expose them to the client, prefix them with `NEXT_PUBLIC_`. * To expose them to the client, prefix them with `NEXT_PUBLIC_`.
*/ */
export const clientSchema = z.object({ export const clientSchema = z.object({
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string(), // NEXT_PUBLIC_BAR: z.string(),
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
}); });
/** /**
@ -32,6 +33,4 @@ export const clientSchema = z.object({
*/ */
export const clientEnv = { export const clientEnv = {
// NEXT_PUBLIC_BAR: process.env.NEXT_PUBLIC_BAR, // NEXT_PUBLIC_BAR: process.env.NEXT_PUBLIC_BAR,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
}; };

@ -0,0 +1,59 @@
import axios from 'axios';
import { formidable } from 'formidable';
import type { NextApiRequest, NextApiResponse } from 'next';
import { v4 as uuidv4 } from 'uuid';
import { env } from '~/env/server.mjs';
const BASE_URL = `${env.SUPABASE_URL}/storage/v1/object`;
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const headers = {
'Content-Type': 'multipart/form-data',
apiKey: env.SUPABASE_ANON_KEY,
authorization: 'Bearer ' + env.SUPABASE_ANON_KEY,
};
if (req.method === 'POST') {
try {
const form = formidable({ multiples: false });
form.parse(req, async (err, fields, files) => {
if (err) {
throw err;
}
const { file } = files;
const actualFile = file instanceof Array ? file[0] : file;
const filePath = `${uuidv4()}-${actualFile.originalFilename}`;
const { key } = fields;
const data = await axios.post(
`${BASE_URL}/${key}/${filePath}`,
actualFile,
{
headers,
},
);
return res.status(200).json({
url: data.data.key,
});
});
} catch (error: unknown) {
return Promise.reject(error);
}
}
if (req.method === 'GET') {
const { key, filePath } = req.query;
const data = await axios.get(`${BASE_URL}/public/${key}/${filePath}`);
res.status(200).json(data);
}
}

@ -1,10 +1,10 @@
import axios from 'axios';
import clsx from 'clsx'; import clsx from 'clsx';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { v4 as uuidv4 } from 'uuid';
import { PaperClipIcon } from '@heroicons/react/24/outline'; import { PaperClipIcon } from '@heroicons/react/24/outline';
import { Button, Select, TextArea, TextInput } from '@tih/ui'; import { Button, Select, TextArea, TextInput } from '@tih/ui';
@ -14,7 +14,6 @@ import {
ROLES, ROLES,
} from '~/components/resumes/browse/constants'; } from '~/components/resumes/browse/constants';
import { supabase } from '~/utils/supabaseClient';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
const TITLE_PLACEHOLDER = const TITLE_PLACEHOLDER =
@ -55,21 +54,25 @@ export default function SubmitResumeForm() {
return; return;
} }
const formData = new FormData();
formData.append('key', 'resumes');
formData.append('file', resumeFile);
// Prefix with uuid so that it is always unique // Prefix with uuid so that it is always unique
const url = `${uuidv4()}-${resumeFile.name}`; const res = await axios.post('/api/file-storage', formData, {
const { error } = await supabase.storage headers: {
.from('resumes') 'Content-Type': 'multipart/form-data',
.upload(url, resumeFile); },
});
if (error) { const { url } = res.data;
console.error(error);
}
await resumeCreateMutation.mutate({ await resumeCreateMutation.mutate({
...data, ...data,
url, url,
}); });
router.push('/resumes'); // Router.push('/resumes');
reset();
}; };
const onUploadFile = (event: React.ChangeEvent<HTMLInputElement>) => { const onUploadFile = (event: React.ChangeEvent<HTMLInputElement>) => {

@ -1,8 +0,0 @@
import { createClient } from '@supabase/supabase-js';
import { env } from '~/env/client.mjs';
const supabaseUrl = env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save