diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..78e5623149 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,47 @@ +name: 'Lint Code' +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] +jobs: + lint_python: + name: Lint Python Files + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 + - name: Print working directory + run: pwd + - name: Run Linter + run: | + pwd + # This command finds all Python files recursively and runs flake8 on them + find . -name "*.py" -exec flake8 {} + + echo "Linted all the python files successfully" + lint_js: + name: Lint JavaScript Files + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 14 + - name: Install JSHint + run: npm install jshint --global + - name: Run Linter + run: | + # This command finds all JavaScript files recursively and runs JSHint on them + find ./server/database -name "*.js" -exec jshint {} + + echo "Linted all the js files successfully" diff --git a/server/database/app.js b/server/database/app.js index 00f52b2008..b871c9d18b 100644 --- a/server/database/app.js +++ b/server/database/app.js @@ -1,39 +1,40 @@ +/*jshint esversion: 8 */ const express = require('express'); const mongoose = require('mongoose'); const fs = require('fs'); -const cors = require('cors') -const app = express() +const cors = require('cors'); + +const app = express(); const port = 3030; -app.use(cors()) +app.use(cors()); app.use(require('body-parser').urlencoded({ extended: false })); const reviews_data = JSON.parse(fs.readFileSync("reviews.json", 'utf8')); const dealerships_data = JSON.parse(fs.readFileSync("dealerships.json", 'utf8')); -mongoose.connect("mongodb://mongo_db:27017/",{'dbName':'dealershipsDB'}); - +mongoose.connect("mongodb://mongo_db:27017/", { dbName: 'dealershipsDB' }); const Reviews = require('./review'); - const Dealerships = require('./dealership'); -try { - Reviews.deleteMany({}).then(()=>{ - Reviews.insertMany(reviews_data['reviews']); - }); - Dealerships.deleteMany({}).then(()=>{ - Dealerships.insertMany(dealerships_data['dealerships']); - }); - -} catch (error) { - res.status(500).json({ error: 'Error fetching documents' }); +async function initializeDatabase() { + try { + await Reviews.deleteMany({}); + await Reviews.insertMany(reviews_data.reviews); + + await Dealerships.deleteMany({}); + await Dealerships.insertMany(dealerships_data.dealerships); + } catch (error) { + console.error('Error fetching documents:', error); + } } +initializeDatabase(); // Express route to home app.get('/', async (req, res) => { - res.send("Welcome to the Mongoose API") + res.send("Welcome to the Mongoose API"); }); // Express route to fetch all reviews @@ -49,7 +50,7 @@ app.get('/fetchReviews', async (req, res) => { // Express route to fetch reviews by a particular dealer app.get('/fetchReviews/dealer/:id', async (req, res) => { try { - const documents = await Reviews.find({dealership: req.params.id}); + const documents = await Reviews.find({ dealership: req.params.id }); res.json(documents); } catch (error) { res.status(500).json({ error: 'Error fetching documents' }); @@ -58,42 +59,57 @@ app.get('/fetchReviews/dealer/:id', async (req, res) => { // Express route to fetch all dealerships app.get('/fetchDealers', async (req, res) => { -//Write your code here + try { + const documents = await Dealerships.find(); + res.json(documents); + } catch (error) { + res.status(500).json({ error: 'Error fetching documents' }); + } }); // Express route to fetch Dealers by a particular state app.get('/fetchDealers/:state', async (req, res) => { -//Write your code here + try { + const documents = await Dealerships.find({ state: req.params.state }); + res.json(documents); + } catch (error) { + res.status(500).json({ error: 'Error fetching documents' }); + } }); // Express route to fetch dealer by a particular id app.get('/fetchDealer/:id', async (req, res) => { -//Write your code here + try { + const documents = await Dealerships.find({ id: req.params.id }); + res.json(documents); + } catch (error) { + res.status(500).json({ error: 'Error fetching documents' }); + } }); -//Express route to insert review +// Express route to insert review app.post('/insert_review', express.raw({ type: '*/*' }), async (req, res) => { - data = JSON.parse(req.body); - const documents = await Reviews.find().sort( { id: -1 } ) - let new_id = documents[0]['id']+1 - - const review = new Reviews({ - "id": new_id, - "name": data['name'], - "dealership": data['dealership'], - "review": data['review'], - "purchase": data['purchase'], - "purchase_date": data['purchase_date'], - "car_make": data['car_make'], - "car_model": data['car_model'], - "car_year": data['car_year'], - }); - try { + const data = JSON.parse(req.body); + const documents = await Reviews.find().sort({ id: -1 }); + const new_id = documents[0].id + 1; + + const review = new Reviews({ + id: new_id, + name: data.name, + dealership: data.dealership, + review: data.review, + purchase: data.purchase, + purchase_date: data.purchase_date, + car_make: data.car_make, + car_model: data.car_model, + car_year: data.car_year, + }); + const savedReview = await review.save(); res.json(savedReview); } catch (error) { - console.log(error); + console.error(error); res.status(500).json({ error: 'Error inserting review' }); } }); diff --git a/server/database/dealership.js b/server/database/dealership.js index b10d6b4730..0023d8e01b 100644 --- a/server/database/dealership.js +++ b/server/database/dealership.js @@ -1,43 +1,44 @@ +/*jshint esversion: 8 */ const mongoose = require('mongoose'); -const Schema = mongoose.Schema; +const { Schema } = mongoose; -const dealerships = new Schema({ - id: { +const dealershipSchema = new Schema({ + id: { type: Number, required: true, - }, - city: { + }, + city: { type: String, - required: true + required: true, }, state: { type: String, - required: true + required: true, }, address: { type: String, - required: true + required: true, }, zip: { type: String, - required: true + required: true, }, lat: { type: String, - required: true + required: true, }, long: { type: String, - required: true + required: true, }, short_name: { type: String, }, full_name: { type: String, - required: true - } + required: true, + }, }); -module.exports = mongoose.model('dealerships', dealerships); +module.exports = mongoose.model('Dealership', dealershipSchema); diff --git a/server/database/inventory.js b/server/database/inventory.js index 2c22fd092c..c5f577e415 100644 --- a/server/database/inventory.js +++ b/server/database/inventory.js @@ -1,33 +1,34 @@ +/*jshint esversion: 8 */ const { Int32 } = require('mongodb'); const mongoose = require('mongoose'); -const Schema = mongoose.Schema; +const { Schema } = mongoose; -const cars = new Schema({ -dealer_id: { +const carSchema = new Schema({ + dealer_id: { type: Number, - required: true -}, -make: { + required: true, + }, + make: { type: String, - required: true + required: true, }, -model: { + model: { type: String, - required: true + required: true, }, -bodyType: { + bodyType: { type: String, - required: true + required: true, }, -year: { + year: { type: Number, - required: true + required: true, }, -mileage: { + mileage: { type: Number, - required: true - } + required: true, + }, }); -module.exports = mongoose.model('cars', cars); +module.exports = mongoose.model('Car', carSchema); diff --git a/server/database/review.js b/server/database/review.js index 4759725a3a..d8937b18a6 100644 --- a/server/database/review.js +++ b/server/database/review.js @@ -1,15 +1,16 @@ +/*jshint esversion: 8 */ const mongoose = require('mongoose'); -const Schema = mongoose.Schema; +const { Schema } = mongoose; -const reviews = new Schema({ - id: { +const reviewSchema = new Schema({ + id: { type: Number, required: true, - }, - name: { + }, + name: { type: String, - required: true + required: true, }, dealership: { type: Number, @@ -17,28 +18,28 @@ const reviews = new Schema({ }, review: { type: String, - required: true + required: true, }, purchase: { type: Boolean, - required: true + required: true, }, purchase_date: { type: String, - required: true + required: true, }, car_make: { type: String, - required: true + required: true, }, car_model: { type: String, - required: true + required: true, }, car_year: { type: Number, - required: true + required: true, }, }); -module.exports = mongoose.model('reviews', reviews); +module.exports = mongoose.model('Review', reviewSchema); diff --git a/server/djangoapp/.env b/server/djangoapp/.env index 01822e542a..d7eff95343 100644 --- a/server/djangoapp/.env +++ b/server/djangoapp/.env @@ -1,2 +1,2 @@ -backend_url =your backend url -sentiment_analyzer_url=your code engine deployment url +backend_url =https://abhijeetpadw-3030.theiadockernext-0-labs-prod-theiak8s-4-tor01.proxy.cognitiveclass.ai/ +sentiment_analyzer_url=https://sentianalyzer.1nm6f9s0g2km.us-south.codeengine.appdomain.cloud/ \ No newline at end of file diff --git a/server/djangoapp/admin.py b/server/djangoapp/admin.py index 433657fc64..9e573e9a4f 100644 --- a/server/djangoapp/admin.py +++ b/server/djangoapp/admin.py @@ -1,12 +1,9 @@ -# from django.contrib import admin -# from .models import related models +from django.contrib import admin +from .models import CarMake, CarModel - -# Register your models here. - -# CarModelInline class - -# CarModelAdmin class +# Registering models with their respective admins +admin.site.register(CarMake) +admin.site.register(CarModel) # CarMakeAdmin class with CarModelInline diff --git a/server/djangoapp/models.py b/server/djangoapp/models.py index eb101a68c8..6a85b19791 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -1,25 +1,38 @@ -# Uncomment the following imports before adding the Model code +from django.db import models +from django.core.validators import MaxValueValidator, MinValueValidator -# from django.db import models -# from django.utils.timezone import now -# from django.core.validators import MaxValueValidator, MinValueValidator +class CarMake(models.Model): + name = models.CharField(max_length=100) + description = models.TextField() -# Create your models here. + def __str__(self): + return self.name -# Create a Car Make model `class CarMake(models.Model)`: -# - Name -# - Description -# - Any other fields you would like to include in car make model -# - __str__ method to print a car make object +class CarModel(models.Model): + car_make = models.ForeignKey( + CarMake, + on_delete=models.CASCADE + ) # Many-to-One relationship + name = models.CharField(max_length=100) + CAR_TYPES = [ + ('SEDAN', 'Sedan'), + ('SUV', 'SUV'), + ('WAGON', 'Wagon'), + # Add more choices as required + ] + type = models.CharField(max_length=10, choices=CAR_TYPES, default='SUV') + year = models.IntegerField( + default=2023, + validators=[ + MaxValueValidator(2023), + MinValueValidator(2015) + ] + ) -# Create a Car Model model `class CarModel(models.Model):`: -# - Many-To-One relationship to Car Make model (One Car Make has many -# Car Models, using ForeignKey field) -# - Name -# - Type (CharField with a choices argument to provide limited choices -# such as Sedan, SUV, WAGON, etc.) -# - Year (IntegerField) with min value 2015 and max value 2023 -# - Any other fields you would like to include in car model -# - __str__ method to print a car make object + def __str__(self): + return self.name + + +# Ensure there's a newline at the end of the file. diff --git a/server/djangoapp/populate.py b/server/djangoapp/populate.py index 1927e09e18..7cc4165e4e 100644 --- a/server/djangoapp/populate.py +++ b/server/djangoapp/populate.py @@ -1,2 +1,111 @@ +from .models import CarMake, CarModel + + +"""This module populates the database with car makes and models.""" + + def initiate(): - print("Populate not implemented. Add data manually") + """Initialize the database with car make and model data.""" + + car_make_data = [ + { + "name": "NISSAN", + "description": "Great cars. Japanese technology" + }, + { + "name": "Mercedes", + "description": "Great cars. German technology" + }, + { + "name": "Audi", + "description": "Great cars. German technology" + }, + { + "name": "Kia", + "description": "Great cars. Korean technology" + }, + { + "name": "Toyota", + "description": "Great cars. Japanese technology" + }, + ] + + car_make_instances = [ + CarMake.objects.create( + name=data["name"], + description=data["description"] + ) + for data in car_make_data + ] + + # CarModel instances with corresponding CarMake + car_model_data = [ + { + "name": "Pathfinder", "type": "SUV", "year": 2023, + "car_make": car_make_instances[0] + }, + { + "name": "Qashqai", "type": "SUV", "year": 2023, + "car_make": car_make_instances[0] + }, + { + "name": "XTRAIL", "type": "SUV", "year": 2023, + "car_make": car_make_instances[0] + }, + { + "name": "A-Class", "type": "SUV", "year": 2023, + "car_make": car_make_instances[1] + }, + { + "name": "C-Class", "type": "SUV", "year": 2023, + "car_make": car_make_instances[1] + }, + { + "name": "E-Class", "type": "SUV", "year": 2023, + "car_make": car_make_instances[1] + }, + { + "name": "A4", "type": "SUV", "year": 2023, + "car_make": car_make_instances[2] + }, + { + "name": "A5", "type": "SUV", "year": 2023, + "car_make": car_make_instances[2] + }, + { + "name": "A6", "type": "SUV", "year": 2023, + "car_make": car_make_instances[2] + }, + { + "name": "Sorrento", "type": "SUV", "year": 2023, + "car_make": car_make_instances[3] + }, + { + "name": "Carnival", "type": "SUV", "year": 2023, + "car_make": car_make_instances[3] + }, + { + "name": "Cerato", "type": "Sedan", "year": 2023, + "car_make": car_make_instances[3] + }, + { + "name": "Corolla", "type": "Sedan", "year": 2023, + "car_make": car_make_instances[4] + }, + { + "name": "Camry", "type": "Sedan", "year": 2023, + "car_make": car_make_instances[4] + }, + { + "name": "Kluger", "type": "SUV", "year": 2023, + "car_make": car_make_instances[4] + }, + ] + + for data in car_model_data: + CarModel.objects.create( + name=data["name"], + car_make=data["car_make"], + type=data["type"], + year=data["year"], + ) diff --git a/server/djangoapp/restapis.py b/server/djangoapp/restapis.py index 90709d9e3b..7242368edb 100644 --- a/server/djangoapp/restapis.py +++ b/server/djangoapp/restapis.py @@ -1,22 +1,51 @@ -# Uncomment the imports below before you add the function code -# import requests +import requests import os from dotenv import load_dotenv +# Ensure two blank lines before functions load_dotenv() -backend_url = os.getenv( - 'backend_url', default="http://localhost:3030") +backend_url = os.getenv('backend_url', default="http://localhost:3030") sentiment_analyzer_url = os.getenv( - 'sentiment_analyzer_url', - default="http://localhost:5050/") + 'sentiment_analyzer_url', default="http://localhost:5050/" +) -# def get_request(endpoint, **kwargs): -# Add code for get requests to back end -# def analyze_review_sentiments(text): -# request_url = sentiment_analyzer_url+"analyze/"+text -# Add code for retrieving sentiments +def get_request(endpoint, **kwargs): + params = "" + if kwargs: + for key, value in kwargs.items(): + params += f"{key}={value}&" -# def post_review(data_dict): -# Add code for posting review + request_url = f"{backend_url}{endpoint}?{params}" + print(f"GET from {request_url}") + + try: + response = requests.get(request_url) + return response.json() + except requests.exceptions.RequestException as err: + print(f"Network exception occurred: {err}") + + +def analyze_review_sentiments(text): + request_url = f"{sentiment_analyzer_url}analyze/{text}" + print(f"Analyzing sentiments for: {request_url}") + + try: + response = requests.get(request_url) + return response.json() + except requests.exceptions.RequestException as err: + print(f"Unexpected {err=}, {type(err)=}") + print("Network exception occurred") + + +def post_review(data_dict): + request_url = f"{backend_url}/insert_review" + print(f"Posting review to: {request_url}") + + try: + response = requests.post(request_url, json=data_dict) + print(response.json()) + return response.json() + except requests.exceptions.RequestException as err: + print(f"Network exception occurred: {err}") diff --git a/server/djangoapp/urls.py b/server/djangoapp/urls.py index 0edc274f90..e6cd398389 100644 --- a/server/djangoapp/urls.py +++ b/server/djangoapp/urls.py @@ -1,18 +1,39 @@ -# Uncomment the imports before you add the code -# from django.urls import path +from django.urls import path from django.conf.urls.static import static from django.conf import settings -# from . import views +from . import views app_name = 'djangoapp' + urlpatterns = [ - # # path for registration + # Path for registration + path(route='register', view=views.registration, name='registration'), + + # Path for login + path(route='login', view=views.login_user, name='login'), + + # Path for logout + path(route='logout', view=views.logout_request, name='logout'), - # path for login - # path(route='login', view=views.login_user, name='login'), + # Path for getting cars + path(route='get_cars', view=views.get_cars, name='getcars'), - # path for dealer reviews view + # Paths for dealerships + path(route='get_dealers/', view=views.get_dealerships, name='get_dealers'), + path(route='get_dealers/', + view=views.get_dealerships, + name='get_dealers_by_state'), - # path for add a review view + # Path for dealer details + path(route='dealer/', + view=views.get_dealer_details, + name='dealer_details'), + # Path for reviews and adding a review + path(route='reviews/dealer/', + view=views.get_dealer_reviews, + name='dealer_details'), + path(route='add_review', + view=views.add_review, + name='add_review'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/server/djangoapp/views.py b/server/djangoapp/views.py index b16409f419..b4a910a4cc 100644 --- a/server/djangoapp/views.py +++ b/server/djangoapp/views.py @@ -1,65 +1,121 @@ -# Uncomment the required imports before adding the code - -# from django.shortcuts import render -# from django.http import HttpResponseRedirect, HttpResponse -# from django.contrib.auth.models import User -# from django.shortcuts import get_object_or_404, render, redirect -# from django.contrib.auth import logout -# from django.contrib import messages -# from datetime import datetime - +from django.contrib.auth.models import User +from django.contrib.auth import logout, login, authenticate from django.http import JsonResponse -from django.contrib.auth import login, authenticate +from django.views.decorators.csrf import csrf_exempt +from .populate import initiate +from .models import CarMake, CarModel +from .restapis import get_request, analyze_review_sentiments, post_review import logging import json -from django.views.decorators.csrf import csrf_exempt -# from .populate import initiate - # Get an instance of a logger logger = logging.getLogger(__name__) -# Create your views here. - -# Create a `login_request` view to handle sign in request +# Create a `login_request` view to handle sign-in request @csrf_exempt def login_user(request): - # Get username and password from request.POST dictionary data = json.loads(request.body) username = data['userName'] password = data['password'] - # Try to check if provide credential can be authenticated + + # Authenticate user user = authenticate(username=username, password=password) - data = {"userName": username} - if user is not None: - # If user is valid, call login method to login current user + response_data = {"userName": username} + + if user: login(request, user) - data = {"userName": username, "status": "Authenticated"} - return JsonResponse(data) - -# Create a `logout_request` view to handle sign out request -# def logout_request(request): -# ... - -# Create a `registration` view to handle sign up request -# @csrf_exempt -# def registration(request): -# ... - -# # Update the `get_dealerships` view to render the index page with -# a list of dealerships -# def get_dealerships(request): -# ... - -# Create a `get_dealer_reviews` view to render the reviews of a dealer -# def get_dealer_reviews(request,dealer_id): -# ... - -# Create a `get_dealer_details` view to render the dealer details -# def get_dealer_details(request, dealer_id): -# ... - -# Create a `add_review` view to submit a review -# def add_review(request): -# ... + response_data["status"] = "Authenticated" + return JsonResponse(response_data) + + +# Create a `logout_request` view to handle sign-out request +def logout_request(request): + logout(request) + return JsonResponse({"userName": ""}) + + +# Create a `registration` view to handle sign-up request +@csrf_exempt +def registration(request): + data = json.loads(request.body) + username = data['userName'] + password = data['password'] + first_name = data['firstName'] + last_name = data['lastName'] + email = data['email'] + + try: + # Check if the user already exists + User.objects.get(username=username) + return JsonResponse({"userName": username, + "error": "Already Registered"}) + except User.DoesNotExist: + logger.debug(f"{username} is a new user") + + # Create a new user and log them in + user = User.objects.create_user( + username=username, + first_name=first_name, + last_name=last_name, + password=password, + email=email + ) + login(request, user) + return JsonResponse({"userName": username, "status": "Authenticated"}) + + +# Get dealerships, optionally filtered by state +def get_dealerships(request, state="All"): + endpoint = f"/fetchDealers/{state}" if state != "All" else "/fetchDealers" + dealerships = get_request(endpoint) + return JsonResponse({"status": 200, "dealers": dealerships}) + + +# Get reviews for a specific dealer +def get_dealer_reviews(request, dealer_id): + if dealer_id: + endpoint = f"/fetchReviews/dealer/{dealer_id}" + reviews = get_request(endpoint) + + for review_detail in reviews: + sentiment = analyze_review_sentiments(review_detail['review']) + review_detail['sentiment'] = sentiment['sentiment'] + + return JsonResponse({"status": 200, "reviews": reviews}) + return JsonResponse({"status": 400, "message": "Bad Request"}) + + +# Get details for a specific dealer +def get_dealer_details(request, dealer_id): + if dealer_id: + endpoint = f"/fetchDealer/{dealer_id}" + dealership = get_request(endpoint) + return JsonResponse({"status": 200, "dealer": dealership}) + return JsonResponse({"status": 400, "message": "Bad Request"}) + + +# Add a new review +def add_review(request): + if not request.user.is_anonymous: + data = json.loads(request.body) + try: + post_review(data) + return JsonResponse({"status": 200}) + except Exception: + return JsonResponse({"status": 401, "message": + "Error in posting review"}) + return JsonResponse({"status": 403, "message": "Unauthorized"}) + + +# Get cars and initiate population if none exist +def get_cars(request): + if CarMake.objects.count() == 0: + initiate() + + car_models = CarModel.objects.select_related('car_make').all() + cars = [ + {"CarModel": cm.name, "CarMake": cm.car_make.name} + for cm in car_models + ] + return JsonResponse({"CarModels": cars}) diff --git a/server/djangoproj/settings.py b/server/djangoproj/settings.py index e0b1092a5c..392dfd2e82 100644 --- a/server/djangoproj/settings.py +++ b/server/djangoproj/settings.py @@ -13,30 +13,36 @@ import os 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/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY =\ +SECRET_KEY = ( 'django-insecure-ccow$tz_=9%dxu4(0%^(z%nx32#s@(zt9$ih@)5l54yny)wm-0' +) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] -CSRF_TRUSTED_ORIGINS = [] +ALLOWED_HOSTS = [ + 'localhost', + 'abhijeetpadw-8000.theiadockernext-0-labs-prod-' + 'theiak8s-4-tor01.proxy.cognitiveclass.ai', +] + +CSRF_TRUSTED_ORIGINS = [ + 'https://abhijeetpadw-8000.theiadockernext-0-labs-prod-' + 'theiak8s-4-tor01.proxy.cognitiveclass.ai', +] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [], } # Application definition - INSTALLED_APPS = [ 'djangoapp.apps.DjangoappConfig', 'django.contrib.admin', @@ -51,6 +57,7 @@ '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', @@ -61,7 +68,11 @@ TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [ + os.path.join(BASE_DIR, 'frontend/static'), + os.path.join(BASE_DIR, 'frontend/build'), + os.path.join(BASE_DIR, 'frontend/build/static'), + ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -76,10 +87,7 @@ WSGI_APPLICATION = 'djangoproj.wsgi.application' - -# Database -# https://docs.djangoproject.com/en/3.2/ref/settings/#databases - +# Database configuration DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -89,50 +97,41 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': - 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + 'NAME': 'django.contrib.auth.password_validation.' + 'UserAttributeSimilarityValidator', }, { - 'NAME': - 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'NAME': 'django.contrib.auth.password_validation.' + 'MinimumLengthValidator', }, { - 'NAME': - 'django.contrib.auth.password_validation.CommonPasswordValidator', + 'NAME': 'django.contrib.auth.password_validation.' + 'CommonPasswordValidator', }, { - 'NAME': - 'django.contrib.auth.password_validation.NumericPasswordValidator', + 'NAME': 'django.contrib.auth.password_validation.' + 'NumericPasswordValidator', }, ] - -# Internationalization -# https://docs.djangoproject.com/en/3.2/topics/i18n/ - +# Internationalization settings LANGUAGE_CODE = 'en-us' - TIME_ZONE = 'UTC' - USE_I18N = True - USE_L10N = True - USE_TZ = True - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.2/howto/static-files/ - +# Static and media files configuration STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') MEDIA_ROOT = os.path.join(STATIC_ROOT, 'media') MEDIA_URL = '/media/' -# Default primary key field type -# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, 'frontend/static'), + os.path.join(BASE_DIR, 'frontend/build'), + os.path.join(BASE_DIR, 'frontend/build/static'), +] +# Default primary key field type DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - -STATICFILES_DIRS = [] - diff --git a/server/djangoproj/urls.py b/server/djangoproj/urls.py index 6808da9141..37547adc44 100644 --- a/server/djangoproj/urls.py +++ b/server/djangoproj/urls.py @@ -1,4 +1,5 @@ -"""djangoproj URL Configuration +""" +djangoproj URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.2/topics/http/urls/ @@ -23,4 +24,17 @@ path('admin/', admin.site.urls), path('djangoapp/', include('djangoapp.urls')), path('', TemplateView.as_view(template_name="Home.html")), + path('about/', TemplateView.as_view(template_name="About.html")), + path('contact/', TemplateView.as_view(template_name="Contact.html")), + path('login/', TemplateView.as_view(template_name="index.html")), + path('register/', TemplateView.as_view(template_name="index.html")), + path('dealers/', TemplateView.as_view(template_name="index.html")), + path( + 'dealer//', + TemplateView.as_view(template_name="index.html") + ), + path( + 'postreview//', + TemplateView.as_view(template_name="index.html") + ), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/server/frontend/package-lock.json b/server/frontend/package-lock.json index 0797425307..b9244446c9 100644 --- a/server/frontend/package-lock.json +++ b/server/frontend/package-lock.json @@ -16,6 +16,9 @@ "react-router-dom": "^6.19.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -646,9 +649,17 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -1891,6 +1902,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/server/frontend/src/App.js b/server/frontend/src/App.js index aceac6974d..85b3808cc4 100644 --- a/server/frontend/src/App.js +++ b/server/frontend/src/App.js @@ -1,10 +1,20 @@ import LoginPanel from "./components/Login/Login" +import RegisterPanel from "./components/Register/Register" +import Dealers from './components/Dealers/Dealers'; +import Dealer from "./components/Dealers/Dealer" +import PostReview from "./components/Dealers/PostReview" + import { Routes, Route } from "react-router-dom"; function App() { return ( } /> + } /> + } /> + } /> + } /> + ); } diff --git a/server/frontend/src/components/Dealers/PostReview.jsx b/server/frontend/src/components/Dealers/PostReview.jsx index 5ef16de3df..8eec4bd19f 100644 --- a/server/frontend/src/components/Dealers/PostReview.jsx +++ b/server/frontend/src/components/Dealers/PostReview.jsx @@ -4,120 +4,150 @@ import "./Dealers.css"; import "../assets/style.css"; import Header from '../Header/Header'; - const PostReview = () => { const [dealer, setDealer] = useState({}); const [review, setReview] = useState(""); - const [model, setModel] = useState(); + const [model, setModel] = useState(""); const [year, setYear] = useState(""); const [date, setDate] = useState(""); const [carmodels, setCarmodels] = useState([]); - let curr_url = window.location.href; - let root_url = curr_url.substring(0,curr_url.indexOf("postreview")); - let params = useParams(); - let id =params.id; - let dealer_url = root_url+`djangoapp/dealer/${id}`; - let review_url = root_url+`djangoapp/add_review`; - let carmodels_url = root_url+`djangoapp/get_cars`; - - const postreview = async ()=>{ - let name = sessionStorage.getItem("firstname")+" "+sessionStorage.getItem("lastname"); - //If the first and second name are stores as null, use the username - if(name.includes("null")) { + const params = useParams(); + const id = params.id; + + const rootUrl = window.location.origin; + const dealerUrl = `${rootUrl}/djangoapp/dealer/${id}`; + const reviewUrl = `${rootUrl}/djangoapp/add_review`; + const carModelsUrl = `${rootUrl}/djangoapp/get_cars`; + + const postReview = async () => { + let name = `${sessionStorage.getItem("firstname")} ${sessionStorage.getItem("lastname")}`; + if (name.includes("null")) { name = sessionStorage.getItem("username"); } - if(!model || review === "" || date === "" || year === "" || model === "") { - alert("All details are mandatory") + + if (!model || !review || !date || !year) { + alert("All details are mandatory"); return; } - let model_split = model.split(" "); - let make_chosen = model_split[0]; - let model_chosen = model_split[1]; - - let jsoninput = JSON.stringify({ - "name": name, - "dealership": id, - "review": review, - "purchase": true, - "purchase_date": date, - "car_make": make_chosen, - "car_model": model_chosen, - "car_year": year, - }); + const [makeChosen, modelChosen] = model.split(" "); - console.log(jsoninput); - const res = await fetch(review_url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: jsoninput, - }); - - const json = await res.json(); - if (json.status === 200) { - window.location.href = window.location.origin+"/dealer/"+id; - } - - } - const get_dealer = async ()=>{ - const res = await fetch(dealer_url, { - method: "GET" + const jsonInput = JSON.stringify({ + name, + dealership: id, + review, + purchase: true, + purchase_date: date, + car_make: makeChosen, + car_model: modelChosen, + car_year: year, }); - const retobj = await res.json(); - - if(retobj.status === 200) { - let dealerobjs = Array.from(retobj.dealer) - if(dealerobjs.length > 0) - setDealer(dealerobjs[0]) + + try { + const res = await fetch(reviewUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: jsonInput, + }); + + const json = await res.json(); + if (json.status === 200) { + window.location.href = `${rootUrl}/dealer/${id}`; + } else { + alert("Failed to post review."); + } + } catch (error) { + console.error("Error posting review:", error); + alert("An error occurred while posting the review."); } - } + }; - const get_cars = async ()=>{ - const res = await fetch(carmodels_url, { - method: "GET" - }); - const retobj = await res.json(); - - let carmodelsarr = Array.from(retobj.CarModels) - setCarmodels(carmodelsarr) - } - useEffect(() => { - get_dealer(); - get_cars(); - },[]); + const getDealer = async () => { + try { + const res = await fetch(dealerUrl); + const retObj = await res.json(); + if (retObj.status === 200 && retObj.dealer.length > 0) { + setDealer(retObj.dealer[0]); + } else { + console.error("Dealer not found."); + } + } catch (error) { + console.error("Error fetching dealer:", error); + } + }; + + const getCars = async () => { + try { + const res = await fetch(carModelsUrl); + const retObj = await res.json(); + setCarmodels(retObj.CarModels || []); + } catch (error) { + console.error("Error fetching car models:", error); + } + }; + + useEffect(() => { + getDealer(); + getCars(); + }, []); return (
-
-
-

