diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..1d5c207add --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,59 @@ +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/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000..13566b81b0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/ibm-fullstack-developer-capstone.iml b/.idea/ibm-fullstack-developer-capstone.iml new file mode 100644 index 0000000000..f550522af9 --- /dev/null +++ b/.idea/ibm-fullstack-developer-capstone.iml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000..c0aedc043d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,28 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..105ce2da2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000000..d23208fbb7 --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..b3e366d224 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..eebee4e0d1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..35eb1ddfbb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/server/.jshintrc b/server/.jshintrc new file mode 100644 index 0000000000..dcf20f7b76 --- /dev/null +++ b/server/.jshintrc @@ -0,0 +1,13 @@ +{ + "esversion": 8, + "node": true, + "strict": false, + "undef": true, + "unused": true, + "globals": { + "describe": false, + "it": false, + "before": false, + "after": false + } +} diff --git a/server/database/app.js b/server/database/app.js index 00f52b2008..aab10d91c2 100644 --- a/server/database/app.js +++ b/server/database/app.js @@ -1,39 +1,45 @@ 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')); +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'}); +initializeDatabase(); 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); + console.log("Database initialized"); + } catch (error) { + console.error("Error initializing database:", error); + } } // 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 @@ -48,45 +54,69 @@ 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}); - res.json(documents); - } catch (error) { - res.status(500).json({ error: 'Error fetching documents' }); - } + try { + const dealerId = Number(req.params.id); + if (isNaN(dealerId)) { + return res.status(400).json({error: 'Ungültige Dealer-ID'}); + } + const documents = await Reviews.find({dealership: dealerId}); + if (documents.length === 0) { + return res.status(404).json( + {error: 'Keine Reviews für diesen Dealer gefunden'}); + } + res.json(documents); + } catch (error) { + res.status(500).json({error: 'Fehler beim Abrufen der Reviews'}); + } }); // 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 dealerId = Number(req.params.id); + const documents = await Dealerships.find({id: dealerId}); + 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 data = JSON.parse(req.body); + const documents = await Reviews.find().sort( { id: -1 } ); + let new_id = documents.length > 0 ? documents[0].id + 1 : 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/inventory.js b/server/database/inventory.js index 2c22fd092c..8f0f81631e 100644 --- a/server/database/inventory.js +++ b/server/database/inventory.js @@ -1,4 +1,3 @@ -const { Int32 } = require('mongodb'); const mongoose = require('mongoose'); const Schema = mongoose.Schema; diff --git a/server/djangoapp/.env b/server/djangoapp/.env index 01822e542a..bbdb0e2f99 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 = http://localhost:3030 +sentiment_analyzer_url = http://127.0.0.1:5000 diff --git a/server/djangoapp/admin.py b/server/djangoapp/admin.py index 433657fc64..8d58498ac4 100644 --- a/server/djangoapp/admin.py +++ b/server/djangoapp/admin.py @@ -1,5 +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 +11,5 @@ # CarMakeAdmin class with CarModelInline # Register models here +admin.site.register(CarMake) +admin.site.register(CarModel) diff --git a/server/djangoapp/models.py b/server/djangoapp/models.py index eb101a68c8..c819f9b3e1 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -1,25 +1,105 @@ # 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. -# 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 - - -# 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 +# CarMake: +class CarMake(models.Model): + + name = models.CharField( + max_length=100 + ) + + description = models.TextField( + blank=True, + null=True + ) + + country = models.CharField( + max_length=100, + blank=True + ) + + founded_year = models.PositiveIntegerField( + null=True, + blank=True + ) + + website = models.URLField( + blank=True + ) + + def __str__(self): + return self.name + + +# CarModel: +class CarModel(models.Model): + + car_make = models.ForeignKey( + CarMake, + on_delete=models.CASCADE + ) + + name = models.CharField( + max_length=100 + ) + + CAR_TYPES = [ + ('SEDAN', 'Sedan'), + ('WAGON', 'Wagon'), + ('COUPE', 'Coupe'), + ('CONVERTIBLE', 'Convertible'), + ('SUV', 'SUV'), + ('VAN', 'Van'), + ('PICKUP', 'Pickup'), + ('HATCHBACK', 'Hatchback'), + ] + + type = models.CharField( + max_length=20, + choices=CAR_TYPES, + default='SUV' + ) + + year = models.IntegerField( + default=2023, + validators=[MaxValueValidator(2023), MinValueValidator(2015)] + ) + + ENGINE_TYPES = [ + ('PETROL', 'Benzin'), + ('DIESEL', 'Diesel'), + ('ELECTRIC', 'Elektro'), + ('HYBRID', 'Hybrid'), + ] + + engine_type = models.CharField( + max_length=10, + choices=ENGINE_TYPES, + default='PETROL' + ) + + transmission = models.CharField( + max_length=10, + choices=[('AUTO', 'Automatik'), ('MANUAL', 'Manuell')], + default='AUTO' + ) + + color = models.CharField( + max_length=30, + blank=True + ) + + price = models.DecimalField( + max_digits=10, + decimal_places=2, + null=True, + blank=True + ) + + def __str__(self): + return self.name diff --git a/server/djangoapp/populate.py b/server/djangoapp/populate.py index 1927e09e18..562cd6abbd 100644 --- a/server/djangoapp/populate.py +++ b/server/djangoapp/populate.py @@ -1,2 +1,59 @@ +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..d24155f19c 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,56 @@ '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 +# GET REQUEST +# ############################################################################# -# def post_review(data_dict): -# Add code for posting review +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 requests.RequestException: + # If any error occurs + print("Network exception occurred") + return None + + +# Sentiment Analyzer: +# ############################################################################# + +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") + return None + + +# Post 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 requests.RequestException: + print("Network exception occurred") + return None diff --git a/server/djangoapp/urls.py b/server/djangoapp/urls.py index 0edc274f90..257d88fcab 100644 --- a/server/djangoapp/urls.py +++ b/server/djangoapp/urls.py @@ -1,18 +1,72 @@ # 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 + # Registration: + path( + route='register', + view=views.registration, + name='register' + ), + + # Login + path( + route='login', + view=views.login_user, + name='login' + ), + + # Logout + path( + route='logout', + view=views.logout_user, + name='logout' + ), + + # GET CARS: + path( + route='get_cars', + view=views.get_cars, + name='getcars' + ), + + # Dealerships: + path( + route='get_dealers', + view=views.get_dealerships, + name='get_dealers' + ), - # path for login - # path(route='login', view=views.login_user, name='login'), + # Dealershops by state: + path( + route='get_dealers/', + view=views.get_dealerships, + name='get_dealers_by_state' + ), - # path for dealer reviews view + # Dealer Details: + path( + route='dealer/', + view=views.get_dealer_details, + name='dealer_details' + ), + # Dealer Reviews: + path( + route='reviews/dealer/', + view=views.get_dealer_reviews, + name='dealer_details' + ), - # path for add a review view + # Add Review: + 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..392b02edbd 100644 --- a/server/djangoapp/views.py +++ b/server/djangoapp/views.py @@ -1,20 +1,17 @@ # 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 +# MODEL IMPORTS: +from .models import CarMake, CarModel +from .restapis import get_request, analyze_review_sentiments # Get an instance of a logger logger = logging.getLogger(__name__) @@ -22,44 +19,171 @@ # Create your views here. -# Create a `login_request` view to handle sign in request +# LOGIN VIEW @csrf_exempt def login_user(request): - # Get username and password from request.POST dictionary + if request.method == "POST": + try: + data = json.loads(request.body) + username = data.get("userName") + password = data.get("password") + user = authenticate(username=username, password=password) + if user: + login(request, user) + return JsonResponse( + {"userName": username, "status": "Authenticated"} + ) + else: + return JsonResponse( + {"error": "Invalid credentials"}, + status=401 + ) + except json.JSONDecodeError: + return JsonResponse( + {"error": "Invalid JSON"}, + status=400 + ) + except KeyError: + return JsonResponse( + {"error": "Missing username or password"}, + status=400 + ) + else: + return JsonResponse( + {"error": "POST request required"}, + status=405 + ) + + +# Logout: +# ############################################################################# + +@csrf_exempt +def logout_user(request): + if request.method in ["POST", "GET"]: + logout(request) # Django killt die Session + return JsonResponse({"userName": ""}) + else: + return JsonResponse({"error": "POST request required"}, status=405) + + +# Registration: +# ############################################################################# + +@csrf_exempt +def registration(request): + + # Load JSON data from the request body data = json.loads(request.body) username = data['userName'] password = data['password'] - # Try to check if provide credential can be authenticated - 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 + first_name = data['firstName'] + last_name = data['lastName'] + email = data['email'] + username_exist = False + try: + # Check if the user already exists + User.objects.get(username=username) + username_exist = True + except User.DoesNotExist: + # 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) + return JsonResponse(data) + else: + data = {"userName": username, "error": "Already Registered"} + return JsonResponse(data) + + +# Dealerships: +# ############################################################################# + +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}) + + +# Dealer Details: +# ############################################################################# + +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"}) + + +# Dealer Reviews: +# ############################################################################# + +def get_dealer_reviews(request, dealer_id): + 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) + if response and 'sentiment' in response: + review_detail['sentiment'] = response['sentiment'] + else: + review_detail['sentiment'] = 'unknown' + return JsonResponse({"status": 200, "reviews": reviews}) + else: + return JsonResponse({"status": 400, "message": "Bad Request"}) + + +# Add Review: +# ############################################################################# + +def add_review(request): + + if not request.user.is_anonymous: + try: + return JsonResponse({"status": 200}) + except Exception as e: + logger.error(f"Error posting review: {e}") + return JsonResponse( + {"status": 401, "message": "Error in posting review"}) + + else: + return JsonResponse({"status": 403, "message": "Unauthorized"}) + + +# GET CARS: +# ############################################################################# +def get_cars(request): -# Create a `logout_request` view to handle sign out request -# def logout_request(request): -# ... + count = CarMake.objects.filter().count() -# Create a `registration` view to handle sign up request -# @csrf_exempt -# def registration(request): -# ... + print(count) -# # Update the `get_dealerships` view to render the index page with -# a list of dealerships -# def get_dealerships(request): -# ... + if (count == 0): + initiate() -# Create a `get_dealer_reviews` view to render the reviews of a dealer -# def get_dealer_reviews(request,dealer_id): -# ... + car_models = CarModel.objects.select_related('car_make') -# Create a `get_dealer_details` view to render the dealer details -# def get_dealer_details(request, dealer_id): -# ... + cars = [] -# Create a `add_review` view to submit a review -# def add_review(request): -# ... + 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..0d7b81f52d 100644 --- a/server/djangoproj/settings.py +++ b/server/djangoproj/settings.py @@ -1,20 +1,19 @@ """ -Django settings for djangoproj project. +Konfiguration für das Django-Projekt 'djangoproj'. -Generated by 'django-admin startproject' using Django 3.2.5. +Dieses Projekt ist Teil des IBM Full Stack Developer Capstone. +Diese Datei enthält globale Einstellungen wie Datenbankpfade, +installierte Apps, Middleware und statische Dateien. -For more information on this file, see -https://docs.djangoproject.com/en/3.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.2/ref/settings/ +Erstellt mit Django 3.2.5 +Weitere Infos: https://docs.djangoproject.com/en/3.2/topics/settings/ """ import os from pathlib import Path -# Build paths inside the project like this: BASE_DIR / 'subdir'. +# Pfad zum Basisverzeichnis des Projekts BASE_DIR = Path(__file__).resolve().parent.parent @@ -28,8 +27,15 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] -CSRF_TRUSTED_ORIGINS = [] +ALLOWED_HOSTS = [ + 'localhost', + '127.0.0.1', +] + +CSRF_TRUSTED_ORIGINS = [ + "http://127.0.0.1:8000", + "http://localhost:8000", +] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [], @@ -61,7 +67,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 +99,11 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': - 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + 'NAME': ( + 'django.contrib.auth.password_validation.' + 'UserAttributeSimilarityValidator' + ), + }, { 'NAME': @@ -134,5 +147,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..7c4bf37dc8 100644 --- a/server/djangoproj/urls.py +++ b/server/djangoproj/urls.py @@ -20,7 +20,40 @@ from django.conf import settings urlpatterns = [ + + # ADMIN path('admin/', admin.site.urls), + + # DJANGOAPP: path('djangoapp/', include('djangoapp.urls')), + # HOME PAGE: path('', TemplateView.as_view(template_name="Home.html")), + + # ABOUT PAGE: + path('about/', TemplateView.as_view(template_name="About.html")), + + # CONTACT PAGE: + path('contact/', TemplateView.as_view(template_name="Contact.html")), + + # LOGIN PAGE: + path('login/', TemplateView.as_view(template_name="index.html")), + + # REGISTRATION PAGE: + path('register/', TemplateView.as_view(template_name="index.html")), + + # Dealers: + path('dealers/', TemplateView.as_view(template_name="index.html")), + + # Dealer by ID: + path( + 'dealer/', + TemplateView.as_view(template_name="index.html") + ), + + # Post Review: + 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..bdb21fad35 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,18 @@ } }, "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, + "license": "MIT", + "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 +1903,18 @@ "@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==", + "license": "MIT", + "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..269f6dee5f 100644 --- a/server/frontend/src/App.js +++ b/server/frontend/src/App.js @@ -1,10 +1,18 @@ import LoginPanel from "./components/Login/Login" +import Register 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/Register/Register.jsx b/server/frontend/src/components/Register/Register.jsx new file mode 100644 index 0000000000..b2438b5329 --- /dev/null +++ b/server/frontend/src/components/Register/Register.jsx @@ -0,0 +1,148 @@ +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 = () => { + // State variables for form inputs + const [userName, setUserName] = useState(""); + const [password, setPassword] = useState(""); + const [email, setEmail] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setlastName] = useState(""); + + // Redirect to home + const gohome = () => { + window.location.href = window.location.origin; + } + + // Handle form submission + const register = async (e) => { + e.preventDefault(); + + let register_url = window.location.origin + "/djangoapp/register"; + + // Send POST request to register endpoint + 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) { + // Save username in session and reload home + 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; diff --git a/server/frontend/static/About.html b/server/frontend/static/About.html index 484efd960f..befe29366c 100644 --- a/server/frontend/static/About.html +++ b/server/frontend/static/About.html @@ -1,68 +1,95 @@ - + - + + + + + + Best Cars - About Us -
- + + -
- -
-
- Card image -
-

