Skip to content
Merged
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
17 changes: 16 additions & 1 deletion frontend/app/api/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { createClient } from "@supabase/supabase-js";
const MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024;
const PDF_MIME_TYPES = new Set(["application/pdf"]);
const STORAGE_BUCKET = "resources";
const RESOURCE_TYPES = new Set([
"lecture_notes",
"study_guide",
"class_overview",
"link",
]);

const isUuid = (value: string) =>
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
Expand Down Expand Up @@ -82,6 +88,7 @@ export async function POST(req: NextRequest) {
const file = formData.get("file");
const classId = (formData.get("class_id") as string | null)?.trim();
const title = (formData.get("title") as string | null)?.trim();
const resourceType = (formData.get("resource_type") as string | null)?.trim();

if (!(file instanceof File)) {
return NextResponse.json({ error: "A PDF file is required." }, { status: 400 });
Expand All @@ -98,6 +105,13 @@ export async function POST(req: NextRequest) {
);
}

if (!resourceType || !RESOURCE_TYPES.has(resourceType)) {
return NextResponse.json(
{ error: "resource_type must be a valid value." },
{ status: 400 },
);
}

if (file.size === 0 || file.size > MAX_FILE_SIZE_BYTES) {
return NextResponse.json(
{ error: `PDF must be between 1 byte and ${MAX_FILE_SIZE_BYTES} bytes.` },
Expand Down Expand Up @@ -156,6 +170,7 @@ export async function POST(req: NextRequest) {
profile_id: userId,
course_id: classId,
title,
resource_type: resourceType,
file_key: filePath,
preview_key: filePath,
})
Expand Down Expand Up @@ -202,4 +217,4 @@ export async function POST(req: NextRequest) {
}

return NextResponse.json({ data: resource }, { status: 200 });
}
}
37 changes: 37 additions & 0 deletions frontend/app/upload/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ const emptyCourseRequest: CourseRequestForm = {
justification: "",
};

const resourceTypeOptions = [
{ label: "Lecture Notes", value: "lecture_notes" },
{ label: "Study Guide", value: "study_guide" },
{ label: "Class Overview", value: "class_overview" },
{ label: "Link", value: "link" },
] as const;

export default function UploadPage() {
const [classes, setClasses] = useState<ClassOption[]>([]);
const [classSearch, setClassSearch] = useState("");
Expand All @@ -56,6 +63,7 @@ export default function UploadPage() {
const [numPages, setNumPages] = useState<number | null>(null);
const [pageNumber, setPageNumber] = useState(1);
const [title, setTitle] = useState("");
const [resourceType, setResourceType] = useState("");
const [accessToken, setAccessToken] = useState<string | null>(null);
const [result, setResult] = useState<string | null>(null);
const [submitError, setSubmitError] = useState<string | null>(null);
Expand Down Expand Up @@ -288,6 +296,12 @@ export default function UploadPage() {
return;
}

if (!resourceType) {
setSubmitError("Please select a resource type.");
setIsUploading(false);
return;
}

if (!accessToken) {
setSubmitError("Missing access token. Please re-authenticate.");
setIsUploading(false);
Expand All @@ -298,6 +312,7 @@ export default function UploadPage() {
formData.append("file", file);
formData.append("class_id", classId);
formData.append("title", title);
formData.append("resource_type", resourceType);

try {
const res = await fetch("/api/upload", {
Expand Down Expand Up @@ -535,6 +550,28 @@ export default function UploadPage() {
/>
</div>

<div className="upload-field">
<label className="upload-label" htmlFor="resource-type">
Resource type
</label>
<select
id="resource-type"
className="upload-input"
value={resourceType}
onChange={(e) => setResourceType(e.target.value)}
required
>
<option value="" disabled>
Select a type
</option>
{resourceTypeOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>

{/* remove in production, kept for testing purposes */}

{/*<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
alter table public.resources
add column if not exists resource_type text;

do $$
begin
if not exists (
select 1
from pg_constraint
where conname = 'check_resource_type'
) then
alter table public.resources
add constraint check_resource_type
check (resource_type in ('lecture_notes', 'study_guide', 'class_overview', 'link'));
end if;
end $$;