GooDrive/backend/drive/services.py
Louis Mazin 97a25e4df8 nice
2026-10-13 21:15:53 +02:00

95 lines
3.3 KiB
Python

"""Reusable file-tree permission, validation and serialization helpers."""
import uuid
from django.core.exceptions import ValidationError
from django.http import Http404
from .models import DriveItem
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from .models import DriveItem
def error(message, code=400):
return Response({'detail':message}, status=code)
def item_info(item):
return {
'id':str(item.id), 'name':item.name, 'is_folder':item.is_folder,
'size':item.size,'mime_type':item.mime_type,'owner_id':item.owner_id,
'owner':item.owner.username,'parent':str(item.parent_id) if item.parent_id else None,
'is_shared':item.is_shared,'is_starred':item.is_starred,
'trashed_at':item.trashed_at.isoformat() if item.trashed_at else None,
'created_at':item.created_at.isoformat(),'updated_at':item.updated_at.isoformat(),
}
def get_item(pk):
try:
return DriveItem.objects.select_related('owner','parent').get(pk=pk)
except (DriveItem.DoesNotExist,ValueError,ValidationError):
raise Http404
def chain(item):
ancestors=[]
visited=set()
while item is not None:
if item.pk in visited:
raise Http404
visited.add(item.pk)
ancestors.append(item)
item=item.parent
return ancestors
def readable(item, user, allow_trashed=False):
ancestors=chain(item)
if not allow_trashed and any(n.trashed_at is not None for n in ancestors):
return False
if user.is_staff or user.is_superuser or item.owner_id == user.pk:
return True
return bool(ancestors[-1].is_shared and ancestors[-1].owner_id == item.owner_id)
def writable(item,user):
return user.is_staff or user.is_superuser or item.owner_id == user.pk
def valid_name(name):
if not isinstance(name,str) or not name.strip() or len(name)>255:
raise ValueError('Nom invalide (1 à 255 caractères).')
name=name.strip()
if name in ('.','..') or any(c in name for c in ('/','\\','\x00','\r','\n')):
raise ValueError('Ce nom contient des caractères interdits.')
return name.strip()
def target_parent(parent_id, user, owner_id=None):
if not parent_id:
return None
try:
parent=DriveItem.objects.select_related('parent').get(pk=uuid.UUID(str(parent_id)))
except (ValueError,DriveItem.DoesNotExist,AttributeError):
raise ValueError('Dossier de destination introuvable.')
if not parent.is_folder or not readable(parent,user) or not writable(parent,user):
raise PermissionError('Dossier de destination inaccessible en écriture.')
if owner_id is not None and parent.owner_id != owner_id:
raise PermissionError('Le propriétaire doit être identique.')
return parent
def descendant_files(node):
# Collect all physical paths before cascade delete.
files=[]
pending=[node.pk]
while pending:
batch=pending[:500];pending=pending[500:]
children=list(DriveItem.objects.filter(parent_id__in=batch).values_list('pk','is_folder','storage_key'))
pending.extend(row[0] for row in children)
for _,is_folder,key in children:
if not is_folder and key: files.append(key)
if not node.is_folder and node.storage_key:files.append(node.storage_key)
return files