Skip to content
This repository has been archived by the owner on Jul 29, 2023. It is now read-only.

Commit

Permalink
Merge pull request #70 from davidcr01/3.3
Browse files Browse the repository at this point in the history
Release v3.3.0
  • Loading branch information
davidcr01 authored Jul 13, 2023
2 parents 22a5798 + 6f69f90 commit 9d0fd6a
Show file tree
Hide file tree
Showing 15 changed files with 61 additions and 31 deletions.
4 changes: 2 additions & 2 deletions Wordle+/django/djangoproject/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

AUTH_USER_MODEL = 'djapi.CustomUser'
TOKEN_EXPIRED_AFTER_SECONDS = 3600
TOKEN_EXPIRED_AFTER_SECONDS = 3600 # Time of the token expiration in seconds

MEDIA_URL = '/avatars/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'avatars')
Expand All @@ -154,6 +154,6 @@

CORS_ALLOWED_ORIGINS = [
'http://localhost:8100', # Ionic in local (dev)
'http://localhost:8080', # Ionic in Docker
'http://localhost', # Ionic in Docker
]
CORS_ALLOW_REDIRECTS = False
1 change: 0 additions & 1 deletion Wordle+/django/djangoproject/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
router.register('api/friendrequest', FriendRequestViewSet, basename='friendrequest')

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
Expand Down
11 changes: 7 additions & 4 deletions Wordle+/django/djapi/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,16 @@ class NotificationAdmin(admin.ModelAdmin):
list_display = ('id', 'player', 'text', 'link', 'timestamp')

