Add site builder service to Docker Compose and remove obsolete scripts
- Introduced a new service `igny8_site_builder` in `docker-compose.app.yml` for site building functionality, including environment variables and volume mappings. - Deleted several outdated scripts: `create_test_users.py`, `test_image_write_access.py`, `update_free_plan.py`, and the database file `db.sqlite3` to clean up the backend. - Updated Django settings and URL configurations to integrate the new site builder module.
This commit is contained in:
5
backend/igny8_core/modules/site_builder/__init__.py
Normal file
5
backend/igny8_core/modules/site_builder/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Site Builder module (Phase 3)
|
||||
"""
|
||||
|
||||
|
||||
9
backend/igny8_core/modules/site_builder/apps.py
Normal file
9
backend/igny8_core/modules/site_builder/apps.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SiteBuilderConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'igny8_core.modules.site_builder'
|
||||
verbose_name = 'Site Builder'
|
||||
|
||||
|
||||
78
backend/igny8_core/modules/site_builder/serializers.py
Normal file
78
backend/igny8_core/modules/site_builder/serializers.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from igny8_core.business.site_building.models import SiteBlueprint, PageBlueprint
|
||||
|
||||
|
||||
class PageBlueprintSerializer(serializers.ModelSerializer):
|
||||
site_blueprint_id = serializers.PrimaryKeyRelatedField(
|
||||
source='site_blueprint',
|
||||
queryset=SiteBlueprint.objects.all(),
|
||||
write_only=True
|
||||
)
|
||||
site_blueprint = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PageBlueprint
|
||||
fields = [
|
||||
'id',
|
||||
'site_blueprint_id',
|
||||
'site_blueprint',
|
||||
'slug',
|
||||
'title',
|
||||
'type',
|
||||
'blocks_json',
|
||||
'status',
|
||||
'order',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]
|
||||
read_only_fields = [
|
||||
'site_blueprint',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]
|
||||
|
||||
|
||||
class SiteBlueprintSerializer(serializers.ModelSerializer):
|
||||
pages = PageBlueprintSerializer(many=True, read_only=True)
|
||||
site_id = serializers.IntegerField(write_only=True, required=False)
|
||||
sector_id = serializers.IntegerField(write_only=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = SiteBlueprint
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'config_json',
|
||||
'structure_json',
|
||||
'status',
|
||||
'hosting_type',
|
||||
'version',
|
||||
'deployed_version',
|
||||
'site_id',
|
||||
'sector_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'pages',
|
||||
]
|
||||
read_only_fields = [
|
||||
'structure_json',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'pages',
|
||||
]
|
||||
|
||||
def validate(self, attrs):
|
||||
site_id = attrs.pop('site_id', None)
|
||||
sector_id = attrs.pop('sector_id', None)
|
||||
if self.instance is None:
|
||||
if not site_id:
|
||||
raise serializers.ValidationError({'site_id': 'This field is required.'})
|
||||
if not sector_id:
|
||||
raise serializers.ValidationError({'sector_id': 'This field is required.'})
|
||||
attrs['site_id'] = site_id
|
||||
attrs['sector_id'] = sector_id
|
||||
return attrs
|
||||
|
||||
18
backend/igny8_core/modules/site_builder/urls.py
Normal file
18
backend/igny8_core/modules/site_builder/urls.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.urls import include, path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from igny8_core.modules.site_builder.views import (
|
||||
PageBlueprintViewSet,
|
||||
SiteAssetView,
|
||||
SiteBlueprintViewSet,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'blueprints', SiteBlueprintViewSet, basename='site_blueprint')
|
||||
router.register(r'pages', PageBlueprintViewSet, basename='page_blueprint')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
path('assets/', SiteAssetView.as_view(), name='site_builder_assets'),
|
||||
]
|
||||
|
||||
150
backend/igny8_core/modules/site_builder/views.py
Normal file
150
backend/igny8_core/modules/site_builder/views.py
Normal file
@@ -0,0 +1,150 @@
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from igny8_core.api.base import SiteSectorModelViewSet
|
||||
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
|
||||
from igny8_core.api.response import success_response, error_response
|
||||
from igny8_core.api.throttles import DebugScopedRateThrottle
|
||||
from igny8_core.business.site_building.models import SiteBlueprint, PageBlueprint
|
||||
from igny8_core.business.site_building.services import (
|
||||
PageGenerationService,
|
||||
SiteBuilderFileService,
|
||||
StructureGenerationService,
|
||||
)
|
||||
from igny8_core.modules.site_builder.serializers import (
|
||||
PageBlueprintSerializer,
|
||||
SiteBlueprintSerializer,
|
||||
)
|
||||
|
||||
|
||||
class SiteBlueprintViewSet(SiteSectorModelViewSet):
|
||||
"""
|
||||
CRUD + AI actions for site blueprints.
|
||||
"""
|
||||
|
||||
queryset = SiteBlueprint.objects.all().prefetch_related('pages')
|
||||
serializer_class = SiteBlueprintSerializer
|
||||
permission_classes = [IsAuthenticatedAndActive, IsEditorOrAbove]
|
||||
throttle_scope = 'site_builder'
|
||||
throttle_classes = [DebugScopedRateThrottle]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
from igny8_core.auth.models import Site, Sector
|
||||
|
||||
site_id = serializer.validated_data.pop('site_id', None)
|
||||
sector_id = serializer.validated_data.pop('sector_id', None)
|
||||
if not site_id or not sector_id:
|
||||
raise ValidationError({'detail': 'site_id and sector_id are required.'})
|
||||
|
||||
try:
|
||||
site = Site.objects.get(id=site_id)
|
||||
except Site.DoesNotExist:
|
||||
raise ValidationError({'site_id': 'Site not found.'})
|
||||
|
||||
try:
|
||||
sector = Sector.objects.get(id=sector_id, site=site)
|
||||
except Sector.DoesNotExist:
|
||||
raise ValidationError({'sector_id': 'Sector does not belong to the selected site.'})
|
||||
|
||||
serializer.save(account=site.account, site=site, sector=sector)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def generate_structure(self, request, pk=None):
|
||||
blueprint = self.get_object()
|
||||
business_brief = request.data.get('business_brief') or \
|
||||
blueprint.config_json.get('business_brief', '')
|
||||
objectives = request.data.get('objectives') or \
|
||||
blueprint.config_json.get('objectives', [])
|
||||
style = request.data.get('style') or \
|
||||
blueprint.config_json.get('style', {})
|
||||
|
||||
service = StructureGenerationService()
|
||||
result = service.generate_structure(
|
||||
site_blueprint=blueprint,
|
||||
business_brief=business_brief,
|
||||
objectives=objectives,
|
||||
style_preferences=style,
|
||||
metadata=request.data.get('metadata', {}),
|
||||
)
|
||||
return Response(result, status=status.HTTP_202_ACCEPTED if 'task_id' in result else status.HTTP_200_OK)
|
||||
|
||||
|
||||
class PageBlueprintViewSet(SiteSectorModelViewSet):
|
||||
"""
|
||||
CRUD endpoints for page blueprints with content generation hooks.
|
||||
"""
|
||||
|
||||
queryset = PageBlueprint.objects.select_related('site_blueprint')
|
||||
serializer_class = PageBlueprintSerializer
|
||||
permission_classes = [IsAuthenticatedAndActive, IsEditorOrAbove]
|
||||
throttle_scope = 'site_builder'
|
||||
throttle_classes = [DebugScopedRateThrottle]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
page = serializer.save()
|
||||
# Align account/site/sector with parent blueprint
|
||||
page.account = page.site_blueprint.account
|
||||
page.site = page.site_blueprint.site
|
||||
page.sector = page.site_blueprint.sector
|
||||
page.save(update_fields=['account', 'site', 'sector'])
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def generate_content(self, request, pk=None):
|
||||
page = self.get_object()
|
||||
service = PageGenerationService()
|
||||
result = service.generate_page_content(page, force_regenerate=request.data.get('force', False))
|
||||
return success_response(result, request=request)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def regenerate(self, request, pk=None):
|
||||
page = self.get_object()
|
||||
service = PageGenerationService()
|
||||
result = service.regenerate_page(page)
|
||||
return success_response(result, request=request)
|
||||
|
||||
|
||||
class SiteAssetView(APIView):
|
||||
"""
|
||||
File management for Site Builder assets.
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.file_service = SiteBuilderFileService()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
site_id = request.query_params.get('site_id')
|
||||
folder = request.query_params.get('folder')
|
||||
if not site_id:
|
||||
return error_response('site_id is required', status.HTTP_400_BAD_REQUEST, request)
|
||||
files = self.file_service.list_files(request.user, int(site_id), folder=folder)
|
||||
return success_response({'files': files}, request)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
site_id = request.data.get('site_id')
|
||||
version = int(request.data.get('version', 1))
|
||||
folder = request.data.get('folder', 'images')
|
||||
upload = request.FILES.get('file')
|
||||
if not site_id or not upload:
|
||||
return error_response('site_id and file are required', status.HTTP_400_BAD_REQUEST, request)
|
||||
info = self.file_service.upload_file(request.user, int(site_id), upload, folder=folder, version=version)
|
||||
return success_response(info, request, status.HTTP_201_CREATED)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
site_id = request.data.get('site_id')
|
||||
file_path = request.data.get('path')
|
||||
version = int(request.data.get('version', 1))
|
||||
if not site_id or not file_path:
|
||||
return error_response('site_id and path are required', status.HTTP_400_BAD_REQUEST, request)
|
||||
deleted = self.file_service.delete_file(request.user, int(site_id), file_path, version=version)
|
||||
if deleted:
|
||||
return success_response({'deleted': True}, request, status.HTTP_204_NO_CONTENT)
|
||||
return error_response('File not found', status.HTTP_404_NOT_FOUND, request)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user