From ba9e668fcfdba9cbcc77a41759c89b0f4ebb5f22 Mon Sep 17 00:00:00 2001 From: Son Nguyen Date: Tue, 18 Jun 2024 11:12:12 +0700 Subject: [PATCH 1/3] Final: Enhanced app's loading times (#196) --- MovieVerse-Backend/.idea/workspace.xml | 60 +++++- MovieVerse-Backend/README.md | 12 +- .../crawler/ai/adjust_crawling_strategy.py | 26 +++ .../crawler/ai/content_summarization.py | 14 ++ .../app/python/crawler/ai/image_analysis.py | 40 ++++ .../crawler/ai/sentiment_trend_analysis.py | 16 ++ .../app/python/crawler/ai/text_analysis.py | 12 ++ .../python/crawler/crawler_orchestrator.py | 36 ++++ .../app/python/crawler/datasources.py | 60 ++++++ .../app/python/crawler/exceptions.py | 18 ++ .../app/python/crawler/models.py | 37 ++++ .../app/python/crawler/parser.py | 65 ++++++ .../app/python/crawler/scheduler.py | 10 + .../app/python/crawler/scraper.py | 11 + MovieVerse-Mobile/app/python/crawler/tasks.py | 23 +++ MovieVerse-Mobile/app/python/crawler/utils.py | 58 ++++++ .../app/python/crawler/validators.py | 81 ++++++++ MovieVerse-Mobile/app/python/databases/app.js | 93 +++++++++ .../app/python/databases/config.js | 18 ++ .../app/python/databases/publish.js | 22 ++ .../app/python/django_backend/.flake8 | 27 +++ .../app/python/django_backend/db.sqlite3 | Bin 0 -> 188416 bytes .../django_backend/django_backend/__init__.py | 0 .../django_backend/django_backend/asgi.py | 16 ++ .../django_backend/django_backend/settings.py | 123 +++++++++++ .../django_backend/django_backend/urls.py | 25 +++ .../django_backend/django_backend/wsgi.py | 16 ++ .../app/python/django_backend/manage.py | 22 ++ .../django_backend/movieverse/__init__.py | 0 .../python/django_backend/movieverse/admin.py | 44 ++++ .../python/django_backend/movieverse/apps.py | 6 + .../django_backend/movieverse/caching.py | 10 + .../django_backend/movieverse/decorators.py | 14 ++ .../django_backend/movieverse/exceptions.py | 12 ++ .../python/django_backend/movieverse/forms.py | 11 + .../movieverse/migrations/0001_initial.py | 72 +++++++ .../movieverse/migrations/0002_review.py | 26 +++ .../movieverse/migrations/__init__.py | 0 .../django_backend/movieverse/models.py | 66 ++++++ .../movieverse/notifications.py | 12 ++ .../django_backend/movieverse/serializers.py | 20 ++ .../django_backend/movieverse/signals.py | 11 + .../templates/movieverse/actor_detail.html | 105 ++++++++++ .../templates/movieverse/comment_item.html | 8 + .../templates/movieverse/director_detail.html | 93 +++++++++ .../templates/movieverse/index.html | 106 ++++++++++ .../templates/movieverse/movie_detail.html | 97 +++++++++ .../templates/movieverse/search.html | 108 ++++++++++ .../python/django_backend/movieverse/tests.py | 3 + .../python/django_backend/movieverse/urls.py | 20 ++ .../python/django_backend/movieverse/views.py | 192 ++++++++++++++++++ .../app/python/flask_backend/.flake8 | 27 +++ .../app/python/flask_backend/flask.py | 148 ++++++++++++++ .../python/flask_backend/micro-services.py | 164 +++++++++++++++ .../app/python/micro-services.py | 164 +++++++++++++++ MovieVerse-Mobile/app/python/requirements.txt | 16 ++ 56 files changed, 2491 insertions(+), 5 deletions(-) create mode 100644 MovieVerse-Mobile/app/python/crawler/ai/adjust_crawling_strategy.py create mode 100644 MovieVerse-Mobile/app/python/crawler/ai/content_summarization.py create mode 100644 MovieVerse-Mobile/app/python/crawler/ai/image_analysis.py create mode 100644 MovieVerse-Mobile/app/python/crawler/ai/sentiment_trend_analysis.py create mode 100644 MovieVerse-Mobile/app/python/crawler/ai/text_analysis.py create mode 100644 MovieVerse-Mobile/app/python/crawler/crawler_orchestrator.py create mode 100644 MovieVerse-Mobile/app/python/crawler/datasources.py create mode 100644 MovieVerse-Mobile/app/python/crawler/exceptions.py create mode 100644 MovieVerse-Mobile/app/python/crawler/models.py create mode 100644 MovieVerse-Mobile/app/python/crawler/parser.py create mode 100644 MovieVerse-Mobile/app/python/crawler/scheduler.py create mode 100644 MovieVerse-Mobile/app/python/crawler/scraper.py create mode 100644 MovieVerse-Mobile/app/python/crawler/tasks.py create mode 100644 MovieVerse-Mobile/app/python/crawler/utils.py create mode 100644 MovieVerse-Mobile/app/python/crawler/validators.py create mode 100644 MovieVerse-Mobile/app/python/databases/app.js create mode 100644 MovieVerse-Mobile/app/python/databases/config.js create mode 100644 MovieVerse-Mobile/app/python/databases/publish.js create mode 100644 MovieVerse-Mobile/app/python/django_backend/.flake8 create mode 100644 MovieVerse-Mobile/app/python/django_backend/db.sqlite3 create mode 100644 MovieVerse-Mobile/app/python/django_backend/django_backend/__init__.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/django_backend/asgi.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/django_backend/settings.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/django_backend/urls.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/django_backend/wsgi.py create mode 100755 MovieVerse-Mobile/app/python/django_backend/manage.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/__init__.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/admin.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/apps.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/caching.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/decorators.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/exceptions.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/forms.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/0001_initial.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/0002_review.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/__init__.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/models.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/notifications.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/serializers.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/signals.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/actor_detail.html create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/comment_item.html create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/director_detail.html create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/index.html create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/movie_detail.html create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/search.html create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/tests.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/urls.py create mode 100644 MovieVerse-Mobile/app/python/django_backend/movieverse/views.py create mode 100644 MovieVerse-Mobile/app/python/flask_backend/.flake8 create mode 100644 MovieVerse-Mobile/app/python/flask_backend/flask.py create mode 100644 MovieVerse-Mobile/app/python/flask_backend/micro-services.py create mode 100644 MovieVerse-Mobile/app/python/micro-services.py create mode 100644 MovieVerse-Mobile/app/python/requirements.txt diff --git a/MovieVerse-Backend/.idea/workspace.xml b/MovieVerse-Backend/.idea/workspace.xml index 28638314..e0433711 100644 --- a/MovieVerse-Backend/.idea/workspace.xml +++ b/MovieVerse-Backend/.idea/workspace.xml @@ -5,8 +5,61 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -241,4 +295,4 @@ - + \ No newline at end of file diff --git a/MovieVerse-Backend/README.md b/MovieVerse-Backend/README.md index 06bf4761..45636efe 100644 --- a/MovieVerse-Backend/README.md +++ b/MovieVerse-Backend/README.md @@ -51,6 +51,7 @@ The microservices architecture of MovieVerse is designed to segregate the applic - Redis or RabbitMQ as a broker for Celery - BeautifulSoup4 and Requests for web scraping in the Crawler Service - Transformers and PyTorch for AI functionalities within the Crawler Service +- Python 3.8 or higher (and an IDE that supports Python and can run Python scripts) To satisfy these prerequisites, simply run the following command: @@ -68,7 +69,12 @@ pip install -r requirements.txt ```bash cd mobile-backend ``` -3. Follow the specific installation instructions for each service below. +3. Create a Virtual Environment (optional but recommended): + ```bash + python3 -m venv venv + source venv/bin/activate + ``` +4. Follow the specific installation instructions for each service below. ### Running the Services @@ -210,7 +216,9 @@ MovieVerse currently uses MongoDB, Redis, and MySQL as its primary databases. To [x] Received Hello from RabbitMQ ``` -Note that these servers are for your local development environment only. For our production environment, our databases might look different (in fact, they do!). +Note that these servers are for your local development environment only, in order for you to see how our backend services interact with each other. + +In our production environment, we use cloud-based services like AWS, Azure, and Google Cloud to host our databases and services. This thus will look different from what you might see on your end. #### Machine Learning Services diff --git a/MovieVerse-Mobile/app/python/crawler/ai/adjust_crawling_strategy.py b/MovieVerse-Mobile/app/python/crawler/ai/adjust_crawling_strategy.py new file mode 100644 index 00000000..bc373294 --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/ai/adjust_crawling_strategy.py @@ -0,0 +1,26 @@ +import requests + + +def adjust_crawling_strategy(sentiment_trend, crawling_params): + """ + Adjust crawling parameters based on sentiment trend analysis. + + :param sentiment_trend: DataFrame with sentiment trend analysis. + :param crawling_params: Dictionary of current crawling parameters. + :return: Adjusted crawling parameters. + """ + recent_trend = sentiment_trend['rolling_avg_sentiment'].iloc[-1] + if recent_trend > 0.5: + crawling_params['frequency'] *= 1.1 # Increase frequency + else: + crawling_params['frequency'] *= 0.9 # Decrease frequency + return crawling_params + + +def fetch_movie_data(url): + headers = {'User-Agent': 'Mozilla/5.0'} + response = requests.get(url, headers=headers) + if response.status_code == 200: + return response.text + else: + return None diff --git a/MovieVerse-Mobile/app/python/crawler/ai/content_summarization.py b/MovieVerse-Mobile/app/python/crawler/ai/content_summarization.py new file mode 100644 index 00000000..ce39afef --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/ai/content_summarization.py @@ -0,0 +1,14 @@ +from transformers import pipeline + +summarizer = pipeline("summarization") + + +def summarize_content(content): + """ + Summarize the content using a pre-trained summarization model. + + :param content: String containing the content to summarize. + :return: Summarized content. + """ + summary = summarizer(content, max_length=50, min_length=25, do_sample=False) + return summary[0]['summary_text'] diff --git a/MovieVerse-Mobile/app/python/crawler/ai/image_analysis.py b/MovieVerse-Mobile/app/python/crawler/ai/image_analysis.py new file mode 100644 index 00000000..eaa803e8 --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/ai/image_analysis.py @@ -0,0 +1,40 @@ +from PIL import Image +import requests +from torchvision import models, transforms +import torch +from io import BytesIO + +# Load a pretrained image classification model +model = models.resnet50(pretrained=True) +model.eval() + +# Define image transformations +transform = transforms.Compose([ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), +]) + + +def classify_image(image_url): + try: + response = requests.get(image_url) + image = Image.open(BytesIO(response.content)) + tensor = transform(image).unsqueeze(0) + + with torch.no_grad(): + outputs = model(tensor) + _, predicted = torch.max(outputs, 1) + + # Translate predicted category index to a label here + return predicted.item() + except Exception as e: + raise e + + +def analyze_image(image_url): + try: + return classify_image(image_url) + except Exception as e: + raise e diff --git a/MovieVerse-Mobile/app/python/crawler/ai/sentiment_trend_analysis.py b/MovieVerse-Mobile/app/python/crawler/ai/sentiment_trend_analysis.py new file mode 100644 index 00000000..9882bae2 --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/ai/sentiment_trend_analysis.py @@ -0,0 +1,16 @@ +import pandas as pd +from datetime import datetime + + +def analyze_sentiment_trend(sentiment_data): + """ + Analyze sentiment trends over time from collected data. + + :param sentiment_data: List of dictionaries containing 'date' and 'sentiment' keys. + :return: DataFrame with sentiment trend analysis. + """ + df = pd.DataFrame(sentiment_data) + df['date'] = pd.to_datetime(df['date']) + df.sort_values('date', inplace=True) + df['rolling_avg_sentiment'] = df['sentiment'].rolling(window=7).mean() + return df diff --git a/MovieVerse-Mobile/app/python/crawler/ai/text_analysis.py b/MovieVerse-Mobile/app/python/crawler/ai/text_analysis.py new file mode 100644 index 00000000..ac7c4fcd --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/ai/text_analysis.py @@ -0,0 +1,12 @@ +from transformers import pipeline + +# Load a sentiment analysis pipeline +sentiment_pipeline = pipeline("sentiment-analysis") + + +def analyze_text_sentiment(text): + try: + result = sentiment_pipeline(text) + return result + except Exception as e: + raise e diff --git a/MovieVerse-Mobile/app/python/crawler/crawler_orchestrator.py b/MovieVerse-Mobile/app/python/crawler/crawler_orchestrator.py new file mode 100644 index 00000000..5705cd43 --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/crawler_orchestrator.py @@ -0,0 +1,36 @@ +from .scraper import fetch_movie_data +from .parser import parse_movie_data +from .ai.text_analysis import analyze_text_sentiment +from .ai.image_analysis import classify_image +from .tasks import crawl_movie_data_and_store +from .models import MovieDetail +from django.core.exceptions import ObjectDoesNotExist + + +def orchestrate_crawling(url): + try: + # Step 1: Fetch data + html_content = fetch_movie_data(url) + if not html_content: + raise ValueError("Failed to fetch data from URL.") + + # Step 2: Parse data + movie_data = parse_movie_data(html_content) + if not movie_data: + raise ValueError("Failed to parse movie data.") + + # Step 3: Analyze text sentiment + sentiment_result = analyze_text_sentiment(movie_data['description']) + movie_data['sentiment'] = sentiment_result + + # Step 4: Image analysis + image_analysis_result = classify_image(movie_data['poster_url']) + movie_data['image_analysis'] = image_analysis_result + + # Step 5: Store data in the database + crawl_movie_data_and_store(movie_data) + + print("Crawling and data processing completed successfully.") + + except Exception as e: + print(f"Error during the crawling process: {e}") diff --git a/MovieVerse-Mobile/app/python/crawler/datasources.py b/MovieVerse-Mobile/app/python/crawler/datasources.py new file mode 100644 index 00000000..7f776e5e --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/datasources.py @@ -0,0 +1,60 @@ +DATA_SOURCES = [ + 'https://www.imdb.com/chart/top', + 'https://www.rottentomatoes.com/top/bestofrt/', + 'https://www.metacritic.com/browse/movies/score/metascore/all/filtered', + 'https://www.themoviedb.org/movie/top_rated', + 'https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW', + 'https://www.the-numbers.com/movie/budgets/all', + 'https://www.the-numbers.com/movie/production-companies/', + 'https://www.the-numbers.com/movie/keywords', + 'https://www.the-numbers.com/movie/genres', + 'https://www.the-numbers.com/movie/franchises', + 'https://www.the-numbers.com/movie/creative-type', + 'https://www.the-numbers.com/movie/production-method', + 'https://www.the-numbers.com/movie/source', + 'https://www.the-numbers.com/movie/production-status', + 'https://www.the-numbers.com/movie/production-countries', + 'https://www.the-numbers.com/movie/languages', + 'https://www.the-numbers.com/movie/certifications', + 'https://www.the-numbers.com/movie/mpaa-ratings', + 'https://www.the-numbers.com/movie/running-times', + 'https://www.the-numbers.com/movie/decades', + 'https://www.the-numbers.com/movie/release-dates', + 'https://www.the-numbers.com/movie/release-types', + 'https://www.the-numbers.com/movie/weekend-box-office-chart', + 'https://www.the-numbers.com/movie/weekly-box-office-chart', + 'https://www.the-numbers.com/movie/weekend-per-theater-chart', + 'https://www.the-numbers.com/movie/weekly-per-theater-chart', + 'https://www.the-numbers.com/movie/theater-count', + 'https://www.the-numbers.com/movie/market', + 'https://www.the-numbers.com/movie/production-countries', + 'https://www.the-numbers.com/movie/production-method', + 'https://www.the-numbers.com/movie/source', + 'https://www.the-numbers.com/movie/production-status', + 'https://www.the-numbers.com/movie/production-countries', + 'https://www.the-numbers.com/movie/languages', + 'https://www.the-numbers.com/movie/certifications', + 'https://www.the-numbers.com/movie/mpaa-ratings', + 'https://www.the-numbers.com/movie/running-times', + 'https://www.the-numbers.com/movie/decades', + 'https://www.the-numbers.com/movie/release-dates', + 'https://www.the-numbers.com/movie/release-types', + 'https://www.the-numbers.com/movie/weekend-box-office-chart', + 'https://www.the-numbers.com/movie/weekly-box-office-chart', + 'https://www.the-numbers.com/movie/weekend-per-theater-chart', + 'https://www.the-numbers.com/movie/weekly-per-theater-chart', + 'https://www.the-numbers.com/movie/theater-count', + 'https://www.the-numbers.com/movie/market', + 'https://www.the-numbers.com/movie/production-companies', + 'https://www.the-numbers.com/movie/keywords', + 'https://www.the-numbers.com/movie/genres', + 'https://www.the-numbers.com/movie/franchises', + 'https://www.the-numbers.com/movie/creative-type', + 'https://www.the-numbers.com/movie/production-method', + 'https://www.the-numbers.com/movie/source', +] + + +def fetch_from_sources(crawl_movie_data_and_store=None): + for source in DATA_SOURCES: + crawl_movie_data_and_store.delay(source) diff --git a/MovieVerse-Mobile/app/python/crawler/exceptions.py b/MovieVerse-Mobile/app/python/crawler/exceptions.py new file mode 100644 index 00000000..6fb4c712 --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/exceptions.py @@ -0,0 +1,18 @@ +class DataFetchError(Exception): + pass + + +class ParsingError(Exception): + pass + + +class DataValidationError(Exception): + pass + + +class DataSaveError(Exception): + pass + + +class DataFetchError(Exception): + pass diff --git a/MovieVerse-Mobile/app/python/crawler/models.py b/MovieVerse-Mobile/app/python/crawler/models.py new file mode 100644 index 00000000..c4d3cb3a --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/models.py @@ -0,0 +1,37 @@ +from django.db import models + + +class MovieDetail(models.Model): + movie = models.OneToOneField('moviereviews.Movie', on_delete=models.CASCADE, related_name='details') + description = models.TextField() + poster_url = models.URLField() + cast = models.TextField() + director = models.CharField(max_length=255) + genres = models.TextField() + duration = models.CharField(max_length=255) + rating = models.FloatField() + release_date = models.DateField() + trailer_url = models.URLField() + imdb_url = models.URLField() + rotten_tomatoes_url = models.URLField() + metacritic_url = models.URLField() + awards = models.TextField() + box_office = models.CharField(max_length=255) + budget = models.CharField(max_length=255) + company = models.CharField(max_length=255) + country = models.CharField(max_length=255) + language = models.CharField(max_length=255) + tagline = models.CharField(max_length=255) + website = models.URLField() + writers = models.TextField() + year = models.IntegerField() + id = models.CharField(max_length=255) + imdb_rating = models.FloatField() + imdb_votes = models.IntegerField() + metascore = models.IntegerField() + rotten_tomatoes_rating = models.IntegerField() + rotten_tomatoes_reviews = models.IntegerField() + rotten_tomatoes_fresh = models.IntegerField() + + def __str__(self): + return self.movie.title + " Details" diff --git a/MovieVerse-Mobile/app/python/crawler/parser.py b/MovieVerse-Mobile/app/python/crawler/parser.py new file mode 100644 index 00000000..18cec352 --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/parser.py @@ -0,0 +1,65 @@ +from bs4 import BeautifulSoup + + +def parse_movie_data(html_content): + soup = BeautifulSoup(html_content, 'html.parser') + movie_data = { + 'name': soup.find('h1', class_='movie-title').text.strip(), + 'description': soup.find('div', class_='movie-description').text.strip(), + 'poster_url': soup.find('img', class_='movie-poster')['src'], + 'cast': [cast.text.strip() for cast in soup.find_all('div', class_='movie-cast-member')], + 'director': soup.find('div', class_='movie-director').text.strip(), + 'genres': [genre.text.strip() for genre in soup.find_all('span', class_='movie-genre')], + 'duration': soup.find('div', class_='movie-duration').text.strip(), + 'rating': float(soup.find('span', class_='movie-rating').text.strip()), + 'release_date': soup.find('div', class_='movie-release-date').text.strip(), + 'trailer_url': soup.find('a', class_='movie-trailer')['href'], + 'imdb_url': soup.find('a', class_='movie-imdb')['href'], + 'rotten_tomatoes_url': soup.find('a', class_='movie-rotten-tomatoes')['href'], + 'metacritic_url': soup.find('a', class_='movie-metacritic')['href'], + 'reviews': [ + { + 'author': review.find('div', class_='review-author').text.strip(), + 'content': review.find('div', class_='review-content').text.strip(), + 'rating': float(review.find('span', class_='review-rating').text.strip()) + } + for review in soup.find_all('div', class_='movie-review') + ], + 'similar_movies': [ + { + 'name': movie.find('h2', class_='movie-title').text.strip(), + 'poster_url': movie.find('img', class_='movie-poster')['src'], + 'rating': float(movie.find('span', class_='movie-rating').text.strip()) + } + for movie in soup.find_all('div', class_='movie-similar') + ], + 'recommendations': [ + { + 'name': movie.find('h2', class_='movie-title').text.strip(), + 'poster_url': movie.find('img', class_='movie-poster')['src'], + 'rating': float(movie.find('span', class_='movie-rating').text.strip()) + } + for movie in soup.find_all('div', class_='movie-recommendation') + ], + 'awards': soup.find('div', class_='movie-awards').text.strip(), + 'box_office': soup.find('div', class_='movie-boxoffice').text.strip(), + 'budget': soup.find('div', class_='movie-budget').text.strip(), + 'company': soup.find('div', class_='movie-company').text.strip(), + 'country': soup.find('div', class_='movie-country').text.strip(), + 'language': soup.find('div', class_='movie-language').text.strip(), + 'tagline': soup.find('div', class_='movie-tagline').text.strip(), + 'website': soup.find('div', class_='movie-website').text.strip(), + 'writers': [writer.text.strip() for writer in soup.find_all('div', class_='movie-writer')], + 'year': int(soup.find('div', class_='movie-year').text.strip()), + 'id': 'tt' + soup.find('span', class_='movie-imdb-id').text.strip(), + 'imdb_rating': float(soup.find('span', class_='movie-imdb-rating').text.strip()), + 'imdb_votes': int(soup.find('span', class_='movie-imdb-votes').text.strip().replace(',', '')), + 'metascore': int(soup.find('span', class_='movie-metascore').text.strip()), + 'rotten_tomatoes_rating': int( + soup.find('span', class_='movie-rotten-tomatoes-rating').text.strip().replace('%', '')), + 'rotten_tomatoes_reviews': int( + soup.find('span', class_='movie-rotten-tomatoes-reviews').text.strip().replace(',', '')), + 'rotten_tomatoes_fresh': int( + soup.find('span', class_='movie-rotten-tomatoes-fresh').text.strip().replace('%', '')), + } + return movie_data diff --git a/MovieVerse-Mobile/app/python/crawler/scheduler.py b/MovieVerse-Mobile/app/python/crawler/scheduler.py new file mode 100644 index 00000000..03c66d01 --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/scheduler.py @@ -0,0 +1,10 @@ +from datetime import datetime, timedelta +from apscheduler.schedulers.background import BackgroundScheduler +from tasks import crawl_movie_data_and_store + +scheduler = BackgroundScheduler() + + +def start_scheduler(): + scheduler.add_job(crawl_movie_data_and_store, 'interval', hours=24, args=['http://example.com/movies']) + scheduler.start() diff --git a/MovieVerse-Mobile/app/python/crawler/scraper.py b/MovieVerse-Mobile/app/python/crawler/scraper.py new file mode 100644 index 00000000..5351dcdb --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/scraper.py @@ -0,0 +1,11 @@ +import requests +from bs4 import BeautifulSoup + + +def fetch_movie_data(url): + headers = {'User-Agent': 'Mozilla/5.0'} + response = requests.get(url, headers=headers) + if response.status_code == 200: + return response.text + else: + return None diff --git a/MovieVerse-Mobile/app/python/crawler/tasks.py b/MovieVerse-Mobile/app/python/crawler/tasks.py new file mode 100644 index 00000000..5127da9f --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/tasks.py @@ -0,0 +1,23 @@ +from celery import shared_task +from .scraper import fetch_movie_data +from .parser import parse_movie_data +from moviereviews.models import Movie +from models import MovieDetail + + +@shared_task +def crawl_movie_data_and_store(url): + html_content = fetch_movie_data(url) + if html_content: + movie_data = parse_movie_data(html_content) + movie, created = Movie.objects.get_or_create(title=movie_data['name']) + MovieDetail.objects.update_or_create( + movie=movie, + defaults={ + 'description': movie_data['description'], + 'poster_url': movie_data['poster_url'], + 'cast': ','.join(movie_data['cast']), + } + ) + return movie.title + return None diff --git a/MovieVerse-Mobile/app/python/crawler/utils.py b/MovieVerse-Mobile/app/python/crawler/utils.py new file mode 100644 index 00000000..3b508608 --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/utils.py @@ -0,0 +1,58 @@ +from bs4 import BeautifulSoup + + +def clean_text(text): + return ' '.join(text.split()) + + +def parse_movie_data(html_content): + movie = {} + soup = BeautifulSoup(html_content, 'html.parser') + movie['name'] = soup.find('h1', class_='movie-title').text.strip() + movie['description'] = soup.find('div', class_='movie-description').text.strip() + movie['poster_url'] = soup.find('img', class_='movie-poster')['src'] + movie['cast'] = [cast.text.strip() for cast in soup.find_all('div', class_='movie-cast-member')] + movie['director'] = soup.find('div', class_='movie-director').text.strip() + movie['genres'] = [genre.text.strip() for genre in soup.find_all('span', class_='movie-genre')] + movie['duration'] = soup.find('div', class_='movie-duration').text.strip() + movie['rating'] = float(soup.find('span', class_='movie-rating').text.strip()) + movie['release_date'] = soup.find('div', class_='movie-release-date').text.strip() + movie['trailer_url'] = soup.find('a', class_='movie-trailer')['href'] + movie['imdb_url'] = soup.find('a', class_='movie-imdb')['href'] + movie['rotten_tomatoes_url'] = soup.find('a', class_='movie-rotten-tomatoes')['href'] + movie['metacritic_url'] = soup.find('a', class_='movie-metacritic')['href'] + movie['reviews'] = [ + { + 'author': review.find('div', class_='review-author').text.strip(), + 'content': review.find('div', class_='review-content').text.strip(), + 'rating': float(review.find('span', class_='review-rating').text.strip()) + } + for review in soup.find_all('div', class_='movie-review') + ] + movie['similar_movies'] = [ + { + 'name': movie.find('h2', class_='movie-title').text.strip(), + 'poster_url': movie.find('img', class_='movie-poster')['src'], + 'rating': float(movie.find('span', class_='movie-rating').text.strip()) + } + for movie in soup.find_all('div', class_='movie-similar') + ] + movie['recommendations'] = [ + { + 'name': movie.find('h2', class_='movie-title').text.strip(), + 'poster_url': movie.find('img', class_='movie-poster')['src'], + 'rating': float(movie.find('span', class_='movie-rating').text.strip()) + } + for movie in soup.find_all('div', class_='movie-recommendation') + ] + movie['awards'] = soup.find('div', class_='movie-awards').text.strip() + movie['box_office'] = soup.find('div', class_='movie-box_office').text.strip() + movie['budget'] = soup.find('div', class_='movie-budget').text.strip() + movie['company'] = soup.find('div', class_='movie-company').text.strip() + movie['country'] = soup.find('div', class_='movie-country').text.strip() + movie['language'] = soup.find('div', class_='movie-language').text.strip() + movie['tagline'] = soup.find('div', class_='movie-tagline').text.strip() + movie['website'] = soup.find('div', class_='movie-website').text.strip() + movie['writers'] = [writer.text.strip() for writer in soup.find_all('div', class_='movie-writer')] + movie['year'] = int(soup.find('div', class_='movie-year').text.strip()) + movie['id'] = 'tt' + soup.find('span', class_='movie-imdb-id').text.strip() diff --git a/MovieVerse-Mobile/app/python/crawler/validators.py b/MovieVerse-Mobile/app/python/crawler/validators.py new file mode 100644 index 00000000..2c22a1da --- /dev/null +++ b/MovieVerse-Mobile/app/python/crawler/validators.py @@ -0,0 +1,81 @@ +from django.core.exceptions import ValidationError + + +def validate_movie_data(data): + if not data.get('name') or not data.get('description'): + raise ValidationError("Missing required movie fields") + + if not data.get('poster_url'): + raise ValidationError("Missing required movie poster URL") + + if not data.get('cast'): + raise ValidationError("Missing required movie cast") + + if not data.get('director'): + raise ValidationError("Missing required movie director") + + if not data.get('genres'): + raise ValidationError("Missing required movie genres") + + if not data.get('duration'): + raise ValidationError("Missing required movie duration") + + if not data.get('rating'): + raise ValidationError("Missing required movie rating") + + if not data.get('release_date'): + raise ValidationError("Missing required movie release date") + + if not data.get('trailer_url'): + raise ValidationError("Missing required movie trailer URL") + + if not data.get('imdb_url'): + raise ValidationError("Missing required movie IMDb URL") + + if not data.get('rotten_tomatoes_url'): + raise ValidationError("Missing required movie Rotten Tomatoes URL") + + if not data.get('metacritic_url'): + raise ValidationError("Missing required movie Metacritic URL") + + if not data.get('reviews'): + raise ValidationError("Missing required movie reviews") + + if not data.get('similar_movies'): + raise ValidationError("Missing required similar movies") + + if not data.get('recommendations'): + raise ValidationError("Missing required movie recommendations") + + if not data.get('awards'): + raise ValidationError("Missing required movie awards") + + if not data.get('box_office'): + raise ValidationError("Missing required movie box office") + + if not data.get('budget'): + raise ValidationError("Missing required movie budget") + + if not data.get('company'): + raise ValidationError("Missing required movie company") + + if not data.get('country'): + raise ValidationError("Missing required movie country") + + if not data.get('language'): + raise ValidationError("Missing required movie language") + + if not data.get('tagline'): + raise ValidationError("Missing required movie tagline") + + if not data.get('website'): + raise ValidationError("Missing required movie website") + + if not data.get('writers'): + raise ValidationError("Missing required movie writers") + + if not data.get('year'): + raise ValidationError("Missing required movie year") + + if not data.get('id'): + raise ValidationError("Missing required movie ID") diff --git a/MovieVerse-Mobile/app/python/databases/app.js b/MovieVerse-Mobile/app/python/databases/app.js new file mode 100644 index 00000000..9ecaf2e8 --- /dev/null +++ b/MovieVerse-Mobile/app/python/databases/app.js @@ -0,0 +1,93 @@ +const express = require('express'); +const mongoose = require('mongoose'); +const mysql = require('mysql2'); +const redis = require('redis'); +const amqp = require('amqplib'); +const { MongoClient, ObjectId } = require('mongodb'); +const config = require('./config'); + +const app = express(); + +// Connect to MySQL +const mysqlConnection = mysql.createConnection({ + host: '127.0.0.1', // Change to localhost + user: config.MYSQL_USER, // Make sure it's 'root' if you're using that + password: config.MYSQL_PASSWORD, // Correct password if you're using one + database: config.MYSQL_DB // Ensure the database exists +}); + +mysqlConnection.connect((err) => { + if (err) { + console.error('Error connecting to MySQL:', err); + return; + } + console.log('Connected to MySQL'); +}); + +// Connect to MongoDB +mongoose + .connect(config.MONGO_URI1, { }) + .then(() => console.log('Connected to MongoDB')) + .catch(err => console.error('MongoDB Connection Error:', err)); + +// Connect to Redis +const redisClient = redis.createClient({ + url: `redis://${config.redisHost}:${config.redisPort}` +}); + +redisClient + .connect() + .then(() => { + console.log('Redis Connected'); + // Test Redis by setting and getting a simple key-value pair + return redisClient.set('testKey', 'Hello from Redis'); + }) + .then(() => redisClient.get('testKey')) + .then(value => console.log(`Redis Test: ${value}`)) + .catch(err => console.error('Redis Connection Error:', err)); + +app.get('/', (req, res) => { + const message = 'Congratulations! MovieVerse server is running! MongoDB, MySQL, and Redis connections have been established.'; + console.log(message); + res.send(message); +}); + +const PORT = config.port || 5000; +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + console.log(`Visit http://localhost:${PORT}/ to test the connection.`); +}); + +async function connectToRabbitMQ() { + try { + const connection = await amqp.connect(`amqp://${config.rabbitMQHost}`); + const channel = await connection.createChannel(); + const queue = 'task_queue'; + + await channel.assertQueue(queue, { durable: true }); + channel.prefetch(1); + + console.log('RabbitMQ Connected'); + + console.log(' [*] Waiting for messages in %s. To exit press CTRL+C', queue); + + channel.consume(queue, async (msg) => { + const content = msg.content.toString(); + console.log(" [x] Received %s", content); + + // Simulate a long-running task + await new Promise(resolve => setTimeout(resolve, 10000)); + + console.log(" [x] Done"); + + channel.ack(msg); + }, { noAck: false }); + } + catch (error) { + console.error('RabbitMQ Connection Error:', error); + } +} + +connectToRabbitMQ(); + +module.exports = app; diff --git a/MovieVerse-Mobile/app/python/databases/config.js b/MovieVerse-Mobile/app/python/databases/config.js new file mode 100644 index 00000000..a40d299f --- /dev/null +++ b/MovieVerse-Mobile/app/python/databases/config.js @@ -0,0 +1,18 @@ +// Replace with your own configuration -- mine might be different + +module.exports = { + MONGO_URI1: 'mongodb://localhost:27017/MovieVerse', + MONGO_URI2: 'mongodb://localhost:27017/MovieVerse_movies', + MONGO_URI3: 'mongodb://localhost:27017/MovieVerse_users', + MONGO_URI4: 'mongodb://localhost:27017/MovieVerse_reviews', + MONGO_URI5: 'mongodb://localhost:27017/MovieVerse_people', + MONGO_URI6: 'mongodb://localhost:27017/MovieVerse_genres', + redisHost: '127.0.0.1', + redisPort: 6379, + rabbitMQHost: 'localhost', + port: 9090, + MYSQL_HOST: 'localhost:3306', + MYSQL_USER: 'root', + MYSQL_PASSWORD: '09112004', + MYSQL_DB: 'MovieVerse' +} diff --git a/MovieVerse-Mobile/app/python/databases/publish.js b/MovieVerse-Mobile/app/python/databases/publish.js new file mode 100644 index 00000000..ad8f1272 --- /dev/null +++ b/MovieVerse-Mobile/app/python/databases/publish.js @@ -0,0 +1,22 @@ +const amqp = require('amqplib'); +const config = require('./config'); + +async function publishMessage() { + try { + const connection = await amqp.connect(`amqp://${config.rabbitMQHost}`); + const channel = await connection.createChannel(); + const queue = 'task_queue'; + + await channel.assertQueue(queue, { durable: true }); + + const message = 'Hello from RabbitMQ'; // Test string - to be replaced by backend data + channel.sendToQueue(queue, Buffer.from(message), { persistent: true }); + + console.log('Message published:', message); + } + catch (error) { + console.error('Error publishing message:', error); + } +} + +publishMessage(); diff --git a/MovieVerse-Mobile/app/python/django_backend/.flake8 b/MovieVerse-Mobile/app/python/django_backend/.flake8 new file mode 100644 index 00000000..e3f299bb --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/.flake8 @@ -0,0 +1,27 @@ +[flake8] +# Ignore certain error codes +ignore = + E501, # Line too long + W503, # Line break occurred before a binary operator (conflicts with W504) + +# Exclude files or directories +exclude = + .git, + __pycache__, + migrations, + settings.py, + +# Set maximum line length allowed +max-line-length = 120 + +# Set the complexity threshold +max-complexity = 10 + +# Include warning for TODO comments +enable-extensions = T100 + +# Show source code for each error +show-source = True + +# Count the total number of errors +count = True diff --git a/MovieVerse-Mobile/app/python/django_backend/db.sqlite3 b/MovieVerse-Mobile/app/python/django_backend/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..62e0b72fd2544debe0e140cdbdef2e5e445cf5da GIT binary patch literal 188416 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 literal 0 HcmV?d00001 diff --git a/MovieVerse-Mobile/app/python/django_backend/django_backend/__init__.py b/MovieVerse-Mobile/app/python/django_backend/django_backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MovieVerse-Mobile/app/python/django_backend/django_backend/asgi.py b/MovieVerse-Mobile/app/python/django_backend/django_backend/asgi.py new file mode 100644 index 00000000..ffcd916d --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/django_backend/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for django_backend project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_backend.settings') + +application = get_asgi_application() diff --git a/MovieVerse-Mobile/app/python/django_backend/django_backend/settings.py b/MovieVerse-Mobile/app/python/django_backend/django_backend/settings.py new file mode 100644 index 00000000..aa7fdc5d --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/django_backend/settings.py @@ -0,0 +1,123 @@ +""" +Django settings for django_backend project. + +Generated by 'django-admin startproject' using Django 5.0. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! + +# SECURITY WARNING: don't run with debug turned on in production! + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'movieverse', + 'rest_framework', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'django_backend.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'django_backend.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/MovieVerse-Mobile/app/python/django_backend/django_backend/urls.py b/MovieVerse-Mobile/app/python/django_backend/django_backend/urls.py new file mode 100644 index 00000000..6abe2062 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/django_backend/urls.py @@ -0,0 +1,25 @@ +""" +URL configuration for django_backend project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path +from django.urls.conf import include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/', include('movieverse.urls')), + path('', include('movieverse.urls')), +] diff --git a/MovieVerse-Mobile/app/python/django_backend/django_backend/wsgi.py b/MovieVerse-Mobile/app/python/django_backend/django_backend/wsgi.py new file mode 100644 index 00000000..9300270a --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/django_backend/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for django_backend project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_backend.settings') + +application = get_wsgi_application() diff --git a/MovieVerse-Mobile/app/python/django_backend/manage.py b/MovieVerse-Mobile/app/python/django_backend/manage.py new file mode 100755 index 00000000..ea62d66d --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_backend.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/__init__.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/admin.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/admin.py new file mode 100644 index 00000000..93bb2f44 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/admin.py @@ -0,0 +1,44 @@ +from django.contrib import admin +from .models import Movie, Actor, Director, Genre + +admin.site.site_header = "MovieVerse Application - Backend Administration" + + +class MovieAdmin(admin.ModelAdmin): + list_display = ('title', 'overview', 'poster_path', 'vote_average', 'release_date') + search_fields = ('title', 'overview', 'poster_path', 'vote_average', 'release_date') + list_filter = ('title', 'release_date') + ordering = ('title',) + + +admin.site.register(Movie, MovieAdmin) + + +class ActorAdmin(admin.ModelAdmin): + list_display = ('name', 'date_of_birth', 'profile_path', 'biography') + search_fields = ('name', 'date_of_birth', 'profile_path', 'biography') + list_filter = ('name', 'date_of_birth') + ordering = ('name',) + + +admin.site.register(Actor, ActorAdmin) + + +class DirectorAdmin(admin.ModelAdmin): + list_display = ('name', 'date_of_birth', 'profile_path', 'biography') + search_fields = ('name', 'date_of_birth', 'profile_path', 'biography') + list_filter = ('name', 'date_of_birth') + ordering = ('name',) + + +admin.site.register(Director, DirectorAdmin) + + +class GenreAdmin(admin.ModelAdmin): + list_display = ('name',) + search_fields = ('name',) + list_filter = ('name',) + ordering = ('name',) + + +admin.site.register(Genre, GenreAdmin) diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/apps.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/apps.py new file mode 100644 index 00000000..341fef90 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MovieverseConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'movieverse' diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/caching.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/caching.py new file mode 100644 index 00000000..b0fb2dca --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/caching.py @@ -0,0 +1,10 @@ +from django.core.cache import cache +from .models import Movie + +def get_top_movies(): + if 'top_movies' in cache: + return cache.get('top_movies') + else: + top_movies = Movie.objects.order_by('-rating')[:10] + cache.set('top_movies', top_movies, timeout=3600) # Cache for 1 hour + return top_movies diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/decorators.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/decorators.py new file mode 100644 index 00000000..4cddf5aa --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/decorators.py @@ -0,0 +1,14 @@ +from django.http import HttpResponseForbidden +from .models import Movie + +def user_is_movie_creator(function): + def wrap(request, *args, **kwargs): + if kwargs['movie_id'] and request.user.is_authenticated: + movie = Movie.objects.get(pk=kwargs['movie_id']) + if movie.creator != request.user: + return HttpResponseForbidden("You are not allowed to edit this movie.") + else: + return function(request, *args, **kwargs) + else: + return HttpResponseForbidden("You need to be logged in to perform this action.") + return wrap diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/exceptions.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/exceptions.py new file mode 100644 index 00000000..eefe8be5 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/exceptions.py @@ -0,0 +1,12 @@ +from rest_framework.views import exception_handler +from rest_framework.response import Response +from rest_framework import status + + +def custom_exception_handler(exc, context): + response = exception_handler(exc, context) + + if response is not None: + response.data['status_code'] = response.status_code + + return response diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/forms.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/forms.py new file mode 100644 index 00000000..af8228d5 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/forms.py @@ -0,0 +1,11 @@ +from django import forms +from .models import Review + +class ReviewForm(forms.ModelForm): + class Meta: + model = Review + fields = ['rating', 'comment'] + widgets = { + 'comment': forms.Textarea(attrs={'placeholder': 'Your comment'}), + } + diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/0001_initial.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/0001_initial.py new file mode 100644 index 00000000..28c9c719 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/0001_initial.py @@ -0,0 +1,72 @@ +# Generated by Django 3.2 on 2024-06-17 07:57 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Actor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('date_of_birth', models.DateField()), + ('profile_path', models.CharField(max_length=255)), + ('biography', models.TextField()), + ], + ), + migrations.CreateModel( + name='Director', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('date_of_birth', models.DateField()), + ('profile_path', models.CharField(max_length=255)), + ('biography', models.TextField()), + ], + ), + migrations.CreateModel( + name='Genre', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='Movie', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('overview', models.TextField()), + ('poster_path', models.CharField(max_length=255)), + ('vote_average', models.FloatField()), + ('release_date', models.DateField()), + ], + ), + migrations.CreateModel( + name='Like', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('movie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='movieverse.movie')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.TextField()), + ('movie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='movieverse.movie')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/0002_review.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/0002_review.py new file mode 100644 index 00000000..dcff4e0d --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/0002_review.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2 on 2024-06-17 08:26 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('movieverse', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Review', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.TextField()), + ('rating', models.FloatField()), + ('movie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='movieverse.movie')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/__init__.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/models.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/models.py new file mode 100644 index 00000000..a1e45f36 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/models.py @@ -0,0 +1,66 @@ +from django.db import models + + +class Movie(models.Model): + title = models.CharField(max_length=255) + overview = models.TextField() + poster_path = models.CharField(max_length=255) + vote_average = models.FloatField() + release_date = models.DateField() + + def __str__(self): + return self.title + + +class Actor(models.Model): + name = models.CharField(max_length=255) + date_of_birth = models.DateField() + profile_path = models.CharField(max_length=255) + biography = models.TextField() + + def __str__(self): + return self.name + + +class Director(models.Model): + name = models.CharField(max_length=255) + date_of_birth = models.DateField() + profile_path = models.CharField(max_length=255) + biography = models.TextField() + + def __str__(self): + return self.name + + +class Genre(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + + +class Like(models.Model): + movie = models.ForeignKey(Movie, on_delete=models.CASCADE) + user = models.ForeignKey('auth.User', on_delete=models.CASCADE) + + def __str__(self): + return f'{self.user.username} likes {self.movie.title}' + + +class Comment(models.Model): + movie = models.ForeignKey(Movie, on_delete=models.CASCADE) + user = models.ForeignKey('auth.User', on_delete=models.CASCADE) + text = models.TextField() + + def __str__(self): + return f'{self.user.username} commented on {self.movie.title}' + + +class Review(models.Model): + movie = models.ForeignKey(Movie, on_delete=models.CASCADE) + user = models.ForeignKey('auth.User', on_delete=models.CASCADE) + text = models.TextField() + rating = models.FloatField() + + def __str__(self): + return f'{self.user.username} reviewed {self.movie.title}' diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/notifications.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/notifications.py new file mode 100644 index 00000000..035ef3bd --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/notifications.py @@ -0,0 +1,12 @@ +from celery import shared_task +from django.core.mail import send_mail + +@shared_task +def send_review_notification_email(user_email): + send_mail( + 'Your Review is Posted', + 'Thank you for submitting your review. It is now live on our site.', + 'info@movie-verse.com', + [user_email], + fail_silently=False, + ) diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/serializers.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/serializers.py new file mode 100644 index 00000000..02a59aef --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/serializers.py @@ -0,0 +1,20 @@ +from rest_framework import serializers +from .models import Movie, Director, Actor + + +class MovieSerializer(serializers.ModelSerializer): + class Meta: + model = Movie + fields = ['id', 'title', 'overview', 'poster_path', 'vote_average', 'release_date'] + + +class ActorSerializer(serializers.ModelSerializer): + class Meta: + model = Actor + fields = ['id', 'name', 'date_of_birth', 'profile_path', 'biography'] + + +class DirectorSerializer(serializers.ModelSerializer): + class Meta: + model = Director + fields = ['id', 'name', 'date_of_birth', 'profile_path', 'biography'] \ No newline at end of file diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/signals.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/signals.py new file mode 100644 index 00000000..a74478a2 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/signals.py @@ -0,0 +1,11 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver +from .models import Review, Movie + +@receiver(post_save, sender=Review) +def update_movie_rating(sender, instance, **kwargs): + movie = instance.movie + reviews = Review.objects.filter(movie=movie) + average_rating = reviews.aggregate(Avg('rating'))['rating__avg'] + movie.average_rating = average_rating + movie.save() diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/actor_detail.html b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/actor_detail.html new file mode 100644 index 00000000..3c446a97 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/actor_detail.html @@ -0,0 +1,105 @@ + + + + + Actor Detail for {{ actor.name }} + + + + +
+

