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/README.md b/README.md index 5884e26a5b..bdbee7954d 100644 --- a/README.md +++ b/README.md @@ -1 +1,13 @@ -# coding-project-template \ No newline at end of file +# Car Dealership Website - Capstone Project +## Overview +A website for Best Cars Dealership that provides a central database of dealership reviews across branches within the country. Customers can create a personal account and view and create reviews. +## Features +- Branch Lookup: Search and find dealership branches across the country +- Customer Reviews: View customer reviews for different branches +- Write a Review: Authenticated users can create and share their own reviews +- User Profiles: Create and manage user profiles to track submitted reviews +## Installation +## Technologies Used +- Django +- Node.js +- React diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000000..fb5c4e382a --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.12.0-slim-bookworm + +ENV PYTHONBUFFERED 1 +ENV PYTHONWRITEBYTECODE 1 + +ENV APP=/app + +# Change the workdir. +WORKDIR $APP + +# Install the requirements +COPY requirements.txt $APP + +RUN pip3 install -r requirements.txt + +# Copy the rest of the files +COPY . $APP + +EXPOSE 8000 + +RUN chmod +x /app/entrypoint.sh + +ENTRYPOINT ["/bin/bash","/app/entrypoint.sh"] + +CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "djangoproj.wsgi"] \ No newline at end of file diff --git a/server/database/app.js b/server/database/app.js index 00f52b2008..685cac18aa 100644 --- a/server/database/app.js +++ b/server/database/app.js @@ -1,11 +1,12 @@ +/*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')); @@ -20,10 +21,10 @@ const Dealerships = require('./dealership'); try { Reviews.deleteMany({}).then(()=>{ - Reviews.insertMany(reviews_data['reviews']); + Reviews.insertMany(reviews_data.reviews); }); Dealerships.deleteMany({}).then(()=>{ - Dealerships.insertMany(dealerships_data['dealerships']); + Dealerships.insertMany(dealerships_data.dealerships); }); } catch (error) { @@ -33,7 +34,7 @@ try { // 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 @@ -56,37 +57,72 @@ app.get('/fetchReviews/dealer/:id', async (req, res) => { } }); +// Express route to fetch reviews carmake +app.get('/fetchReviews/car/:carmake', async (req, res) => { + try { + const documents = await Reviews.find({car_make: req.params.carmake}); + res.json(documents); + } catch (error) { + res.status(500).json({ error: 'Error fetching documents' }); + } + }); + +// Express route to fetch reviews :carmake/:model +app.get('/fetchReviews/car/:carmake/:model', async (req, res) => { + try { + const documents = await Reviews.find(); + res.json(documents); + } catch (error) { + res.status(500).json({ error: 'Error fetching documents' }); + } + }); + // 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 dealers = await Dealerships.find({ state: req.params.state }); + res.json(dealers); + } catch (err) { + 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 (err) { + res.status(500).json({ error: 'Error fetching documents' }); + } }); //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 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'], + "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 { diff --git a/server/database/dealership.js b/server/database/dealership.js index b10d6b4730..4c87270316 100644 --- a/server/database/dealership.js +++ b/server/database/dealership.js @@ -1,3 +1,4 @@ +/*jshint esversion: 8 */ const mongoose = require('mongoose'); const Schema = mongoose.Schema; diff --git a/server/database/inventory.js b/server/database/inventory.js index 2c22fd092c..6c04f39d22 100644 --- a/server/database/inventory.js +++ b/server/database/inventory.js @@ -1,3 +1,4 @@ +/*jshint esversion: 8 */ const { Int32 } = require('mongodb'); const mongoose = require('mongoose'); diff --git a/server/database/review.js b/server/database/review.js index 4759725a3a..217c828902 100644 --- a/server/database/review.js +++ b/server/database/review.js @@ -1,3 +1,4 @@ +/*jshint esversion: 8 */ const mongoose = require('mongoose'); const Schema = mongoose.Schema; diff --git a/server/deployment.yaml b/server/deployment.yaml new file mode 100644 index 0000000000..8a0ba8f9b5 --- /dev/null +++ b/server/deployment.yaml @@ -0,0 +1,29 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + run: dealership + name: dealership +spec: + replicas: 1 + selector: + matchLabels: + run: dealership + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + run: dealership + spec: + containers: + - image: us.icr.io/sn-labs-tanisidas21/dealership:latest + imagePullPolicy: Always + name: dealership + ports: + - containerPort: 8000 + protocol: TCP + restartPolicy: Always \ No newline at end of file diff --git a/server/djangoapp/.env b/server/djangoapp/.env index 01822e542a..853dc544bc 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://tanisidas21-3030.theiadockernext-1-labs-prod-theiak8s-4-tor01.proxy.cognitiveclass.ai +sentiment_analyzer_url=https://sentianalyzer.1k9wiokevvjw.us-south.codeengine.appdomain.cloud/ diff --git a/server/djangoapp/admin.py b/server/djangoapp/admin.py index 433657fc64..7994d4a085 100644 --- a/server/djangoapp/admin.py +++ b/server/djangoapp/admin.py @@ -1,6 +1,5 @@ -# from django.contrib import admin -# from .models import related models - +from django.contrib import admin +from .models import CarMake, CarModel # Register your models here. @@ -11,3 +10,5 @@ # CarMakeAdmin class with CarModelInline # Register models here +admin.site.register(CarMake) +admin.site.register(CarModel) diff --git a/server/djangoapp/migrations/0001_initial.py b/server/djangoapp/migrations/0001_initial.py new file mode 100644 index 0000000000..8bdf651060 --- /dev/null +++ b/server/djangoapp/migrations/0001_initial.py @@ -0,0 +1,54 @@ +# Generated by Django 5.0.7 on 2024-08-06 12:56 + +import django.core.validators +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CarMake', + fields=[ + ('id', + models.BigAutoField( + auto_created=True, + primary_key=True, serialize=False, + verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('description', models.TextField()), + ], + ), + migrations.CreateModel( + name='CarModel', + fields=[ + ('id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('type', models.CharField( + choices=[('SEDAN', + 'Sedan'), ('SUV', 'SUV'), + ('WAGON', 'Wagon')], + default='SUV', max_length=10)), + ('year', models.IntegerField( + default=2023, + validators=[ + django.core.validators.MaxValueValidator(2023), + django.core.validators.MinValueValidator(2015)])), + ('car_make', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='djangoapp.carmake')), + ], + ), + ] diff --git a/server/djangoapp/migrations/__init__.py b/server/djangoapp/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/server/djangoapp/models.py b/server/djangoapp/models.py index eb101a68c8..8388f0ae4c 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -1,9 +1,7 @@ # Uncomment the following imports before adding the Model code -# from django.db import models -# from django.utils.timezone import now -# from django.core.validators import MaxValueValidator, MinValueValidator - +from django.db import models +from django.core.validators import MaxValueValidator, MinValueValidator # Create your models here. @@ -14,6 +12,15 @@ # - __str__ method to print a car make object +class CarMake(models.Model): + name = models.CharField(max_length=100) + description = models.TextField() + # Other fields as needed + + def __str__(self): + return self.name # Return the name as the string representation + + # 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) @@ -23,3 +30,25 @@ # - 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 + +class CarModel(models.Model): + car_make = models.ForeignKey(CarMake, on_delete=models.CASCADE) + 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) + ] + ) + # Other fields as needed + + def __str__(self): + return self.name # Return the name as the string representation diff --git a/server/djangoapp/populate.py b/server/djangoapp/populate.py index 1927e09e18..c436d2c452 100644 --- a/server/djangoapp/populate.py +++ b/server/djangoapp/populate.py @@ -1,2 +1,90 @@ +from .models import CarMake, CarModel + + def initiate(): - print("Populate not implemented. Add data manually") + 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 = [] + for data in car_make_data: + car_make_instances.append(CarMake.objects.create( + name=data['name'], + description=data['description'] + )) + + # Create CarModel instances with the corresponding CarMake instances + 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..5deacbe289 100644 --- a/server/djangoapp/restapis.py +++ b/server/djangoapp/restapis.py @@ -1,5 +1,5 @@ # Uncomment the imports below before you add the function code -# import requests +import requests import os from dotenv import load_dotenv @@ -11,12 +11,49 @@ 'sentiment_analyzer_url', default="http://localhost:5050/") + # def get_request(endpoint, **kwargs): +def get_request(endpoint, **kwargs): + params = "" + if (kwargs): + for key, value in kwargs.items(): + params = params + key + "=" + value + "&" + + request_url = backend_url+endpoint+"?"+params + + print("GET from {} ".format(request_url)) + try: + # Call get method of requests library with URL and parameters + response = requests.get(request_url) + return response.json() + except Exception as e: + print(f"Error: {e}") + # Add code for get requests to back end + # def analyze_review_sentiments(text): +def analyze_review_sentiments(text): + request_url = sentiment_analyzer_url+"analyze/"+text + try: + # Call get method of requests library with URL and parameters + response = requests.get(request_url) + return response.json() + except Exception as err: + print(f"Unexpected {err=}, {type(err)=}") + print("Network exception occurred") + # request_url = sentiment_analyzer_url+"analyze/"+text # Add code for retrieving sentiments + # def post_review(data_dict): # Add code for posting review +def post_review(data_dict): + request_url = backend_url+"/insert_review" + try: + response = requests.post(request_url, json=data_dict) + print(response.json()) + return response.json() + except Exception: + print("Network error occurred") diff --git a/server/djangoapp/urls.py b/server/djangoapp/urls.py index 0edc274f90..87aa84c18c 100644 --- a/server/djangoapp/urls.py +++ b/server/djangoapp/urls.py @@ -1,18 +1,28 @@ # 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='register'), # path for login - # path(route='login', view=views.login_user, name='login'), - + path(route='login', view=views.login_user, name='login'), + # path for logout + path(route='logout', view=views.logout_request, name='logout'), # path for dealer reviews view - + path(route='reviews/dealer/', + view=views.get_dealer_reviews, name='dealer_details'), # path for add a review view - + path(route='add_review', view=views.add_review, name='add_review'), + # path for cars + path(route='get_cars', view=views.get_cars, name='getcars'), + # path 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(route='dealer/', view=views.get_dealer_details, + name='dealer_details'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/server/djangoapp/views.py b/server/djangoapp/views.py index b16409f419..c78a02146b 100644 --- a/server/djangoapp/views.py +++ b/server/djangoapp/views.py @@ -1,20 +1,15 @@ # 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 from django.http import JsonResponse from django.contrib.auth import login, authenticate import logging import json from django.views.decorators.csrf import csrf_exempt -# from .populate import initiate - +from .populate import initiate +from .models import CarMake, CarModel +from .restapis import get_request, analyze_review_sentiments # Get an instance of a logger logger = logging.getLogger(__name__) @@ -38,28 +33,107 @@ def login_user(request): data = {"userName": username, "status": "Authenticated"} return JsonResponse(data) + # Create a `logout_request` view to handle sign out request -# def logout_request(request): -# ... +def logout_request(request): + logout(request) + data = {"userName": ""} + return JsonResponse(data) + # Create a `registration` view to handle sign up request # @csrf_exempt -# def registration(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'] + username_exist = False + try: + # Check if user already exists + User.objects.get(username=username) + username_exist = True + except Exception: + # If not, simply log this is a new user + logger.debug("{} is new user".format(username)) + + # If it is a new user + if not username_exist: + # Create user in auth_user table + user = User.objects.create_user( + username=username, + first_name=first_name, + last_name=last_name, + password=password, + email=email) + # Login the user and redirect to list page + login(request, user) + data = {"userName": username, "status": "Authenticated"} + return JsonResponse(data) + else: + data = {"userName": username, "error": "Already Registered"} + return JsonResponse(data) + + +def get_dealerships(request, state="All"): + if (state == "All"): + endpoint = "/fetchDealers" + else: + endpoint = "/fetchDealers/" + state + dealerships = get_request(endpoint) + return JsonResponse({"status": 200, "dealers": dealerships}) -# # 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): -# ... +def get_dealer_reviews(request, dealer_id): + # if dealer id has been provided + if (dealer_id): + endpoint = "/fetchReviews/dealer/"+str(dealer_id) + reviews = get_request(endpoint) + for review_detail in reviews: + response = analyze_review_sentiments(review_detail['review']) + print(response) + review_detail['sentiment'] = response['sentiment'] + return JsonResponse({"status": 200, "reviews": reviews}) + else: + return JsonResponse({"status": 400, "message": "Bad Request"}) + # Create a `get_dealer_details` view to render the dealer details -# def get_dealer_details(request, dealer_id): -# ... +def get_dealer_details(request, dealer_id): + if (dealer_id): + endpoint = "/fetchDealer/"+str(dealer_id) + dealership = get_request(endpoint) + return JsonResponse({"status": 200, "dealer": dealership}) + else: + return JsonResponse({"status": 400, "message": "Bad Request"}) + # Create a `add_review` view to submit a review -# def add_review(request): -# ... +def add_review(request): + if (request.user.is_anonymous is False): + # data = json.loads(request.body) + try: + # response = post_review(data) + return JsonResponse({"status": 200}) + except Exception: + return JsonResponse( + {"status": 401, "message": "Error in posting review"}) + else: + return JsonResponse({"status": 403, "message": "Unauthorized"}) + + +def get_cars(request): + count = CarMake.objects.filter().count() + print(count) + if (count == 0): + initiate() + car_models = CarModel.objects.select_related('car_make') + cars = [] + for car_model in car_models: + cars.append( + {"CarModel": car_model.name, "CarMake": car_model.car_make.name}) + return JsonResponse({"CarModels": cars}) diff --git a/server/djangoproj/settings.py b/server/djangoproj/settings.py index e0b1092a5c..a32e74aec0 100644 --- a/server/djangoproj/settings.py +++ b/server/djangoproj/settings.py @@ -28,8 +28,13 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] -CSRF_TRUSTED_ORIGINS = [] +ALLOWED_HOSTS = [ + 'localhost', + 'https://tanisidas21-8000.theiadockernext-1-labs-prod-theiak8s-4-' + 'tor01.proxy.cognitiveclass.ai'] +CSRF_TRUSTED_ORIGINS = [ + 'https://tanisidas21-8000.theiadockernext-1-labs-prod-theiak8s-4-' + 'tor01.proxy.cognitiveclass.ai'] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [], @@ -61,7 +66,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': [ @@ -89,8 +98,10 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': - 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + 'NAME': ( + 'django.contrib.auth.password_validation.' + 'UserAttributeSimilarityValidator' + ), }, { 'NAME': @@ -134,5 +145,8 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -STATICFILES_DIRS = [] - +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, 'frontend/static'), + os.path.join(BASE_DIR, 'frontend/build'), + os.path.join(BASE_DIR, 'frontend/build/static'), +] diff --git a/server/djangoproj/urls.py b/server/djangoproj/urls.py index 6808da9141..3dd0defae4 100644 --- a/server/djangoproj/urls.py +++ b/server/djangoproj/urls.py @@ -23,4 +23,13 @@ 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/entrypoint.sh b/server/entrypoint.sh new file mode 100644 index 0000000000..b24c7e80a4 --- /dev/null +++ b/server/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Make migrations and migrate the database. +echo "Making migrations and migrating the database. " +python manage.py makemigrations --noinput +python manage.py migrate --noinput +python manage.py collectstatic --noinput +exec "$@" \ No newline at end of file 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.css b/server/frontend/src/App.css index 74b5e05345..f19f1f6020 100644 --- a/server/frontend/src/App.css +++ b/server/frontend/src/App.css @@ -25,7 +25,7 @@ } .App-link { - color: #61dafb; + color: #F59F8A; } @keyframes App-logo-spin { diff --git a/server/frontend/src/App.js b/server/frontend/src/App.js index aceac6974d..45ef8d389b 100644 --- a/server/frontend/src/App.js +++ b/server/frontend/src/App.js @@ -1,10 +1,18 @@ import LoginPanel from "./components/Login/Login" +import RegisterPanel from "./components/Register/Register" import { Routes, Route } from "react-router-dom"; +import Dealers from './components/Dealers/Dealers'; +import Dealer from "./components/Dealers/Dealer" +import PostReview from "./components/Dealers/PostReview" function App() { return ( } /> + } /> + } /> + } /> + } /> ); } diff --git a/server/frontend/src/components/Dealers/PostReview.jsx b/server/frontend/src/components/Dealers/PostReview.jsx index 5ef16de3df..ef0cf4db10 100644 --- a/server/frontend/src/components/Dealers/PostReview.jsx +++ b/server/frontend/src/components/Dealers/PostReview.jsx @@ -94,7 +94,7 @@ const PostReview = () => {
-

{dealer.full_name}

+

{dealer.full_name}

Purchase Date setDate(e.target.value)}/> diff --git a/server/frontend/src/components/Header/Header.jsx b/server/frontend/src/components/Header/Header.jsx index c46f1e3f5f..438211aa66 100644 --- a/server/frontend/src/components/Header/Header.jsx +++ b/server/frontend/src/components/Header/Header.jsx @@ -38,9 +38,9 @@ if ( curr_user !== null && curr_user !== "") { } return (
-