mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-12 07:04:44 -05:00
Form implementation, api routes
This commit is contained in:
26
src/app/api/content/[...path]/route.ts
Normal file
26
src/app/api/content/[...path]/route.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
import { auth } from "@clerk/nextjs/server";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { path: string[] } }
|
||||
) {
|
||||
const { userId } = auth();
|
||||
if (!userId) {
|
||||
return new NextResponse('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
const filePath = path.join(process.cwd(), 'content', ...params.path);
|
||||
|
||||
try {
|
||||
const file = await fs.readFile(filePath);
|
||||
const response = new NextResponse(file);
|
||||
response.headers.set('Content-Type', 'application/pdf');
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error reading file:', error);
|
||||
return new NextResponse('File not found', { status: 404 });
|
||||
}
|
||||
}
|
||||
58
src/app/api/forms/[id]/route.ts
Normal file
58
src/app/api/forms/[id]/route.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { db } from "~/server/db";
|
||||
import { informedConsentForms, contents } from "~/server/db/schema";
|
||||
import { auth } from "@clerk/nextjs/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const { userId } = auth();
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id)) {
|
||||
return NextResponse.json({ error: 'Invalid ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
// First, get the content associated with this form
|
||||
const [form] = await db
|
||||
.select({
|
||||
contentId: informedConsentForms.contentId,
|
||||
location: contents.location,
|
||||
})
|
||||
.from(informedConsentForms)
|
||||
.innerJoin(contents, eq(informedConsentForms.contentId, contents.id))
|
||||
.where(eq(informedConsentForms.id, id));
|
||||
|
||||
if (!form) {
|
||||
return NextResponse.json({ error: 'Form not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Delete the file from the file system
|
||||
const fullPath = path.join(process.cwd(), form.location);
|
||||
try {
|
||||
await fs.access(fullPath);
|
||||
await fs.unlink(fullPath);
|
||||
} catch (error) {
|
||||
console.warn(`File not found or couldn't be deleted: ${fullPath}`);
|
||||
}
|
||||
|
||||
// Delete the form and content from the database
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.delete(informedConsentForms).where(eq(informedConsentForms.id, id));
|
||||
await tx.delete(contents).where(eq(contents.id, form.contentId));
|
||||
});
|
||||
|
||||
return NextResponse.json({ message: "Form deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error('Error deleting form:', error);
|
||||
return NextResponse.json({ error: 'Failed to delete form' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
104
src/app/api/forms/route.ts
Normal file
104
src/app/api/forms/route.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { db } from "~/server/db";
|
||||
import { contents, informedConsentForms, contentTypes } from "~/server/db/schema";
|
||||
import { auth } from "@clerk/nextjs/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { saveFile } from "~/lib/fileStorage";
|
||||
import fs from 'fs/promises';
|
||||
|
||||
// Function to generate a random string
|
||||
const generateRandomString = (length: number) => {
|
||||
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { userId } = auth();
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const forms = await db.select({
|
||||
id: informedConsentForms.id,
|
||||
title: contents.title,
|
||||
location: contents.location,
|
||||
previewLocation: contents.previewLocation,
|
||||
studyId: informedConsentForms.studyId,
|
||||
participantId: informedConsentForms.participantId,
|
||||
contentId: informedConsentForms.contentId,
|
||||
}).from(informedConsentForms)
|
||||
.innerJoin(contents, eq(informedConsentForms.contentId, contents.id));
|
||||
|
||||
return NextResponse.json(forms);
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { userId } = auth();
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const file = formData.get('file') as File;
|
||||
const title = formData.get('title') as string;
|
||||
const studyId = formData.get('studyId') as string;
|
||||
const participantId = formData.get('participantId') as string;
|
||||
|
||||
if (!file || !title || !studyId || !participantId) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const [formContentType] = await db
|
||||
.select()
|
||||
.from(contentTypes)
|
||||
.where(eq(contentTypes.name, "Informed Consent Form"));
|
||||
|
||||
const [previewContentType] = await db
|
||||
.select()
|
||||
.from(contentTypes)
|
||||
.where(eq(contentTypes.name, "Preview Image"));
|
||||
|
||||
if (!formContentType || !previewContentType) {
|
||||
return NextResponse.json({ error: 'Content type not found' }, { status: 500 });
|
||||
}
|
||||
|
||||
// Generate a random filename with the same extension
|
||||
const fileExtension = file.name.split('.').pop(); // Get the file extension
|
||||
const randomFileName = `${generateRandomString(12)}.${fileExtension}`; // Generate random filename with 12 characters
|
||||
const { pdfPath, previewPath } = await saveFile(file, `${formContentType.id}/${randomFileName}`, previewContentType.id);
|
||||
|
||||
const [content] = await db
|
||||
.insert(contents)
|
||||
.values({
|
||||
contentTypeId: formContentType.id,
|
||||
uploader: userId,
|
||||
location: pdfPath,
|
||||
previewLocation: previewPath,
|
||||
title: title,
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (!content) {
|
||||
throw new Error("Content not found");
|
||||
}
|
||||
|
||||
const [form] = await db
|
||||
.insert(informedConsentForms)
|
||||
.values({
|
||||
studyId: parseInt(studyId),
|
||||
participantId: parseInt(participantId),
|
||||
contentId: content.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return NextResponse.json(form);
|
||||
} catch (error) {
|
||||
console.error('Error uploading form:', error);
|
||||
return NextResponse.json({ error: 'Failed to upload form' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { db } from "~/server/db";
|
||||
import { contents, informedConsentForms, contentTypes } from "~/server/db/schema";
|
||||
import { auth } from "@clerk/nextjs/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { saveFile } from '~/lib/fileStorage';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { userId } = auth();
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const file = formData.get('file') as File;
|
||||
const studyId = formData.get('studyId') as string;
|
||||
const participantId = formData.get('participantId') as string;
|
||||
|
||||
if (!file || !studyId || !participantId) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const [contentType] = await db
|
||||
.select()
|
||||
.from(contentTypes)
|
||||
.where(eq(contentTypes.name, "Informed Consent Form"));
|
||||
|
||||
if (!contentType) {
|
||||
return NextResponse.json({ error: 'Content type not found' }, { status: 500 });
|
||||
}
|
||||
|
||||
const [content] = await db
|
||||
.insert(contents)
|
||||
.values({
|
||||
contentTypeId: contentType.id,
|
||||
uploader: userId,
|
||||
location: '', // We'll update this after saving the file
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (!content) {
|
||||
throw new Error("Content not found");
|
||||
}
|
||||
|
||||
const fileLocation = await saveFile(file, content.id);
|
||||
|
||||
await db
|
||||
.update(contents)
|
||||
.set({ location: fileLocation })
|
||||
.where(eq(contents.id, content.id));
|
||||
|
||||
const [form] = await db
|
||||
.insert(informedConsentForms)
|
||||
.values({
|
||||
studyId: parseInt(studyId),
|
||||
participantId: parseInt(participantId),
|
||||
contentId: content.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return NextResponse.json(form);
|
||||
} catch (error) {
|
||||
console.error('Error uploading informed consent form:', error);
|
||||
return NextResponse.json({ error: 'Failed to upload form' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
15
src/app/forms/page.tsx
Normal file
15
src/app/forms/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import Layout from "~/components/layout";
|
||||
import { FormsGrid } from "~/components/forms/FormsGrid";
|
||||
import { UploadFormButton } from "~/components/forms/UploadFormButton";
|
||||
|
||||
export default function FormsPage() {
|
||||
return (
|
||||
<Layout>
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-3xl font-bold">Forms</h1>
|
||||
<UploadFormButton />
|
||||
</div>
|
||||
<FormsGrid />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useSignIn } from "@clerk/nextjs"
|
||||
import { useSignIn, useSignUp } from "@clerk/nextjs"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"
|
||||
import { Input } from "~/components/ui/input"
|
||||
@@ -12,28 +12,66 @@ import { FcGoogle } from "react-icons/fc"
|
||||
import { FaApple } from "react-icons/fa"
|
||||
|
||||
export default function SignInPage() {
|
||||
const { isLoaded, signIn, setActive } = useSignIn()
|
||||
const [emailAddress, setEmailAddress] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const router = useRouter()
|
||||
const { isLoaded, signIn, setActive } = useSignIn();
|
||||
const { signUp } = useSignUp();
|
||||
const [emailAddress, setEmailAddress] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!isLoaded) return
|
||||
e.preventDefault();
|
||||
if (!isLoaded) return;
|
||||
|
||||
try {
|
||||
const result = await signIn.create({
|
||||
identifier: emailAddress,
|
||||
password,
|
||||
})
|
||||
});
|
||||
|
||||
if (result.status === "complete") {
|
||||
await setActive({ session: result.createdSessionId })
|
||||
router.push("/dash")
|
||||
await setActive({ session: result.createdSessionId });
|
||||
router.push("/dash");
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as { errors?: { message: string }[] };
|
||||
console.error("Error:", error.errors?.[0]?.message ?? "Unknown error")
|
||||
console.error("Error:", error.errors?.[0]?.message ?? "Unknown error");
|
||||
|
||||
// If the error indicates the user does not exist, trigger sign-up
|
||||
if (error.errors?.[0]?.message.includes("not found")) {
|
||||
if (!signUp) {
|
||||
console.error("Sign-up functionality is not available.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const signUpResult = await signUp.create({
|
||||
emailAddress,
|
||||
password,
|
||||
});
|
||||
|
||||
if (signUpResult.status === "complete") {
|
||||
await setActive({ session: signUpResult.createdSessionId });
|
||||
|
||||
// Create a user entry in the database
|
||||
const response = await fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: emailAddress }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error("Error creating user in database:", errorData.error);
|
||||
return; // Optionally handle the error (e.g., show a message to the user)
|
||||
}
|
||||
|
||||
router.push("/dash");
|
||||
}
|
||||
} catch (signUpErr) {
|
||||
const signUpError = signUpErr as { errors?: { message: string }[] };
|
||||
console.error("Sign-up Error:", signUpError.errors?.[0]?.message ?? "Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +90,7 @@ export default function SignInPage() {
|
||||
<div className="min-h-screen bg-gradient-to-b from-blue-100 to-white flex items-center justify-center">
|
||||
<Card className="w-[350px]">
|
||||
<CardHeader>
|
||||
<CardTitle>Sign In to HRIStudio</CardTitle>
|
||||
<CardTitle>Sign in to HRIStudio</CardTitle>
|
||||
<CardDescription>Enter your email and password to sign in</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function SignUpPage() {
|
||||
<div className="min-h-screen bg-gradient-to-b from-blue-100 to-white flex items-center justify-center">
|
||||
<Card className="w-[350px]">
|
||||
<CardHeader>
|
||||
<CardTitle>Sign Up for HRIStudio</CardTitle>
|
||||
<CardTitle>Sign up for HRIStudio</CardTitle>
|
||||
<CardDescription>Create an account to get started</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
Reference in New Issue
Block a user