{{ actor.name }}

+

{{ actor.bio }}

+
+ +
+

Popular Movies

+
    + {% for movie in actor.movies.all %} +
  • {{ movie.title }}
  • + {% endfor %} +
+
+ + + + \ No newline at end of file diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/comment_item.html b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/comment_item.html new file mode 100644 index 00000000..161beaa6 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/comment_item.html @@ -0,0 +1,8 @@ + +
    +
  • +

    {{ comment.user.username }}

    +

    {{ comment.text }}

    +

    {{ comment.created_at }}

    +
  • +
\ No newline at end of file diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/director_detail.html b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/director_detail.html new file mode 100644 index 00000000..3d6cb268 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/director_detail.html @@ -0,0 +1,93 @@ + + + + + Director Detail for {{ director.name }} + + + +
+

{{ director.name }}

+

{{ director.bio }}

+
+
+

Director Details

+

Birth Date: {{ director.birth_date }}

+

Birth Place: {{ director.birth_place }}

+
+ + \ No newline at end of file diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/index.html b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/index.html new file mode 100644 index 00000000..17cbcbb5 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/index.html @@ -0,0 +1,106 @@ + + + + + MovieVerse + + + +
+

MovieVerse Homepage

+

Search for your favorite movies

+
+ + + diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/movie_detail.html b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/movie_detail.html new file mode 100644 index 00000000..db294c70 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/movie_detail.html @@ -0,0 +1,97 @@ + + + + Movie Detail for {{ movie.title }} + + + + +
+

