diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..ce669ac383 --- /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" \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6e47617de..8dc1c91721 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,4 @@ dmypy.json # Pyre type checker .pyre/ +server/djangoproj/settings.py diff --git a/README.md b/README.md index 5884e26a5b..056257a155 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# coding-project-template \ No newline at end of file +# IBM fullstack developer homework + +## Folder screanshots + +website screanshots + +## Folder server + +source code diff --git a/screenshots/CICD.png b/screenshots/CICD.png new file mode 100644 index 0000000000..4b177141cf Binary files /dev/null and b/screenshots/CICD.png differ diff --git a/screenshots/about_us.png b/screenshots/about_us.png new file mode 100644 index 0000000000..9be5aa09bc Binary files /dev/null and b/screenshots/about_us.png differ diff --git a/screenshots/added_review.png b/screenshots/added_review.png new file mode 100644 index 0000000000..5e7544e76d Binary files /dev/null and b/screenshots/added_review.png differ diff --git a/screenshots/admin_login.png b/screenshots/admin_login.png new file mode 100644 index 0000000000..7c07b64590 Binary files /dev/null and b/screenshots/admin_login.png differ diff --git a/screenshots/admin_logout.png b/screenshots/admin_logout.png new file mode 100644 index 0000000000..bf860ca1bc Binary files /dev/null and b/screenshots/admin_logout.png differ diff --git a/screenshots/car models.png b/screenshots/car models.png new file mode 100644 index 0000000000..22e99a36aa Binary files /dev/null and b/screenshots/car models.png differ diff --git a/screenshots/cars.png b/screenshots/cars.png new file mode 100644 index 0000000000..8b32b197ed Binary files /dev/null and b/screenshots/cars.png differ diff --git a/screenshots/contact_us.png b/screenshots/contact_us.png new file mode 100644 index 0000000000..6bab984899 Binary files /dev/null and b/screenshots/contact_us.png differ diff --git a/screenshots/dealer_details.png b/screenshots/dealer_details.png new file mode 100644 index 0000000000..61f4ad0157 Binary files /dev/null and b/screenshots/dealer_details.png differ diff --git a/screenshots/dealer_id_reviews.png b/screenshots/dealer_id_reviews.png new file mode 100644 index 0000000000..c8ec5b1377 Binary files /dev/null and b/screenshots/dealer_id_reviews.png differ diff --git a/screenshots/dealer_review.png b/screenshots/dealer_review.png new file mode 100644 index 0000000000..f457212cbc Binary files /dev/null and b/screenshots/dealer_review.png differ diff --git a/screenshots/dealersbystate.png b/screenshots/dealersbystate.png new file mode 100644 index 0000000000..3ec8548e4d Binary files /dev/null and b/screenshots/dealersbystate.png differ diff --git a/screenshots/dealership_review_submission.png b/screenshots/dealership_review_submission.png new file mode 100644 index 0000000000..a637b4e3ee Binary files /dev/null and b/screenshots/dealership_review_submission.png differ diff --git a/screenshots/dealerships.png b/screenshots/dealerships.png new file mode 100644 index 0000000000..e55d8f1262 Binary files /dev/null and b/screenshots/dealerships.png differ diff --git a/screenshots/deployed_add_review.png b/screenshots/deployed_add_review.png new file mode 100644 index 0000000000..ed3557f59d Binary files /dev/null and b/screenshots/deployed_add_review.png differ diff --git a/screenshots/deployed_dealer_detail.png b/screenshots/deployed_dealer_detail.png new file mode 100644 index 0000000000..4f1bcaf547 Binary files /dev/null and b/screenshots/deployed_dealer_detail.png differ diff --git a/screenshots/deployed_landingpage.png b/screenshots/deployed_landingpage.png new file mode 100644 index 0000000000..91ca04098c Binary files /dev/null and b/screenshots/deployed_landingpage.png differ diff --git a/screenshots/deployed_loggedin.png b/screenshots/deployed_loggedin.png new file mode 100644 index 0000000000..9a9e59e099 Binary files /dev/null and b/screenshots/deployed_loggedin.png differ diff --git a/screenshots/django_server.png b/screenshots/django_server.png new file mode 100644 index 0000000000..0e995c8980 Binary files /dev/null and b/screenshots/django_server.png differ diff --git a/screenshots/docker.png b/screenshots/docker.png new file mode 100644 index 0000000000..70683e7c67 Binary files /dev/null and b/screenshots/docker.png differ diff --git a/screenshots/get_dealers.png b/screenshots/get_dealers.png new file mode 100644 index 0000000000..ac782f1924 Binary files /dev/null and b/screenshots/get_dealers.png differ diff --git a/screenshots/get_dealers_loggedin.png b/screenshots/get_dealers_loggedin.png new file mode 100644 index 0000000000..7163fad69f Binary files /dev/null and b/screenshots/get_dealers_loggedin.png differ diff --git a/screenshots/githab-URL.png b/screenshots/githab-URL.png new file mode 100644 index 0000000000..30b49d9d7c Binary files /dev/null and b/screenshots/githab-URL.png differ diff --git a/screenshots/kansasDealers.png b/screenshots/kansasDealers.png new file mode 100644 index 0000000000..956529f816 Binary files /dev/null and b/screenshots/kansasDealers.png differ diff --git a/screenshots/login1.png b/screenshots/login1.png new file mode 100644 index 0000000000..afe73807ef Binary files /dev/null and b/screenshots/login1.png differ diff --git a/screenshots/login2.png b/screenshots/login2.png new file mode 100644 index 0000000000..7ff97ffd24 Binary files /dev/null and b/screenshots/login2.png differ diff --git a/screenshots/logout.png b/screenshots/logout.png new file mode 100644 index 0000000000..3e547ef95c Binary files /dev/null and b/screenshots/logout.png differ diff --git a/screenshots/sentiment_analyzer.png b/screenshots/sentiment_analyzer.png new file mode 100644 index 0000000000..79a6702b2e Binary files /dev/null and b/screenshots/sentiment_analyzer.png differ diff --git a/screenshots/sign-up.png b/screenshots/sign-up.png new file mode 100644 index 0000000000..ce28260ea4 Binary files /dev/null and b/screenshots/sign-up.png differ 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..6bf5cd0912 100644 --- a/server/database/app.js +++ b/server/database/app.js @@ -1,11 +1,13 @@ +/* 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 +22,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 +35,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 @@ -59,34 +61,52 @@ 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 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..ba2e5d5208 100644 --- a/server/database/dealership.js +++ b/server/database/dealership.js @@ -1,3 +1,5 @@ +/* jshint esversion: 6 */ + const mongoose = require('mongoose'); const Schema = mongoose.Schema; diff --git a/server/database/inventory.js b/server/database/inventory.js index 2c22fd092c..386a7451ac 100644 --- a/server/database/inventory.js +++ b/server/database/inventory.js @@ -1,3 +1,5 @@ +/* jshint esversion: 6 */ + const { Int32 } = require('mongodb'); const mongoose = require('mongoose'); diff --git a/server/database/review.js b/server/database/review.js index 4759725a3a..c01b516e36 100644 --- a/server/database/review.js +++ b/server/database/review.js @@ -1,3 +1,5 @@ +/* jshint esversion: 6 */ + const mongoose = require('mongoose'); const Schema = mongoose.Schema; diff --git a/server/deployment.yaml b/server/deployment.yaml new file mode 100644 index 0000000000..5ffe19e96e --- /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-rosaspace/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..5fb68320a5 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://rosaspace-3030.theiadockernext-1-labs-prod-theiak8s-4-tor01.proxy.cognitiveclass.ai +sentiment_analyzer_url=https://sentianalyzer.1kfb1z37z4ju.us-south.codeengine.appdomain.cloud/ diff --git a/server/djangoapp/admin.py b/server/djangoapp/admin.py index 433657fc64..8669b35334 100644 --- a/server/djangoapp/admin.py +++ b/server/djangoapp/admin.py @@ -1,6 +1,7 @@ -# from django.contrib import admin -# from .models import related models +from django.contrib import admin +# from .models import related models +from .models import CarMake, CarModel # Register your models here. @@ -11,3 +12,7 @@ # CarMakeAdmin class with CarModelInline # Register models here + +# Registering models with their respective admins +admin.site.register(CarMake) +admin.site.register(CarModel) diff --git a/server/djangoapp/apps.py b/server/djangoapp/apps.py index 3800cc769c..ff2365a747 100644 --- a/server/djangoapp/apps.py +++ b/server/djangoapp/apps.py @@ -2,4 +2,4 @@ class DjangoappConfig(AppConfig): - name = 'djangoapp' + name = "djangoapp" diff --git a/server/djangoapp/models.py b/server/djangoapp/models.py index eb101a68c8..ceabea5e73 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -1,8 +1,8 @@ # Uncomment the following imports before adding the Model code -# from django.db import models +from django.db import models # from django.utils.timezone import now -# from django.core.validators import MaxValueValidator, MinValueValidator +from django.core.validators import MaxValueValidator, MinValueValidator # Create your models here. @@ -23,3 +23,34 @@ # - 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 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 + + +class CarModel(models.Model): + # Many-to-One relationship + 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..01d64fcb8c 100644 --- a/server/djangoapp/populate.py +++ b/server/djangoapp/populate.py @@ -1,2 +1,109 @@ +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], + }, + # Add more CarModel instances as needed + ] + + 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..e67d8f35b0 100644 --- a/server/djangoapp/restapis.py +++ b/server/djangoapp/restapis.py @@ -1,22 +1,66 @@ # Uncomment the imports below before you add the function code -# import requests +import requests import os from dotenv import load_dotenv 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 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 + # this one ignores *all* errors on the line + response = requests.get(request_url) # noqa: F821 + return response.json() + except Exception: + # If any error occurs + print("Network exception occurred") + finally: + print("get_request call complete!") + # def analyze_review_sentiments(text): # request_url = sentiment_analyzer_url+"analyze/"+text # Add code for retrieving sentiments +def analyze_review_sentiments(text): + request_url = sentiment_analyzer_url + "analyze/" + text + try: + # Call get method of requests library with URL and parameters + # this one ignores *all* errors on the line + response = requests.get(request_url) # noqa: F821 + return response.json() + except Exception as err: + print(f"Unexpected {err=}, {type(err)=}") + print("Network exception occurred") + finally: + print("analyze_review_sentiments call complete!") + # def post_review(data_dict): # Add code for posting review +def post_review(data_dict): + request_url = backend_url + "/insert_review" + try: + # this one ignores *all* errors on the line + response = requests.post(request_url, json=data_dict) # noqa: F821 + print(response.json()) + return response.json() + except Exception: + print("Network exception occurred") + finally: + print("post_review call complete!") diff --git a/server/djangoapp/urls.py b/server/djangoapp/urls.py index 0edc274f90..9ca6066725 100644 --- a/server/djangoapp/urls.py +++ b/server/djangoapp/urls.py @@ -1,18 +1,34 @@ # 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' +app_name = "djangoapp" urlpatterns = [ # # 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(route="logout", view=views.logout_request, name="logout"), + path(route="get_cars", view=views.get_cars, name="getcars"), # path for dealer reviews view - + 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", + ), + 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"), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/server/djangoapp/views.py b/server/djangoapp/views.py index b16409f419..9b08998856 100644 --- a/server/djangoapp/views.py +++ b/server/djangoapp/views.py @@ -2,9 +2,9 @@ # from django.shortcuts import render # from django.http import HttpResponseRedirect, HttpResponse -# from django.contrib.auth.models import User +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.auth import logout # from django.contrib import messages # from datetime import datetime @@ -13,7 +13,9 @@ 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, post_review # Get an instance of a logger @@ -22,13 +24,14 @@ # Create your views here. + # 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'] + username = data["userName"] + password = data["password"] # Try to check if provide credential can be authenticated user = authenticate(username=username, password=password) data = {"userName": username} @@ -38,28 +41,121 @@ 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): + # context = {} + + 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 + # email_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)) + finally: + print("registration request successful") + + # 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) + # # Update the `get_dealerships` view to render the index page with # a list of dealerships # def get_dealerships(request): -# ... +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}) + # 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: + post_review(data) + return JsonResponse({"status": 200}) + except Exception: + return JsonResponse({"status": 401, + "message": "Error in posting review"}) + finally: + print("add_review request successful!") + else: + return JsonResponse({"status": 403, "message": "Unauthorized"}) + + +# def get_cars(request): +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/asgi.py b/server/djangoproj/asgi.py index c9336c553c..91b3a676de 100644 --- a/server/djangoproj/asgi.py +++ b/server/djangoproj/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoproj.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoproj.settings") application = get_asgi_application() diff --git a/server/djangoproj/settings.py b/server/djangoproj/settings.py index e0b1092a5c..040394393b 100644 --- a/server/djangoproj/settings.py +++ b/server/djangoproj/settings.py @@ -22,87 +22,98 @@ # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY =\ - 'django-insecure-ccow$tz_=9%dxu4(0%^(z%nx32#s@(zt9$ih@)5l54yny)wm-0' +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','https://127.0.0.1:8000'] +# CSRF_TRUSTED_ORIGINS = ['https://127.0.0.1:8000'] +# ALLOWED_HOSTS = ['localhost', +# 'https://rosaspace-8000.theiadockernext-1-labs-' + +# 'prod-theiak8s-4-tor01.proxy.cognitiveclass.ai/'] +# CSRF_TRUSTED_ORIGINS = ['https://rosaspace-8000.theiadockernext-1-labs-' + +# 'prod-theiak8s-4-tor01.proxy.cognitiveclass.ai/'] REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': [], + "DEFAULT_AUTHENTICATION_CLASSES": [], } # Application definition INSTALLED_APPS = [ - 'djangoapp.apps.DjangoappConfig', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + "djangoapp.apps.DjangoappConfig", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'djangoproj.urls' +ROOT_URLCONF = "djangoproj.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', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "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": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'djangoproj.wsgi.application' +WSGI_APPLICATION = "djangoproj.wsgi.application" # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } 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", }, ] @@ -110,9 +121,9 @@ # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -124,15 +135,18 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'static') -MEDIA_ROOT = os.path.join(STATIC_ROOT, 'media') -MEDIA_URL = '/media/' +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 -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - -STATICFILES_DIRS = [] +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +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..2a8c95ac8b 100644 --- a/server/djangoproj/urls.py +++ b/server/djangoproj/urls.py @@ -13,14 +13,27 @@ 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, include -from django.views.generic import TemplateView -from django.conf.urls.static import static -from django.conf import settings + +from django.contrib import admin # type: ignore +from django.urls import path, include # type: ignore +from django.views.generic import TemplateView # type: ignore +from django.conf.urls.static import static # type: ignore +from django.conf import settings # type: ignore urlpatterns = [ - path('admin/', admin.site.urls), - path('djangoapp/', include('djangoapp.urls')), - path('', TemplateView.as_view(template_name="Home.html")), + 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/djangoproj/wsgi.py b/server/djangoproj/wsgi.py index 171ab807e3..b0c3e2a268 100644 --- a/server/djangoproj/wsgi.py +++ b/server/djangoproj/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoproj.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoproj.settings") application = get_wsgi_application() 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.js b/server/frontend/src/App.js index aceac6974d..45e6482ebf 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/Dealers.jsx b/server/frontend/src/components/Dealers/Dealers.jsx index db3410680c..7845ded024 100644 --- a/server/frontend/src/components/Dealers/Dealers.jsx +++ b/server/frontend/src/components/Dealers/Dealers.jsx @@ -88,7 +88,7 @@ return( } ))} - ; + ) } 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..d2f9363850 100644 --- a/server/frontend/static/About.html +++ b/server/frontend/static/About.html @@ -1,12 +1,17 @@ + + + +
- -
- - +
- + \ No newline at end of file diff --git a/server/frontend/static/Contact.html b/server/frontend/static/Contact.html new file mode 100644 index 0000000000..48f86e1432 --- /dev/null +++ b/server/frontend/static/Contact.html @@ -0,0 +1,66 @@ + + + + + + + + +
+ + +
+
+ + Card image + +
+
+
+ Card image +
+ +
+
+

