new migrations in apps.epic app; new models, urls, views handle the founder of a New Game inviting a friend via email to a game gatekeeper; ea. may drop coin in any of up to 6 avail. slots; FTs & ITs passing
This commit is contained in:
27
src/apps/epic/migrations/0003_roominvite.py
Normal file
27
src/apps/epic/migrations/0003_roominvite.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 6.0 on 2026-03-13 22:19
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('epic', '0002_alter_room_renewal_period'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RoomInvite',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('invitee_email', models.EmailField(max_length=254)),
|
||||
('status', models.CharField(choices=[('PENDING', 'Pending'), ('ACCEPTED', 'Accepted'), ('DECLINED', 'Declined')], default='PENDING', max_length=10)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('inviter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_invites', to=settings.AUTH_USER_MODEL)),
|
||||
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='epic.room')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -41,6 +41,7 @@ class Room(models.Model):
|
||||
board_state = models.JSONField(default=dict)
|
||||
seed_count = models.IntegerField(default=12)
|
||||
|
||||
|
||||
class GateSlot(models.Model):
|
||||
EMPTY = "EMPTY"
|
||||
RESERVED = "RESERVED"
|
||||
@@ -66,6 +67,25 @@ class GateSlot(models.Model):
|
||||
filled_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
|
||||
class RoomInvite(models.Model):
|
||||
PENDING = "PENDING"
|
||||
ACCEPTED = "ACCEPTED"
|
||||
DECLINED = "DECLINED"
|
||||
STATUS_CHOICES = [
|
||||
(PENDING, "Pending"),
|
||||
(ACCEPTED, "Accepted"),
|
||||
(DECLINED, "Declined"),
|
||||
]
|
||||
|
||||
room = models.ForeignKey(Room, on_delete=models.CASCADE, related_name="invites")
|
||||
inviter = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="sent_invites"
|
||||
)
|
||||
invitee_email = models.EmailField()
|
||||
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=PENDING)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Room)
|
||||
def create_gate_slots(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from datetime import timedelta
|
||||
from django.db.models import Q
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from apps.lyric.models import Token, User
|
||||
from apps.epic.models import Room, GateSlot, debit_token
|
||||
from apps.epic.models import GateSlot, Room, RoomInvite, debit_token
|
||||
|
||||
|
||||
class RoomCreationTest(TestCase):
|
||||
@@ -62,3 +63,31 @@ class CoinTokenInUseTest(TestCase):
|
||||
html = self.coin.tooltip_room_html()
|
||||
self.assertIn(f'href="{room_url}"', html)
|
||||
self.assertIn(self.room.name, html)
|
||||
|
||||
|
||||
class RoomInviteTest(TestCase):
|
||||
def setUp(self):
|
||||
self.founder = User.objects.create(email="founder@example.com")
|
||||
self.room = Room.objects.create(name="Dragon's Den", owner=self.founder)
|
||||
|
||||
def test_founder_can_invite_by_email(self):
|
||||
invite = RoomInvite.objects.create(
|
||||
room=self.room,
|
||||
inviter=self.founder,
|
||||
invitee_email="friend@example.com",
|
||||
)
|
||||
self.assertEqual(invite.status, RoomInvite.PENDING)
|
||||
|
||||
def test_invited_room_appears_in_my_games_queryset(self):
|
||||
friend = User.objects.create(email="friend@example.com")
|
||||
RoomInvite.objects.create(
|
||||
room=self.room,
|
||||
inviter=self.founder,
|
||||
invitee_email=friend.email,
|
||||
)
|
||||
rooms = Room.objects.filter(
|
||||
Q(owner=friend) |
|
||||
Q(gate_slots__gamer=friend) |
|
||||
Q(invites__invitee_email=friend.email, invites__status=RoomInvite.PENDING)
|
||||
).distinct()
|
||||
self.assertIn(self.room, rooms)
|
||||
|
||||
@@ -8,5 +8,6 @@ urlpatterns = [
|
||||
path('rooms/create_room', views.create_room, name='create_room'),
|
||||
path('room/<uuid:room_id>/gate/', views.gatekeeper, name='gatekeeper'),
|
||||
path('room/<uuid:room_id>/gate/<int:slot_number>/drop_token', views.drop_token, name='drop_token'),
|
||||
path('room/<uuid:room_id>/gate/invite', views.invite_gamer, name='invite_gamer'),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
from apps.epic.models import Room, debit_token
|
||||
from apps.epic.models import Room, RoomInvite, debit_token
|
||||
from apps.lyric.models import Token
|
||||
|
||||
|
||||
@@ -17,9 +17,14 @@ def create_room(request):
|
||||
def gatekeeper(request, room_id):
|
||||
room = Room.objects.get(id=room_id)
|
||||
slots = room.gate_slots.order_by("slot_number")
|
||||
user_has_slot = (
|
||||
request.user.is_authenticated
|
||||
and room.gate_slots.filter(gamer=request.user).exists()
|
||||
)
|
||||
return render(request, "apps/gameboard/room.html", {
|
||||
'room': room,
|
||||
'slots': slots,
|
||||
"room": room,
|
||||
"slots": slots,
|
||||
"user_has_slot": user_has_slot,
|
||||
})
|
||||
|
||||
@login_required
|
||||
@@ -35,3 +40,17 @@ def drop_token(request, room_id, slot_number):
|
||||
if token:
|
||||
debit_token(request.user, slot, token)
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
|
||||
@login_required
|
||||
def invite_gamer(request, room_id):
|
||||
if request.method == "POST":
|
||||
room = Room.objects.get(id=room_id)
|
||||
email = request.POST.get("invitee_email", "").strip()
|
||||
if email:
|
||||
RoomInvite.objects.get_or_create(
|
||||
room=room,
|
||||
inviter=request.user,
|
||||
invitee_email=email,
|
||||
defaults={"status": RoomInvite.PENDING}
|
||||
)
|
||||
return redirect("epic:gatekeeper", room_id=room_id)
|
||||
|
||||
Reference in New Issue
Block a user