class TournamentsForm(forms.ModelForm):
word_length = forms.ChoiceField(choices=[
word_length = forms.ChoiceField(choices=[ # Dropdown tab
(4, '4'),
(5, '5'),
(6, '6'),
(7, '7'),
(8, '8'),
])

max_players = forms.ChoiceField(choices=[
(2, '2'),
max_players = forms.ChoiceField(choices=[ # Dropdown tab
(2, '2'),
(4, '4'),
(8, '8'),
(16, '16'),
Expand Down Expand Up @@ -129,6 +129,8 @@ def save_model(self, request, obj, form, change):

tournament.num_players += 1
super().save_model(request, obj, form, change)

# If the tournament is full, create the rounds and the games of the first round
if (tournament.num_players >= tournament.max_players):
tournament.is_closed = True
rounds = int(math.log2(tournament.max_players))
Expand All @@ -147,7 +149,7 @@ def save_model(self, request, obj, form, change):

player = obj.player
message = f"You were assigned in {tournament.name}. Good luck!"
link = "http://localhost:8100/tabs/tournaments"
link = "http://localhost/tabs/tournaments"
notification = Notification.objects.create(player=player, text=message, link=link)
notification.save()

Expand Down Expand Up @@ -196,6 +198,7 @@ class RoundAdmin(admin.ModelAdmin):
class RoundGameAdmin(admin.ModelAdmin):
list_display = ('id', 'round', 'game',)

# Register all the models
admin.site.register(RoundGame, RoundGameAdmin)
admin.site.register(Round, RoundAdmin)
admin.site.register(Game, GameAdmin)
Expand Down
16 changes: 14 additions & 2 deletions Wordle+/django/djapi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ def clean(self):

def __str__(self):
return f"{self.sender.user.username} - {self.receiver.user.username}"


# Model to store the friend requests between players.
class FriendRequest(models.Model):
sender = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='requests_sent')
receiver = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='requests_received')
Expand All @@ -122,6 +123,7 @@ def clean(self):
def __str__(self):
return f"{self.sender.user.username} - {self.receiver.user.username}"

# Model to store the multiplayer and tournament games between players.
class Game(models.Model):
player1 = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='player1_wordle')
player2 = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='player2_wordle')
Expand All @@ -139,13 +141,15 @@ class Game(models.Model):
def __str__(self):
return f"{self.player1.user.username} - {self.player2.user.username}"

# Model to store the rounds of a tournament.
class Round(models.Model):
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
number = models.PositiveIntegerField()

def __str__(self):
return f"Tournament: {self.tournament.name}, Round: {self.number}"

# Model to relate the games to a round.
class RoundGame(models.Model):
round = models.ForeignKey(Round, on_delete=models.CASCADE)
game = models.ForeignKey(Game, on_delete=models.CASCADE)
Expand All @@ -163,12 +167,20 @@ def assign_permissions(sender, instance, created, **kwargs):
# Obtain the necessary permissions to manage CustomUser and Player
customuser_content_type = ContentType.objects.get(app_label='djapi', model='customuser')
player_content_type = ContentType.objects.get(app_label='djapi', model='player')
tournament_content_type = ContentType.objects.get(app_label='djapi', model='tournament')
round_content_type = ContentType.objects.get(app_label='djapi', model='round')
roundgame_content_type = ContentType.objects.get(app_label='djapi', model='roundgame')
participation_content_type = ContentType.objects.get(app_label='djapi', model='participation')

customuser_permissions = Permission.objects.filter(content_type=customuser_content_type)
player_permissions = Permission.objects.filter(content_type=player_content_type)
tournament_permissions = Permission.objects.filter(content_type=tournament_content_type)
round_permissions = Permission.objects.filter(content_type=round_content_type)
roundgame_permissions = Permission.objects.filter(content_type=roundgame_content_type)
participation_permissions = Permission.objects.filter(content_type=participation_content_type)

# Assign the permissions to the "Staff" group
staff_group.permissions.set(customuser_permissions | player_permissions)
staff_group.permissions.set(customuser_permissions | player_permissions | tournament_permissions | roundgame_permissions | round_permissions | participation_permissions)

# Assign the user to the "Staff" group
staff_group.user_set.add(instance)
2 changes: 1 addition & 1 deletion Wordle+/django/djapi/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class Meta:
model = FriendList
fields = ['friend']

# Return the sender or receiver depending on the friend field.
def get_friend(self, obj):
request = self.context.get('request')
player = request.user.player
Expand Down Expand Up @@ -183,7 +184,6 @@ def get_player1(self, obj):
def get_player2(self, obj):
return obj.player2.user.username


class GameCreateSerializer(serializers.ModelSerializer):
player2 = serializers.SerializerMethodField()
class Meta:
Expand Down
1 change: 1 addition & 0 deletions Wordle+/django/djapi/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .models import Game, RoundGame, Round
import math

# Singal executed every time a tournament game is completed
@receiver(post_save, sender=Game)
def game_completed(sender, instance, created, **kwargs):
if instance.is_tournament_game and instance.winner:
Expand Down
2 changes: 2 additions & 0 deletions Wordle+/django/djapi/token_expire.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from django.http import JsonResponse
from django.conf import settings

# Middleware to check if the token has expired. The middleware
# is executed everytime an API endpoint is used.
class TokenExpirationMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
Expand Down
38 changes: 23 additions & 15 deletions Wordle+/django/djapi/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@


class CustomUserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = CustomUser.objects.all().order_by('-date_joined')
serializer_class = CustomUserSerializer

Expand Down Expand Up @@ -68,9 +65,6 @@ def patch(self, request):
return Response(serializer.errors, status=400)

class PlayerViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows players to be viewed or edited.
"""
queryset = Player.objects.all()
serializer_class = PlayerSerializer

Expand Down Expand Up @@ -111,6 +105,7 @@ def destroy(self, request, *args, **kwargs):
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)

# Method to get the ranking players
@action(detail=False, methods=['get'])
def ranking(self, request):
filter_param = request.GET.get('filter')
Expand Down Expand Up @@ -140,9 +135,6 @@ def list(self, request, *args, **kwargs):
return Response(usernames)

class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
permission_classes = [permissions.IsAuthenticated]
Expand All @@ -163,7 +155,6 @@ def post(self, request, *args, **kwargs):
token.delete()
token = Token.objects.create(user=user)

# Serialize the token along with any other data you want to include in the response
response_data = {
'token': token.key,
'user_id': user.id,
Expand Down Expand Up @@ -196,6 +187,7 @@ class ClassicWordleViewSet(viewsets.GenericViewSet):
queryset = ClassicWordle.objects.all()
serializer_class = ClassicWordleSerializer

# Gets limited number of classic worldes from most recent to oldest
def list(self, request):
player = getattr(request.user, 'player', None)
if not player:
Expand Down Expand Up @@ -223,6 +215,7 @@ class AvatarView(APIView):
"""
permission_classes = [permissions.IsAuthenticated]

# Gets the avatar image. Only returned if the requesting player is the owner.
def get(self, request, user_id):
try:
user = get_object_or_404(CustomUser, id=user_id)
Expand All @@ -237,6 +230,7 @@ def get(self, request, user_id):
except CustomUser.DoesNotExist:
return Response({'detail': 'The specified user does not exist.'}, status=404)

# Save the player avatar. If there is an existing one, is removed.
def post(self, request, user_id):
try:
user = get_object_or_404(CustomUser, id=user_id)
Expand Down Expand Up @@ -266,6 +260,7 @@ class NotificationsViewSet(viewsets.ModelViewSet):
serializer_class = NotificationSerializer
permission_classes = [permissions.IsAuthenticated, IsOwnerPermission]

# Gets limited number of notifications from the most recent to the oldest.
def list(self, request):
limit = int(request.query_params.get('limit', 10))
player = getattr(request.user, 'player', None)
Expand Down Expand Up @@ -293,6 +288,7 @@ class TournamentViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Tournament.objects.order_by('max_players')
serializer_class = TournamentSerializer

# Get a list of all the tournaments filtered by its word length.
def get_queryset(self):
queryset = super().get_queryset()

Expand All @@ -305,6 +301,7 @@ def get_queryset(self):

return queryset

# Gets the information of a specific tournament
@action(detail=True, methods=['get'])
def tournament_info(self, request, pk=None):
tournament = Tournament.objects.get(pk=pk)
Expand All @@ -317,7 +314,7 @@ def tournament_info(self, request, pk=None):
serializer = TournamentSerializer(tournament)
return Response(serializer.data)


# Gets the tournament that the player is joined in.
@action(detail=False, methods=['get'])
def player_tournaments(self, request):
player = getattr(request.user, 'player', None)
Expand All @@ -328,6 +325,7 @@ def player_tournaments(self, request):
serializer = TournamentSerializer(tournaments, many=True)
return Response(serializer.data)

# Gets the rounds of a specific tournament.
@action(detail=True, methods=['get'])
def tournament_rounds(self, request, pk=None):
tournament = self.get_object()
Expand All @@ -344,6 +342,7 @@ def tournament_rounds(self, request, pk=None):
return Response({'error': 'No rounds found for this tournament.'}, status=404)
return Response(serializer.data)

# Gets the games of a specific round of a specific tournament.
@action(detail=True, methods=['get'], url_path='round_games/(?P<round_number>\d+)')
def round_games(self, request, pk=None, round_number=None):
player = getattr(request.user, 'player', None)
Expand Down Expand Up @@ -437,7 +436,7 @@ def create(self, request, *args, **kwargs):

# Create the related notification to the player
message = f"You were assigned in {tournament.name}. Good luck!"
link = "http://localhost:8100/tabs/tournaments"
link = "http://localhost/tabs/tournaments"
notification = Notification.objects.create(player=player, text=message, link=link)
notification.save()

Expand All @@ -457,6 +456,7 @@ def list(self, request, *args, **kwargs):
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

# Deletes a friend.
def destroy(self, request, *args, **kwargs):
player = getattr(request.user, 'player', None)
friend_id = kwargs.get('pk')
Expand Down Expand Up @@ -491,6 +491,8 @@ def list(self, request, *args, **kwargs):
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

# Creates a new friend request. It checks if the players exist and if there
# is an existing friend request between them.
def create(self, request, *args, **kwargs):
sender = getattr(request.user, 'player', None)
if not sender:
Expand Down Expand Up @@ -524,12 +526,13 @@ def create(self, request, *args, **kwargs):
Notification.objects.create(
player=receiver,
text='You have a new friend request!',
link='http://localhost:8100/friendlist'
link='http://localhost/friendlist'
)

serializer = FriendRequestSerializer(friend_request)
return Response(serializer.data, status=status.HTTP_201_CREATED)

# Accepts a friend request. The friend relationship is created.
@action(detail=True, methods=['post'])
def accept(self, request, *args, **kwargs):
instance = self.get_object()
Expand All @@ -547,17 +550,18 @@ def accept(self, request, *args, **kwargs):
Notification.objects.create(
player=instance.sender,
text=f"You are now friends with {receiver.user.username}.",
link='http://localhost:8100/friendlist'
link='http://localhost/friendlist'
)
Notification.objects.create(
player=receiver,
text=f"You are now friends with {instance.sender.user.username}.",
link='http://localhost:8100/friendlist'
link='http://localhost/friendlist'
)

instance.delete()
return Response({'message': 'Friend request accepted'}, status=200)

# Rejects a friend request. It is deleted.
@action(detail=True, methods=['post'])
def reject(self, request, *args, **kwargs):
instance = self.get_object()
Expand Down Expand Up @@ -683,6 +687,7 @@ def partial_update(self, request, *args, **kwargs):
player2_time = request.data.get('player2_time')
player1_time = instance.player1_time

# Determine the winner if all data is available.
if player2_xp is not None:
player1_xp = instance.player1_xp
if player2_xp > player1_xp:
Expand All @@ -707,6 +712,9 @@ def partial_update(self, request, *args, **kwargs):
serializer.is_valid(raise_exception=True)
serializer.save()

player.xp += serializer.validated_data['player2_xp']
player.save()

return Response({'winner': instance.winner.user.username})

# Patch method to update the tournament game. Executed by both players
Expand Down
1 change: 0 additions & 1 deletion Wordle+/ionic/.dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
ionic-app/node_modules
ionic-app/src/env.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ <h1>Notifications</h1>
<ion-item *ngFor="let notification of notifications">
<a [href]="notification.link">{{ notification.text }}</a>
</ion-item>
<ion-item *ngIf="notifications && notifications.length === 0">
No notifications here!
</ion-item>
</ion-list>
</ion-content>
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class GameTournamentPage implements OnInit {
this.tournamentId = params['tournamentId'];
this.gameId = params['idGame'];
this.selfUsername = await this.storageService.getUsername();
this.loadTournamentInfo();
});
}

Expand Down Expand Up @@ -66,6 +67,7 @@ export class GameTournamentPage implements OnInit {
}
},
(error) => {
console.log(error);
this.showAlert("Ups!", "You can't play this game!");
}
);
Expand Down Expand Up @@ -98,7 +100,7 @@ export class GameTournamentPage implements OnInit {
}
else {
if (response.winner === this.selfUsername) {
setTimeout( () => this.showAlert('Congratulations!', 'You won! You will be in the next round!'), 2500);
setTimeout( () => this.showAlert('Congratulations!', 'You won! Amazing!'), 2500);
} else {
setTimeout( () => this.showAlert('Bad news!', 'You lost. Try next time!'), 2500);
}
Expand Down
Loading

0 comments on commit 9d0fd6a

Please sign in to comment.