Contact Customer Service

+

support@example.com

+ +

Contact Our Public Relations team

+

PR@example.com

+ +

Contact the bestcars.com offices

+

312-611-1111

+ +

Become a bestcars.com dealer

+

Visit growwithbescars.com

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/server/frontend/static/Home.html b/server/frontend/static/Home.html index fb0c3fb617..61ca895b4d 100644 --- a/server/frontend/static/Home.html +++ b/server/frontend/static/Home.html @@ -8,6 +8,22 @@ const logout = async (e) => { //Include the code for logout here. +let logout_url = window.location.origin+"/djangoapp/logout"; + const res = await fetch(logout_url, { + method: "GET", + }); + + const json = await res.json(); + if (json) { + let username = sessionStorage.getItem('username'); + sessionStorage.removeItem('username'); + window.location.href = window.location.origin; + window.location.reload(); + alert("Logging out "+username+"...") + } + else { + alert("The user could not be logged out.") + } }; let checkSession = ()=>{ @@ -25,11 +41,15 @@ } + + -
+ + +
...
+ diff --git a/server/frontend/static/person.png b/server/frontend/static/person.png index 72ce98a965..77b03344e0 100644 Binary files a/server/frontend/static/person.png and b/server/frontend/static/person.png differ diff --git a/server/frontend/static/person1.png b/server/frontend/static/person1.png new file mode 100644 index 0000000000..72ce98a965 Binary files /dev/null and b/server/frontend/static/person1.png differ diff --git a/server/frontend/static/person2.png b/server/frontend/static/person2.png new file mode 100644 index 0000000000..a2cc2c8fee Binary files /dev/null and b/server/frontend/static/person2.png differ diff --git a/server/frontend/static/person3.png b/server/frontend/static/person3.png new file mode 100644 index 0000000000..e4ed85e0b2 Binary files /dev/null and b/server/frontend/static/person3.png differ diff --git a/server/manage.py b/server/manage.py index af7e44b832..4d9551ce86 100755 --- a/server/manage.py +++ b/server/manage.py @@ -6,7 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoproj.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoproj.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main()