AlanJereb.com
Programmation

Dernière révérence : L'application Social Beaver se déconnecte

Apr 22nd, 2024

L'expérience de l'application sociale,

Social Beaver

, se met hors ligne après plus de trois ans d'utilisation continue et ininterrompue. Ce qui a commencé comme un mini-projet pour maîtriser le conteneur de gestion d'état React Redux s'est transformé en un réseau social de type Twitter fonctionnant pleinement sur Firebase.

Portée de cette publication

La portée de cette publication n'est ni éducative ni destinée à fournir un aperçu détaillé du code et à expliquer toutes les fonctionnalités et les raisons derrière ma mise en œuvre du code.

Non, je vais rester bref et simple. Tout le monde sait aujourd'hui comment fonctionnent les réseaux sociaux et à quoi s'attendre en en utilisant un. Il n'est pas nécessaire pour moi d'entrer dans les détails. La publication est principalement pour moi de me souvenir de la plateforme et pour quiconque lit de voir ce que je faisais dans mes premières années de codage.

Dans les paragraphes suivants, je mentionnerai brièvement quelques-unes des principales fonctionnalités de la plateforme ainsi que leurs captures d'écran.

Si, pour une raison quelconque, vous décidez de lire jusqu'au bout, vous êtes gâté. Je montrerai comment un simple commentaire sur une publication a déclenché une série de choses en arrière-plan. Une explication banale pour les programmeurs chevronnés, mais cela pourrait être intéressant pour tous les autres.

De quoi était fait Social Beaver ?

Avant tout, je dois mentionner que la plateforme était codée en pensant d'abord aux mobiles. Je voulais qu'elle soit réactive. Je crois que de nos jours, plus de personnes utilisent les réseaux sociaux sur leurs appareils mobiles que sur leurs ordinateurs fixes.

Inscription/Connexion

Ce n'est pas un réseau social sans utilisateurs, et vous ne pouvez pas avoir d'utilisateurs sans qu'ils créent un compte et puissent se connecter avec celui-ci.

Un appel au service d'authentification de Firebase côté serveur, et hop, l'utilisateur est connecté. Je ne suis plus fan de Firebase, mais ils ont vraiment simplifié la tâche des développeurs.

Application entièrement responsive - page d'authentification

Application entièrement responsive - page d'authentification

Page d'accueil

Les utilisateurs pouvaient accéder à la page d'accueil sans être connectés, mais ils devaient être connectés pour interagir avec la plateforme. Cela a été fait à la fois pour la sécurité des utilisateurs et pour mon porte-monnaie.

Les données liées aux comptes pouvaient être rapidement modérées, et une modération rapide m'évitait des maux de tête si Social Beaver devenait la cible d'une attaque de spam de contenu. Le stockage et les requêtes deviennent rapidement coûteux sur Firebase. J'ai en effet fixé le plafond mensuel maximal de mes dépenses là-bas, mais je préfère éviter de l'atteindre si possible.

Page d'accueil (version bureau)

Page d'accueil (version mobile)

Profil

Les utilisateurs pouvaient changer leur photo, leur biographie, leur emplacement et leur lien vers leur site Web. Ce n'est qu'une fraction de ce que les utilisateurs devraient pouvoir faire avec leurs profils, car ils n'avaient aucun moyen de changer leur mot de passe, de demander leurs données utilisateur ou de configurer une sécurité supplémentaire pour l'authentification comme la double authentification (2FA) et des phrases anti-hameçonnage, etc.

Mais comme il s'agissait d'un projet d'apprentissage, sans que j'aie l'intention de le pousser à la masse, vous pouvez comprendre pourquoi les fonctionnalités ci-dessus font défaut.

Éditeur de profil

Éditeur de profil

Affichage des publications et commentaires

Ce ne serait pas appelé un réseau social si les utilisateurs ne pouvaient pas créer de publications, lire des publications et exprimer leurs opinions sous forme de commentaires ou de likes. Il n'y a vraiment rien d'innovant à propos de ces fonctionnalités, mais elles devaient être mentionnées pour les raisons mentionnées ci-dessus.

Ajouter un commentaire

Ajouter un commentaire

Un petit supplément

Vous avez atteint la fin de l'article. Merci ! Comme promis, je vais vous montrer ce qu'un commentaire à une publication déclenche en arrière-plan.

La photo ci-dessus vous montre l'interface utilisateur pour ajouter un commentaire. Lorsque vous appuyez sur le bouton de soumission, le frontend envoie une requête POST au backend.

export const submitComment = (biteId: string, commentData: string) => (dispatch: Dispatch) => {
    axios.post(`/bites/${biteId}/comment`, commentData)
        .then((res) => {
            dispatch({
                type: SUBMIT_COMMENT,
                payload: res.data,
            });
            dispatch<any>(clearErrors())
        })
        .catch((err) => {
            dispatch({
                type: SET_ERRORS,
                payload: err.response.data,
            });
        });
}

