From 17bcee764bd6246d7356fe2a461f148b52351413 Mon Sep 17 00:00:00 2001 From: AdrienLSH Date: Tue, 7 May 2024 19:53:28 +0200 Subject: [PATCH] profiles: online status and friend rework --- frontend/static/js/api/Client.js | 2 +- frontend/static/js/api/MyProfile.js | 30 ++++++++-------- frontend/static/js/api/{notice => }/Notice.js | 34 ++++++++++++++----- frontend/static/js/api/Profile.js | 16 ++++----- frontend/static/js/views/ProfilePageView.js | 23 ++++++++----- notice/consumers.py | 18 +++++++++- profiles/serializers.py | 33 ++++++++++++++---- profiles/viewsets/MyProfileViewSet.py | 2 +- profiles/viewsets/ProfileViewSet.py | 6 ++-- 9 files changed, 111 insertions(+), 53 deletions(-) rename frontend/static/js/api/{notice => }/Notice.js (73%) diff --git a/frontend/static/js/api/Client.js b/frontend/static/js/api/Client.js index 3732b9f..6476084 100644 --- a/frontend/static/js/api/Client.js +++ b/frontend/static/js/api/Client.js @@ -4,8 +4,8 @@ import { Profiles } from "./Profiles.js"; import { Channels } from './chat/Channels.js'; import { MyProfile } from "./MyProfile.js"; import { Tourmanents } from "./tournament/Tournaments.js"; -import Notice from "./notice/Notice.js"; import { Channel } from "./chat/Channel.js"; +import Notice from "./Notice.js"; import LanguageManager from './LanguageManager.js'; function getCookie(name) diff --git a/frontend/static/js/api/MyProfile.js b/frontend/static/js/api/MyProfile.js index 17dd2df..88e20eb 100644 --- a/frontend/static/js/api/MyProfile.js +++ b/frontend/static/js/api/MyProfile.js @@ -15,26 +15,26 @@ class MyProfile extends Profile * @type {[Profile]} */ this.blockedUsers = []; - /** - * @type {[Profile]} - */ - this.friendList = []; - /** - * @type {[Profile]} - */ - this.incomingFriendRequests = []; - /** - * @type {[Profile]} - */ - this.outgoingFriendRequests = []; + // /** + // * @type {[Profile]} + // */ + // this.friendList = []; + // /** + // * @type {[Profile]} + // */ + // this.incomingFriendRequests = []; + // /** + // * @type {[Profile]} + // */ + // this.outgoingFriendRequests = []; } async init() { await super.init(); await this.getBlockedUsers(); - await this.getFriends(); - await this.getIncomingFriendRequests() - await this.getOutgoingFriendRequests() + // await this.getFriends(); + // await this.getIncomingFriendRequests() + // await this.getOutgoingFriendRequests() } async getBlockedUsers() { diff --git a/frontend/static/js/api/notice/Notice.js b/frontend/static/js/api/Notice.js similarity index 73% rename from frontend/static/js/api/notice/Notice.js rename to frontend/static/js/api/Notice.js index f0db7e1..67b4b55 100644 --- a/frontend/static/js/api/notice/Notice.js +++ b/frontend/static/js/api/Notice.js @@ -1,8 +1,7 @@ -import {Client} from '../Client.js'; -import {createNotification} from '../../utils/noticeUtils.js' -import { client, lastView } from '../../index.js'; -import { Profile } from '../Profile.js'; -import ProfilePageView from '../../views/ProfilePageView.js'; +import {Client} from './Client.js'; +import {createNotification} from '../utils/noticeUtils.js' +import { lastView } from '../index.js'; +import ProfilePageView from '../views/ProfilePageView.js'; export default class Notice { @@ -23,6 +22,7 @@ export default class Notice { this._socket.onclose = _ => this._socket = undefined; this._socket.onmessage = message => { const data = JSON.parse(message.data); + console.log(data) if (data.type === 'friend_request') { this.friend_request(data.author); @@ -32,6 +32,10 @@ export default class Notice { this.friend_removed(data.friend); } else if (data.type === 'friend_request_canceled') { this.friend_request_canceled(data.author); + } else if (data.type === 'online') { + this.online(data.user) + } else if (data.type === 'offline') { + this.offline(data.user) } }; } @@ -43,8 +47,22 @@ export default class Notice { } } + _setOnlineStatus(user, status) { + if (lastView instanceof ProfilePageView && lastView.profile.id === user.id) { + lastView.profile.online = status; + lastView.loadFriendshipStatus(); + } + } + + online(user) { + this._setOnlineStatus(user, true) + } + + offline(user) { + this._setOnlineStatus(user, false) + } + friend_request(author) { - client.me.incomingFriendRequests.push(new Profile(author.username, author.id, author.avatar)); createNotification('Friend Request', `${author.username} sent you a friend request.`); if (lastView instanceof ProfilePageView && lastView.profile.id === author.id) { lastView.profile.hasIncomingRequest = true; @@ -53,7 +71,6 @@ export default class Notice { } new_friend(friend) { - client.me.friendList.push(new Profile(friend.username, friend.id, friend.avatar)); createNotification('New Friend', `${friend.username} accepted your friend request.`); if (lastView instanceof ProfilePageView && lastView.profile.id === friend.id) { lastView.profile.isFriend = true; @@ -64,15 +81,14 @@ export default class Notice { } friend_removed(exFriend) { - client.me.friendList = client.me.friendList.filter(friend => friend.id !== exFriend.id); if (lastView instanceof ProfilePageView && lastView.profile.id === exFriend.id) { lastView.profile.isFriend = false; + lastView.profile.online = null; lastView.loadFriendshipStatus(); } } friend_request_canceled(author) { - client.me.incomingFriendRequests = client.me.incomingFriendRequests.filter(user => user.id !== author.id); if (lastView instanceof ProfilePageView && lastView.profile.id === author.id) { lastView.profile.hasIncomingRequest = false; lastView.loadFriendshipStatus(); diff --git a/frontend/static/js/api/Profile.js b/frontend/static/js/api/Profile.js index 0a1643f..faeef20 100644 --- a/frontend/static/js/api/Profile.js +++ b/frontend/static/js/api/Profile.js @@ -59,19 +59,19 @@ export class Profile extends AExchangeable if (response.status !== 200) return response.status; - let response_data = await response.json(); - this.id = response_data.id; - this.username = response_data.username; - this.avatar = response_data.avatar; - this.online = response_data.online + const responseData = await response.json(); + this.id = responseData.id; + this.username = responseData.username; + this.avatar = responseData.avatar; + this.online = responseData.online if (!this.client.me || this.client.me.id === this.id) return; + this.hasIncomingRequest = responseData.has_incoming_request; + this.hasOutgoingRequest = responseData.has_outgoing_request; + this.isFriend = responseData.is_friend; this.isBlocked = this.client.me._isBlocked(this); - this.hasIncomingRequest = this.client.me._hasIncomingRequestFrom(this); - this.hasOutgoingRequest = this.client.me._hasOutgoingRequestTo(this); - this.isFriend = this.client.me._isFriend(this); } /** diff --git a/frontend/static/js/views/ProfilePageView.js b/frontend/static/js/views/ProfilePageView.js index 63a2168..7ebc850 100644 --- a/frontend/static/js/views/ProfilePageView.js +++ b/frontend/static/js/views/ProfilePageView.js @@ -38,7 +38,8 @@ export default class extends AbstractView { loadFriendshipStatus() { const addFriendButton = document.getElementById('addFriendButton'), - removeFriendButton = document.getElementById('removeFriendButton'); + removeFriendButton = document.getElementById('removeFriendButton'), + statusIndicator = document.getElementById('statusIndicator'); if (this.profile.hasIncomingRequest) { removeFriendButton.classList.add('d-none'); @@ -59,6 +60,12 @@ export default class extends AbstractView { removeFriendButton.classList.add('d-none'); addFriendButton.classList.remove('d-none'); } + + statusIndicator.classList.remove('bg-success', 'bg-danger'); + if (this.profile.online === true) + statusIndicator.classList.add('bg-success'); + else if (this.profile.online === false) + statusIndicator.classList.add('bg-danger'); } async getHtml() { @@ -70,7 +77,7 @@ export default class extends AbstractView { return `
-

