Skip to content

Commit

Permalink
feat(backend): add login and register functionality (#267)
Browse files Browse the repository at this point in the history
Co-authored-by: CEYDANUR ŞEN <128148248+ceydanursen@users.noreply.github.com>
Co-authored-by: Yunus Kağan Aydın <133023181+yunuskaydin@users.noreply.github.com>
Co-authored-by: yunuskaydin <yunuskaganaydin@gmail.com>
Co-authored-by: Mücahit Erdoğan Ünlü <94074126+MucahitErdoganUnlu@users.noreply.github.com>
Co-authored-by: Ozan Karakaya <ozank018@gmail.com>
Co-authored-by: MucahitErdoganUnlu <mucahit.719@hotmail.com>
Co-authored-by: Ümit Can Evleksiz <68506701+umitcan07@users.noreply.github.com>
Co-authored-by: Ümit Can Evleksiz <evleksizumitcan@gmail.com>
  • Loading branch information
9 people authored Apr 27, 2024
1 parent 37d36aa commit cd8edf8
Show file tree
Hide file tree
Showing 31 changed files with 551 additions and 4 deletions.
5 changes: 5 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DB_NAME=<database_name>
MYSQL_USER=<mysql_username>
MYSQL_PASSWORD=<mysql_password>
DB_HOST=<IP_address>
DB_PORT=<port_number>
4 changes: 3 additions & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,6 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
.idea/

test.rest
29 changes: 29 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# base image
FROM python:3.12-slim-bullseye
# setup environment variable
ENV DockerHOME=/home/app/webapp

# set work directory
RUN mkdir -p $DockerHOME

# where your code lives
WORKDIR $DockerHOME

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install dependencies for mysqlclient
RUN apt-get update && apt-get install -y pkg-config python3-dev default-libmysqlclient-dev build-essential

# install dependencies
RUN pip install --upgrade pip

# copy whole project to your docker home directory.
COPY . $DockerHOME
# run this command to install all dependencies
RUN pip install --no-cache-dir -r requirements.txt
# port where the Django app runs
EXPOSE 8000
# start server
CMD python manage.py runserver
117 changes: 117 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Backend Development

See the [Full Version](https://github.com/bounswe/bounswe2024group11/wiki/How-to-Prepare-Your-Local-PC-for-the-Backend-Development/_edit) on Wiki

## How to Run with Docker

### Prerequisites

- Docker

### Steps

- 1. Clone the repository

```bash
git clone
```

- 2. Change directory to the project's backend directory

```bash
cd ./bounswe2024group11/backend
```

- 3. Create a `.env` file in the `./backend` directory and add the following environment variables

```bash
cp .env.example .env
```

- 4. Enter the values for the environment variables in the `.env` file.

You can take the credentials for the deployment from the Whatsapp group.
Or you can use local credentials for development purposes.

- 4.b (Optional) If you want to use local credentials, migrate the database

Run your MySQL server.

Then run the following commands to migrate the database

```bash
python manage.py makemigrations
python manage.py migrate
```

- 5. Install Docker and Docker Compose

- [Docker](https://docs.docker.com/get-docker/)
- [Docker Compose](https://docs.docker.com/compose/install/)

- 6. Run the following command to start the backend server

```bash
docker-compose up
```

## How to Run Manually

We need to create a virtual environment and do all backend-related jobs inside that virtual environment.

```bash
# Change directory to the project's backend directory
cd ./bounswe2024group11/backend

# create a virtual environment

## On bash/zsh shells (i.e., on macOS and the most of the Linux-based distros)
python3 -m venv venv

## On Windows
python -m venv venv

# Activate the virtual environment

## On bash/zsh shells (i.e., on macOS and the most of the Linux-based distros)
source ./venv/bin/activate

## On Windows
venv\Scripts\Activate.ps1 (Powershell)
venv\Scripts\activate.bat (cmd)
```

While initializing our project, we decided to use Python 3.12. So create your virtual environments accordingly.
You may want to take a look at [Python Documentation](https://docs.python.org/3/library/venv.html).

### Installations

Before starting development, we need to install all the requirements. Inside the `./backend` directory:

```bash
# Install requirements
pip install -r requirements.txt
```

Alternatively, you can install them manually. However, **the list of required libraries may and probably will change** from time to time. So, at the very beginning of the project, you can use these commands, but be careful when you want to use them later.

```bash
# Install Django
pip install Django

# Install Django REST Framework
pip install djangorestframework
```

### Run the app

To check if everything is okay, try to run the Django project. Inside the `./backend` directory:

```bash
# Run the Django project server
python manage.py runserver
```

If you see a page without an error when you visit the URL in the terminal, which is similar to `http://127.0.0.1:8000`, your server works properly.

Congratulations! You have cloned the project, created a virtual environment, and installed the dependencies. Now, you are ready to start your development.
Empty file added backend/api/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions backend/api/serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from rest_framework import serializers
from user.models import User

class UserSerializer(serializers.ModelSerializer):
class Meta(object):
model = User
fields = '__all__'
Empty file added backend/api/tests/__init__.py
Empty file.
26 changes: 26 additions & 0 deletions backend/api/tests/test_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from rest_framework.test import APITestCase
from django.urls import reverse
from faker import Faker

class TestSetUp(APITestCase):
def setUp(self):
self.register_url = reverse('signup')
self.login_url = reverse('login')


self.user_data1 = {
'username': Faker().user_name(),
'email': Faker().email(),
'password': Faker().password(),
}

self.user_data2 = {
'username': Faker().user_name(),
'email': Faker().email(),
'password': Faker().password(),
}

return super().setUp()

def tearDown(self):
return super().tearDown()
38 changes: 38 additions & 0 deletions backend/api/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from .test_setup import TestSetUp
from django.urls import reverse
from faker import Faker

class TestView(TestSetUp):

def test_register_user(self):
response = self.client.post(self.register_url, self.user_data1, format='json')
print(response.data)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data['user']['username'], self.user_data1['username'])
self.assertEqual(response.data['user']['email'], self.user_data1['email'])
self.assertTrue('token' in response.data)

def test_register_nonunique_username(self):
response = self.client.post(self.register_url, self.user_data1, format='json')
response = self.client.post(self.register_url, self.user_data1, format='json')
print(response.data)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data['username'][0], 'user with this username already exists.')

def test_false_login_user(self):
response = self.client.post(self.login_url, self.user_data1, format='json')
print(response.data)
self.assertEqual(response.status_code, 401)

def test_login_user(self):
self.client.post(self.register_url, self.user_data1, format='json')
response = self.client.post(self.login_url, self.user_data1, format='json')
print(response.data)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['user']['username'], self.user_data1['username'])
self.assertEqual(response.data['user']['email'], self.user_data1['email'])
self.assertTrue('token' in response.data)




9 changes: 9 additions & 0 deletions backend/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path
from . import views

urlpatterns = [
# path('test_token', views.test_token, name="test_token"),
path('signup', views.register, name="signup"),
path('login', views.login, name="login"),

]
45 changes: 45 additions & 0 deletions backend/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from rest_framework.response import Response
from rest_framework.decorators import api_view
from user.models import User
from .serializer import UserSerializer
from rest_framework import status
from rest_framework.authtoken.models import Token
from django.shortcuts import get_object_or_404

@api_view(['POST'])
def register(request):
required_fields = ['username', 'password', 'email']
if not all([field in request.data for field in required_fields]):
return Response({"res":"Please provide all required fields."}, status=status.HTTP_400_BAD_REQUEST)

serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
user = User.objects.get(username=request.data['username'])
user.set_password(request.data['password'])
user.save()
token = Token.objects.create(user=user)
return Response({"token": token.key, "user": serializer.data}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['POST'])
def login(request):
required_fields = ['username', 'password']
if not all([field in request.data for field in required_fields]):
return Response({"res":"Please provide both username and password."}, status=status.HTTP_400_BAD_REQUEST)

try:
user = User.objects.get(username=request.data['username'])
except User.DoesNotExist:
return Response({"res": "Username not found."}, status=status.HTTP_401_UNAUTHORIZED)

if not user.check_password(request.data['password']):
return Response({"res":"password does not match."}, status=status.HTTP_401_UNAUTHORIZED)
token, created = Token.objects.get_or_create(user=user)
serializer = UserSerializer(instance=user)
return Response({"token": token.key, "user": serializer.data}, status=status.HTTP_200_OK)


#@api_view(['GET'])
#def test_token(request):
# return Response({"res":"Token is valid!"})
19 changes: 16 additions & 3 deletions backend/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"""

from pathlib import Path
from dotenv import load_dotenv
import os

load_dotenv('./.env')

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
Expand All @@ -25,7 +29,7 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['164.90.189.150', 'localhost', '127.0.0.1']


# Application definition
Expand All @@ -38,8 +42,12 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'user',
]

AUTH_USER_MODEL = 'user.User'

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
Expand Down Expand Up @@ -76,12 +84,17 @@

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.mysql',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('MYSQL_USER'),
'PASSWORD': os.getenv('MYSQL_PASSWORD'),
'HOST': os.getenv('DB_HOST'), # Or your MySQL server's IP address
'PORT': os.getenv('DB_PORT'), # Default MySQL port
}
}



# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators

Expand Down
1 change: 1 addition & 0 deletions backend/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@

urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('api.urls')),
]
18 changes: 18 additions & 0 deletions backend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# version is deprecated after docker 25.X
# version: '3.7'


services:
app:
build:
context: .
ports:
- "8000:8000"
volumes:
- .:/home/app/webapp
command: >
sh -c "python manage.py makemigrations &&
python manage.py migrate &&
python manage.py runserver 0.0.0.0:8000"
env_file: .env
restart: "on-failure"
Binary file modified backend/requirements.txt
Binary file not shown.
Empty file added backend/user/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions backend/user/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

from .models import User
# Register your models here.

admin.site.register(User)
6 changes: 6 additions & 0 deletions backend/user/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class UserConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'user'
Loading

0 comments on commit cd8edf8

Please sign in to comment.