Person1

-

Person1 Title

-

Some text that explains the person1 in about 2 short sentences

-

person1@example.com

-
-
+
+ + + +
+ +
+ Card image +
+

Jake Thompson

+

Senior Car Sales Consultant

+

+ “I’ve been helping folks in Ohio find their perfect car for over 15 + years. + To me, it’s not about pushing a sale — it’s about making sure + you drive off with something that truly fits your life.” +

+

jthompson@bestcars.com

+
+
-
- Card image -
-

Person2

-

Person2 Title

-

Some text that explains the person2 in about 2 short sentences

-

person2@example.com

-
-
+
+ Card image +
+

Emily Carter

+

Family Car Advisor

+

+ “I specialize in helping families find safe, reliable, and spacious + vehicles. + I always listen closely to your needs — because when your + car fits your lifestyle, every trip becomes easier.” +

+

ecarter@bestcars.com

+
+
-
- Card image -
-

Person3

-

Person3 Title

-

Some text that explains the person3 in about 2 short sentences

-

person3@example.com

-
-
-
-
+
+ Card image +
+

Marcus Rivera

+

Vehicle Specialist

+

+ “I love breaking down the details of every model so my customers + feel confident and excited about their choice. + Whether it’s your + first car or your fifth, I’m here to make it simple and + stress-free.” +

+

mrivera@bestcars.com

+
+
- -
+
+ diff --git a/server/frontend/static/Contact.html b/server/frontend/static/Contact.html new file mode 100644 index 0000000000..71d5074817 --- /dev/null +++ b/server/frontend/static/Contact.html @@ -0,0 +1,87 @@ + + + + + Best Cars - Contact + + + + + + +
+ +
+ +
+ + Card image + +

+ Contact Us +

+ +
+

Contact Customer Service

+

support@bestcars.com

+
+ +
+

Contact our National Advertising Team

+

+ nationalsales@bestcars.com +

+
+ +
+

Contact our Local Advertising Team

+

pr@bestcars.com

+
+ +
+

Telephone

+

312-611-1111

+
+ + +
+ + + diff --git a/server/frontend/static/Home.html b/server/frontend/static/Home.html index fb0c3fb617..2233914c8f 100644 --- a/server/frontend/static/Home.html +++ b/server/frontend/static/Home.html @@ -7,16 +7,32 @@