diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000000..6795963570 --- /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..6682559eb6 100644 --- a/server/database/app.js +++ b/server/database/app.js @@ -42,7 +42,7 @@ app.get('/fetchReviews', async (req, res) => { const documents = await Reviews.find(); res.json(documents); } catch (error) { - res.status(500).json({ error: 'Error fetching documents' }); + res.status(500).json({ error: 'Error fetching all reviews' }); } }); @@ -52,23 +52,44 @@ app.get('/fetchReviews/dealer/:id', async (req, res) => { const documents = await Reviews.find({dealership: req.params.id}); res.json(documents); } catch (error) { - res.status(500).json({ error: 'Error fetching documents' }); + res.status(500).json({ error: 'Error fetching reviews by dealership' }); } }); // 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 all dealerships' }); + } }); // 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 dealerships by state' }); + } }); // Express route to fetch dealer by a particular id app.get('/fetchDealer/:id', async (req, res) => { //Write your code here + try { + const document = await Dealerships.findOne({id: req.params.id}); + if (!document) { + return res.status(404).json({ error: 'Dealer not found' }); + } + res.json(document); + } catch (error) { + res.status(500).json({ error: 'Error fetching dealer by ID' }); + } }); //Express route to insert review @@ -101,4 +122,4 @@ app.post('/insert_review', express.raw({ type: '*/*' }), async (req, res) => { // Start the Express server app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); -}); +}); \ No newline at end of file diff --git a/server/deployment.yaml b/server/deployment.yaml new file mode 100644 index 0000000000..c38842d4d1 --- /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-rajarshirayi/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..60d2ee3edc 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://rajarshirayi-3030.theiadockernext-0-labs-prod-theiak8s-4-tor01.proxy.cognitiveclass.ai +sentiment_analyzer_url=https://sentianalyzer.1zbei3afjojs.us-south.codeengine.appdomain.cloud diff --git a/server/djangoapp/admin.py b/server/djangoapp/admin.py index 433657fc64..0dd04677a1 100644 --- a/server/djangoapp/admin.py +++ b/server/djangoapp/admin.py @@ -1,6 +1,3 @@ -# from django.contrib import admin -# from .models import related models - # Register your models here. @@ -11,3 +8,9 @@ # CarMakeAdmin class with CarModelInline # Register models here +from django.contrib import admin +from .models import CarMake, CarModel + +# Registering models with their respective admins +admin.site.register(CarMake) +admin.site.register(CarModel) \ No newline at end of file diff --git a/server/djangoapp/models.py b/server/djangoapp/models.py index eb101a68c8..93b56d2681 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -1,25 +1,37 @@ -# Uncomment the following imports before adding the Model code +# Uncomment the following imports before adding the Model codee -# 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. +class CarMake(models.Model): + name = models.CharField(max_length=100) + description = models.TextField() -# 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 + def __str__(self): + return self.name +class CarModel(models.Model): + car_make = models.ForeignKey(CarMake, on_delete=models.CASCADE) + name = models.CharField(max_length=50) + CAR_TYPES = [ + ('SEDAN', 'Sedan'), + ('SUV', 'SUV'), + ('WAGON', 'Wagon'), + ('HATCHBACK', 'Hatchback'), + ('COUPE', 'Coupe'), + ('MINIVAN', 'Minivan'), + ('CONVERTIBLE', 'Convertible'), + ('PICKUP', 'Pickup'), + ] + type = models.CharField(max_length=20, choices=CAR_TYPES, default='SUV') + year = models.IntegerField( + default=2023, + validators=[ + MaxValueValidator(2025), + MinValueValidator(2015), + ] + ) -# Create a Car Model model `class CarModel(models.Model):`: -# - Many-To-One relationship to Car Make model (One Car Make has many -# Car Models, using ForeignKey field) -# - Name -# - Type (CharField with a choices argument to provide limited choices -# such as Sedan, SUV, WAGON, etc.) -# - Year (IntegerField) with min value 2015 and max value 2023 -# - Any other fields you would like to include in car model -# - __str__ method to print a car make object + def __str__(self): + return self.name \ No newline at end of file diff --git a/server/djangoapp/populate.py b/server/djangoapp/populate.py index 1927e09e18..e0036ea082 100644 --- a/server/djangoapp/populate.py +++ b/server/djangoapp/populate.py @@ -1,2 +1,92 @@ +#comment +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'] + ) \ No newline at end of file diff --git a/server/djangoapp/restapis.py b/server/djangoapp/restapis.py index 90709d9e3b..d781194014 100644 --- a/server/djangoapp/restapis.py +++ b/server/djangoapp/restapis.py @@ -1,5 +1,4 @@ -# Uncomment the imports below before you add the function code -# import requests +import requests import os from dotenv import load_dotenv @@ -11,12 +10,40 @@ 'sentiment_analyzer_url', default="http://localhost:5050/") -# def get_request(endpoint, **kwargs): -# Add code for get requests to back end -# def analyze_review_sentiments(text): -# request_url = sentiment_analyzer_url+"analyze/"+text -# Add code for retrieving sentiments +def get_request(endpoint, **kwargs): + params = "" + if (kwargs): + for key, value in kwargs.items(): + params = 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: + # If any error occurs + print("Network exception occurred") -# def post_review(data_dict): -# Add code for posting review + +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") + + +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 as e: + print(f"Network exception occurred: {e}") + return {"status": 500, "message": "Internal Server Error"} \ No newline at end of file diff --git a/server/djangoapp/urls.py b/server/djangoapp/urls.py index 0edc274f90..64eb5c96af 100644 --- a/server/djangoapp/urls.py +++ b/server/djangoapp/urls.py @@ -1,18 +1,39 @@ -# Uncomment the imports before you add the code -# from django.urls import path +from django.urls import path from django.conf.urls.static import static from django.conf import settings -# from . import views +from . import views app_name = 'djangoapp' urlpatterns = [ # # path for registration - - # path for login - # path(route='login', view=views.login_user, name='login'), - + path(route='register', view=views.registration, name='register'), + # path for login/logout + path(route='login', view=views.login_user, name='login'), + path('logout/', views.logout_request, name='logout'), + # path for getting the list of cars + path(route='get_cars', view=views.get_cars, name='getcars'), + # path for getting dealerships + path( + route='get_dealers/', + view=views.get_dealerships, + name='get_dealers'), + path( + route='get_dealers/', + view=views.get_dealerships, + name='get_dealers_by_state'), + # path for getting dealership details + path( + route='dealer/', + view=views.get_dealer_details, + name='dealer_details'), # path for dealer reviews view - + path( + route='reviews/dealer/', + view=views.get_dealer_reviews, + name='dealer_details'), # path for add a review view - -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + path( + route='add_review', + view=views.add_review, + name='add_review'), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/server/djangoapp/views.py b/server/djangoapp/views.py index b16409f419..3e1c240459 100644 --- a/server/djangoapp/views.py +++ b/server/djangoapp/views.py @@ -1,27 +1,19 @@ # 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.http import JsonResponse -from django.contrib.auth import login, authenticate +from django.contrib.auth import login, logout, 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, post_review # Get an instance of a logger logger = logging.getLogger(__name__) -# Create your views here. - # Create a `login_request` view to handle sign in request @csrf_exempt def login_user(request): @@ -38,28 +30,127 @@ 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): + # if request.method == 'POST': # Logout via POST for better CSRF protection + logout(request) + return JsonResponse( + {"userName":""} + ) + # return JsonResponse( + # {"success": False, "error": "Invalid request method"}, status=400 + # ) + # 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 + # email_exist = False # Remove unused variable + try: + # Check if user already exists + User.objects.get(username=username) + username_exist = True + except Exception: # Use specific exception and remove unused whitespace + # 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 the list page + login(request, user) + data = {"userName": username, "status": "Authenticated"} + return JsonResponse(data) + else: + data = {"username": username, "error": "Already Registered"} + return JsonResponse(data) + + +# Method to get the list of cars +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}) + + +# Method to get list of 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}) + -# # Update the `get_dealerships` view to render the index page with -# a list of dealerships -# def get_dealerships(request): -# ... +# Method to view reviews for individual dealer +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: + # Call analyze_review_sentiments + # check if the response is valid + response = analyze_review_sentiments( + review_detail['review']) + print(response) + + if response is not None and 'sentiment' in response: + review_detail['sentiment'] = response['sentiment'] + else: + # If there's no sentiment, default to 'neutral' + review_detail['sentiment'] = 'neutral' + + return JsonResponse({"status": 200, "reviews": reviews}) + + else: + return JsonResponse({"status": 400, "message": "Bad Request"}) -# Create a `get_dealer_reviews` view to render the reviews of a dealer -# def get_dealer_reviews(request,dealer_id): -# ... # Create a `get_dealer_details` view to render the dealer details -# def get_dealer_details(request, dealer_id): -# ... +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): -# ... +# Create an `add_review` view to submit a review +def add_review(request): + if(request.user.is_anonymous == False): + data = json.loads(request.body) + try: + response = post_review(data) + return JsonResponse({"status":200}) + except: + return JsonResponse({"status":401,"message":"Error in posting review"}) + else: + return JsonResponse({"status":403,"message":"Unauthorized"}) \ No newline at end of file diff --git a/server/djangoproj/settings.py b/server/djangoproj/settings.py index e0b1092a5c..a9d4df1b52 100644 --- a/server/djangoproj/settings.py +++ b/server/djangoproj/settings.py @@ -13,7 +13,6 @@ import os from pathlib import Path - # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -22,87 +21,88 @@ # 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://rajarshirayi-8000.theiadockernext-0-labs-prod-theiak8s-4-tor01.proxy.cognitiveclass.ai"] + +CSRF_TRUSTED_ORIGINS = ["https://rajarshirayi-8000.theiadockernext-0-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 +110,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 +124,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'), +] \ No newline at end of file diff --git a/server/djangoproj/urls.py b/server/djangoproj/urls.py index 6808da9141..e6eba358f5 100644 --- a/server/djangoproj/urls.py +++ b/server/djangoproj/urls.py @@ -23,4 +23,11 @@ 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..194283bcb2 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", @@ -5884,9 +5908,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001562", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", - "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==", + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", "funding": [ { "type": "opencollective", @@ -5900,7 +5924,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", diff --git a/server/frontend/src/App.js b/server/frontend/src/App.js index aceac6974d..7520df21e1 100644 --- a/server/frontend/src/App.js +++ b/server/frontend/src/App.js @@ -1,11 +1,22 @@ -import LoginPanel from "./components/Login/Login" +import React from "react"; +import LoginPanel from "./components/Login/Login"; // Importing the Login component +import Register from "./components/Register/Register"; // Importing the Register components +import Dealers from './components/Dealers/Dealers'; +import Dealer from './components/Dealers/Dealer'; +import PostReview from "./components/Dealers/PostReview"; import { Routes, Route } from "react-router-dom"; function App() { return ( - } /> + {/* Route for the Login page */} + } /> + } /> + + } /> + } /> + } /> ); } -export default App; +export default App; \ No newline at end of file diff --git a/server/frontend/src/components/Dealers/Dealer.jsx b/server/frontend/src/components/Dealers/Dealer.jsx index 4c162e8a1b..2c592a2d6a 100644 --- a/server/frontend/src/components/Dealers/Dealer.jsx +++ b/server/frontend/src/components/Dealers/Dealer.jsx @@ -29,10 +29,11 @@ const Dealer = () => { method: "GET" }); const retobj = await res.json(); + console.log("Full response object for dealer:", retobj); + console.log("Dealer data from response:", retobj.dealer); if(retobj.status === 200) { - let dealerobjs = Array.from(retobj.dealer) - setDealer(dealerobjs[0]) + setDealer(retobj.dealer) } } diff --git a/server/frontend/src/components/Dealers/PostReview.jsx b/server/frontend/src/components/Dealers/PostReview.jsx index 5ef16de3df..d662e2c11b 100644 --- a/server/frontend/src/components/Dealers/PostReview.jsx +++ b/server/frontend/src/components/Dealers/PostReview.jsx @@ -69,9 +69,7 @@ const PostReview = () => { const retobj = await res.json(); if(retobj.status === 200) { - let dealerobjs = Array.from(retobj.dealer) - if(dealerobjs.length > 0) - setDealer(dealerobjs[0]) + setDealer(retobj.dealer) } } diff --git a/server/frontend/src/components/Register/Register.jsx b/server/frontend/src/components/Register/Register.jsx new file mode 100644 index 0000000000..d7981a606e --- /dev/null +++ b/server/frontend/src/components/Register/Register.jsx @@ -0,0 +1,101 @@ +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; \ No newline at end of file diff --git a/server/frontend/static/About.html b/server/frontend/static/About.html index 484efd960f..3a945ba8f6 100644 --- a/server/frontend/static/About.html +++ b/server/frontend/static/About.html @@ -1,6 +1,7 @@ - + +