chore: error handling (#6657)
* fix: ai completions * fix: reset password endpoints * fix: intake issue list * fix: identifier validation, uuid validation
This commit is contained in:
parent
6fac320a05
commit
da469dac18
9 changed files with 65 additions and 21 deletions
14
apiserver/plane/app/views/external/base.py
vendored
14
apiserver/plane/app/views/external/base.py
vendored
|
|
@ -3,7 +3,7 @@ import os
|
||||||
from typing import List, Dict, Tuple
|
from typing import List, Dict, Tuple
|
||||||
|
|
||||||
# Third party import
|
# Third party import
|
||||||
import litellm
|
from openai import OpenAI
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
@ -116,12 +116,14 @@ def get_llm_response(task, prompt, api_key: str, model: str, provider: str) -> T
|
||||||
if provider.lower() == "gemini":
|
if provider.lower() == "gemini":
|
||||||
model = f"gemini/{model}"
|
model = f"gemini/{model}"
|
||||||
|
|
||||||
response = litellm.completion(
|
client = OpenAI(api_key=api_key)
|
||||||
|
chat_completion = client.chat.completions.create(
|
||||||
model=model,
|
model=model,
|
||||||
messages=[{"role": "user", "content": final_text}],
|
messages=[
|
||||||
api_key=api_key,
|
{"role": "user", "content": final_text}
|
||||||
|
]
|
||||||
)
|
)
|
||||||
text = response.choices[0].message.content.strip()
|
text = chat_completion.choices[0].message.content
|
||||||
return text, None
|
return text, None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_exception(e)
|
log_exception(e)
|
||||||
|
|
@ -175,7 +177,7 @@ class WorkspaceGPTIntegrationEndpoint(BaseAPIView):
|
||||||
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
|
||||||
def post(self, request, slug):
|
def post(self, request, slug):
|
||||||
api_key, model, provider = get_llm_config()
|
api_key, model, provider = get_llm_config()
|
||||||
|
|
||||||
if not api_key or not model or not provider:
|
if not api_key or not model or not provider:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "LLM provider API key and model are required"},
|
{"error": "LLM provider API key and model are required"},
|
||||||
|
|
|
||||||
|
|
@ -174,14 +174,17 @@ class IntakeIssueViewSet(BaseViewSet):
|
||||||
|
|
||||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
intake_id = Intake.objects.filter(
|
intake = Intake.objects.filter(
|
||||||
workspace__slug=slug, project_id=project_id
|
workspace__slug=slug, project_id=project_id
|
||||||
).first()
|
).first()
|
||||||
|
if not intake:
|
||||||
|
return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
project = Project.objects.get(pk=project_id)
|
project = Project.objects.get(pk=project_id)
|
||||||
filters = issue_filters(request.GET, "GET", "issue__")
|
filters = issue_filters(request.GET, "GET", "issue__")
|
||||||
intake_issue = (
|
intake_issue = (
|
||||||
IntakeIssue.objects.filter(
|
IntakeIssue.objects.filter(
|
||||||
intake_id=intake_id.id, project_id=project_id, **filters
|
intake_id=intake.id, project_id=project_id, **filters
|
||||||
)
|
)
|
||||||
.select_related("issue")
|
.select_related("issue")
|
||||||
.prefetch_related("issue__labels")
|
.prefetch_related("issue__labels")
|
||||||
|
|
|
||||||
|
|
@ -547,7 +547,7 @@ class IssueViewSet(BaseViewSet):
|
||||||
)
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if the role is guest and guest_view_all_features is false and owned by is not
|
if the role is guest and guest_view_all_features is false and owned by is not
|
||||||
the requesting user then dont show the issue
|
the requesting user then dont show the issue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -1116,8 +1116,22 @@ class IssueMetaEndpoint(BaseAPIView):
|
||||||
|
|
||||||
class IssueDetailIdentifierEndpoint(BaseAPIView):
|
class IssueDetailIdentifierEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
def strict_str_to_int(self, s):
|
||||||
|
if not s.isdigit() and not (s.startswith('-') and s[1:].isdigit()):
|
||||||
|
raise ValueError("Invalid integer string")
|
||||||
|
return int(s)
|
||||||
|
|
||||||
def get(self, request, slug, project_identifier, issue_identifier):
|
def get(self, request, slug, project_identifier, issue_identifier):
|
||||||
|
|
||||||
|
# Check if the issue identifier is a valid integer
|
||||||
|
try:
|
||||||
|
issue_identifier = self.strict_str_to_int(issue_identifier)
|
||||||
|
except ValueError:
|
||||||
|
return Response(
|
||||||
|
{"error": "Invalid issue identifier"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
# Fetch the project
|
# Fetch the project
|
||||||
project = Project.objects.get(
|
project = Project.objects.get(
|
||||||
identifier__iexact=project_identifier,
|
identifier__iexact=project_identifier,
|
||||||
|
|
@ -1240,7 +1254,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView):
|
||||||
)
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if the role is guest and guest_view_all_features is false and owned by is not
|
if the role is guest and guest_view_all_features is false and owned by is not
|
||||||
the requesting user then dont show the issue
|
the requesting user then dont show the issue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,7 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||||
issue_id=str(issue_id),
|
issue_id=str(issue_id),
|
||||||
project_id=str(project_id),
|
project_id=str(project_id),
|
||||||
current_instance=json.dumps(
|
current_instance=json.dumps(
|
||||||
{"module_name": module_issue.first().module.name}
|
{"module_name": module_issue.first().module.name if (module_issue.first() and module_issue.first().module) else None}
|
||||||
),
|
),
|
||||||
epoch=int(timezone.now().timestamp()),
|
epoch=int(timezone.now().timestamp()),
|
||||||
notification=True,
|
notification=True,
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,20 @@ class ResetPasswordEndpoint(View):
|
||||||
def post(self, request, uidb64, token):
|
def post(self, request, uidb64, token):
|
||||||
try:
|
try:
|
||||||
# Decode the id from the uidb64
|
# Decode the id from the uidb64
|
||||||
id = smart_str(urlsafe_base64_decode(uidb64))
|
try:
|
||||||
user = User.objects.get(id=id)
|
id = smart_str(urlsafe_base64_decode(uidb64))
|
||||||
|
user = User.objects.get(id=id)
|
||||||
|
except (ValueError, User.DoesNotExist):
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD_TOKEN"],
|
||||||
|
error_message="INVALID_PASSWORD_TOKEN",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_app=True),
|
||||||
|
"accounts/reset-password?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
# check if the token is valid for the user
|
# check if the token is valid for the user
|
||||||
if not PasswordResetTokenGenerator().check_token(user, token):
|
if not PasswordResetTokenGenerator().check_token(user, token):
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ from celery import shared_task
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from plane.app.serializers import IssueActivitySerializer
|
|
||||||
from plane.bgtasks.notification_task import notifications
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
|
from plane.app.serializers import IssueActivitySerializer
|
||||||
|
from plane.bgtasks.notification_task import notifications
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
CommentReaction,
|
CommentReaction,
|
||||||
Cycle,
|
Cycle,
|
||||||
|
|
@ -32,7 +32,7 @@ from plane.settings.redis import redis_instance
|
||||||
from plane.utils.exception_logger import log_exception
|
from plane.utils.exception_logger import log_exception
|
||||||
from plane.bgtasks.webhook_task import webhook_activity
|
from plane.bgtasks.webhook_task import webhook_activity
|
||||||
from plane.utils.issue_relation_mapper import get_inverse_relation
|
from plane.utils.issue_relation_mapper import get_inverse_relation
|
||||||
|
from plane.utils.valid_uuid import is_valid_uuid
|
||||||
|
|
||||||
# Track Changes in name
|
# Track Changes in name
|
||||||
def track_name(
|
def track_name(
|
||||||
|
|
@ -1568,9 +1568,14 @@ def issue_activity(
|
||||||
try:
|
try:
|
||||||
issue_activities = []
|
issue_activities = []
|
||||||
|
|
||||||
|
# check if project_id is valid
|
||||||
|
if not is_valid_uuid(project_id):
|
||||||
|
return
|
||||||
|
|
||||||
project = Project.objects.get(pk=project_id)
|
project = Project.objects.get(pk=project_id)
|
||||||
workspace_id = project.workspace_id
|
workspace_id = project.workspace_id
|
||||||
|
|
||||||
|
|
||||||
if issue_id is not None:
|
if issue_id is not None:
|
||||||
if origin:
|
if origin:
|
||||||
ri = redis_instance()
|
ri = redis_instance()
|
||||||
|
|
|
||||||
8
apiserver/plane/utils/valid_uuid.py
Normal file
8
apiserver/plane/utils/valid_uuid.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
def is_valid_uuid(uuid_str):
|
||||||
|
try:
|
||||||
|
uuid.UUID(uuid_str, version=4)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
Django==4.2.18
|
Django==4.2.18
|
||||||
# rest framework
|
# rest framework
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
# postgres
|
# postgres
|
||||||
psycopg==3.1.18
|
psycopg==3.1.18
|
||||||
psycopg-binary==3.1.18
|
psycopg-binary==3.1.18
|
||||||
psycopg-c==3.1.18
|
psycopg-c==3.1.18
|
||||||
|
|
@ -37,7 +37,7 @@ uvicorn==0.29.0
|
||||||
# sockets
|
# sockets
|
||||||
channels==4.1.0
|
channels==4.1.0
|
||||||
# ai
|
# ai
|
||||||
litellm==1.51.0
|
openai==1.63.2
|
||||||
# slack
|
# slack
|
||||||
slack-sdk==3.27.1
|
slack-sdk==3.27.1
|
||||||
# apm
|
# apm
|
||||||
|
|
@ -66,4 +66,4 @@ PyJWT==2.8.0
|
||||||
opentelemetry-api==1.28.1
|
opentelemetry-api==1.28.1
|
||||||
opentelemetry-sdk==1.28.1
|
opentelemetry-sdk==1.28.1
|
||||||
opentelemetry-instrumentation-django==0.49b1
|
opentelemetry-instrumentation-django==0.49b1
|
||||||
opentelemetry-exporter-otlp==1.28.1
|
opentelemetry-exporter-otlp==1.28.1
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
# debug toolbar
|
# debug toolbar
|
||||||
django-debug-toolbar==4.3.0
|
django-debug-toolbar==4.3.0
|
||||||
# formatter
|
# formatter
|
||||||
ruff==0.4.2
|
ruff==0.9.7
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue