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

Bug - Change to API Response #529

Merged
merged 13 commits into from
Nov 11, 2024
10 changes: 5 additions & 5 deletions backend/core/api/public/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

from rest_framework.exceptions import PermissionDenied
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework import status

from backend.models import TeamMemberPermission, Organization, Client
from backend.core.api.public.helpers.response import APIResponse

import logging

Expand All @@ -21,21 +21,21 @@ def _wrapped_view(request, *args, **kwargs):
logger.info(
f"Authentication credentials were not provided in api request |" f" {request.META.get('REMOTE_ADDR', 'Unknown IP')}"
)
return Response({"detail": "Authentication credentials were not provided."}, status=status.HTTP_401_UNAUTHORIZED)
return APIResponse(False, {"detail": "Authentication credentials were not provided."}, status=status.HTTP_401_UNAUTHORIZED)

if request.team_id and not request.team:
return Response({"detail": "Team not found."}, status=status.HTTP_404_NOT_FOUND)
return APIResponse(False, {"detail": "Team not found."}, status=status.HTTP_404_NOT_FOUND)

if request.team:
# Check for team permissions based on team_id and scopes
if not request.team.is_owner(token.user) and not request.team.is_logged_in_as_team(request):
team_permissions = TeamMemberPermission.objects.filter(team=request.team, user=token.user).first()
if not team_permissions or not all(scope in team_permissions.scopes for scope in scopes):
return Response({"detail": "Permission denied."}, status=status.HTTP_403_FORBIDDEN)
return APIResponse(False, {"detail": "Permission denied."}, status=status.HTTP_403_FORBIDDEN)

# Check for global API Key permissions based on token scopes
if not all(scope in token.scopes for scope in scopes):
return Response({"detail": "Permission denied."}, status=status.HTTP_403_FORBIDDEN)
return APIResponse(False, {"detail": "Permission denied."}, status=status.HTTP_403_FORBIDDEN)

token.update_last_used()

Expand Down
9 changes: 5 additions & 4 deletions backend/core/api/public/endpoints/Invoices/delete.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from django.http import QueryDict
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from backend.core.api.public.decorators import require_scopes
from backend.core.api.public.types import APIRequest
from backend.core.api.public.helpers.response import APIResponse

from backend.models import Invoice, QuotaLimit


