Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Feedback submission #558

Draft
wants to merge 53 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
3e7889c
backend
harsh3dev Jan 18, 2025
523a1f7
update api
harsh3dev Jan 19, 2025
eb4adea
remove logging
harsh3dev Jan 19, 2025
64f0eb8
integrate frontend
harsh3dev Jan 19, 2025
900f497
fix override issue
harsh3dev Jan 19, 2025
bfc63dc
update app for feedback
harsh3dev Jan 20, 2025
4921628
add frontend tests
harsh3dev Jan 21, 2025
dc27d25
add backend tests
harsh3dev Jan 21, 2025
21c1e35
resolve conflict
harsh3dev Jan 21, 2025
f008ef3
add poetry file
harsh3dev Jan 21, 2025
15f242f
update error
harsh3dev Jan 22, 2025
ce32827
Merge branch
harsh3dev Jan 22, 2025
80331a4
update poetry
harsh3dev Jan 22, 2025
cf43d92
fix backend tests
harsh3dev Jan 22, 2025
911f314
Merge branch 'main' into feature
harsh3dev Jan 23, 2025
5738e4f
fix
harsh3dev Jan 23, 2025
0b3996a
Merge branch 'feature' of https://github.com/harsh3dev/Nest into feature
harsh3dev Jan 23, 2025
e888187
update package.json
harsh3dev Jan 23, 2025
1245417
Merge branch 'main' of https://github.com/OWASP/Nest into feature
harsh3dev Jan 26, 2025
3678db4
resolve conflicts and change components to chakra ui
harsh3dev Jan 26, 2025
acf9cd2
update form
harsh3dev Jan 26, 2025
6d727af
remove shadcn components
harsh3dev Jan 26, 2025
4d8c455
fix
harsh3dev Jan 26, 2025
2846e66
Update backend/apps/feedback/api/feedback.py
harsh3dev Jan 27, 2025
f4f14f3
update requested changes
harsh3dev Jan 28, 2025
15a155b
tests
harsh3dev Jan 28, 2025
ed32542
Merge branch 'feature' of https://github.com/harsh3dev/Nest into feature
harsh3dev Jan 28, 2025
3e65a27
changes
harsh3dev Jan 28, 2025
268d57f
Merge branch 'main' of https://github.com/OWASP/Nest into feature
harsh3dev Jan 28, 2025
8baac60
pre commit
harsh3dev Jan 28, 2025
b70185e
check
harsh3dev Jan 28, 2025
d2dae82
add package-lock.json
harsh3dev Jan 28, 2025
ebc35b7
update
harsh3dev Jan 28, 2025
7ae97d3
fix test
harsh3dev Jan 28, 2025
72e788b
Merge branch 'main' of https://github.com/OWASP/Nest into feature
harsh3dev Jan 28, 2025
663e731
update
harsh3dev Jan 28, 2025
ce5db3a
Merge branch 'main' of https://github.com/OWASP/Nest into feature
harsh3dev Jan 28, 2025
66e68fa
.
harsh3dev Jan 28, 2025
1e23459
Merge branch 'main' of https://github.com/OWASP/Nest into feature
harsh3dev Jan 29, 2025
93ba308
add poetry
harsh3dev Jan 29, 2025
c0eb02d
update placement and icon
harsh3dev Jan 29, 2025
fc77079
pre commit
harsh3dev Jan 29, 2025
bd10806
update component
harsh3dev Jan 29, 2025
20b5060
Merge branch 'main' into feature
harsh3dev Feb 2, 2025
740ddfa
fix format
harsh3dev Feb 3, 2025
c3db979
Merge branch 'main' of https://github.com/OWASP/Nest into feature
harsh3dev Feb 3, 2025
f49f08a
rename test file
harsh3dev Feb 3, 2025
636a6ff
fix: enhance structuredClone to handle undefined values
harsh3dev Feb 3, 2025
0360316
fix: improve structuredClone function formatting
harsh3dev Feb 3, 2025
04213f1
Merge branch 'main' into feature
harsh3dev Feb 3, 2025
cfe1690
update poetry
harsh3dev Feb 4, 2025
1978b89
Merge branch 'main' of https://github.com/OWASP/Nest into feature
harsh3dev Feb 4, 2025
65e60c1
Merge branch 'main' of https://github.com/OWASP/Nest into feature
harsh3dev Mar 27, 2025
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
3 changes: 3 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ DJANGO_ALGOLIA_WRITE_API_KEY=None
DJANGO_ALLOWED_HOSTS=*
DJANGO_AWS_ACCESS_KEY_ID=None
DJANGO_AWS_SECRET_ACCESS_KEY=None
DJANGO_AWS_STORAGE_BUCKET_NAME=None
DJANGO_AWS_S3_REGION_NAME=None
DJANGO_FEEDBACK_SHEET_KEY=None
DJANGO_CONFIGURATION=Test
DJANGO_DB_HOST=None
DJANGO_DB_NAME=None
Expand Down
Empty file.
1 change: 1 addition & 0 deletions backend/apps/feedback/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Register your models here.
Empty file.
95 changes: 95 additions & 0 deletions backend/apps/feedback/api/feedback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Handle feedback submission, saving to local DB and uploading to S3."""

import csv
from datetime import datetime, timezone
from io import StringIO

import boto3
import botocore
from django.conf import settings
from django.core.exceptions import ValidationError
from rest_framework import status, viewsets
from rest_framework.permissions import AllowAny
from rest_framework.response import Response


class FeedbackViewSet(viewsets.ModelViewSet):
"""ViewSet for handling feedback."""

permission_classes = [AllowAny]

def create(self, request):
"""Handle POST request for feedback submission."""
try:
s3_client = self._get_s3_client()
output, writer = self._get_or_create_tsv(s3_client)
self._write_feedback_to_tsv(writer, request.data)
self._upload_tsv_to_s3(s3_client, output)
return Response(status=status.HTTP_201_CREATED)
except ValidationError:
return Response({"error": "Invalid Credentials"}, status=status.HTTP_400_BAD_REQUEST)

Comment on lines +21 to +31
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Error handling could be more specific

The catch block only handles ValidationError, but other exceptions (like network issues or S3 failures) are not caught, which could expose internal errors to users.

Implement more comprehensive error handling:

def create(self, request):
    """Handle POST request for feedback submission."""
    try:
        s3_client = self._get_s3_client()
        output, writer = self._get_or_create_tsv(s3_client)
        self._write_feedback_to_tsv(writer, request.data)
        self._upload_tsv_to_s3(s3_client, output)
        return Response(status=status.HTTP_201_CREATED)
    except ValidationError:
        return Response({"error": "Invalid Credentials"}, status=status.HTTP_400_BAD_REQUEST)
+    except botocore.exceptions.ClientError as e:
+        return Response(
+            {"error": "Failed to process feedback"},
+            status=status.HTTP_500_INTERNAL_SERVER_ERROR
+        )
+    except Exception:
+        return Response(
+            {"error": "An unexpected error occurred"},
+            status=status.HTTP_500_INTERNAL_SERVER_ERROR
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def create(self, request):
"""Handle POST request for feedback submission."""
try:
s3_client = self._get_s3_client()
output, writer = self._get_or_create_tsv(s3_client)
self._write_feedback_to_tsv(writer, request.data)
self._upload_tsv_to_s3(s3_client, output)
return Response(status=status.HTTP_201_CREATED)
except ValidationError:
return Response({"error": "Invalid Credentials"}, status=status.HTTP_400_BAD_REQUEST)
def create(self, request):
"""Handle POST request for feedback submission."""
try:
s3_client = self._get_s3_client()
output, writer = self._get_or_create_tsv(s3_client)
self._write_feedback_to_tsv(writer, request.data)
self._upload_tsv_to_s3(s3_client, output)
return Response(status=status.HTTP_201_CREATED)
except ValidationError:
return Response({"error": "Invalid Credentials"}, status=status.HTTP_400_BAD_REQUEST)
except botocore.exceptions.ClientError as e:
return Response(
{"error": "Failed to process feedback"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
except Exception:
return Response(
{"error": "An unexpected error occurred"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)

def _get_s3_client(self):
"""Initialize and returns the S3 client."""
return boto3.client(
"s3",
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
region_name=settings.AWS_S3_REGION_NAME,
)

def _get_or_create_tsv(self, s3_client, tsv_key="feedbacks.tsv"):
"""Get the existing TSV file or creates a new one if it doesn't exist."""
output = StringIO()
writer = csv.writer(output, delimiter="\t")

