feat(collection): enhance collection sharing logic and improve data handling on invite acceptance

This commit is contained in:
Sean Morley
2025-12-19 13:14:11 -05:00
parent 01950598b2
commit efff6cbd97
2 changed files with 34 additions and 6 deletions

View File

@@ -93,7 +93,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
if self.action == 'destroy':
return Collection.objects.filter(user=self.request.user.id)
if self.action in ['update', 'partial_update']:
if self.action in ['update', 'partial_update', 'leave']:
return Collection.objects.filter(
Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct()
@@ -115,9 +115,9 @@ class CollectionViewSet(viewsets.ModelViewSet):
Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct()
# For list action, include collections owned by the user or shared with the user, that are not archived
# For list action and default base queryset, return collections owned by the user (exclude shared)
return Collection.objects.filter(
(Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
Q(user=self.request.user.id) & Q(is_archived=False)
).distinct()
def get_queryset(self):
@@ -131,8 +131,10 @@ class CollectionViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
# List should only return collections owned by the requesting user (shared collections are available
# via the `shared` action).
queryset = Collection.objects.filter(
(Q(user=request.user.id) | Q(shared_with=request.user)) & Q(is_archived=False)
Q(user=request.user.id) & Q(is_archived=False)
).distinct().select_related('user').prefetch_related(
Prefetch(
'locations__images',

View File

@@ -210,11 +210,37 @@
});
if (res.ok) {
// Try to parse returned collection data
let data: any = null;
try {
data = await res.json();
} catch (e) {
data = null;
}
// Remove invite from list
invites = invites.filter((i) => i.id !== invite.id);
addToast('success', `${$t('invites.accepted')} "${invite.name}"`);
// Optionally refresh shared collections
await goto(window.location.pathname, { invalidateAll: true });
// If API returned the accepted collection, add it to sharedCollections immediately
if (data && (data.collection || data.result || data.id)) {
// Normalize expected shapes: {collection: {...}} or collection object directly
const newCollection = data.collection ? data.collection : data;
// Prepend so it's visible at top
sharedCollections = [newCollection as SlimCollection, ...sharedCollections];
} else {
// Fallback: refresh shared collections from API
try {
const sharedRes = await fetch(`/api/collections/shared/?nested=true`);
if (sharedRes.ok) {
const sharedData = await sharedRes.json();
// Prefer results if paginated
sharedCollections = sharedData.results ? sharedData.results : sharedData;
}
} catch (e) {
// ignore fallback errors; user already got success toast
}
}
} else {
const error = await res.json();
addToast('error', error.error || $t('invites.accept_failed'));