From 4b8b0b3217d6c95533bd20fcb508f2a4839cf2c9 Mon Sep 17 00:00:00 2001 From: Akintayo Durotoye Date: Wed, 25 Dec 2024 13:32:27 +0000 Subject: [PATCH] updated --- .env.example | 4 - .../migration.sql | 11 ++ prisma/migrations/migration_lock.toml | 4 +- prisma/schema.prisma | 3 + src/client/App.jsx | 104 ++++++++++++++++-- src/server/controllers/movie.js | 58 +++++++--- src/server/controllers/user.js | 71 +++++++++--- 7 files changed, 205 insertions(+), 50 deletions(-) delete mode 100644 .env.example create mode 100644 prisma/migrations/20241225132850_add_userid_to_movie/migration.sql 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

    - {movies.map(movie => { + {movies.map((movie) => { return (
  • {movie.title}

    diff --git a/src/server/controllers/movie.js b/src/server/controllers/movie.js index d4733b61..abd4f36d 100644 --- a/src/server/controllers/movie.js +++ b/src/server/controllers/movie.js @@ -1,31 +1,55 @@ -import jwt from 'jsonwebtoken'; -import { PrismaClient } from '@prisma/client' +import jwt from "jsonwebtoken"; +import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); -const jwtSecret = 'mysecret'; +const jwtSecret = "mysecret"; const getAllMovies = async (req, res) => { + try { const movies = await prisma.movie.findMany(); - res.json({ data: movies }); + } catch (error) { + console.error("Error fetching movies:", error.message); + res.status(500).json({ error: "Internal server error." }); + } }; const createMovie = async (req, res) => { - const { title, description, runtimeMins } = req.body; + const { title, description, runtimeMins } = req.body; - try { - const token = null; - // todo verify the token - } catch (e) { - return res.status(401).json({ error: 'Invalid token provided.' }) - } + try { + // todo verify the token + const authHeader = req.headers.authorization; - const createdMovie = null; + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return res + .status(401) + .json({ error: "Authorization token missing or invalid." }); + } + const token = authHeader.split(" ")[1]; + + const decoded = jwt.verify(token, jwtSecret); + console.log("Token decoded:", decoded); + + // Create the movie + const createdMovie = await prisma.movie.create({ + data: { + title, + description, + runtimeMins, + userId: decoded.userId, + }, + }); + res.status(201).json({ data: createdMovie }); + } catch (error) { + console.log("error creating movie", error.message); + + if (error.name === "JsonWebTokenError") { + return res.status(401).json({ error: "Invalid token provided." }); + } - res.json({ data: createdMovie }); + return res.status(501).json({ error: "Internal error." }); + } }; -export { - getAllMovies, - createMovie -}; +export { getAllMovies, createMovie }; diff --git a/src/server/controllers/user.js b/src/server/controllers/user.js index 05db4183..31ee82a5 100644 --- a/src/server/controllers/user.js +++ b/src/server/controllers/user.js @@ -6,31 +6,66 @@ const prisma = new PrismaClient(); const jwtSecret = 'mysecret'; const register = async (req, res) => { - const { username, password } = req.body; + try { - const createdUser = null; + const { username, password } = req.body; - res.json({ data: createdUser }); -}; - -const login = async (req, res) => { - const { username, password } = req.body; + // check if username alreaady exists + const existingUser = await prisma.user.findUnique({ + where: { username} + }) + if(existingUser) { + return res.status(400).json({error: 'username already taken'}) + } + // const createdUser = null; + + // hash the password + const hashedPassword = await bcrypt.hash(password, 10); + console.log("hashed password", hashedPassword) - const foundUser = null; - if (!foundUser) { - return res.status(401).json({ error: 'Invalid username or password.' }); + const createdUser = await prisma.user.create({ + data: { + username: username, + password: hashedPassword + } + }) + res.status(201).json({ message: ` ${createdUser.username} was successfully created !` + }); + } catch (error){ + console.error('Registration error:', error.message); + res.status(500).json({ error: 'Internal server error!'}) } +}; - const passwordsMatch = false; - - if (!passwordsMatch) { - return res.status(401).json({ error: 'Invalid username or password.' }); +const login = async (req, res) => { + try { + + const { username, password } = req.body; + + const foundUser = await prisma.user.findUnique({ + where: { username} + }) + + if (!foundUser) { + return res.status(401).json({ error: 'Invalid username or password.' }); + } + + const passwordsMatch = await bcrypt.compare(password, foundUser.password) + + if (!passwordsMatch) { + return res.status(401).json({ error: 'Invalid username or password.' }); + } + + const token = jwt.sign({ + userId: foundUser.id, username: foundUser.username + }, jwtSecret, { expiresIn: '1h' }) + + res.json({ message: 'Login successfully!', token }); + } catch (error) { + console.error('Login error:', error.message); + res.status(500).json({ error: 'Internal server error!'}) } - - const token = null; - - res.json({ data: token }); }; export {