try:
response = s3_client.get_object(
Bucket=settings.AWS_STORAGE_BUCKET_NAME,
Key=tsv_key,
)
# read the content from the body of the response
existing_content = response["Body"].read()
# decode the content to utf-8 format
decoded_content = existing_content.decode("utf-8")
# write the decoded content to the output file
output.write(decoded_content)
# move the cursor to the end of the file
output.seek(0, 2)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's going on here and can we make it a bit more readable please?

except botocore.exceptions.ClientError as e:
if e.response["Error"]["Code"] == "NoSuchKey":
writer.writerow(
["Name", "Email", "Message", "is_anonymous", "is_nestbot", "created_at"]
)
return output, writer

def _write_feedback_to_tsv(self, writer, feedback_data):
"""Write the new feedback data to the TSV file."""
writer.writerow(
(
feedback_data["name"],
feedback_data["email"],
feedback_data["message"],
feedback_data["is_anonymous"],
feedback_data["is_nestbot"],
datetime.now(timezone.utc).isoformat(),
)
)
Comment on lines +66 to +77
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider input sanitization for TSV data

The feedback data is directly written to the TSV without sanitizing inputs. This could cause issues if user inputs contain tab characters, newlines, or other special characters that might break the TSV format.

Implement input sanitization:

def _write_feedback_to_tsv(self, writer, feedback_data):
    """Write the new feedback data to the TSV file."""