{{ movie.title }}

+

{{ movie.year }}

+
+
+

{{ movie.plot }}

+
+ + + diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/search.html b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/search.html new file mode 100644 index 00000000..28aab55b --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/templates/movieverse/search.html @@ -0,0 +1,108 @@ + + + + + MovieVerse Search Results + + + + +
+

MovieVerse Search Results

+

Results for: {{ query }}

+ + +
+ +
+ {% for movie in movies %} +
+

{{ movie.title }}

+

{{ movie.year }}

+ {{ movie.title }} +
+ {% endfor %} +
+ + diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/tests.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/urls.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/urls.py new file mode 100644 index 00000000..789f0980 --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/urls.py @@ -0,0 +1,20 @@ +from django.urls import path, include +from django.contrib import admin +from . import views +from rest_framework.routers import DefaultRouter + +admin.site.site_header = "MovieVerse Application - Backend Administration" + +router = DefaultRouter() +router.register(r'movies', views.MovieViewSet) +router.register(r'actors', views.ActorViewSet) + +urlpatterns = [ + path('', views.index, name='index'), + path('search/', views.search, name='search'), + path('/', views.movie_detail, name='movie_detail'), + path('/', views.actor_detail, name='actor_detail'), + path('like//', views.like_movie, name='like_movie'), + path('comment//', views.add_comment, name='add_comment'), + path('api/', include(router.urls)), +] diff --git a/MovieVerse-Mobile/app/python/django_backend/movieverse/views.py b/MovieVerse-Mobile/app/python/django_backend/movieverse/views.py new file mode 100644 index 00000000..072ea24d --- /dev/null +++ b/MovieVerse-Mobile/app/python/django_backend/movieverse/views.py @@ -0,0 +1,192 @@ +from rest_framework.views import APIView +from rest_framework.response import Response +from .serializers import MovieSerializer, ActorSerializer, DirectorSerializer +from rest_framework import viewsets, permissions, filters +from django.shortcuts import render +from django.http import JsonResponse +from django.template.loader import render_to_string +from .models import Movie, Actor, Director, Like, Comment + + +class MovieListAPIView(APIView): + """ + List all movies or create a new movie. + """ + + def get(self, request, format=None): + movies = Movie.objects.all() + serializer = MovieSerializer(movies, many=True) + return Response(serializer.data) + + def post(self, request, format=None): + serializer = MovieSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + + def put(self, request, pk, format=None): + movie = Movie.objects.get(pk=pk) + serializer = MovieSerializer(movie, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=400) + + def delete(self, request, pk, format=None): + movie = Movie.objects.get(pk=pk) + movie.delete() + return Response(status=204) + + def patch(self, request, pk, format=None): + movie = Movie.objects.get(pk=pk) + serializer = MovieSerializer(movie, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=400) + + +class MovieViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows movies to be viewed or edited. + """ + queryset = Movie.objects.all() + serializer_class = MovieSerializer + permission_classes = [permissions.IsAuthenticated] + filter_backends = [filters.SearchFilter] + search_fields = ['title', 'overview', 'release_date'] + ordering_fields = ['title', 'release_date'] + ordering = ['title'] + lookup_field = 'title' + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + + def get_queryset(self): + return Movie.objects.all() + + def get_object(self): + return Movie.objects.get(pk=self.kwargs['pk']) + + def get_serializer(self): + return MovieSerializer() + + def get_permissions(self): + return [permissions.IsAuthenticated()] + + +class ActorViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows actors to be viewed or edited. + """ + queryset = Actor.objects.all() + serializer_class = ActorSerializer + permission_classes = [permissions.IsAuthenticated] + filter_backends = [filters.SearchFilter] + search_fields = ['name', 'date_of_birth'] + ordering_fields = ['name', 'date_of_birth'] + ordering = ['name'] + lookup_field = 'name' + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + + def get_queryset(self): + return Actor.objects.all() + + def get_object(self): + return Actor.objects.get(pk=self.kwargs['pk']) + + def get_serializer(self): + return ActorSerializer() + + def get_permissions(self): + return [permissions.IsAuthenticated()] + + +class DirectorViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows directors to be viewed or edited. + """ + queryset = Director.objects.all() + serializer_class = DirectorSerializer + permission_classes = [permissions.IsAuthenticated] + filter_backends = [filters.SearchFilter] + search_fields = ['name', 'date_of_birth'] + ordering_fields = ['name', 'date_of_birth'] + ordering = ['name'] + lookup_field = 'name' + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + + def get_queryset(self): + return Director.objects.all() + + def get_object(self): + return Director.objects.get(pk=self.kwargs['pk']) + + def get_serializer(self): + return DirectorSerializer() + + def get_permissions(self): + return [permissions.IsAuthenticated()] + + +def index(request): + return render(request, 'movieverse/index.html') + + +def search(request): + query = request.GET.get('query') + movies = Movie.objects.filter(title__icontains=query) if query else Movie.objects.none() + return render(request, 'movieverse/search.html', {'movies': movies, 'query': query}) + + +def movie_detail(request, movie_id): + movie = Movie.objects.get(pk=movie_id) + return render(request, 'movieverse/movie_detail.html', {'movie': movie}) + + +def actor_detail(request, actor_id): + actor = Actor.objects.get(pk=actor_id) + return render(request, 'movieverse/actor_detail.html', {'actor': actor}) + + +def director_detail(request, director_id): + director = Director.objects.get(pk=director_id) + return render(request, 'movieverse/director_detail.html', {'director': director}) + + +def like_movie(request, movie_id): + movie = Movie.objects.get(pk=movie_id) + user = request.user + + if user.is_authenticated: + like, created = Like.objects.get_or_create(user=user, movie=movie) + if not created: + like.delete() + liked = False # Indicate that the movie was unliked + else: + liked = True # Indicate that the movie was liked + else: + liked = False # For unauthenticated users + + return JsonResponse({'liked': liked, 'like_count': movie.like_set.count()}) + + +def add_comment(request, movie_id): + movie = Movie.objects.get(pk=movie_id) + user = request.user + + comment_text = request.POST.get('comment_text') + if comment_text: + comment = Comment.objects.create(user=user, movie=movie, text=comment_text) + + # Render the new comment as HTML + html = render_to_string('movieverse/comment_item.html', {'comment': comment}) + + return JsonResponse({'html': html}) + else: + return JsonResponse({'error': 'Comment text is required'}, status=400) diff --git a/MovieVerse-Mobile/app/python/flask_backend/.flake8 b/MovieVerse-Mobile/app/python/flask_backend/.flake8 new file mode 100644 index 00000000..e3f299bb --- /dev/null +++ b/MovieVerse-Mobile/app/python/flask_backend/.flake8 @@ -0,0 +1,27 @@ +[flake8] +# Ignore certain error codes +ignore = + E501, # Line too long + W503, # Line break occurred before a binary operator (conflicts with W504) + +# Exclude files or directories +exclude = + .git, + __pycache__, + migrations, + settings.py, + +# Set maximum line length allowed +max-line-length = 120 + +# Set the complexity threshold +max-complexity = 10 + +# Include warning for TODO comments +enable-extensions = T100 + +# Show source code for each error +show-source = True + +# Count the total number of errors +count = True diff --git a/MovieVerse-Mobile/app/python/flask_backend/flask.py b/MovieVerse-Mobile/app/python/flask_backend/flask.py new file mode 100644 index 00000000..84b053a2 --- /dev/null +++ b/MovieVerse-Mobile/app/python/flask_backend/flask.py @@ -0,0 +1,148 @@ +import hashlib + + +# Utility Functions +def hash_password(password): + return hashlib.sha256(password.encode()).hexdigest() + + +# User model +class User: + def __init__(self, username, password): + self.username = username + self.password = hash_password(password) + self.data = {} + + def add_data(self, key, value): + self.data[key] = value + + def get_data(self, key): + return self.data.get(key, "Not found") + + def update_password(self, new_password): + self.password = hash_password(new_password) + + +# Backend service +class BackendService: + def __init__(self): + self.users = {} + + def register_user(self, username, password): + if username in self.users: + return "Username already exists." + self.users[username] = User(username, password) + return "User registered successfully." + + def login_user(self, username, password): + user = self.users.get(username) + if not user: + return "User not found." + if user.password != hash_password(password): + return "Incorrect password." + return "Login successful." + + def add_user_data(self, username, key, value): + user = self.users.get(username) + if not user: + return "User not found." + user.add_data(key, value) + return "Data added successfully." + + def retrieve_user_data(self, username, key): + user = self.users.get(username) + if not user: + return "User not found." + return user.get_data(key) + + def change_user_password(self, username, old_password, new_password): + user = self.users.get(username) + if not user: + return "User not found." + if user.password != hash_password(old_password): + return "Incorrect old password." + user.update_password(new_password) + return "Password updated successfully." + + def delete_user(self, username): + if username in self.users: + del self.users[username] + return "User deleted successfully." + return "User not found." + + def list_users(self): + return ", ".join(self.users.keys()) if self.users else "No users found." + + +# Admin Panel for user management +class AdminPanel: + def __init__(self, service): + self.service = service + + def run(self): + while True: + print("\nAdmin Panel:") + print("1. List Users") + print("2. Delete User") + print("3. Exit") + choice = input("Enter choice: ") + + if choice == "1": + print("Users:", self.service.list_users()) + elif choice == "2": + username = input("Enter username to delete: ") + print(self.service.delete_user(username)) + elif choice == "3": + break + else: + print("Invalid choice.") + + +# Main function to run the service +def main(): + service = BackendService() + admin_panel = AdminPanel(service) + + while True: + print("\nMain Menu:") + print("1. Register") + print("2. Login") + print("3. Add Data") + print("4. Retrieve Data") + print("5. Change Password") + print("6. Admin Panel") + print("7. Exit") + choice = input("Enter choice: ") + + if choice == "1": + username = input("Enter username: ") + password = input("Enter password: ") + print(service.register_user(username, password)) + elif choice == "2": + username = input("Enter username: ") + password = input("Enter password: ") + print(service.login_user(username, password)) + elif choice == "3": + username = input("Enter username: ") + key = input("Enter data key: ") + value = input("Enter data value: ") + print(service.add_user_data(username, key, value)) + elif choice == "4": + username = input("Enter username: ") + key = input("Enter data key: ") + print(service.retrieve_user_data(username, key)) + elif choice == "5": + username = input("Enter username: ") + old_password = input("Enter old password: ") + new_password = input("Enter new password: ") + print(service.change_user_password(username, old_password, new_password)) + elif choice == "6": + admin_panel.run() + elif choice == "7": + break + else: + print("Invalid choice.") + + +if __name__ == "__main__": + main() diff --git a/MovieVerse-Mobile/app/python/flask_backend/micro-services.py b/MovieVerse-Mobile/app/python/flask_backend/micro-services.py new file mode 100644 index 00000000..269ca4d1 --- /dev/null +++ b/MovieVerse-Mobile/app/python/flask_backend/micro-services.py @@ -0,0 +1,164 @@ +microservices = { + "movie_service": { + "description": "Handles all movie-related operations", + "framework": "Django", + "database": "MySQL", + "endpoints": [ + "/api/movies", + "/api/movies/{id}", + ] + }, + "user_service": { + "description": "Manages user authentication and profiles", + "framework": "Flask", + "database": "MongoDB", + "endpoints": [ + "/api/users", + "/api/users/{id}", + ] + }, + "rating_service": { + "description": "Handles movie ratings and reviews", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/ratings", + "/api/ratings/{id}", + ] + }, + "recommendation_service": { + "description": "Generates movie recommendations for users", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/recommendations", + "/api/recommendations/{id}", + ] + }, + "search_service": { + "description": "Handles movie search", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/search", + "/api/search/{id}", + ] + }, + "notification_service": { + "description": "Handles notifications for users", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/notifications", + "/api/notifications/{id}", + ] + }, + "payment_service": { + "description": "Handles payments for users", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/payments", + "/api/payments/{id}", + ] + }, + "analytics_service": { + "description": "Handles analytics for the application", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/analytics", + "/api/analytics/{id}", + ] + }, + "admin_service": { + "description": "Handles admin operations", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/admin", + "/api/admin/{id}", + ] + }, + "account_service": { + "description": "Handles user accounts", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/accounts", + "/api/accounts/{id}", + ] + }, + "subscription_service": { + "description": "Handles user subscriptions", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/subscriptions", + "/api/subscriptions/{id}", + ] + }, + "content_service": { + "description": "Handles content management", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/content", + "/api/content/{id}", + ] + }, + "review_service": { + "description": "Handles movie reviews", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/reviews", + "/api/reviews/{id}", + ] + }, + "genre_service": { + "description": "Handles movie genres", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/genres", + "/api/genres/{id}", + ] + }, + "actor_service": { + "description": "Handles movie actors", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/actors", + "/api/actors/{id}", + ] + }, + "director_service": { + "description": "Handles movie directors", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/directors", + "/api/directors/{id}", + ] + }, + "crew_service": { + "description": "Handles movie crew", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/crew", + "/api/crew/{id}", + ] + }, + "company_service": { + "description": "Handles movie companies", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/companies", + "/api/companies/{id}", + ] + }, +} diff --git a/MovieVerse-Mobile/app/python/micro-services.py b/MovieVerse-Mobile/app/python/micro-services.py new file mode 100644 index 00000000..269ca4d1 --- /dev/null +++ b/MovieVerse-Mobile/app/python/micro-services.py @@ -0,0 +1,164 @@ +microservices = { + "movie_service": { + "description": "Handles all movie-related operations", + "framework": "Django", + "database": "MySQL", + "endpoints": [ + "/api/movies", + "/api/movies/{id}", + ] + }, + "user_service": { + "description": "Manages user authentication and profiles", + "framework": "Flask", + "database": "MongoDB", + "endpoints": [ + "/api/users", + "/api/users/{id}", + ] + }, + "rating_service": { + "description": "Handles movie ratings and reviews", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/ratings", + "/api/ratings/{id}", + ] + }, + "recommendation_service": { + "description": "Generates movie recommendations for users", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/recommendations", + "/api/recommendations/{id}", + ] + }, + "search_service": { + "description": "Handles movie search", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/search", + "/api/search/{id}", + ] + }, + "notification_service": { + "description": "Handles notifications for users", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/notifications", + "/api/notifications/{id}", + ] + }, + "payment_service": { + "description": "Handles payments for users", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/payments", + "/api/payments/{id}", + ] + }, + "analytics_service": { + "description": "Handles analytics for the application", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/analytics", + "/api/analytics/{id}", + ] + }, + "admin_service": { + "description": "Handles admin operations", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/admin", + "/api/admin/{id}", + ] + }, + "account_service": { + "description": "Handles user accounts", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/accounts", + "/api/accounts/{id}", + ] + }, + "subscription_service": { + "description": "Handles user subscriptions", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/subscriptions", + "/api/subscriptions/{id}", + ] + }, + "content_service": { + "description": "Handles content management", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/content", + "/api/content/{id}", + ] + }, + "review_service": { + "description": "Handles movie reviews", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/reviews", + "/api/reviews/{id}", + ] + }, + "genre_service": { + "description": "Handles movie genres", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/genres", + "/api/genres/{id}", + ] + }, + "actor_service": { + "description": "Handles movie actors", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/actors", + "/api/actors/{id}", + ] + }, + "director_service": { + "description": "Handles movie directors", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/directors", + "/api/directors/{id}", + ] + }, + "crew_service": { + "description": "Handles movie crew", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/crew", + "/api/crew/{id}", + ] + }, + "company_service": { + "description": "Handles movie companies", + "framework": "Flask", + "database": "MySQL", + "endpoints": [ + "/api/companies", + "/api/companies/{id}", + ] + }, +} diff --git a/MovieVerse-Mobile/app/python/requirements.txt b/MovieVerse-Mobile/app/python/requirements.txt new file mode 100644 index 00000000..429458ed --- /dev/null +++ b/MovieVerse-Mobile/app/python/requirements.txt @@ -0,0 +1,16 @@ +Django==3.2 +djangorestframework==3.12 +gunicorn==20.1.0 +pytz==2021.1 +transformers~=4.41.2 +requests~=2.32.3 +pillow~=10.3.0 +pandas~=2.2.2 +celery~=5.4.0 +beautifulsoup4~=4.12.3 +APScheduler~=3.10.4 +numpy~=1.26.4 +nltk~=3.8.1 +scikit-learn~=1.5.0 +streamlit~=1.35.0 +scipy~=1.13.1 From c404aff61234650021abdd0a5e272f847afafa19 Mon Sep 17 00:00:00 2001 From: Son Nguyen Date: Tue, 18 Jun 2024 11:23:37 +0700 Subject: [PATCH 2/3] Final: Enhanced app's loading times (#196) --- MovieVerse-Backend/.idea/workspace.xml | 137 ++++++--------- .../django_backend/django_backend/settings.py | 3 +- .../movieverse/static/images/favicon.ico | Bin 0 -> 15406 bytes .../flask_backend/micro-services.py | 164 ------------------ 4 files changed, 58 insertions(+), 246 deletions(-) create mode 100644 MovieVerse-Backend/django_backend/movieverse/static/images/favicon.ico delete mode 100644 MovieVerse-Backend/flask_backend/micro-services.py diff --git a/MovieVerse-Backend/.idea/workspace.xml b/MovieVerse-Backend/.idea/workspace.xml index e0433711..e9be3321 100644 --- a/MovieVerse-Backend/.idea/workspace.xml +++ b/MovieVerse-Backend/.idea/workspace.xml @@ -5,61 +5,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true", - "Django Server.Django-Backend.executor": "Run", - "Django Server.MovieVerse-Backend.executor": "Run", - "Python.flask.executor": "Run", - "Python.genre_classifier.executor": "Run", - "Python.movie-recommendation.executor": "Run", - "Python.plot-summarizer.executor": "Run", - "Python.sentiment_analysis.executor": "Run", - "RunOnceActivity.OpenDjangoStructureViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "SHARE_PROJECT_CONFIGURATION_FILES": "true", - "git-widget-placeholder": "main-deployment-branch", - "last_opened_file_path": "/Users/davidnguyen/WebstormProjects/The-MovieVerse-Database/MovieVerse-Backend/django_backend/movieverse", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "settings.editor.selected.configurable": "reference.settings.ide.settings.new.ui", - "vue.rearranger.settings.migration": "true" + +}]]> @@ -123,7 +73,7 @@ - + + + + + @@ -251,6 +225,7 @@ + @@ -279,6 +254,7 @@ + @@ -292,6 +268,7 @@ + diff --git a/MovieVerse-Backend/django_backend/django_backend/settings.py b/MovieVerse-Backend/django_backend/django_backend/settings.py index aa7fdc5d..c5eb8f46 100644 --- a/MovieVerse-Backend/django_backend/django_backend/settings.py +++ b/MovieVerse-Backend/django_backend/django_backend/settings.py @@ -23,8 +23,7 @@ # SECURITY WARNING: don't run with debug turned on in production! -ALLOWED_HOSTS = [] - +ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'movie-verse.com', 'www.movie-verse.com'] # Application definition diff --git a/MovieVerse-Backend/django_backend/movieverse/static/images/favicon.ico b/MovieVerse-Backend/django_backend/movieverse/static/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3fee70ca195c6e45bca3b02b1ece2290cba91667 GIT binary patch literal 15406 zcmeHOdu&@*8NX#42oNx&PSapWm3ZwhR1j1Hp=sjPO)wrsWs?|ER}|51+W4bY*(i@R zNCSf^Y%v?caa* z`4Ode#enJL65~9~*XoL96&{vHU+c;DPfsKKRa; zJbC#kZ?XK8k4r9oC4cGq8*<$OM|%@c{pISf$Et%qyCE%yA0JHXp6cLFCmmpFx&lM) zzI1+S1Jr&;{DUw)Hk>8Yet6wccfeFsQ{N)CXGCio^IGhxx|4NsI0(6cgk1h8Xtue*lR0Q2vcv zG|ucpc^rktr(%~uQ&)xMbs^tn(p$jOTX4KtZ7kx?B4a)ncK2Z4zBb#Yt-ol{{P&?k z9P}FT*MupTjpB12;h>l4-JliwmCD+WA7LRX)amCZZQWr)Y) z56d|iGz|~sVsAEnz}?I6U41qjpJP@4_yFf%z9D{k%ki--5r(VR3WFl;1{^ERzneDy$orf;&idb&AM8dA!}mBl+4}45{J$!`@3sTyQ&&Mb z{Wo#e1l4SKwidJbe5$p_Vdy&MvCHx?}|91rq3nGi*3 zpWCXs{3P0|lElGo(!z8klupm~3$$n01c`(K(q0g8&^~aE?*S;(t1lly{0Xjy_LfH0 zZ%9|U;=n!rT7~EB(7(sa4SKa`H5wZ~Cda3^PN&>!P%B=+=k3U21|`M$Y(lt%Wgf)$ zoY89k^U+-F_v?SMlc0%KID)bkI|(Ke^x8|G(QCz*$)jCU*CT{29+$r2@{&F1zynSG6Q&FEl z{nh=;c>U6+it)EewvrAxXL-Dw{HZKMu5K_jUm<(tBHG^nXtUYwTdfReS9}inM&+F4 z@pAL0_GC9nYXeJ5V+3uTC#I&R$UTvF*=)7fm;FI~!Ng#t2j4 z(NtnFc42mQc5iM0%%b>VOj9f8n;YK_{_eZBLE;h#4bwaqM>&QQ_a1o}%x2=Rcr%x? z{QtJ_$1#s{h@h>U=IOYvtsH(QlX zo%S7?p4qh(`X6==fvVmIJyvO-E}x%r<93rD-mCM2r$0L_^|{>kPyET(gMI}g`EjWL zTfzUrc^~}n>mE4rln3XcY+iEyJvKj79rCiYwXgeNJeaW=ugYH|qCcws=vMKsIpIz3 zVJOb+)pG81@9ZDE@V)(#ZS&7BW4fU=^JnXPn!C0_|8o9R4_7bwAUvAkM&)Q~^utfS znX${3%wMB;ljg3i=1+U}UmfeS;PhPv(P5e36mBQVi|CM<2dlmlK=M4DV zO<}3)=at46uIEBwMgJB3^Yu-o{S2`_$NM1PKc?5u*eA=qekjy$`Ta`&t@PhY|IO?X zitHaN{TKIuyuIXxmGOTF2D;6K?I$~Y{117A z?B|g&X1`tU{OjsNU-u~4K-MNTC-V2Ve4Z^7zIw`o=Ske)9q2?K*t==n+l^X?qVJmQ z&+FxH!@8ui=4+MT9xr|dA-5c-UvS{L3v4r5MVhIng46Q8-dBl6vSh5 zu(Xsp-?6;BgxuqdKl;JI(t`G9+4$2diXYUg>(1x4jo92w`iw}ak=IV&DVhJd>Ty_D zko29#L;Z8zlm77=Am&$P{56U4?@o}Q zZve-q^tq{sL0x~4bWa!7*PhmE;&-th#CREK?CP@{(f2CN9otog*xVHR&cf83=8knx z*R;}E7v%nZ6fX+<)`gda`b>963znx1oOlMTpmdqRi88POk-z)aO`Tll)OJ#M4#=Zd zUi`eI`RrdhH-%{qFn_RC3}RT&C(p}P#*og#Xj;_`U(05l|Bvd%s20D7=V*@OnWw9m p&yTWDMy}EriR}Q-^Mr7GysFpK{mh2vu&Ax)Gp^jJXkdLB_&;l>s7U|- literal 0 HcmV?d00001 diff --git a/MovieVerse-Backend/flask_backend/micro-services.py b/MovieVerse-Backend/flask_backend/micro-services.py deleted file mode 100644 index 269ca4d1..00000000 --- a/MovieVerse-Backend/flask_backend/micro-services.py +++ /dev/null @@ -1,164 +0,0 @@ -microservices = { - "movie_service": { - "description": "Handles all movie-related operations", - "framework": "Django", - "database": "MySQL", - "endpoints": [ - "/api/movies", - "/api/movies/{id}", - ] - }, - "user_service": { - "description": "Manages user authentication and profiles", - "framework": "Flask", - "database": "MongoDB", - "endpoints": [ - "/api/users", - "/api/users/{id}", - ] - }, - "rating_service": { - "description": "Handles movie ratings and reviews", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/ratings", - "/api/ratings/{id}", - ] - }, - "recommendation_service": { - "description": "Generates movie recommendations for users", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/recommendations", - "/api/recommendations/{id}", - ] - }, - "search_service": { - "description": "Handles movie search", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/search", - "/api/search/{id}", - ] - }, - "notification_service": { - "description": "Handles notifications for users", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/notifications", - "/api/notifications/{id}", - ] - }, - "payment_service": { - "description": "Handles payments for users", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/payments", - "/api/payments/{id}", - ] - }, - "analytics_service": { - "description": "Handles analytics for the application", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/analytics", - "/api/analytics/{id}", - ] - }, - "admin_service": { - "description": "Handles admin operations", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/admin", - "/api/admin/{id}", - ] - }, - "account_service": { - "description": "Handles user accounts", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/accounts", - "/api/accounts/{id}", - ] - }, - "subscription_service": { - "description": "Handles user subscriptions", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/subscriptions", - "/api/subscriptions/{id}", - ] - }, - "content_service": { - "description": "Handles content management", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/content", - "/api/content/{id}", - ] - }, - "review_service": { - "description": "Handles movie reviews", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/reviews", - "/api/reviews/{id}", - ] - }, - "genre_service": { - "description": "Handles movie genres", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/genres", - "/api/genres/{id}", - ] - }, - "actor_service": { - "description": "Handles movie actors", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/actors", - "/api/actors/{id}", - ] - }, - "director_service": { - "description": "Handles movie directors", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/directors", - "/api/directors/{id}", - ] - }, - "crew_service": { - "description": "Handles movie crew", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/crew", - "/api/crew/{id}", - ] - }, - "company_service": { - "description": "Handles movie companies", - "framework": "Flask", - "database": "MySQL", - "endpoints": [ - "/api/companies", - "/api/companies/{id}", - ] - }, -} From c05fd3cff9805111a22fc76c6bfa60b8bb203c9b Mon Sep 17 00:00:00 2001 From: Son Nguyen Date: Tue, 18 Jun 2024 11:24:01 +0700 Subject: [PATCH 3/3] Final: Enhanced app's loading times (#196) --- .../movieverse/static/images/favicon.ico | Bin 15406 -> 15406 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/MovieVerse-Backend/django_backend/movieverse/static/images/favicon.ico b/MovieVerse-Backend/django_backend/movieverse/static/images/favicon.ico index 3fee70ca195c6e45bca3b02b1ece2290cba91667..63338c9d72d0a99eb4075e59715f5554aff311d8 100644 GIT binary patch literal 15406 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 XlL_;xrcf^Y%v?caa* z`4Ode#enJL65~9~*XoL96&{vHU+c;DPfsKKRa; zJbC#kZ?XK8k4r9oC4cGq8*<$OM|%@c{pISf$Et%qyCE%yA0JHXp6cLFCmmpFx&lM) zzI1+S1Jr&;{DUw)Hk>8Yet6wccfeFsQ{N)CXGCio^IGhxx|4NsI0(6cgk1h8Xtue*lR0Q2vcv zG|ucpc^rktr(%~uQ&)xMbs^tn(p$jOTX4KtZ7kx?B4a)ncK2Z4zBb#Yt-ol{{P&?k z9P}FT*MupTjpB12;h>l4-JliwmCD+WA7LRX)amCZZQWr)Y) z56d|iGz|~sVsAEnz}?I6U41qjpJP@4_yFf%z9D{k%ki--5r(VR3WFl;1{^ERzneDy$orf;&idb&AM8dA!}mBl+4}45{J$!`@3sTyQ&&Mb z{Wo#e1l4SKwidJbe5$p_Vdy&MvCHx?}|91rq3nGi*3 zpWCXs{3P0|lElGo(!z8klupm~3$$n01c`(K(q0g8&^~aE?*S;(t1lly{0Xjy_LfH0 zZ%9|U;=n!rT7~EB(7(sa4SKa`H5wZ~Cda3^PN&>!P%B=+=k3U21|`M$Y(lt%Wgf)$ zoY89k^U+-F_v?SMlc0%KID)bkI|(Ke^x8|G(QCz*$)jCU*CT{29+$r2@{&F1zynSG6Q&FEl z{nh=;c>U6+it)EewvrAxXL-Dw{HZKMu5K_jUm<(tBHG^nXtUYwTdfReS9}inM&+F4 z@pAL0_GC9nYXeJ5V+3uTC#I&R$UTvF*=)7fm;FI~!Ng#t2j4 z(NtnFc42mQc5iM0%%b>VOj9f8n;YK_{_eZBLE;h#4bwaqM>&QQ_a1o}%x2=Rcr%x? z{QtJ_$1#s{h@h>U=IOYvtsH(QlX zo%S7?p4qh(`X6==fvVmIJyvO-E}x%r<93rD-mCM2r$0L_^|{>kPyET(gMI}g`EjWL zTfzUrc^~}n>mE4rln3XcY+iEyJvKj79rCiYwXgeNJeaW=ugYH|qCcws=vMKsIpIz3 zVJOb+)pG81@9ZDE@V)(#ZS&7BW4fU=^JnXPn!C0_|8o9R4_7bwAUvAkM&)Q~^utfS znX${3%wMB;ljg3i=1+U}UmfeS;PhPv(P5e36mBQVi|CM<2dlmlK=M4DV zO<}3)=at46uIEBwMgJB3^Yu-o{S2`_$NM1PKc?5u*eA=qekjy$`Ta`&t@PhY|IO?X zitHaN{TKIuyuIXxmGOTF2D;6K?I$~Y{117A z?B|g&X1`tU{OjsNU-u~4K-MNTC-V2Ve4Z^7zIw`o=Ske)9q2?K*t==n+l^X?qVJmQ z&+FxH!@8ui=4+MT9xr|dA-5c-UvS{L3v4r5MVhIng46Q8-dBl6vSh5 zu(Xsp-?6;BgxuqdKl;JI(t`G9+4$2diXYUg>(1x4jo92w`iw}ak=IV&DVhJd>Ty_D zko29#L;Z8zlm77=Am&$P{56U4?@o}Q zZve-q^tq{sL0x~4bWa!7*PhmE;&-th#CREK?CP@{(f2CN9otog*xVHR&cf83=8knx z*R;}E7v%nZ6fX+<)`gda`b>963znx1oOlMTpmdqRi88POk-z)aO`Tll)OJ#M4#=Zd zUi`eI`RrdhH-%{qFn_RC3}RT&C(p}P#*og#Xj;_`U(05l|Bvd%s20D7=V*@OnWw9m p&yTWDMy}EriR}Q-^Mr7GysFpK{mh2vu&Ax)Gp^jJXkdLB_&;l>s7U|-