Soumettre un commentaire sur UI

En utilisant Express.js, le backend écoute les événements POST de la route.

app.post('/bites/:biteId/comment', FBAuth, commentBite)

Point de terminaison pour ajouter un commentaire

Lorsque l'événement est reçu sur la route surveillée, le middleware

FBAuth

s'assure d'abord que l'utilisateur est authentifié et peut envoyer de tels événements.

export const FBAuth = (req: Request, res: Response, next: NextFunction) => {
    let idToken;
    if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
        idToken = req.headers.authorization.split('Bearer ')[1];
    } else {
        // 403 - unauthorized
        console.error('No token found');
        return res.status(403).json({ error: 'Unauthorized' }).end();
    }
    // verify token
    admin
        .auth()
        .verifyIdToken(idToken)
        .then((decodedToken) => {
            req.user = decodedToken;
            return db  
                .collection('users')
                .where('userId', '==', req.user.uid)
                .limit(1)
                .get()
        })
        .then((data) => {
            req.user.handle = data.docs[0].data().handle;
            req.user.imageUrl = data.docs[0].data().imageUrl;
            return next();
        })
        .catch((err) => {
            console.error('Error while verifying token ', err);
            return res.status(403).json(err);
        })
}

Middleware d'authentification

Si l'authentification réussit, le middleware FBAuth ajoute le nom d'utilisateur et l'URL de l'image de profil de l'utilisateur à la requête et la transmet à la fonction qui met à jour la base de données de Firebase (Firestore) avec le commentaire nouvellement ajouté.

export const commentBite = (req: Request, res: Response) => {
    if (req.body.body.trim() === "") {
        return res.status(400).json({ comment: "Must not be empty"}).end();
    }
    const newComment: any = {
        body: req.body.body,
        userHandle: req.user?.handle,
        createdAt: new Date().toISOString(),
        biteId: req.params.biteId,
        userImage: req.user.imageUrl
    };
    db
        .doc(`bites/${newComment.biteId}`)
        .get()
        .then((doc) => {
            if (!doc.exists) {
                res.status(404).json({ error: "Bite not found" }).end();
            }
            return doc.ref.update({ commentCount: doc.data()!.commentCount + 1 })
        .then(() => {
            return db
                .collection('comments')
                .add(newComment)
            })
        })
        .then(() => {
            return res.json(newComment);
        })
        .catch((err) => {
            console.error(err);
            return res.status(500).json({ error: "Something went wrong" });
        });
}

Ajout de commentaire à la base de données

Enfin, un écouteur est déclenché avec un commentaire nouvellement ajouté, qui, en réponse, envoie une notification pour le commentaire reçu à l'utilisateur dont la publication l'a reçu.

export const createNotificationOnComment = functions
    .region('europe-west3')
    .firestore
    .document('comments/{id}')
    .onCreate((snapshot) => {
        return db
            .doc(`/bites/${snapshot.data().biteId}`)
            .get()
            .then((doc) => {
                if (doc.exists && doc.data()!.userHandle !== snapshot.data().userHandle) {
                    return db
                        .collection('notifications')
                        .doc(snapshot.id)
                        .set({
                            createdAt: new Date().toISOString(),
                            recipient: doc.data()!.userHandle,
                            sender: snapshot.data().userHandle,
                            type: "comment",
                            read: false,
                            biteId: doc.id
                        })
                }
                return;
            })
            .catch((err) => {
                console.error(err);
            })
    });

Ajout de notification

À ce stade, un WebSocket pourrait pousser une mise à jour de notification en temps réel à l'utilisateur lorsqu'il reçoit le commentaire et la notification, mais pour des raisons de simplicité, j'ai choisi de ne pas l'utiliser.

Nouvelle notification de commentaire

En cliquant sur la notification, le commentaire s'affiche

Ce qui précède n'est qu'un exemple parmi d'autres. De nombreux écouteurs sont déclenchés en arrière-plan. Voici quelques exemples :

  • Créer une notification lorsque qu'un utilisateur aime une publication
  • Supprimer une notification lorsque qu'un utilisateur n'aime plus une publication
  • Mettre à jour toutes les publications avec la nouvelle image de profil de l'utilisateur lorsque l'auteur change sa photo de profil
  • Mettre à jour tous les commentaires avec la nouvelle image de profil de l'utilisateur lorsque l'auteur change sa photo de profil
  • Supprimer l'ancienne photo du stockage lorsque qu'un utilisateur change sa photo de profil
  • Supprimer tous les commentaires, les likes et les notifications lorsque qu'un utilisateur supprime une publication
  • ...