Closed davidcr01 closed 1 year ago
The model and serializer of the Notifications are the following:
class Notifications(models.Model):
player = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='notifications')
text = models.CharField(max_length=200)
link = models.URLField(blank=True)
timestamp = models.DateTimeField(auto_now_add=True)
Notice that the link could be blank. This option is controlled by the blank=True
parameter.
class NotificationsSerializer(serializers.ModelSerializer):
class Meta:
model = Notifications
fields = ['text', 'link']
And the related viewset is:
class NotificationsViewSet(viewsets.ModelViewSet):
queryset = Notifications.objects.all()
serializer_class = NotificationsSerializer
permission_classes = [permissions.IsAuthenticated, IsOwnerPermission]
def list(self, request):
limit = int(request.query_params.get('limit', 10))
player = getattr(request.user, 'player', None)
if not player:
return Response({'error': 'Player not found'}, status=404)
notifications = self.queryset.filter(player=player).order_by('-timestamp')[:limit]
serializer = self.serializer_class(notifications, many=True)
return Response(serializer.data)
def create(self, request):
player = getattr(request.user, 'player', None)
if not player:
return Response({'error': 'Player not found'}, status=404)
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(player=player)
return Response(serializer.data, status=201)
Notice that, instead of passing the player ID as a parameter, we are getting the player from the request.user
value. This strategy is also added to the ClassicWordleViewSet
.
This view filters the notifications by the date and limits the query to 10 or to the specified parameter limit
.
The testing of this new API is:
Requesting the notifications of a player
Requesting the notifications of an administrator (does not have player information)
Posting a new notification attaching the access token:
To implement this new feature, a new component and service have been created:
notification.popover.component
: as done in #25, a popover to show the notifications has been created. This popover has the following HTML structure:<ion-content>
<h1>Notifications</h1>
<ion-list>
<ion-item *ngFor="let notification of notifications">
<a [href]="notification.link">{{ notification.text }}</a>
</ion-item>
</ion-list>
</ion-content>
It iterates though the list of notifications.
On the other hand, in the TS logic:
import { Component, OnInit } from '@angular/core';
import { PopoverController } from '@ionic/angular';
import { NotificationService } from 'src/app/services/notification.service';
@Component({
selector: 'app-notifications-popover',
templateUrl: './notifications-popover.component.html',
styleUrls: ['./notifications-popover.component.scss'],
})
export class NotificationsPopoverComponent implements OnInit {
notifications: any[] = [];
constructor(
private notificationService: NotificationService
) {}
ngOnInit() {
this.loadNotifications();
}
loadNotifications() {
this.notificationService.getNotifications().then((notifications: any[]) => {
this.notifications = notifications || [];
});
}
}
It defines the notifications list and fill it using the NotificationService.
notification.service
: it fetches the notifications, updates them and create new notifications:
getNotifications
: it gets the notifications from several sites: its variable, from the StorageService or from the API. This method avoids requesting to the API the notifications every time the popover is displayedgetNotifications(): Promise<any[]> {
return new Promise(async (resolve) => {
if (this.notifications.length > 0) {
resolve(this.notifications);
} else {
const storedNotifications = await this.storageService.getNotifications();
console.log(storedNotifications);
if (storedNotifications) {
this.notifications = storedNotifications;
resolve(this.notifications);
} else {
(await this.apiService.getNotifications()).subscribe((apiNotifications: any[]) => {
this.notifications = apiNotifications || [];
this.storageService.setNotifications(this.notifications);
resolve(this.notifications);
});
}
}
});
}
refreshNotifications
: it refreshes the notifications list by requesting them to the API. This method is used when is necessary to update the notifications. For example, when the user is redirected to the main page.// Request to the API to get the notifications
async refreshNotifications() {
(await this.apiService.getNotifications()).subscribe((apiNotifications: any[]) => {
this.notifications = apiNotifications || [];
this.storageService.setNotifications(this.notifications);
return this.notifications;
});
}
addNotification
. it creates a new notification and save it in the service, in the Storage and in the backend:async addNotification(notification: { text: string; link?: string }): Promise<void> {
const newNotification = { text: notification.text, link: notification.link || '' };
(await this.apiService.addNotification(newNotification)).subscribe(
(response) => {
console.log('Notification added successfully', response);
},
(error) => {
console.log('Notification could not be added', error);
}
);
const storedNotifications = await this.storageService.getNotifications() || [];
const updatedNotifications = [...storedNotifications, newNotification];
await this.storageService.setNotifications(updatedNotifications);
}
This service uses the api.service
, which has two new methods to get and post notifications. It uses the information passed by parameter and attach the access token:
async getNotifications(limit: number = 5) {
const url = `${this.baseURL}/api/notifications/?limit=${limit}`;
const accessToken = await this.storageService.getAccessToken();
if (!accessToken) {
return throwError('Access token not found');
}
const decryptedToken = this.encryptionService.decryptData(accessToken);
const headers = new HttpHeaders({
Authorization: `Token ${decryptedToken}`,
'Content-Type': 'application/json'
});
return this.http.get(url, {headers});
}
async addNotification(notification: { text: string, link: string }): Promise<Observable<any>> {
let url = `${this.baseURL}/api/notifications/`;
const accessToken = this.storageService.getAccessToken();
if (!accessToken) {
return throwError('Access token not found');
}
const decryptedToken = this.encryptionService.decryptData(await accessToken);
const headers = new HttpHeaders({
Authorization: `Token ${decryptedToken}`,
'Content-Type': 'application/json'
});
return this.http.post(url, notification, { headers });
}
Before this issue, the user had to go manually to the main page when a game was finished. Now, as the platform redirects it automatically, is necessary to implement a new logic:
In the tab1
page (main page), this new logic has been added:
// Change background img depending on the width
async ngOnInit() {
if (window.innerWidth <= 767) {
this.backgroundImage = '../../assets/background_wordle_vertical.png';
} else {
this.backgroundImage = '../../assets/background_wordle_horizontal.png';
}
// Only fetchs the avatar if necessary
const storedAvatarUrl = await this.storageService.getAvatarUrl();
if (storedAvatarUrl) {
this.avatarImage = storedAvatarUrl;
} else {
await this.loadAvatarImage();
}
// Optional param to update the player info: useful when
// finishing a game
this.route.queryParams.subscribe(async params => {
const refresh = params['refresh'];
if (refresh === 'true') {
await this.ionViewWillEnter();
}
});
}
💡 Notice that a new extra parameter has been added to the URL. This parameter indicates that is necessary to update the information on the page. If true, it calls the ionViewWillEnter
method which retrieves all the related information and refreshes the notifications.
When finishing the wordle game, this code redirects the user:
setTimeout(() => {
this.router.navigate(['/tabs/main'], { queryParams: { refresh: 'true' } });
}, 3000)
This video shows how the notifications are updated when the user is redirected to the main page and a new notification is created and added.
https://github.com/davidcr01/WordlePlus/assets/72193239/89dedc08-f915-439d-9cba-483a61f7d728
Description
As a player, is necessary to implement an inbox where some important messages will be stored.
Tasks