Expand All @@ -16,13 +17,13 @@ def delete_invoice_endpoint(request: APIRequest):
try:
invoice = Invoice.objects.get(id=delete_items.get("invoice", ""))
except Invoice.DoesNotExist:
return Response({"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND)
return APIResponse(False, {"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND)

if not invoice.has_access(request.user):
return Response({"error": "You do not have permission to delete this invoice"}, status=status.HTTP_403_FORBIDDEN)
return APIResponse(False, {"error": "You do not have permission to delete this invoice"}, status=status.HTTP_403_FORBIDDEN)

QuotaLimit.delete_quota_usage("invoices-count", request.user, invoice.id, invoice.date_created)

invoice.delete()

return Response({"message": "Invoice successfully deleted"}, status=status.HTTP_200_OK)
return APIResponse(True, {"message": "Invoice successfully deleted"}, status=status.HTTP_200_OK)
5 changes: 3 additions & 2 deletions backend/core/api/public/endpoints/Invoices/download_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from backend.core.api.public.types import APIRequest
from backend.finance.models import Invoice
from backend.core.service.invoices.single.create_pdf import generate_pdf
from backend.core.api.public.helpers.response import APIResponse


@swagger_auto_schema(
Expand Down Expand Up @@ -68,8 +69,8 @@ def download(request: APIRequest, id: str) -> HttpResponse | Response:
else:
invoice = Invoice.objects.get(user=request.user, id=id)
except Invoice.DoesNotExist:
return Response({"success": False, "message": "Invoice not found"}, status=status.HTTP_400_BAD_REQUEST)
return APIResponse(False, {"message": "Invoice not found"}, status=status.HTTP_400_BAD_REQUEST)

if response := generate_pdf(invoice, "attachment"):
return response
return Response({"success": False, "message": "Error generating PDF"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return APIResponse(False, {"message": "Error generating PDF"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
38 changes: 21 additions & 17 deletions backend/core/api/public/endpoints/Invoices/edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from backend.core.api.public.decorators import require_scopes
from backend.core.api.public.types import APIRequest
from backend.core.api.public.helpers.response import APIResponse
from backend.finance.models import Invoice


Expand All @@ -15,20 +15,22 @@
def edit_invoice_endpoint(request: APIRequest):
invoice_id = request.data.get("invoice_id", "")
if not invoice_id:
return Response({"error": "Invoice ID is required"}, status=status.HTTP_400_BAD_REQUEST)
return APIResponse(False, {"error": "Invoice ID is required"}, status=status.HTTP_400_BAD_REQUEST)

try:
invoice = Invoice.objects.get(id=invoice_id)
except Invoice.DoesNotExist:
return Response({"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND)
return APIResponse(False, {"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND)

if request.user.logged_in_as_team and request.user.logged_in_as_team != invoice.organization:
return Response(
return APIResponse(
False,
{"error": "You do not have permission to edit this invoice"},
status=status.HTTP_403_FORBIDDEN,
)
elif request.user != invoice.user:
return Response(
return APIResponse(
False,
{"error": "You do not have permission to edit this invoice"},
status=status.HTTP_403_FORBIDDEN,
)
Expand Down Expand Up @@ -64,12 +66,12 @@ def edit_invoice_endpoint(request: APIRequest):
try:
new_value = datetime.strptime(new_value, "%Y-%m-%d").date() # type: ignore[assignment]
except ValueError:
return Response({"error": "Invalid date format for date_due"}, status=status.HTTP_400_BAD_REQUEST)
return APIResponse(False, {"error": "Invalid date format for date_due"}, status=status.HTTP_400_BAD_REQUEST)
setattr(invoice, column_name, new_value)

invoice.save()

return Response({"message": "Invoice successfully edited"}, status=status.HTTP_200_OK)
return APIResponse(True, {"message": "Invoice successfully edited"}, status=status.HTTP_200_OK)


@api_view(["POST"])
Expand All @@ -79,20 +81,20 @@ def change_status_endpoint(request, invoice_id: int, invoice_status: str):
try:
invoice = Invoice.objects.get(id=invoice_id)
except Invoice.DoesNotExist:
return Response({"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND)
return APIResponse(False, {"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND)

if request.user.logged_in_as_team and request.user.logged_in_as_team != invoice.organization or request.user != invoice.user:
return Response({"error": "You do not have permission to edit this invoice"}, status=status.HTTP_403_FORBIDDEN)
return APIResponse(False, {"error": "You do not have permission to edit this invoice"}, status=status.HTTP_403_FORBIDDEN)

if invoice_status not in ["paid", "draft", "pending"]:
return Response({"error": "Invalid status. Please choose from: pending, paid, draft"}, status=status.HTTP_400_BAD_REQUEST)
return APIResponse(False, {"error": "Invalid status. Please choose from: pending, paid, draft"}, status=status.HTTP_400_BAD_REQUEST)

if invoice.status == invoice_status:
return Response({"error": f"Invoice status is already {invoice_status}"}, status=status.HTTP_400_BAD_REQUEST)
return APIResponse(False, {"error": f"Invoice status is already {invoice_status}"}, status=status.HTTP_400_BAD_REQUEST)

invoice.set_status(invoice_status)

return Response({"message": f"Invoice status been changed to <strong>{invoice_status}</strong>"}, status=status.HTTP_200_OK)
return APIResponse(True, {"message": f"Invoice status been changed to <strong>{invoice_status}</strong>"}, status=status.HTTP_200_OK)


@api_view(["POST"])
Expand All @@ -104,28 +106,30 @@ def edit_discount_endpoint(request, invoice_id: str):
try:
invoice: Invoice = Invoice.objects.get(id=invoice_id)
except Invoice.DoesNotExist:
return Response({"error": "Invoice not found"}, status=status.HTTP_404_NOT_FOUND)
return APIResponse(False, {"error": "Invoice not found"}, status=status.HTTP_404_NOT_FOUND)

if not invoice.has_access(request.user):
return Response({"error": "You don't have permission to make changes to this invoice."}, status=status.HTTP_403_FORBIDDEN)
return APIResponse(False, {"error": "You don't have permission to make changes to this invoice."}, status=status.HTTP_403_FORBIDDEN)

if discount_type == "percentage":
try:
percentage_amount = int(percentage_amount_str)
if percentage_amount < 0 or percentage_amount > 100:
raise ValueError
except ValueError:
return Response({"error": "Please enter a valid percentage amount (between 0 and 100)"}, status=status.HTTP_400_BAD_REQUEST)
return APIResponse(
False, {"error": "Please enter a valid percentage amount (between 0 and 100)"}, status=status.HTTP_400_BAD_REQUEST
)
invoice.discount_percentage = percentage_amount
else:
try:
discount_amount = int(discount_amount_str)
if discount_amount < 0:
raise ValueError
except ValueError:
return Response({"error": "Please enter a valid discount amount"}, status=status.HTTP_400_BAD_REQUEST)
return APIResponse(False, {"error": "Please enter a valid discount amount"}, status=status.HTTP_400_BAD_REQUEST)
invoice.discount_amount = discount_amount

invoice.save()

return Response({"message": "Discount was applied successfully"}, status=status.HTTP_200_OK)
return APIResponse(True, {"message": "Discount was applied successfully"}, status=status.HTTP_200_OK)
5 changes: 3 additions & 2 deletions backend/core/api/public/endpoints/Invoices/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from backend.core.api.public.serializers.invoices import InvoiceSerializer
from backend.core.api.public.swagger_ui import TEAM_PARAMETER
from backend.core.api.public.types import APIRequest
from backend.core.api.public.helpers.response import APIResponse
from backend.finance.models import Invoice


Expand Down Expand Up @@ -52,8 +53,8 @@ def get_invoices_endpoint(request: APIRequest, id: str) -> Response:
else:
invoices = Invoice.objects.filter(user=request.user, id=id)
except Invoice.DoesNotExist:
return Response({"success": False, "message": "Invoice not found"}, status=status.HTTP_400_BAD_REQUEST)
return APIResponse(False, {"message": "Invoice not found"}, status=status.HTTP_400_BAD_REQUEST)

serializer = InvoiceSerializer(invoices, many=True)

return Response({"success": True, "invoice": serializer.data}, status=status.HTTP_200_OK)
return APIResponse(True, {"invoice": serializer.data}, status=status.HTTP_200_OK)
6 changes: 3 additions & 3 deletions backend/core/api/public/endpoints/clients/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import api_view
from rest_framework.response import Response

from backend.core.api.public.decorators import require_scopes
from backend.core.api.public.swagger_ui import TEAM_PARAMETER
from backend.core.api.public.types import APIRequest

from backend.core.service.clients.delete import delete_client, DeleteClientServiceResponse
from backend.core.api.public.helpers.response import APIResponse


@swagger_auto_schema(
Expand Down Expand Up @@ -64,5 +64,5 @@ def client_delete_endpoint(request: APIRequest, id: str):
response: DeleteClientServiceResponse = delete_client(request, id)

if response.failed:
return Response({"success": False, "message": response.error}, status=403 if "do not have permission" in response.error else 404)
return Response({"success": True, "client_id": id}, status=200)
return APIResponse(False, response.error, status=403 if "do not have permission" in response.error else 404)
return APIResponse(True, {"client_id": id}, status=200)
6 changes: 3 additions & 3 deletions backend/core/api/public/endpoints/system_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from django.core.cache import cache

from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response

from backend.core.api.public.permissions import IsSuperuser
from backend.core.api.public.helpers.response import APIResponse


@swagger_auto_schema(
Expand Down Expand Up @@ -46,7 +46,7 @@
@permission_classes([IsSuperuser])
def system_health_endpoint(request):
if not request.user or not request.user.is_superuser:
return Response({"success": False, "message": "User is not permitted to view internal information"}, status=403)
return APIResponse(False, "User is not permitted to view internal information", status=403)

problems = []

Expand All @@ -60,4 +60,4 @@ def system_health_endpoint(request):
except ConnectionError:
problems.append({"id": "redis", "message": "redis failed to connect"})

return Response({"problems": problems, "healthy": not bool(problems)})
return APIResponse({"problems": problems, "healthy": not bool(problems)})
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
from rest_framework.response import Response
import logging
from backend.core.api.public import APIAuthToken
from rest_framework.decorators import api_view

from backend.core.service.asyn_tasks.tasks import Task
from backend.core.api.public.helpers.response import APIResponse


@api_view(["POST"])
def webhook_task_queue_handler_view_endpoint(request):
token: APIAuthToken | None = request.auth

if not token:
return Response({"status": "error", "message": "No token found"}, status=500)
return APIResponse(False, {"status": "error", "message": "No token found"}, status=500)

if not token.administrator_service_type == token.AdministratorServiceTypes.AWS_WEBHOOK_CALLBACK:
return Response({"status": "error", "message": "Invalid API key for this service"}, status=500)
return APIResponse(False, {"status": "error", "message": "Invalid API key for this service"}, status=500)

try:
data: dict = request.data
Expand All @@ -39,8 +39,8 @@ def webhook_task_queue_handler_view_endpoint(request):
# Handle the result (e.g., store it or log it)
print(f"Webhook executed: {func_name} with result: {result}")

return Response({"status": "success", "result": result})
return APIResponse(True, {"status": "success", "result": result})

except Exception as e:
logging.error(f"Error executing webhook task: {str(e)}")
return Response({"status": "error", "message": "An internal error has occurred."}, status=500)
return APIResponse(False, {"status": "error", "message": "An internal error has occurred."}, status=500)
Loading