+    # Sanitize inputs to prevent TSV injection
+    sanitized_data = {
+        key: str(value).replace('\t', ' ').replace('\n', ' ').replace('\r', ' ')
+        if isinstance(value, str) else value
+        for key, value in feedback_data.items()
+    }
+    
    writer.writerow(
        (
-            feedback_data["name"],
-            feedback_data["email"],
-            feedback_data["message"],
-            feedback_data["is_anonymous"],
-            feedback_data["is_nestbot"],
+            sanitized_data["name"],
+            sanitized_data["email"],
+            sanitized_data["message"],
+            sanitized_data["is_anonymous"],
+            sanitized_data["is_nestbot"],
            datetime.now(timezone.utc).isoformat(),
        )
    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _write_feedback_to_tsv(self, writer, feedback_data):
"""Write the new feedback data to the TSV file."""
writer.writerow(
(
feedback_data["name"],
feedback_data["email"],
feedback_data["message"],
feedback_data["is_anonymous"],
feedback_data["is_nestbot"],
datetime.now(timezone.utc).isoformat(),
)
)
def _write_feedback_to_tsv(self, writer, feedback_data):
"""Write the new feedback data to the TSV file."""
# Sanitize inputs to prevent TSV injection
sanitized_data = {
key: str(value).replace('\t', ' ').replace('\n', ' ').replace('\r', ' ')
if isinstance(value, str) else value
for key, value in feedback_data.items()
}
writer.writerow(
(
sanitized_data["name"],
sanitized_data["email"],
sanitized_data["message"],
sanitized_data["is_anonymous"],
sanitized_data["is_nestbot"],
datetime.now(timezone.utc).isoformat(),
)
)


def _upload_tsv_to_s3(self, s3_client, output):
"""Upload the updated TSV file back to S3."""
output.seek(0)
s3_client.put_object(
Bucket=settings.AWS_STORAGE_BUCKET_NAME,
Key="feedbacks.tsv",
Body=output.getvalue(),
ContentType="text/tab-separated-values",
)

def write_feedback_to_tsv(self, writer, feedback_data):
"""Public method to write feedback data to TSV format."""
self._write_feedback_to_tsv(writer, feedback_data)

def get_s3_client(self):
"""Public method to get the S3 client."""
return self._get_s3_client()
9 changes: 9 additions & 0 deletions backend/apps/feedback/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Feedback API URLs."""

from rest_framework import routers

from apps.feedback.api.feedback import FeedbackViewSet

router = routers.SimpleRouter()

router.register(r"feedback", FeedbackViewSet, basename="feedback")
6 changes: 6 additions & 0 deletions backend/apps/feedback/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class FeedbackConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.feedback"
Empty file.
Empty file.
1 change: 1 addition & 0 deletions backend/apps/feedback/models/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Module contains the models for the feedback app."""
Loading
Loading