{dealer.full_name}

- -
- Purchase Date setDate(e.target.value)}/> -
-
- Car Make - -
- -
- Car Year setYear(e.target.value)} max={2023} min={2015}/> -
+
+
+

{dealer.full_name}

+ -
- +
+ Purchase Date setDate(e.target.value)} /> +
+ +
+ Car Make & Model + +
+ +
+ Car Year + setYear(e.target.value)} + max={2023} + min={2015} + placeholder="Enter Year" + /> +
+ +
+ +
-
- ) -} -export default PostReview + ); +}; + +export default PostReview; diff --git a/server/frontend/src/components/Register/Register.jsx b/server/frontend/src/components/Register/Register.jsx new file mode 100644 index 0000000000..9f1a290327 --- /dev/null +++ b/server/frontend/src/components/Register/Register.jsx @@ -0,0 +1,98 @@ +import React, { useState } from "react"; +import "./Register.css"; +import user_icon from "../assets/person.png" +import email_icon from "../assets/email.png" +import password_icon from "../assets/password.png" +import close_icon from "../assets/close.png" + +const Register = () => { + + const [userName, setUserName] = useState(""); + const [password, setPassword] = useState(""); + const [email, setEmail] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setlastName] = useState(""); + + + const gohome = ()=> { + window.location.href = window.location.origin; + } + + const register = async (e) => { + e.preventDefault(); + + let register_url = window.location.origin+"/djangoapp/register"; + + const res = await fetch(register_url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + "userName": userName, + "password": password, + "firstName":firstName, + "lastName":lastName, + "email":email + }), + }); + + const json = await res.json(); + if (json.status) { + sessionStorage.setItem('username', json.userName); + window.location.href = window.location.origin; + } + else if (json.error === "Already Registered") { + alert("The user with same username is already registered"); + window.location.href = window.location.origin; + } +}; + + return( +
+ + +
+
+
+ Username + setUserName(e.target.value)}/> +
+
+ First Name + setFirstName(e.target.value)}/> +
+ +
+ Last Name + setlastName(e.target.value)}/> +
+ +
+ Email + setEmail(e.target.value)}/> +
+ +
+ password + setPassword(e.target.value)}/> +
+ +
+
+ +
+
+
+ ) +} + +export default Register; \ No newline at end of file diff --git a/server/frontend/static/About.html b/server/frontend/static/About.html index 484efd960f..33998a595d 100644 --- a/server/frontend/static/About.html +++ b/server/frontend/static/About.html @@ -1,6 +1,9 @@ + + +