Skip to content
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

#3 backend user schema + api routes #19

Merged
merged 12 commits into from
Oct 30, 2024
29 changes: 24 additions & 5 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,30 @@ enum Role {
}

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
firstName String @default("")
email String @default("")
EventIds String[] @db.ObjectId
Events Event[] @relation(fields: [EventIds], references: [id])
id String @id @default(auto()) @map("_id") @db.ObjectId
role Role @default(VOLUNTEER)
firstName String @default("")
lastName String @default("")
email String @default("")
volunteerDetails VolunteerDetails?
EventIds String[] @db.ObjectId
Events Event[] @relation(fields: [EventIds], references: [id])
}

model VolunteerDetails {
id String @id @default(auto()) @map("_id") @db.ObjectId
ageOver14 Boolean @default(false)
country String @default("")
address String @default("")
city String @default("")
state String @default("")
zipCode Int?
hasLicense Boolean @default(false)
speaksEsp Boolean @default(false)
volunteerType String @default("")
hoursWorked Int @default(0)
user User @relation(fields: [userId], references: [id])
userId String @unique @db.ObjectId
}

model Event {
Expand Down
61 changes: 52 additions & 9 deletions src/app/api/user/route.client.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,57 @@
export const addUser = async (request: {
body: { user: { firstName: string; email: string } };
}) => {
const { body, ...options } = request;
const response = await fetch("/api/user", {
method: "POST",
import { User, VolunteerDetails } from "@prisma/client";

type CreateUserInput = Omit<User, "id" | "Events" | "EventIds">;
type CreateVolunteerDetailsInput = Omit<
VolunteerDetails,
"id" | "user" | "userId"
>;

/**
* Sends an HTTP request to the specified endpoint with the provided method and body.
*
* @param {string} endpoint - The URL endpoint to which the request will be sent.
* @param {"POST" | "GET" | "DELETE" | "PUT"} method - The HTTP method for the request.
* @param {Record<string, any>} [body] - The optional request body, used for POST and PUT requests.
*
* @returns {Promise<any>} - Resolves to the response data in JSON format if the request is successful.
* @throws {Error} - Throws an error if the response status is not OK (status code outside the 200-299 range).
*/
export const fetchApi = async (
endpoint: string,
method: "POST" | "GET" | "DELETE" | "PUT",
body?: Record<string, unknown>
) => {
const response = await fetch(endpoint, {
method,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
...options,
});

const json = await response.json();
if (!response.ok) {
throw new Error("API Error: $(response.statusText)");
}

return response.json();
};

export const addUser = async (
user: CreateUserInput,
volunteerDetails: CreateVolunteerDetailsInput
) => fetchApi("/api/user", "POST", { user, volunteerDetails });

export const getUser = async (userID: string) => {
const url = `/api/user?id=${userID}`;
return fetchApi(url, "GET");
};

export const deleteUser = async (userID: string) => {
const url = `/api/user?id=${userID}`;
return fetchApi(url, "DELETE");
};

return json;
export const updateUser = async (
user: User,
volunteerDetails: VolunteerDetails
) => {
return fetchApi("/api/user", "PUT", { user, volunteerDetails });
};
161 changes: 158 additions & 3 deletions src/app/api/user/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,174 @@ import { NextRequest, NextResponse } from "next/server";

const prisma = new PrismaClient();

/**
* Creates a new user with associated volunteerDetails.
* @param {NextRequest} request - The incoming request.
* @returns {NextResponse} - JSON response with user data or error.
*/
export const POST = async (request: NextRequest) => {
try {
/* @TODO: Add auth */

const { user } = await request.json();
const { user, volunteerDetails } = await request.json();

const savedUser = await prisma.user.create({
data: user,
data: {
...user,
},
});

await prisma.volunteerDetails.create({
data: {
...volunteerDetails,
userId: savedUser.id,
},
});

return NextResponse.json({
code: "SUCCESS",
message: `User created with email: ${savedUser.email}`,
data: savedUser,
});
} catch (error) {
console.error("Error:", error);
return NextResponse.json({
code: "ERROR",
message: error,
});
}
};

/**
* Deletes a user and its associated volunteerDetails.
* @param {NextRequest} request - The incoming request with user ID.
* @returns {NextResponse} - JSON response indicating success or error.
*/
export const DELETE = async (request: NextRequest) => {
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");

// Check if id is null
if (!id) {
return NextResponse.json(
{
code: "BAD_REQUEST",
message: "User ID is required.",
},
{ status: 400 }
);
}

try {
await prisma.volunteerDetails.delete({
where: { userId: id },
});

const deletedUser = await prisma.user.delete({
where: { id },
});

return NextResponse.json({
code: "SUCCESS",
message: "User deleted successfully",
data: deletedUser,
});
} catch (error) {
console.error("Error:", error);
return NextResponse.json({
code: "ERROR",
message: error,
});
}
};

/**
* Fetches a user with associated volunteerDetails.
* @param {NextRequest} request - The incoming request with user ID.
* @returns {NextResponse} - JSON response with user and volunteerDetails or error.
*/
export const GET = async (request: NextRequest) => {
const { searchParams } = new URL(request.url);
const id = searchParams.get("id"); // Assuming the user is queried by 'id'

// Check if id is null
if (!id) {
return NextResponse.json(
{
code: "BAD_REQUEST",
message: "User ID is required.",
},
{ status: 400 }
);
}
try {
const fetchedUser = await prisma.user.findUnique({
where: { id },
include: { volunteerDetails: true },
});

if (!fetchedUser) {
return NextResponse.json(
{
code: "NOT_FOUND",
message: "User not found",
},
{ status: 404 }
);
}

// Do not include volunteerDetails in user we return
const { volunteerDetails, ...user } = fetchedUser;

return NextResponse.json({
code: "SUCCESS",
data: { user, volunteerDetails },
});
} catch (error) {
console.error("Error:", error);
return NextResponse.json({
code: "ERROR",
message: error,
});
}
};

/**
* Updates a user and associated volunteerDetails.
* @param {NextRequest} request - The incoming request with user and volunteerDetails data.
* @returns {NextResponse} - JSON response with updated data or error.
*/
export const PUT = async (request: NextRequest) => {
try {
/* @TODO: Add auth */

const { user, volunteerDetails } = await request.json();

// id: undefined for data because we cannot modify the id
const updatedUser = await prisma.user.update({
where: {
id: user.id,
},
data: {
...user,
id: undefined,
},
});

const updatedVD = await prisma.volunteerDetails.update({
where: {
id: volunteerDetails.id,
},
data: {
...volunteerDetails,
id: undefined,
},
});

return NextResponse.json({
code: "SUCCESS",
message: savedUser.email,
message: `User update with email: ${updatedUser.email}`,
data: { user: updatedUser, volunteerDetails: updatedVD },
});
} catch (error) {
console.error("Error:", error);
Expand Down
41 changes: 11 additions & 30 deletions src/components/ExampleButton.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,21 @@
import { addUser } from "@api/user/route.client";
import { Icon } from "@iconify/react/dist/iconify.js";
import { addEvent } from "@api/event/route.client";

interface ExampleButtonProps {
buttonText: string;
callBack: () => void;
}

const ExampleButton = ({ buttonText }: ExampleButtonProps) => {
const ExampleButton = ({ buttonText, callBack }: ExampleButtonProps) => {
return (
<button
className="bg-slate-700 text-white p-4 rounded-full flex flex-row items-center gap-3"
onClick={async () => {
const response = await addUser({
body: {
user: {
firstName: "Johnny",
email: "[email protected]",
},
},
});
await addEvent({
body: {
event: {
EventName: "eventname",
Description: "desc",
MaxPeople: 10,
DateTime: new Date(),
},
},
});
console.log(response);
}}
>
{buttonText}
<Icon icon="tabler:click" width="20" />
</button>
<view>
<button
className="bg-slate-700 text-white p-4 rounded-full flex flex-row items-center gap-3"
onClick={callBack}
>
{buttonText}
<Icon icon="tabler:click" width="20" />
</button>
</view>
);
};

Expand Down
Loading