${this.username}

+

${this.username}

@@ -99,20 +106,18 @@ export default class extends AbstractView { if (response.status === 200) { removeFriendButton.innerHTML = 'Cancel Request'; removeFriendButton.classList.replace('btn-danger', 'btn-secondary'); - client.me.outgoingFriendRequests.push(this.profile); this.profile.hasOutgoingRequest = true; } else if (response.status === 201) { removeFriendButton.innerHTML = 'Remove Friend'; removeFriendButton.classList.replace('btn-secondary', 'btn-danger'); - this.profile.friend = true; + this.profile.isFriend = true; this.profile.hasIncomingRequest = false; - client.me.incomingFriendRequests = client.me.incomingFriendRequests.filter(profile => profile.id !== this.profile.id); - client.me.friendList.push(this.profile); } } async removeFriend() { - const addFriendButton = document.getElementById('addFriendButton'); + const addFriendButton = document.getElementById('addFriendButton'), + statusIndicator = document.getElementById('statusIndicator'); const response = await client._delete(`/api/profiles/friends/${this.profile.id}`); const body = await response.json(); @@ -121,14 +126,14 @@ export default class extends AbstractView { if (response.ok) { addFriendButton.innerHTML = 'Add Friend'; addFriendButton.classList.remove('d-none'); + statusIndicator.classList.remove('bg-danger', 'bg-success'); document.getElementById('removeFriendButton').classList.add('d-none'); } if (response.status === 200) { this.profile.hasOutgoingRequest = false; - client.me.outgoingFriendRequests = client.me.outgoingFriendRequests.filter(profile => profile.id !== this.profile.id); } else if (response.status === 201) { this.profile.isFriend = false; - client.me.friendList = client.me.friendList.filter(friend => friend.id !== this.profile.id); + this.profile.online = null; } } diff --git a/notice/consumers.py b/notice/consumers.py index 81e78b4..83b9ee6 100644 --- a/notice/consumers.py +++ b/notice/consumers.py @@ -21,7 +21,14 @@ class NoticeManager: consumer.send(notice.data) notice.delete() + for friend in consumer.user.profilemodel.get_friends(): + self.notify_user(friend.user, {'type': 'online', + 'user': ProfileSerializer(consumer.user.profilemodel).data}) + def remove(self, consumer: NoticeConsumer): + for friend in consumer.user.profilemodel.get_friends(): + self.notify_user(friend.user, {'type': 'offline', + 'user': ProfileSerializer(consumer.user.profilemodel).data}) self._list.remove(consumer) def get_consumer_by_user(self, user: User): @@ -44,7 +51,16 @@ class NoticeManager: self.notify_user(user, {'type': 'friend_request_canceled', 'author': ProfileSerializer(friend).data}) def notify_new_friend(self, user: User, friend: ProfileModel): - self.notify_user(user, {'type': 'new_friend', 'friend': ProfileSerializer(friend).data}) + serialized_data = ProfileSerializer(friend).data + if self.get_consumer_by_user(user) is not None: + status = 'online' + else: + status = 'offline' + print(status) + + self.notify_user(user, {'type': 'new_friend', 'friend': serialized_data}) + self.notify_user(user, {'type': 'online', 'user': serialized_data}) + self.notify_user(friend.user, {'type': status, 'user': ProfileSerializer(user.profilemodel).data}) def notify_friend_removed(self, user: User, friend: ProfileModel): self.notify_user(user, {'type': 'friend_removed', 'friend': ProfileSerializer(friend).data}) diff --git a/profiles/serializers.py b/profiles/serializers.py index 0f18a83..3ed1fd4 100644 --- a/profiles/serializers.py +++ b/profiles/serializers.py @@ -11,19 +11,19 @@ class ProfileSerializer(serializers.ModelSerializer): username = serializers.ReadOnlyField(source='user.username') avatar = serializers.ImageField(required=False) online = serializers.SerializerMethodField() + is_friend = serializers.SerializerMethodField() + has_incoming_request = serializers.SerializerMethodField() + has_outgoing_request = serializers.SerializerMethodField() class Meta: model = ProfileModel - fields = ["username", "avatar", "id", 'online'] + fields = ["username", "avatar", "id", 'online', 'is_friend', + 'has_outgoing_request', 'has_incoming_request'] def get_online(self, obj: ProfileModel): from notice.consumers import notice_manager - user = None - request = self.context.get("request") - if request and hasattr(request, "user"): - user = request.user - + user = self.context.get('user') if user is None or not user.is_authenticated: return None @@ -32,6 +32,27 @@ class ProfileSerializer(serializers.ModelSerializer): return notice_manager.get_consumer_by_user(obj.user) is not None + def get_is_friend(self, obj: ProfileModel): + user = self.context.get('user') + if user is None or not user.is_authenticated or user.pk == obj.pk: + return False + + return obj.is_friend(user.profilemodel) + + def get_has_incoming_request(self, obj: ProfileModel): + user = self.context.get('user') + if user is None or not user.is_authenticated or user.pk == obj.pk: + return False + + return obj.is_friend_requesting(user.profilemodel) + + def get_has_outgoing_request(self, obj: ProfileModel): + user = self.context.get('user') + if user is None or not user.is_authenticated or user.pk == obj.pk: + return False + + return obj.is_friend_requested_by(user.profilemodel) + def validate_avatar(self, value): ''' Check that the image is not too large diff --git a/profiles/viewsets/MyProfileViewSet.py b/profiles/viewsets/MyProfileViewSet.py index 5ac6cdb..3afb81f 100644 --- a/profiles/viewsets/MyProfileViewSet.py +++ b/profiles/viewsets/MyProfileViewSet.py @@ -35,4 +35,4 @@ class MyProfileViewSet(viewsets.ModelViewSet): return Response(ProfileSerializer(profile).data) def retrieve(self, pk=None): - return Response(self.serializer_class(self.get_object(), context={'request': self.request}).data) + return Response(self.serializer_class(self.get_object(), context={'user': self.request.user}).data) diff --git a/profiles/viewsets/ProfileViewSet.py b/profiles/viewsets/ProfileViewSet.py index c16a888..b02c952 100644 --- a/profiles/viewsets/ProfileViewSet.py +++ b/profiles/viewsets/ProfileViewSet.py @@ -16,12 +16,12 @@ class ProfileViewSet(viewsets.ModelViewSet): def retrieve(self, request, username=None): user = get_object_or_404(User, username=username) - return Response(self.serializer_class(user.profilemodel, context={'request': request}).data) + return Response(self.serializer_class(user.profilemodel, context={'user': request.user}).data) def retrieve_id(self, request, pk=None): user = get_object_or_404(User, pk=pk) - return Response(self.serializer_class(user.profilemodel, context={'request': request}).data) + return Response(self.serializer_class(user.profilemodel, context={'user': request.user}).data) def list(self, request): - serializer = ProfileSerializer(self.get_queryset(), many=True, context={'request': request}) + serializer = ProfileSerializer(self.get_queryset(), many=True, context={'user': request.user}) return Response(serializer.data)