From 218f8511f53bff333809c596465095b2d2189ac4 Mon Sep 17 00:00:00 2001 From: Chandrapal Badshah <12944530+Chan9390@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:13:57 +0530 Subject: [PATCH] chore: refactor lighthouse config singleton code --- api/src/backend/api/v1/serializers.py | 76 --------------------------- api/src/backend/api/v1/urls.py | 4 +- api/src/backend/api/v1/views.py | 39 ++++++-------- 3 files changed, 17 insertions(+), 102 deletions(-) diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index c6d216145d..ba6de58f40 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -3187,82 +3187,6 @@ class LighthouseTenantConfigSerializer(RLSSerializer): } -class LighthouseTenantConfigCreateSerializer(RLSSerializer, BaseWriteSerializer): - """ - Create serializer for LighthouseTenantConfiguration. - - Note: One record per tenant constraint is enforced by database and model.clean(). - """ - - class Meta: - model = LighthouseTenantConfiguration - fields = [ - "business_context", - "default_provider", - "default_models", - ] - - def validate(self, attrs): - request = self.context.get("request") - tenant_id = self.context.get("tenant_id") or ( - getattr(request, "tenant_id", None) if request else None - ) - - default_provider = attrs.get("default_provider", "") - default_models = attrs.get("default_models", {}) - - if default_provider: - supported = set(LighthouseProviderConfiguration.LLMProviderChoices.values) - if default_provider not in supported: - raise ValidationError( - {"default_provider": f"Unsupported provider '{default_provider}'."} - ) - if not LighthouseProviderConfiguration.objects.filter( - tenant_id=tenant_id, provider_type=default_provider, is_active=True - ).exists(): - raise ValidationError( - { - "default_provider": f"No active configuration found for '{default_provider}'." - } - ) - - if default_models is not None and not isinstance(default_models, dict): - raise ValidationError( - {"default_models": "Must be an object mapping provider -> model_id."} - ) - - for provider_type, model_id in (default_models or {}).items(): - provider_cfg = LighthouseProviderConfiguration.objects.filter( - tenant_id=tenant_id, provider_type=provider_type, is_active=True - ).first() - if not provider_cfg: - raise ValidationError( - { - "default_models": f"No active configuration for provider '{provider_type}'." - } - ) - if not LighthouseProviderModels.objects.filter( - tenant_id=tenant_id, - provider_configuration=provider_cfg, - model_id=model_id, - ).exists(): - raise ValidationError( - { - "default_models": f"Invalid model '{model_id}' for provider '{provider_type}'." - } - ) - - return super().validate(attrs) - - def create(self, validated_data): - try: - return super().create(validated_data) - except IntegrityError: - raise ValidationError( - {"tenant_id": "Tenant Lighthouse configuration already exists."} - ) - - class LighthouseTenantConfigUpdateSerializer(BaseWriteSerializer): class Meta: model = LighthouseTenantConfiguration diff --git a/api/src/backend/api/v1/urls.py b/api/src/backend/api/v1/urls.py index baa6dba61e..01ff43c8f4 100644 --- a/api/src/backend/api/v1/urls.py +++ b/api/src/backend/api/v1/urls.py @@ -37,12 +37,12 @@ from api.v1.views import ( ScheduleViewSet, SchemaView, TaskViewSet, + TenantApiKeyViewSet, TenantFinishACSView, TenantMembersViewSet, TenantViewSet, UserRoleRelationshipView, UserViewSet, - TenantApiKeyViewSet, ) router = routers.DefaultRouter(trailing_slash=False) @@ -154,7 +154,7 @@ urlpatterns = [ path( "lighthouse/config", LighthouseTenantConfigViewSet.as_view( - {"get": "retrieve", "post": "create", "patch": "partial_update"} + {"get": "list", "patch": "partial_update"} ), name="lighthouse-config", ), diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 81af148e7c..e1763b0e5e 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -171,7 +171,6 @@ from api.v1.serializers import ( LighthouseProviderConfigSerializer, LighthouseProviderConfigUpdateSerializer, LighthouseProviderModelsSerializer, - LighthouseTenantConfigCreateSerializer, LighthouseTenantConfigSerializer, LighthouseTenantConfigUpdateSerializer, MembershipSerializer, @@ -4354,35 +4353,29 @@ class LighthouseProviderConfigViewSet(BaseRLSViewSet): @extend_schema_view( list=extend_schema( - tags=["Lighthouse AI"], - summary="List tenant config", - description="List Lighthouse AI tenant-level settings for the current tenant.", - ), - retrieve=extend_schema( tags=["Lighthouse AI"], summary="Get tenant config", - description="Retrieve tenant Lighthouse AI settings by ID.", - ), - create=extend_schema( - tags=["Lighthouse AI"], - summary="Create tenant config", - description="Create Lighthouse AI tenant-level settings. Only one configuration is allowed per tenant.", + description="Retrieve current tenant-level Lighthouse AI settings. Returns a single configuration object.", ), partial_update=extend_schema( tags=["Lighthouse AI"], summary="Update tenant config", - description="Update tenant-level settings. Validates that the default provider is configured and active and that default model IDs exist for the chosen providers.", - ), - destroy=extend_schema( - tags=["Lighthouse AI"], - summary="Delete tenant config", - description="Delete tenant Lighthouse AI settings for the current tenant.", + description="Update tenant-level settings. Validates that the default provider is configured and active and that default model IDs exist for the chosen providers. Auto-creates configuration if it doesn't exist.", ), ) class LighthouseTenantConfigViewSet(BaseRLSViewSet): + """ + Singleton endpoint for tenant-level Lighthouse AI configuration. + + This viewset implements a true singleton pattern: + - GET returns the single configuration object (or 404 if not found) + - PATCH updates/creates the configuration (upsert semantics) + - No ID is required in the URL + """ + queryset = LighthouseTenantConfiguration.objects.all() serializer_class = LighthouseTenantConfigSerializer - http_method_names = ["get", "post", "patch", "delete"] + http_method_names = ["get", "patch"] def get_queryset(self): if getattr(self, "swagger_fake_view", False): @@ -4392,9 +4385,7 @@ class LighthouseTenantConfigViewSet(BaseRLSViewSet): ) def get_serializer_class(self): - if self.action == "create": - return LighthouseTenantConfigCreateSerializer - elif self.action == "partial_update": + if self.action == "partial_update": return LighthouseTenantConfigUpdateSerializer return super().get_serializer_class() @@ -4408,8 +4399,8 @@ class LighthouseTenantConfigViewSet(BaseRLSViewSet): self.check_object_permissions(self.request, obj) return obj - def retrieve(self, request, *args, **kwargs): - """GET endpoint for singleton - no pk required.""" + def list(self, request, *args, **kwargs): + """GET endpoint for singleton - returns single object, not an array.""" instance = self.get_object() serializer = self.get_serializer(instance) return Response(serializer.data)