diff --git a/.github/workflows/python_syntax_check.yml b/.github/workflows/python_syntax_check.yml index 32d038af..cee19b11 100644 --- a/.github/workflows/python_syntax_check.yml +++ b/.github/workflows/python_syntax_check.yml @@ -18,5 +18,4 @@ jobs: - run: pytest . || true - run: pytest --doctest-modules . || true - run: shopt -s globstar && pyupgrade --py36-plus **/*.py || true - - run: safety check diff --git a/.github/workflows/testcases.yml b/.github/workflows/testcases.yml index 5ebd8afd..517af882 100644 --- a/.github/workflows/testcases.yml +++ b/.github/workflows/testcases.yml @@ -7,9 +7,17 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v3 - uses: actions/checkout@v3 - - run: pip install -r requirements.txt || true - - run: python manage.py test - - run: pip install coverage + - run: pip3 install virtualenv + - run: mkdir packTravel + - run: cd packTravel + - run: virtualenv newenv + - run: source newenv/bin/activate + - run: pip3 install -r requirements.txt + - run: pip3 install django + - run: pip3 install django-allauth + - run: pip3 install pymongo + - run: python3 manage.py test + - run: pip3 install coverage - run: coverage run ./manage.py test - name: Upload coverage to Codecov diff --git a/.gitignore b/.gitignore index f799b197..32fecc63 100644 --- a/.gitignore +++ b/.gitignore @@ -108,6 +108,7 @@ celerybeat.pid *.sage.py # Environments +test_env/ .env .venv env/ diff --git a/PackTravel/settings.py b/PackTravel/settings.py index 130ecbcb..9db1e993 100644 --- a/PackTravel/settings.py +++ b/PackTravel/settings.py @@ -25,8 +25,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] - +ALLOWED_HOSTS = ['44.206.227.12:8000', '127.0.0.1'] # Application definition @@ -37,6 +36,12 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.sites', + # django-allauth apps + 'allauth', + 'allauth.account', + 'allauth.socialaccount', + 'allauth.socialaccount.providers.google', # for Google OAuth 2.0 ] MIDDLEWARE = [ @@ -122,8 +127,30 @@ ] LOGIN_REDIRECT_URL = '/' +LOGIN_URL = 'login/' +LOGOUT_URL = 'logout/' +LOGOUT_REDIRECT_URL = 'login/' # Default primary key field type # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +AUTHENTICATION_BACKENDS = [ + 'django.contrib.auth.backends.ModelBackend', + 'allauth.account.auth_backends.AuthenticationBackend' +] + +SITE_ID = 2 + +SOCIALACCOUNT_PROVIDERS = { + 'google': { + 'SCOPE': [ + 'profile', + 'email', + ], + 'AUTH_PARAMS': { + 'access_type': 'online', + } + } +} diff --git a/PackTravel/urls.py b/PackTravel/urls.py index 8f1affbc..ce59ac03 100644 --- a/PackTravel/urls.py +++ b/PackTravel/urls.py @@ -14,22 +14,28 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include from user import views as userView from search import views as searchViews from publish import views as publishViews +from django.contrib.auth.views import LogoutView urlpatterns = [ path('admin/', admin.site.urls), path('search/', searchViews.search_index, name = 'search'), path('publish/', publishViews.publish_index, name = 'publish'), path('index/', userView.index, name ='index'), + path('', userView.index, name ='index'), path('index/',userView.index, name='index'), path('register/', userView.register, name='register'), path('logout/', userView.logout, name='logout'), path('login/', userView.login, name='login'), - path('create_ride/', publishViews.create_ride, name='create_ride'), - path('add_route/', publishViews.add_route, name='add_route'), + path('create_route/', publishViews.create_route, name='create_route'), + # path('add_route/', publishViews.add_route, name='add_route'), path('select_route/', publishViews.select_route, name='select_route'), - path('display_ride/', publishViews.display_ride, name='display_ride') + path('display_ride/', publishViews.display_ride, name='display_ride'), + path('accounts/', include('allauth.urls')), + path('logout/', LogoutView.as_view()), + path('myrides/', userView.my_rides, name = 'search'), + path('delete_ride/', userView.delete_ride, name = 'delete_ride') ] diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..d6c86142 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web gunicorn PackTravel.wsgi:application --log-file - \ No newline at end of file diff --git a/README.md b/README.md index 8ea7b86a..c9d9c580 100644 --- a/README.md +++ b/README.md @@ -5,69 +5,82 @@ - - [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7178601.svg)](https://doi.org/10.5281/zenodo.7178601) -[![codecov](https://codecov.io/gh/amisha-w/PackTravel/branch/main/graph/badge.svg?token=HRFN97UEB7)](https://codecov.io/gh/amisha-w/PackTravel) -![Python Style Checker](https://github.com/amisha-w/PackTravel/actions/workflows/python_style_checker.yml/badge.svg) -![Lint Python](https://github.com/amisha-w/PackTravel/actions/workflows/pylint.yml/badge.svg) +[![codecov](https://codecov.io/gh/amisha-w/PackTravel/branch/main/graph/badge.svg?token=HRFN97UEB7)](https://codecov.io/gh/Prachit99/PackTravel) +![Python Style Checker](https://github.com/Prachit99/PackTravel/actions/workflows/python_style_checker.yml/badge.svg) +![Lint Python](https://github.com/Prachit99/PackTravel/actions/workflows/pylint.yml/badge.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) -[![contributors](https://img.shields.io/github/contributors/amisha-w/PackTravel?style=for-the-badge)](https://github.com/amisha-w/PackTravel/graphs/contributors) -[![Total Lines](https://img.shields.io/tokei/lines/github/amisha-w/PackTravel?style=for-the-badge)](https://img.shields.io/tokei/lines/github/amisha-w/PackTravel) -[![Issues](https://img.shields.io/github/issues/amisha-w/PackTravel?style=for-the-badge)](https://github.com/amisha-w/PackTravel/issues) -[![Closed Issues](https://img.shields.io/github/issues-closed-raw/amisha-w/PackTravel?style=for-the-badge)](https://github.com/amisha-w/PackTravel/issues?q=is%3Aissue+is%3Aclosed) -[![Pull Requests](https://img.shields.io/github/issues-pr/amisha-w/PackTravel?style=for-the-badge)](https://github.com/amisha-w/PackTravel/pulls) -[![Commit Acitivity](https://img.shields.io/github/commit-activity/w/amisha-w/PackTravel?style=for-the-badge)](https://github.com/amisha-w/PackTravel/graphs/commit-activity) -[![Repo Size](https://img.shields.io/github/repo-size/amisha-w/PackTravel?style=for-the-badge)](https://github.com/amisha-w/PackTravel) +[![contributors](https://img.shields.io/github/contributors/Prachit99/PackTravel?style=for-the-badge)](https://github.com/Prachit99/PackTravel/graphs/contributors) +[![Total Lines](https://img.shields.io/tokei/lines/github/Prachit99/PackTravel?style=for-the-badge)](https://img.shields.io/tokei/lines/github/Prachit99/PackTravel) +[![Issues](https://img.shields.io/github/issues/Prachit99/PackTravel?style=for-the-badge)](https://github.com/Prachit99/PackTravel/issues) +[![Closed Issues](https://img.shields.io/github/issues-closed-raw/Prachit99/PackTravel?style=for-the-badge)](https://github.com/Prachit99/PackTravel/issues?q=is%3Aissue+is%3Aclosed) +[![Pull Requests](https://img.shields.io/github/issues-pr/Prachit99/PackTravel?style=for-the-badge)](https://github.com/Prachit99/PackTravel/pulls) +[![Commit Acitivity](https://img.shields.io/github/commit-activity/w/Prachit99/PackTravel?style=for-the-badge)](https://github.com/Prachit99/PackTravel/graphs/commit-activity) +[![Repo Size](https://img.shields.io/github/repo-size/Prachit99/PackTravel?style=for-the-badge)](https://github.com/Prachit99/PackTravel) -

πŸ† Goal

- -_Travelling alone?_ **Try PackTravel** +## Flaws in Phase 1 -**Project Description:** - -Most of the university students do not have a car to travel off-campus and rely mostly on the Wolfline. But what if someone wants to travel outside Wolfline's limit? Well... why not collaborate on PackTravel to travel off-campus by a cab, rental car, etc. +- Only basic sign-up/login option which used to break multiple times. +- One was able to create Rides as well as Routes which would create a lot of data redundancy and multiple rides and routes even for the same source to destination travel ultimately causing more confusion to users. +- No scope of deleting or cancelling a ride or route. +- No scope for the user to track his/her rides creating problems for the user track his/her rides and routes and plan accordingly. +- Application was running only on local servers. Not deployed so very less usability scope as well as very less server support. +https://user-images.githubusercontent.com/18501895/205809978-14a5c1c0-65a3-4171-b6f2-f30449325088.mp4 - +## Enhancement and Improvements in Phase 2: +- Restructed the database and object structure to improve efficiency and avoid data redundancy. +- Users can create or join routes which would create or map the rides automatically based on the source and destination avoiding duplicate data and data redundancy. +- Added User based Rides Page which would help the user to track his rides and work on it accordingly. +- Added Google SSO Sign in functionality to add options to signing up which also covers security loopholes. +- Added delete/cancel ride functionality so that if the user has cancelled his plan of going to a particular destination, he/she can cancel/delete the ride so that others are not dependent on the same creating efficient and easy usability for the users. +- Deployed and Hosted the application on AWS making the application more stable, globally acccessible and not dependent on any local machine. -**So, let's PackTravel 😎** -

Features

+

What is Packtravel?

-
    -
  • Create a PackTravel Ride
  • -
  • Create mmultiple routes to reach a ride's destination: Bus, Cab or Personal
  • -
  • Search and view other's rides
  • -
  • Join a PackTravel Ride's route
  • - -
+Most of the university students do not have a car to travel off-campus and rely mostly on the Wolfline. But what if someone wants to travel outside Wolfline's limit? Well... why not collaborate on PackTravel to travel off-campus by a cab, rental car, etc. -## πŸš€ A bit abut PackTravel +**So, let's go for Packtravel** https://user-images.githubusercontent.com/111834635/194171771-962a585e-5dc7-4ea3-af35-732ebd34e76c.mp4 **Built Using:**
+ + + - - - - + +

Scalability

+How can we scale this project? What are the shortcomings which can be covered if we scale it? Everything answered and explained in the in-detailed document attached below. +Click here to know about Scaling this project to the next level -## πŸ“– Getting started: +

Features

-### πŸ‘€ Who can use our app +
    +
  • Create a PackTravel Ride
  • +
  • Create mmultiple routes to reach a ride's destination: Bus, Cab or Personal
  • +
  • Search and view other's rides
  • +
  • Join a PackTravel Ride's route
  • +
  • Check all your rides
  • +
  • Modify your Ride
  • +
  • Delete a Ride
  • + +
+ + +

Who can use our app?

1. If you are a user who is visiting our app, you can sign up as a user with view access to rides. You can edit or create your own ride. 2. If you are an admin user, you can create, view, update and delete rides and schedule rides. @@ -82,23 +95,23 @@ https://user-images.githubusercontent.com/111834635/194171771-962a585e-5dc7-4ea3 Create a virtual environment: ```bash - python3.x -m venv test_env + python3.x -m venv env ``` Activate the virtual environment: Linux/MacOS: ```bash - source test_env/bin/activate + source env/bin/activate ``` Windows: ```bash - ./test_env/Scripts/activate + ./env/Scripts/activate ``` Clone the project ```bash - git clone https://github.com/amisha-w/PackTravel.git + git clone https://github.com/Prachit99/PackTravel.git ``` Go to the project directory @@ -122,85 +135,54 @@ Start the server - - Site will be hosted at: - `http://127.0.0.1:5000/` + - Site gets hosted at: + `http://127.0.0.1:8000/` -## πŸ› οΈ Tools +## Tools - [Preetier Code Formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - [JS-HTML-CSS Formatter](https://marketplace.visualstudio.com/items?itemName=lonefy.vscode-JS-CSS-HTML-formatter) - [PyLint](https://pylint.org/) -## πŸ§‘β€πŸ’» Functionalities in Action +## Functionalities in Action + +- **Sign Up Page** + - **Login Page** -![](https://github.com/amisha-w/PackTravel/blob/main/images/Login%20(1).gif) + - **Creating a Ride** -![](https://github.com/amisha-w/PackTravel/blob/main/images/Create1.gif) -![](https://github.com/amisha-w/PackTravel/blob/main/images/Create2.gif) - -- **Search for a Ride** -![](https://github.com/amisha-w/PackTravel/blob/main/images/Search.gif) - -- **Joining an existing Ride** -![](https://github.com/amisha-w/PackTravel/blob/main/images/JoinRide.gif) + + - **Adding New Route** -![](https://github.com/amisha-w/PackTravel/blob/main/images/AddRoute11.gif) -![](https://github.com/amisha-w/PackTravel/blob/main/images/AddRoute12.gif) - -- **Sign Up Page** -![](https://github.com/amisha-w/PackTravel/blob/main/images/Register.gif) - - - + + -## πŸ’¬ Chat Channel +- **Search for a Ride** + - +- **My Rides** + -### 🎯 Phase 1: -- [x] Create database ER diagram -- [x] Create Mongo Database -- [x] Create HomePage -- [x] Create Login and Signup Page -- [x] Create Search Page -- [x] Added create Rides Page -- [x] Added create routes Page -- [x] Setup Django -- [x] Add Unit testing -- [x] Add Error Handling mechanisms -- [x] Setup Workflows -### 🎯 Future Enhancements: +## Chat Channel -- [ ] Add machine learning algorithms for predicting lowest priced rides, best pickup and drop-off locations. -- [ ] Add functionality to merge routes -- [ ] Show later departures in search if currently searched rides is not available -- [ ] Increase the geographical area coverage for the application -- [ ] Extend the userbase to students other than that of the North Carolina State University -- [ ] Integrate in-app cab booking services -- [ ] Introduce a two way ride confirmation feature + -## Contributions to the Project -## πŸ‘¨β€πŸ­ Contributors +## Contributors - - - - - + + + + +

Amisha Waghela

Aoishi Das


Ameya Chavan


Kunal Shah


Swarnamalya M


Prachit99

Sahil Sawant Joshi


Ashish Joshi


Sanket Tangade


Karan Gala

-

❗ TroubleShooting and Help Guide

- - 1. For any issues faced while using the application, please email the detailed description and steps to reproduce the error at help@packtravel.io . - 2. If you are a developer, please raise an issue on github with steps to reproduce and possible source of error. - 3. Our email is monitored 24x7 and we usually respond within 1 hour. Happy Emailing :). diff --git a/db.sqlite3 b/db.sqlite3 index 950f319d..b84bbb5e 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/images/MyRides.mov b/images/MyRides.mov new file mode 100644 index 00000000..bb96670f Binary files /dev/null and b/images/MyRides.mov differ diff --git a/images/myRides.gif b/images/myRides.gif new file mode 100644 index 00000000..0dd78d57 Binary files /dev/null and b/images/myRides.gif differ diff --git a/images/previous_architecture.jpg b/images/previous_architecture.jpg new file mode 100644 index 00000000..024e8d0a Binary files /dev/null and b/images/previous_architecture.jpg differ diff --git a/images/proposed_architecture.png b/images/proposed_architecture.png new file mode 100644 index 00000000..143768f2 Binary files /dev/null and b/images/proposed_architecture.png differ diff --git a/images/shortcomings_of_the_project.mp4 b/images/shortcomings_of_the_project.mp4 new file mode 100644 index 00000000..db52f8c4 Binary files /dev/null and b/images/shortcomings_of_the_project.mp4 differ diff --git a/publish/forms.py b/publish/forms.py index 4501a991..718889c7 100644 --- a/publish/forms.py +++ b/publish/forms.py @@ -7,8 +7,8 @@ class RideForm(forms.ModelForm): destination = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Enter your start destination', 'class': 'form-control'})) - rideDate = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': - 'Enter ride date', 'class': 'form-control'})) + # rideDate = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': + # 'Enter ride date', 'class': 'form-control'})) # unityid = # forms.CharField(required=True, diff --git a/publish/models.py b/publish/models.py index 8fb36202..956b1a00 100644 --- a/publish/models.py +++ b/publish/models.py @@ -5,7 +5,7 @@ class Ride(models.Model): # fields of the model destination = models.TextField() - rideDate = models.TextField() + # rideDate = models.TextField() class Meta: app_label = 'PackTravel.publish' diff --git a/publish/tests/test_urls.py b/publish/tests/test_urls.py index a57d6513..29854375 100644 --- a/publish/tests/test_urls.py +++ b/publish/tests/test_urls.py @@ -1,6 +1,6 @@ from django.test import SimpleTestCase from django.urls import reverse, resolve -from publish.views import publish_index, display_ride,create_ride,add_route,select_route +from publish.views import publish_index, display_ride,create_route,select_route class TestUrl(SimpleTestCase): @@ -10,14 +10,14 @@ def test_publish_index_resolved(self): self.assertEquals(resolve(url).func, publish_index) def test_create_ride_resolved(self): - url = reverse('create_ride') - self.assertEquals(resolve(url).func, create_ride) + url = reverse('create_route') + self.assertEquals(resolve(url).func, create_route) def test_login_resolved(self): url = reverse('select_route') self.assertEquals(resolve(url).func, select_route) - def test_add_route_resolved(self): - url = reverse('add_route') - self.assertEquals(resolve(url).func, add_route) + # def test_add_route_resolved(self): + # url = reverse('add_route') + # self.assertEquals(resolve(url).func, add_) diff --git a/publish/views.py b/publish/views.py index 2a1f72e8..7fedccd3 100644 --- a/publish/views.py +++ b/publish/views.py @@ -35,9 +35,13 @@ def publish_index(request): def display_ride(request, ride_id): intializeDB() + print("Ride id", ride_id) ride = ridesDB.find_one({'_id': ride_id}) + # print(f"Ride = {ride}") routes = get_routes(ride) + print(f"Route = {routes}") selected = routeSelect(request.session['username'], routes) + # print(f"Routes = {selected}") context = { "username": request.session['username'], "ride": ride, @@ -56,7 +60,7 @@ def select_route(request): ride = ride.replace("\'", "\"") ride = json.loads(ride) ride_id = ride['_id'] - attachUserToRoute(username, route_id, ride_id) + attach_user_to_route(username, route_id) return redirect(display_ride, ride_id=ride['_id'] ) return render(request, 'publish/publish.html', {"username": username}) @@ -72,123 +76,147 @@ def routeSelect(username, routes): user_routes = user['rides'] print("User routes: ",user_routes) for route in routes: - if route['id'] in user_routes: + if route['_id'] in user_routes: print("FOUND") - return route['id'] + return route['_id'] return None def get_routes(ride): routes = [] - if 'routes' not in ride: + if 'route_id' not in ride: return None - route_ids = ride['routes'] + route_ids = ride['route_id'] for route_id in route_ids: route = routesDB.find_one({'_id': route_id}) if not route: pass - route['id'] = route.pop('_id') + # route['id'] = route.pop('_id') routes.append(route) return routes -def create_ride(request): +def create_route(request): intializeDB() if request.method == 'POST': - ride = { - "_id": - request.POST.get('name')+"_"+request.POST.get('destination') - +"_"+request.POST.get("date")+"_"+ - request.POST.get("hour")+"_"+ - request.POST.get("minute")+"_"+ - request.POST.get("ampm") + route = { + "_id": + f"""{request.POST.get('purpose')}_{request.POST.get('s_point')}_{request.POST.get('destination')} + _{request.POST.get("date")}_{request.POST.get("hour")}_{request.POST.get("minute")} + _{request.POST.get("ampm")}""" + , - "name": request.POST.get('name'), + "purpose": request.POST.get('purpose'), + "s_point": request.POST.get('s_point'), "destination": request.POST.get('destination'), + "type": request.POST.get('type'), "date": request.POST.get("date"), "hour": request.POST.get("hour"), "minute": request.POST.get("minute"), "ampm": request.POST.get("ampm"), "details": request.POST.get("details") } - request.session['ride'] = ride - if ridesDB.find_one({'_id': ride['_id']})== None: - ridesDB.insert_one(ride) - return redirect(display_ride, ride_id=request.session['ride']['_id'] ) - - return render(request, 'publish/publish.html', {"username": request.session['username']}) - -def add_route(request): - intializeDB() - if request.method == 'POST': - - ride = request.POST.get('ride') - ride = ride.replace("\'", "\"") - ride = json.loads(ride) - ride_id = ride['_id'] - ride = ridesDB.find_one({'_id': ride['_id']}) - route = { - "_id": str(ride_id) - +"_"+request.POST.get('type') - +"_"+request.POST.get('spoint') - +"_"+request.POST.get("hour") - +"_"+request.POST.get("minute") - +"_"+request.POST.get("duration") - +"_"+request.POST.get("details") - +"_"+request.POST.get("ampm"), - - "type": request.POST.get('type'), - "spoint": request.POST.get('spoint'), - "hour": request.POST.get("hour"), - "minute": request.POST.get("minute"), - "duration": request.POST.get("duration"), - "details": request.POST.get("details"), - "ampm": request.POST.get("ampm"), - "users": [request.session['username']] - } - request.session["route"] = route - request.session["ride"] = ride - attachUserToRoute(request.session['username'], route["_id"], ride_id) - #check if route is unique - if routesDB.find_one({'_id': route["_id"]})== None: + ride_id = request.POST.get('destination') + attach_user_to_route(request.session['username'], route['_id']) + if routesDB.find_one({'_id': route['_id']}) == None: routesDB.insert_one(route) - if 'routes' not in ride: - ridesDB.update_one({"_id": ride_id}, {"$set": {"routes": [route['_id']]}}) + print("Route added") + if ridesDB.find_one({'_id': ride_id}) == None: + ride = { + "_id": + request.POST.get('destination'), + "destination": request.POST.get('destination'), + "route_id": [route['_id']] + } + ridesDB.insert_one(ride) + print("Ride Added") else: - ride['routes'].append(route['_id']) - ridesDB.update_one({"_id": ride_id}, {"$set": {"routes": ride['routes']}}) - return redirect(display_ride, ride_id=request.session['ride']['_id'] ) - + ride = ridesDB.find_one({'_id': ride_id}) + ride['route_id'].append(route['_id']) + ridesDB.update_one({'_id': ride_id},{"$set": {"route_id": ride['route_id']}}) + print("Ride Updated") + return redirect(display_ride, ride_id=ride_id) return render(request, 'publish/publish.html', {"username": request.session['username']}) -def attachUserToRoute(username, route_id, ride_id): +# def add_route(request): +# intializeDB() +# if request.method == 'POST': +# +# ride = request.POST.get('ride') +# ride = ride.replace("\'", "\"") +# ride = json.loads(ride) +# ride_id = ride['_id'] +# ride = ridesDB.find_one({'_id': ride['_id']}) +# route = { +# "_id": str(ride_id) +# +"_"+request.POST.get('type') +# +"_"+request.POST.get('spoint') +# +"_"+request.POST.get("hour") +# +"_"+request.POST.get("minute") +# +"_"+request.POST.get("duration") +# +"_"+request.POST.get("details") +# +"_"+request.POST.get("ampm"), +# +# "type": request.POST.get('type'), +# "spoint": request.POST.get('spoint'), +# "hour": request.POST.get("hour"), +# "minute": request.POST.get("minute"), +# "duration": request.POST.get("duration"), +# "details": request.POST.get("details"), +# "ampm": request.POST.get("ampm"), +# "users": [request.session['username']] +# } +# request.session["route"] = route +# request.session["ride"] = ride +# attachUserToRoute(request.session['username'], route["_id"], ride_id) +# #check if route is unique +# if routesDB.find_one({'_id': route["_id"]})== None: +# routesDB.insert_one(route) +# if 'routes' not in ride: +# ridesDB.update_one({"_id": ride_id}, {"$set": {"routes": [route['_id']]}}) +# else: +# ride['routes'].append(route['_id']) +# ridesDB.update_one({"_id": ride_id}, {"$set": {"routes": ride['routes']}}) +# return redirect(display_ride, ride_id=request.session['ride']['_id'] ) +# +# return render(request, 'publish/publish.html', {"username": request.session['username']}) + +def attach_user_to_route(username, route_id): intializeDB() user = userDB.find_one({"username": username}) if user == None: - pass - - rides = user['rides'] - #remove other routes for this user and ride - for route in rides.copy(): - if ride_id in route: - rides.remove(route) - #remove user from other routes for this ride - print("foudn ride id in route") - route_instance = routesDB.find_one({'_id': route}) - print("route inst",route_instance) - if route_instance: - print("found, removing user: ",username) - users = route_instance['users'] - print("prev users: ",users) - users.remove(username) - print("now: ",users) - routesDB.update_one({"_id": route}, {"$set": {"users": users}}) - - rides.append(route_id) - userDB.update_one({"username": username}, {"$set": {"rides": rides}}) - # print(rides) - route_instance = routesDB.find_one({'_id': route_id}) - if route_instance: - users = route_instance['users'] - users.append(username) - routesDB.update_one({"_id": route_id}, {"$set": {"users": users}}) + return redirect('home/home.html', {"username": None}) + + user['rides'].append(route_id) + userDB.update_one({"username": username},{"$set": {"rides": user['rides']}}) + print("route added for user") + + # rides = user['rides'] + # #remove other routes for this user and ride + # for route in rides.copy(): + # if ride_id in route: + # rides.remove(route) + # #remove user from other routes for this ride + # print("foudn ride id in route") + # route_instance = routesDB.find_one({'_id': route}) + # print("route inst",route_instance) + # if route_instance: + # print("found, removing user: ",username) + # users = route_instance['users'] + # print("prev users: ",users) + # users.remove(username) + # print("now: ",users) + # routesDB.update_one({"_id": route}, {"$set": {"users": users}}) + # + # rides.append(route_id) + # userDB.update_one({"username": username}, {"$set": {"rides": rides}}) + # # print(rides) + # route_instance = routesDB.find_one({'_id': route_id}) + # if route_instance: + # users = route_instance['users'] + # users.append(username) + # routesDB.update_one({"_id": route_id}, {"$set": {"users": users}}) + +# Add Edit functionality + +# Add Delete functionality diff --git a/requirements.txt b/requirements.txt index e1cd2ae9..6257c3cb 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 00000000..ee92de7a --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.10.6 \ No newline at end of file diff --git a/scalability.md b/scalability.md new file mode 100644 index 00000000..cec6bd10 --- /dev/null +++ b/scalability.md @@ -0,0 +1,27 @@ +

Reviewing the current architecture

+

Current Architecture Diagram

+ +![alt text](https://github.com/Prachit99/PackTravel/blob/ashish_dev/images/previous_architecture.jpg) + +Shortcomings of current arhitecture: +- Not Scalable +- Not Performant +- Security Vulnerability (runs on HTTP insetad of HTTPS) +- Painful Server Maintainance + +

Proposed architecture

+

Proposed Architecture Diagram

+ +![alt text](https://github.com/Prachit99/PackTravel/blob/ashish_dev/images/proposed_architecture.png) + +Steps to implement this proposed architecture: +1. Containerize the current appllication using Docker. We will write a GitHub workflow to dockerize the app after every push and then upload it to AWS Elastic Container Registry and then run it in Elastic Containers. +2. Add an Application Load Balancer (ALB) to enable horizontal scaling and health checks. Now the requests can be distributed between several instances and unhealthy instances can be detected and replaced. The ALB also supports port forwarding so we won’t need intermediate proxies like nginx as the requests can be directly forwarded to the application on port 8000. +3. Elastic Container Service(ECS) allows running docker containers in the cloud. We will use it to run our Django app instances. ECS supports two ways of running containers: running the containers inside a server (EC2 VM or on-premise) or running containers in serverless mode using Fargate. We will use Fargate to avoid managing servers. +4. A database is stateful by nature because it stores data that has to be persistent. So it can’t run as a stateless container in ECS/Fargate. But we don’t want to manage a server or VM for the database. So we will use serversless MongoDB. + +Advantages of the proposed architecture: +- Automatically Scalable - Elastic Container Service can be configured to scale automatically. +- Highly Performant - can handle multiple concurrent users due to load balancer and containers +- Secure - Application Load Balancer supports HTTPS and SSL/TLS certificates that can be added using the AWS Certificates Manager. +- Easy Maintainance - We will use serverless mode in ECS and serverless MongoDB, both are easy to maintain and manage. diff --git a/search/views.py b/search/views.py index 686043c3..1818a577 100644 --- a/search/views.py +++ b/search/views.py @@ -30,9 +30,14 @@ def search_index(request): request.session['alert'] = "Please login to create a ride." return redirect('index') all_rides = list(ridesDB.find()) - processed = list() + all_routes = list(routesDB.find()) + processed, routes = list(), list() + processed_routes = list() for ride in all_rides: + routes = ride['route_id'] + for route in all_routes: + if route['_id'] in routes: + ride.update(route) ride['id'] = ride.pop('_id') processed.append(ride) - return render(request, 'search/search.html', {"username": request.session['username'], "rides":processed}) - \ No newline at end of file + return render(request, 'search/search.html', {"username": request.session['username'], "rides": processed}) diff --git a/templates/home/home.html b/templates/home/home.html index ecaba324..043305d0 100644 --- a/templates/home/home.html +++ b/templates/home/home.html @@ -25,7 +25,7 @@ border-color: #D22B2B; } .jumbo-pic{ - background-image: url("/static/travel.jpeg"); + background-image: url("static/travel.jpeg"); } .full-height-div { diff --git a/templates/nav.html b/templates/nav.html index 4ac6bd27..d7c7620a 100644 --- a/templates/nav.html +++ b/templates/nav.html @@ -226,7 +226,7 @@ \ No newline at end of file diff --git a/user/forms.py b/user/forms.py index e04bb420..3b7ddfed 100644 --- a/user/forms.py +++ b/user/forms.py @@ -9,7 +9,7 @@ class RegisterForm(forms.ModelForm): first_name = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'First Name', 'class': 'form-control'})) last_name = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Last Name', 'class': 'form-control'})) email = forms.EmailField(required=True, max_length=60, widget=forms.EmailInput(attrs={'placeholder': 'abc@mail.com', 'class': 'form-control'})) - password1 = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Password','class': "form-control"})) + password1 = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'placeholder': 'Password','class': "form-control"})) phone_number = forms.CharField(required=True, max_length=11, widget=forms.TextInput(attrs={'placeholder': 'Phone Number', 'class': 'form-control'})) class Meta: @@ -26,7 +26,7 @@ class Meta: class LoginForm(forms.ModelForm): username = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Enter your username', 'class': "form-control"})) - password = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Password', 'class': "form-control"})) + password = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'placeholder': 'Password', 'class': "form-control"})) class Meta: model = User fields = ('username', 'password') diff --git a/user/views.py b/user/views.py index c6651fba..336a8622 100644 --- a/user/views.py +++ b/user/views.py @@ -1,5 +1,5 @@ from http.client import HTTPResponse -from django.shortcuts import render,redirect +from django.shortcuts import render, redirect import requests import json from django.http import HttpResponse @@ -9,28 +9,48 @@ from utils import get_client from .forms import RegisterForm, LoginForm - - - client = None db = None userDB = None -ridesDB = None -routesDB = None +ridesDB = None +routesDB = None + def intializeDB(): global client, db, userDB, ridesDB, routesDB client = get_client() db = client.SEProject userDB = db.userData - ridesDB = db.rides - routesDB = db.routes + ridesDB = db.rides + routesDB = db.routes + # Home page for PackTravel def index(request, username=None): + intializeDB() + if request.user.is_authenticated: + request.session["username"] = request.user.username + request.session['fname'] = request.user.first_name + request.session['lname'] = request.user.last_name + request.session['email'] = request.user.email + user = userDB.find_one({"username": request.user.username}) + if not user: + userObj = { + "username": request.user.username, + "fname": request.user.first_name, + "lname": request.user.last_name, + "email": request.user.email, + "rides": [] + } + userDB.insert_one(userObj) + print("User Added") + else: + print("User Already exists") + print(f'Username: {user["username"]}') + return render(request, 'home/home.html', {"username": request.session["username"]}) if request.session.has_key('username'): - return render(request, 'home/home.html', {"username":request.session["username"]}) - return render(request, 'home/home.html', {"username":None}) + return render(request, 'home/home.html', {"username": request.session["username"]}) + return render(request, 'home/home.html', {"username": None}) def register(request): @@ -39,8 +59,8 @@ def register(request): form = RegisterForm(request.POST) if form.is_valid(): userObj = { - "username": form.cleaned_data["username"], - "unityid": form.cleaned_data["unityid"], + "username": form.cleaned_data["username"], + "unityid": form.cleaned_data["unityid"], "fname": form.cleaned_data["first_name"], "lname": form.cleaned_data["last_name"], "email": form.cleaned_data["email"], @@ -57,19 +77,20 @@ def register(request): request.session['phone'] = userObj["phone"] return redirect(index, username=request.session["username"]) else: - print(form.errors.as_data()) + print(form.errors.as_data()) else: if request.session.has_key('username'): - return index(request,request.session['username']) + return index(request, request.session['username']) form = RegisterForm() return render(request, 'user/register.html', {"form": form}) + def logout(request): - try: - request.session.clear() - except: - pass - return redirect(index) + try: + request.session.clear() + except: + pass + return redirect(index) # @describe: Existing user login @@ -78,11 +99,11 @@ def login(request): if request.session.has_key('username'): return redirect(index, {"username": request.session['username']}) else: - if request.method=="POST": + if request.method == "POST": form = LoginForm(request.POST) if form.is_valid(): username = form.cleaned_data["username"] - passw = form.cleaned_data["password"] + passw = form.cleaned_data["password"] user = userDB.find_one({"username": username}) if user and user["password"] == form.cleaned_data["password"]: @@ -93,7 +114,37 @@ def login(request): request.session['email'] = user["email"] request.session["phone"] = user["phone"] return redirect(index, request.session['username']) - + form = LoginForm() return render(request, 'user/login.html', {"form": form}) + +def my_rides(request): + intializeDB() + if not request.session.has_key('username'): + request.session['alert'] = "Please login to create a ride." + return redirect('index') + all_routes = list(routesDB.find()) + user_list = list(userDB.find()) + final_user, processed = list(), list() + for user in user_list: + if request.session["username"] == user['username']: + final_user = user + user_routes = final_user['rides'] + for route in all_routes: + for i in range(len(user_routes)): + if user_routes[i] == route['_id']: + route['id'] = route['_id'] + processed.append(route) + + return render(request, 'user/myride.html', {"username": request.session['username'], "rides": processed}) + + +def delete_ride(request, ride_id): + intializeDB() + user = userDB.find_one({"username": request.session['username']}) + if user is None: + pass + routesDB.delete_one({"_id": ride_id}) + return redirect("/myrides") + diff --git a/utils.py b/utils.py index 6bb0f5c4..529abd90 100644 --- a/utils.py +++ b/utils.py @@ -1,12 +1,14 @@ import os import sys +import certifi from pymongo import MongoClient def get_client(): with open(os.path.join(sys.path[0], "config.ini"), "r") as f: - content=f.readlines() - client = MongoClient("mongodb+srv://Aoishi:"+content[0]+"@cluster0.zpuftvw.mongodb.net/?retryWrites=true&w=majority") + content = f.readlines() + client = MongoClient( + "mongodb+srv://Aoishi:" + content[0] + "@cluster0.zpuftvw.mongodb.net/?retryWrites=true&w=majority", + tlsCAFile=certifi.where()) - return client