Initial commit: Aurora Moments self-hosted photo/video sharing platform
Django/DRF + PostgreSQL/Redis/Celery backend with accounts, library, moments, and notifications apps; React/TypeScript/Vite frontend with Instagram-style sidebar nav, feed, profiles, nested comment replies, notifications, and photo/video upload with ffmpeg-generated posters. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from apps.core.models import Audience, TimeStampedModel
|
||||
from apps.library.models import LibraryItem
|
||||
|
||||
|
||||
class MomentQuerySet(models.QuerySet):
|
||||
def visible_to(self, user):
|
||||
"""FAMILY and CUSTOM audiences intentionally fall back to
|
||||
owner-only visibility until circles/custom-audience membership
|
||||
(V1.5) exists — better to under-share than leak content."""
|
||||
from apps.accounts.models import Follow
|
||||
|
||||
following_ids = Follow.objects.filter(
|
||||
follower=user, status=Follow.Status.ACCEPTED
|
||||
).values_list("following_id", flat=True)
|
||||
follower_ids = set(
|
||||
Follow.objects.filter(
|
||||
following=user, status=Follow.Status.ACCEPTED
|
||||
).values_list("follower_id", flat=True)
|
||||
)
|
||||
friend_ids = follower_ids & set(following_ids)
|
||||
|
||||
return self.filter(
|
||||
models.Q(owner=user)
|
||||
| models.Q(audience=Audience.PUBLIC)
|
||||
| models.Q(audience=Audience.FOLLOWERS, owner_id__in=following_ids)
|
||||
| models.Q(audience=Audience.FRIENDS, owner_id__in=friend_ids)
|
||||
).distinct()
|
||||
|
||||
|
||||
class Moment(TimeStampedModel):
|
||||
"""The social-media object: one or more LibraryItems shared with an
|
||||
explicit audience. Sharing a Moment never moves or copies the original
|
||||
file — it just makes the existing library item visible to others."""
|
||||
|
||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="moments", on_delete=models.CASCADE)
|
||||
caption = models.TextField(max_length=2200, blank=True)
|
||||
location = models.CharField(max_length=255, blank=True)
|
||||
audience = models.CharField(max_length=10, choices=Audience.choices, default=Audience.PUBLIC)
|
||||
|
||||
objects = MomentQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
|
||||
def __str__(self):
|
||||
return f"Moment({self.id}) by {self.owner}"
|
||||
|
||||
|
||||
class MomentItem(models.Model):
|
||||
moment = models.ForeignKey(Moment, related_name="items", on_delete=models.CASCADE)
|
||||
library_item = models.ForeignKey(LibraryItem, related_name="moment_items", on_delete=models.CASCADE)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ["order"]
|
||||
unique_together = ("moment", "library_item")
|
||||
|
||||
|
||||
class Comment(TimeStampedModel):
|
||||
moment = models.ForeignKey(Moment, related_name="comments", on_delete=models.CASCADE)
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="comments", on_delete=models.CASCADE)
|
||||
parent = models.ForeignKey("self", related_name="replies", on_delete=models.CASCADE, null=True, blank=True)
|
||||
body = models.TextField(max_length=1000)
|
||||
|
||||
class Meta:
|
||||
ordering = ["created_at"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.author}: {self.body[:40]}"
|
||||
|
||||
|
||||
class Reaction(models.Model):
|
||||
class ReactionType(models.TextChoices):
|
||||
LIKE = "like", "Like"
|
||||
|
||||
moment = models.ForeignKey(Moment, related_name="reactions", on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="reactions", on_delete=models.CASCADE)
|
||||
reaction_type = models.CharField(max_length=10, choices=ReactionType.choices, default=ReactionType.LIKE)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("moment", "user")
|
||||
Reference in New Issue
Block a user