Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 55 additions & 55 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 114 additions & 0 deletions mesh/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Contributing to Let's Mesh Backend (API)

Thank you for considering contributing to Let's Mesh! Here are some guidelines to help you get started.

## Table of Contents

1. [Issue Tracking and Management](#issue-tracking-and-management)
2. [Backend Structure](#backend-structure)
3. [Writing tests](#writing-tests)
4. [Communication and Support](#communication-and-support)

## Issue Tracking and Management

We use GitHub Projects to manage and track issues for the Let's Mesh project. You can find our [project board here](https://github.com/orgs/LetsMesh/projects/2).

### How to Report an Issue

1. **Check Existing Issues**: Before creating a new issue (bug/task), please check if the issue has already been reported to avoid duplications.
2. **Open a New Issue**: If the issue does not exist, open a new issue and provide as much detail as possible. For bug reporting, please provide the steps to reproduce the bug.
3. **Link to Project Board**: Ensure your issue is linked to the project board for better tracking.

### How to Work on an Issue

1. **Assign Yourself**: If you are interested in working on an issue, assign it to yourself.
2. **Create a Branch**: Create a new branch from `dev` with a descriptive name related to the issue.
3. **Submit a Pull Request**: Once you have completed your work, submit a pull request (PR) and link it to the issue.

## Backend Structure

Our backend is structured to support both HTTP API requests and WebSocket connections using Django's ASGI and WSGI capabilities. Here’s an overview of our project structure:

- **`mesh/`**: backend
- **`helpers/`**: Custom middlewares & helpers for request/response processing.
- **`utils/`**: Utility functions.
- **`exceptions/`**: Custom exception handling.
- **`tests/`**: Test cases for API and WebSocket endpoints.
- **`asgi.py`**: ASGI configuration for handling asynchronous requests.
- **`routing.py`**: Routing configuration for WebSocket connections.
- **`settings.py`**: Django project settings.
- **`urls.py`**: URL routing configuration.
- **`wsgi.py`**: WSGI configuration for handling synchronous requests.
- Other folders (`accounts/`, `accountSettings/`, `auth`, `profiles/`, etc.): Each folder contains its own URLs and models to handle related operations.
- **`meshapp/`**: frontend

### Getting Started

1. **Determine if you need a new API/app**: If an API already exists for your use case, continue development there. Otherwise, create a new app.
2. **Create a new app**: Ensure you are in the `Site/mesh` directory and run `django-admin startapp api_name`.
3. **Add `urls.py` to the new app**: Follow the example in `Site/mesh/`urls.py`` to add URLs and create a new API.

### Learn by Example

Explore the example available in the project:

- **Files to Check**:

- `Site/mesh/exampleapi/`urls.py``
- `Site/mesh/exampleapi/`views.py``
- `Site/mesh/`urls.py``

- **Steps to Test**:
1. Run `python `manage.py` runserver` in `Site/`.
2. Open a browser and go to [http://127.0.0.1:8000/example/](http://127.0.0.1:8000/example/). You should see "You Got Home".
3. Check the `index` function in `Site/mesh/exampleapi/`views.py``.
4. Check the URL definitions in `Site/mesh/`urls.py`` and `Site/mesh/exampleapi/`urls.py``.

Visit [http://127.0.0.1:8000/example/helloworld/](http://127.0.0.1:8000/example/helloworld/) to see the "Hello World" example.

## Writing Tests

Here are some guidelines for writing tests for our API endpoints in Django:

- Our Django setup has `APPEND_SLASH=true` by default, which appends a trailing slash to URLs and permanently redirects requests.

### Incorrect Way to Send Test Requests (without trailing slash):

```python
from django.test import TestCase, Client

class YourTestClass(TestCase):
def setUp(self):
self.client = Client()

def your_test_case(self):
response = self.client.get("/accounts")
self.assertEqual(response.status_code, 200) # This will not pass ❌
```

This will fail as `response.status_code` is 301 instead of 200, due to the permanent redirect.

### Correct Way to Send Test Requests:

```python
from django.test import TestCase, Client

class YourTestClass(TestCase):
def setUp(self):
self.client = Client()

def your_test_case(self):
response = self.client.get("/accounts", follow=True)
self.assertEqual(response.status_code, 200) # This will pass ✅

response = self.client.get("/accounts/")
self.assertEqual(response.status_code, 200) # This will pass ✅
```

## Communication and Support

For easier communication and support, we invite all contributors to join our Discord server. Our project Discord server is beginner-friendly and a great place to ask questions, get feedback, and stay updated on the latest project developments. [Join our Discord server here](https://discord.gg/eUDKr8u55u).

We appreciate your contributions and look forward to collaborating with you on Let's Mesh!

---
5 changes: 2 additions & 3 deletions mesh/accountSettings/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from . import views as accountsettings_views

urlpatterns = [
path("displayTheme/<int:account_id>", accountsettings_views.display_theme, name="display_theme"),
path("", accountsettings_views.SettingsView.as_view(), name = "settings"),
path("<int:account_id>/", accountsettings_views.SettingsDetailView.as_view(), name = "specific_settings"),
path('', accountsettings_views.SettingsView.as_view(), name = 'settings'),
path('<int:account_id>/', accountsettings_views.SettingsDetailView.as_view(), name = 'specific_settings'),
]
50 changes: 36 additions & 14 deletions mesh/accountSettings/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# Django
from django.http import JsonResponse, HttpResponse
from django.views import View
from django.core.serializers import serialize
from django.core.exceptions import ValidationError, ObjectDoesNotExist

# Libraries
import json

# Models
from .models import Settings, Account

# Utils
from ..utils.validate_data import validate_json_and_required_fields

def display_theme(request, account_id):
Expand Down Expand Up @@ -87,34 +93,50 @@ def post(self, request):
)

class SettingsDetailView(View):
valid_fields = ['isVerified', 'verificationToken', 'hasContentFilterEnabled', 'displayTheme', 'is2FAEnabled']

def get(self, request, account_id, *args, **kwargs):
"""
Handles GET requests when the client fetches for a specific account settings
"""
try:
fields = request.GET.get('fields')
if fields:
fields = [field for field in fields.split(',') if field in self.valid_fields]
else:
fields = self.valid_fields
if len(fields) == 0:
return JsonResponse({'error': 'Invalid fields'}, status=400)

Comment on lines +103 to +110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious... wasn't the validate_json_and_required_fields made for this or is this one a special case that requires us to not use the util function?

Copy link
Contributor Author

@blu3eee blu3eee Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tdlr: validate_json_and_required_fields was not made for this. the two use cases are different.

  • validate_json_and_required_fields is to validate the request body. It takes in a json object, check if the request body has the required fields for the action (i.e. creating account will need enough information) and returns an indicator saying if the request body is valid for the action.

  • The fields checking in this particular case is getting the fields param from the request, then parse it into an array and double-check it with valid fields to see if the requested fields are matched with return object available fields. (Kindof mimicking graphQL behavior in a sense)

    • For example, the account settings have these fields: twoFactorEnabled, theme, and notificationSetting. The fields in the request body will indicate how much information the backend is returning, in some cases, we only need partial information. So, instead of returning the whole accountSetting object from the database, we only return the requested fields. If in the request body, fields="theme,twoFactorEnabled", only those information is returned in the response.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay that makes sense, is there no need to split this into its own function as well or does this seem like it'd be a one-off operation?

settings = Settings.objects.get(accountID= account_id)
settings_detail = serialize('json', [settings])
settings_detail = { field: getattr(settings, field, None) for field in fields }
# Add accountID to the response data
settings_detail['accountID'] = account_id

return JsonResponse(settings_detail, safe = False, status=200)

except Settings.DoesNotExist:
return JsonResponse({'error': 'Settings for this account do not exist'}, status=404)
return JsonResponse({'error': 'Setting for account not found'}, status=404)

def patch(self, request, account_id):
"""
Handle PATCH requests to update a specific setting.
"""
try:
setting = Settings.objects.get(accountID=account_id)
data = json.loads(request.body)

for field, value in data.items():
if field in self.valid_fields:
setattr(setting, field, value)

# After updating, save the setting
setting.save()
return HttpResponse(status=204)
except Settings.DoesNotExist:
return JsonResponse({'error': 'Settings do not exist'}, status=404)

data = json.loads(request.body)
# Here, update the setting's attributes based on the data received
setting.isVerified = data.get('isVerified', setting.isVerified)
setting.verificationToken = data.get('verificationToken', setting.verificationToken)
setting.hasContentFilterEnabled = data.get('hasContentFilterEnabled', setting.hasContentFilterEnabled)
setting.displayTheme = data.get('displayTheme', setting.displayTheme)
setting.is2FAEnabled = data.get('is2FAEnabled', setting.is2FAEnabled)
# After updating, save the setting
setting.save()
return HttpResponse(status=204)
return JsonResponse({'error': 'Setting for account not found'}, status=404)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON format."}, status=400)
except KeyError:
return JsonResponse({"error": "Invalid fields in the request."}, status=400)

Loading