diff --git a/README.md b/README.md index c55ec2e..0d2365a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,68 @@ -# movie_name (To be changed later) -A movie suggesteur based on the current weather and the season. +# Clinema + + +![image](https://github.com/user-attachments/assets/3d9f59ae-7301-4103-8a8c-f8dec5719b61) + +## Overview + +Clinema is a cinema management application that allows users to manage movie listings, bookings, and user accounts. The application is built using a microservices architecture, with separate services for the API, client, and database. The API is developed using Flask, while the client is built with Vite and Tailwind CSS for a modern user interface. The application uses MySQL as the database to store all relevant data. + +## Features +- User authentication and authorization +- Movie listing and details +- Booking management +- Responsive design for mobile and desktop + +## Getting Started +To get started with Clinema, clone the repository and follow the instructions in the respective service directories for setup and deployment. + +## Tech Stack +Clinema utilizes a modern tech stack to ensure efficient development and a seamless user experience. The key technologies include: + +- **client**: A fast build tool and development server for modern web projects, used for the client-side application built with Vite. +- **server**: A Python framework for building web applications, specifically the API developed using Flask. +- **database**: A relational database management system used to store application data, specifically MySQL. + + + +## Technologies Used +- ReactJS +- Vite +- Tailwind CSS +- Flask +- MySQL + +## Installation +To install and run Clinema, follow these steps: + +1. **Clone the repository**: + ```bash + git clone https://github.com/Matsadura/movie_name.git + cd clinema + ``` + +2. **Set up the environment**: + - Ensure you have Docker and Docker Compose installed on your machine. + +3. **Build and run the services**: + ```bash + docker-compose up --build + ``` + +4. **Access the application**: + - The API will be available at `http://localhost:5000` + - The client can be accessed at `http://localhost:5173` + +5. **Database setup**: + - The MySQL database will be automatically set up by Docker. You can connect to it using the credentials defined in the `docker-compose.yml` file. + + + +## Authors + +- [Ali JBARI](https://github.com/ila36IX) +- [Badr ANNABI](https://github.com/Badr-Annabi) +- [Karim ASSIHOUT](https://github.com/ashtkarim) +- [Oumaima NAANAA](https://github.com/naanaa59) +- [Radouane ABOUNOUAS](https://github.com/RadouaneAbn) +- [Zidane ZAOUI](https://github.com/matsadura) diff --git a/client/src/components/MovieCard.jsx b/client/src/components/MovieCard.jsx index 74c00cc..384ee43 100644 --- a/client/src/components/MovieCard.jsx +++ b/client/src/components/MovieCard.jsx @@ -18,7 +18,7 @@ export default function MovieCard({userId, movie_id, title, poster, year, rate, const handleSave = useCallback(async () => { setSave(prevState => !prevState); - await toggleSave(userId, movie_id, 'save'); + await toggleSave(userId, movie_id, true); // HADI KHAS TKUN DYMANIC 3LA HSSAB CHNU KANT 9BL F DATABASE }, [userId, movie_id]); const handleOrder = useCallback(async () => { @@ -33,7 +33,7 @@ export default function MovieCard({userId, movie_id, title, poster, year, rate, const dataToSend = { user_id: userId, movie_id: movie_id, - like: liked + like: true // HADI KHAS TKUN DYMANIC 3LA HSSAB CHNU KANT 9BL F DATABASE }; if (token) { @@ -55,8 +55,8 @@ export default function MovieCard({userId, movie_id, title, poster, year, rate, const token = localStorage.getItem('_token'); const dataToSend = { user_id: userId, - movie_id: movie_id, - save: save + movie_id: movie_id, // HADI KHAS TKUN DYMANIC 3LA HSSAB CHNU KANT 9BL F DATABASE + save: true }; if (token) { diff --git a/client/src/components/Signin.jsx b/client/src/components/Signin.jsx index e01c2d9..b2ba32d 100644 --- a/client/src/components/Signin.jsx +++ b/client/src/components/Signin.jsx @@ -18,6 +18,7 @@ export default function LogIn({ slideRun }) { console.log(res); if (res.status === 200) { localStorage.setItem("_token", res.data.token); + localStorage.setItem("_user_id", res.data.user_id); // TMP window.location.href = "/"; } else { setErrCred(true); diff --git a/client/src/scenes/AllMoviesPage.jsx b/client/src/scenes/AllMoviesPage.jsx index 1fa8a65..7e4ad41 100644 --- a/client/src/scenes/AllMoviesPage.jsx +++ b/client/src/scenes/AllMoviesPage.jsx @@ -36,7 +36,39 @@ const AllMoviesPage = () => { ); if (response.data.results && response.data.results.length > 0) { - return response.data.results[0]; + const movieData = response.data.results[0]; + + // ADD MOVIE TO DATABASE + const token = localStorage.getItem('_token'); + if (token) { + const dataToSend = { + tmdb_id: movieData.id, + name: movieData.title, + description: movieData.overview, + poster: `https://image.tmdb.org/t/p/w500/${movieData.poster_path}`, + adult: movieData.adult, + popularity: movieData.popularity, + year: new Date(movieData.release_date).getFullYear(), + rating: movieData.vote_average + }; + + try { + const addMovieResponse = await axios.post( + `${process.env.REACT_APP_API_URL}/movies`, + dataToSend, + { + headers: { + Authorization: `Bearer ${token}`, + } + } + ); + console.log('Movie added to database:', addMovieResponse.data); + } catch (dbErr) { + console.error('Error adding movie to database:', dbErr); + } + } + + return movieData; } else { return null; } @@ -78,8 +110,8 @@ const AllMoviesPage = () => { return ( /user_movies') @jwt_required() def user_movies(user_id): + """ + GET + - Header: Authorization Bearer Token (required) + """ try: current_user = get_jwt_identity() except Exception as e: @@ -76,6 +80,10 @@ def user_movies(user_id): @app_views.get('//save') @jwt_required() def save_movies(user_id): + """ + GET + - Header: Authorization Bearer Token (required) + """ try: current_user = get_jwt_identity() except Exception as e: @@ -127,6 +135,16 @@ def liked_movies(user_id): @app_views.post('//liked') @jwt_required() def save_or_liked(user_id): + """ + POST + - Header: Authorization Bearer Token (required) + Input: + - movie_id: Integer (required) + - user_id: String (required) + - save: Boolean + - like: Boolean + Only one of save or like is required + """ try: current_user = get_jwt_identity() except Exception as e: @@ -153,7 +171,9 @@ def save_or_liked(user_id): if existing_user_movie: return jsonify({'error': 'User Movie relation already exists'}), 409 - if data['save'] is None and data['like'] is None: + save = data.get('save') + like = data.get('like') + if save is None and like is None: return jsonify({'error': 'Missing required save or like boolean'}), 400 existing_user = storage.get(User, data['user_id']) @@ -195,6 +215,10 @@ def user_movie_id(user_id, user_movie_id): return jsonify({'error': 'Unauthorized to DEL OR PUT this data'}), 403 if request.method == 'DELETE': + """ + DELETE + - Header: Authorization Bearer Token (required) + """ um = storage.get_specific(User_Movie, 'id', user_movie_id) if not um: return jsonify({'error': 'User_movie is not found'}), 404 @@ -203,6 +227,14 @@ def user_movie_id(user_id, user_movie_id): return jsonify({}) if request.method == 'PUT': + """ + PUT + - Header: Authorization Bearer Token (required) + Input: + - save: Boolean + - like: Boolean + Only one of these two is required + """ try: data = request.get_json() except Exception as e: diff --git a/server/api/views/users.py b/server/api/views/users.py index 417096e..24f825c 100644 --- a/server/api/views/users.py +++ b/server/api/views/users.py @@ -40,7 +40,11 @@ @app_views.route('/users/profile', methods=['GET']) @jwt_required() def get_user_profile(): - """Get user profile""" + """Get user profile + + GET + - Header: Authorization Bearer Token (required) + """ try: current_user = get_jwt_identity() except Exception as e: @@ -55,7 +59,15 @@ def get_user_profile(): @app_views.route('/users//profile', methods=['DELETE', 'PUT']) @jwt_required() def handle_user_id(user_id): - """Handle user_id related requests""" + """ + Handle user_id related requests + + POST + - Header: Authorization Bearer Token (required) + Input: + first_name: String (required) + last_name: String (required) + """ try: current_user = get_jwt_identity() except Exception as e: diff --git a/server/models/movie.py b/server/models/movie.py index d9ad33b..181a3b6 100755 --- a/server/models/movie.py +++ b/server/models/movie.py @@ -8,16 +8,17 @@ class Movie(BaseModel, Base): """Representation of a user """ __tablename__ = 'movies' + tmdb_id = Column(Integer, nullable=False) name = Column(String(128), nullable=False) - description = Column(String(512), nullable=True) + description = Column(String(2024), nullable=True) poster = Column(String(512), nullable=True) adult = Column(Boolean, nullable=True) popularity = Column(Float, nullable=True) year = Column(Integer, nullable=True) rating = Column(Float, nullable=True) - user_movies = relationship("User_Movie", - back_populates="movie", - cascade="all, delete-orphan") + # user_movies = relationship("User_Movie", + # back_populates="movie", + # cascade="all, delete-orphan") def __init__(self, *args, **kwargs): """Initializes user""" diff --git a/server/models/user_movie.py b/server/models/user_movie.py index 9f806a6..2f3ce12 100755 --- a/server/models/user_movie.py +++ b/server/models/user_movie.py @@ -11,12 +11,13 @@ class User_Movie(BaseModel, Base): user_id = Column(String(128), ForeignKey('users.id', ondelete='CASCADE'), nullable=False) - movie_id = Column(String(128), - ForeignKey('movies.id', ondelete='CASCADE'), - nullable=False) + # movie_id = Column(String(128), + # ForeignKey('movies.id', ondelete='CASCADE'), + # nullable=False) + movie_id = Column(String(128), nullable=False) save = Column(Boolean, nullable=True) like = Column(Boolean, nullable=True) - movie = relationship("Movie", back_populates="user_movies") + # movie = relationship("Movie", back_populates="user_movies") user = relationship("User", back_populates="user_movies",) def __init__(self, *args, **kwargs):