diff --git a/.env.example b/.env.example deleted file mode 100644 index 73eec69b..00000000 --- a/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -DATABASE_URL="YOUR_DATABASE_URL" - -# This env var must be prefixed with `VITE_` in order to work in the client / Vite React app. -VITE_PORT=4000 diff --git a/prisma/migrations/20241225132850_add_userid_to_movie/migration.sql b/prisma/migrations/20241225132850_add_userid_to_movie/migration.sql new file mode 100644 index 00000000..079c52a8 --- /dev/null +++ b/prisma/migrations/20241225132850_add_userid_to_movie/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - Added the required column `userId` to the `Movie` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Movie" ADD COLUMN "userId" INTEGER NOT NULL; + +-- AddForeignKey +ALTER TABLE "Movie" ADD CONSTRAINT "Movie_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index 6bf90154..fbffa92c 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 68676dd3..c22dd909 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,6 +15,7 @@ model User { id Int @id @default(autoincrement()) username String @unique password String + movies Movie[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -24,6 +25,8 @@ model Movie { title String @unique description String runtimeMins Int + userId Int + user User @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/src/client/App.jsx b/src/client/App.jsx index bae3b635..72c6ae65 100644 --- a/src/client/App.jsx +++ b/src/client/App.jsx @@ -1,20 +1,39 @@ -import { useEffect, useState } from 'react'; -import './App.css'; -import MovieForm from './components/MovieForm'; -import UserForm from './components/UserForm'; +import { useEffect, useState } from "react"; +import "./App.css"; +import MovieForm from "./components/MovieForm"; +import UserForm from "./components/UserForm"; const port = import.meta.env.VITE_PORT; const apiUrl = `http://localhost:${port}`; function App() { const [movies, setMovies] = useState([]); + const [errorMessage, setErrorMessage] = useState(""); + const [successMessage, setSuccessMessage] = useState(""); useEffect(() => { - fetch(`${apiUrl}/movie`) - .then(res => res.json()) - .then(res => setMovies(res.data)); + const fetchMovies = async () => { + try { + const response = await fetch(`${apiUrl}/movie`); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || "Failed to fetch movies"); + } + setMovies(data.data); + } catch (error) { + console.error("Error fetching movies:", error.message); + setErrorMessage("Failed to fetch movies."); + } + }; + fetchMovies(); }, []); + const clearMessages = () => { + setErrorMessage(""); + setSuccessMessage(""); + }; + /** * HINTS! * 1. This handle___ functions below use async/await to handle promises, but the @@ -34,16 +53,80 @@ function App() { * */ const handleRegister = async ({ username, password }) => { + clearMessages(); + try { + const response = await fetch(`${apiUrl}/user/register`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Failed to register"); + } + setSuccessMessage("User registered successfully!"); + } catch (error) { + setErrorMessage("Registration failed: " + error.message); + } }; const handleLogin = async ({ username, password }) => { + clearMessages(); + try { + const response = await fetch(`${apiUrl}/user/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || "Failed to login"); + } + localStorage.setItem("token", data.token) + setSuccessMessage("User logged in successfully !") + } catch (error) { + setErrorMessage("Login failed: " + error.message) + } }; const handleCreateMovie = async ({ title, description, runtimeMins }) => { + clearMessages(); + + const token = localStorage.getItem("token"); + + if(!token){ + setErrorMessage("You must logged in to create a movie") + return; + } - } + try { + const response = await fetch(`${apiUrl}/movie`, { + method: "POST", + headers: { "Content-Type": "application/json", + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ title, description, runtimeMins }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Failed to create movie"); + } + + // Update the movie list + setMovies((prevMovies) => [...prevMovies, data.data]); + setSuccessMessage("Movie created successfully!"); + + } catch (error) { + // console.error("Create movie error:", error.message); + setErrorMessage("Failed to create movie: " + error.message) + } + }; return (
@@ -56,9 +139,12 @@ function App() {

Create a movie

+ {successMessage &&

{successMessage}

} + {errorMessage &&

{errorMessage}

} +

Movie list