-
Notifications
You must be signed in to change notification settings - Fork 2
deploy main #61
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
deploy main #61
Changes from all commits
22a88c6
88e23f9
ecc0791
fc9b1cd
53d6418
6aaf28b
1c67a21
2492fe0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import uuid | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('block_manager', '0004_add_user_to_project'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='project', | ||
| name='share_token', | ||
| field=models.UUIDField( | ||
| blank=True, | ||
| db_index=True, | ||
| default=None, | ||
| help_text='Unique token for public sharing; generated on first share', | ||
| null=True, | ||
| unique=True, | ||
| ), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='project', | ||
| name='is_shared', | ||
| field=models.BooleanField( | ||
| default=False, | ||
| help_text='Whether this project is publicly accessible via share link', | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import uuid | ||
| from rest_framework import status | ||
| from rest_framework.decorators import api_view | ||
| from rest_framework.response import Response | ||
| from django.shortcuts import get_object_or_404 | ||
|
|
||
| from block_manager.models import Project | ||
|
|
||
|
|
||
| @api_view(['GET']) | ||
| def get_shared_project(request, share_token): | ||
| """ | ||
| Public endpoint — returns project metadata for a shared project. | ||
| No authentication required. | ||
| Returns 404 if the token doesn't exist or sharing is disabled. | ||
| """ | ||
| try: | ||
| project = Project.objects.get(share_token=share_token, is_shared=True) | ||
| except Project.DoesNotExist: | ||
| return Response( | ||
| {'error': 'Shared project not found or link is no longer active'}, | ||
| status=status.HTTP_404_NOT_FOUND | ||
| ) | ||
|
|
||
| owner_display_name = None | ||
| if project.user: | ||
| owner_display_name = project.user.display_name or "Anonymous" | ||
|
|
||
| return Response({ | ||
| 'name': project.name, | ||
| 'description': project.description, | ||
| 'framework': project.framework, | ||
| 'owner_display_name': owner_display_name, | ||
| 'share_token': str(project.share_token), | ||
| }) | ||
|
Comment on lines
+10
to
+35
|
||
|
|
||
|
|
||
| @api_view(['GET']) | ||
| def get_shared_architecture(request, share_token): | ||
| """ | ||
| Public endpoint — returns the canvas state for a shared project. | ||
| No authentication required. | ||
| Returns 404 if the token doesn't exist or sharing is disabled. | ||
| """ | ||
| try: | ||
| project = Project.objects.get(share_token=share_token, is_shared=True) | ||
| except Project.DoesNotExist: | ||
| return Response( | ||
| {'error': 'Shared project not found or link is no longer active'}, | ||
| status=status.HTTP_404_NOT_FOUND | ||
| ) | ||
|
|
||
| try: | ||
| architecture = project.architecture | ||
| except Exception: | ||
| return Response({'nodes': [], 'edges': [], 'groupDefinitions': []}) | ||
|
Comment on lines
+53
to
+56
|
||
|
|
||
| if architecture.canvas_state: | ||
| return Response(architecture.canvas_state) | ||
|
|
||
| return Response({'nodes': [], 'edges': [], 'groupDefinitions': []}) | ||
|
|
||
|
|
||
| @api_view(['POST']) | ||
| def enable_sharing(request, project_id): | ||
| """ | ||
| Enable public sharing for a project. | ||
| Authentication required; only the project owner can call this. | ||
| Generates a share_token on first use; reuses the existing token on subsequent calls | ||
| so the perma-link stays stable. | ||
| """ | ||
| if not hasattr(request, 'firebase_user') or not request.firebase_user: | ||
| return Response( | ||
| {'error': 'Authentication required'}, | ||
| status=status.HTTP_401_UNAUTHORIZED | ||
| ) | ||
|
|
||
| project = get_object_or_404(Project, pk=project_id) | ||
|
|
||
| if project.user != request.firebase_user: | ||
| return Response( | ||
| {'error': 'You do not have permission to share this project'}, | ||
| status=status.HTTP_403_FORBIDDEN | ||
| ) | ||
|
|
||
| if project.share_token is None: | ||
| project.share_token = uuid.uuid4() | ||
|
|
||
| project.is_shared = True | ||
| project.save(update_fields=['share_token', 'is_shared']) | ||
|
|
||
| return Response({ | ||
| 'share_token': str(project.share_token), | ||
| 'is_shared': project.is_shared, | ||
| }) | ||
|
|
||
|
|
||
| @api_view(['DELETE']) | ||
| def disable_sharing(request, project_id): | ||
| """ | ||
| Disable public sharing for a project. | ||
| Authentication required; only the project owner can call this. | ||
| The share_token is preserved so re-enabling restores the same URL. | ||
| """ | ||
| if not hasattr(request, 'firebase_user') or not request.firebase_user: | ||
| return Response( | ||
| {'error': 'Authentication required'}, | ||
| status=status.HTTP_401_UNAUTHORIZED | ||
| ) | ||
|
|
||
| project = get_object_or_404(Project, pk=project_id) | ||
|
|
||
| if project.user != request.firebase_user: | ||
| return Response( | ||
| {'error': 'You do not have permission to modify this project'}, | ||
| status=status.HTTP_403_FORBIDDEN | ||
| ) | ||
|
|
||
| project.is_shared = False | ||
| project.save(update_fields=['is_shared']) | ||
|
|
||
| return Response({'is_shared': False}) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential information disclosure in shared project endpoint. The
owner_display_namefield at line 27 falls back to "Anonymous" whendisplay_nameis not set, but this reveals that a user exists even if they haven't set a display name. Consider using a consistent anonymization approach or omitting the field entirely whendisplay_nameis None/empty to avoid revealing user existence patterns.