AlanJereb.com
Programación

Adiós: La aplicación Social Beaver se desconecta.

Apr 22nd, 2024

El experimento de la aplicación social,

Social Beaver

, se desconecta después de más de tres años de uso continuo e ininterrumpido. Lo que comenzó como un mini proyecto para dominar el contenedor de gestión de estado React Redux terminó siendo una red social similar a Twitter que funcionaba completamente en Firebase.

Alcance de esta publicación

El alcance de esta publicación no es ni educativo ni dar una descripción detallada del código y explicar todas las funcionalidades y la lógica detrás de mi implementación del código.

No, lo mantendré breve y simple. Hoy en día, todo el mundo sabe cómo funcionan las redes sociales y qué esperar al usar una. No hay necesidad de entrar en detalles. La publicación es principalmente para que yo recuerde la plataforma y para que cualquiera que la lea vea lo que estaba haciendo en mis primeros años de codificación.

En los siguientes párrafos, mencionaré brevemente algunas de las principales funcionalidades de la plataforma junto con sus capturas de pantalla.

Si, por alguna razón, decides llegar hasta el final, te espera una sorpresa. Mostraré cómo un simple comentario en una publicación desencadenó un montón de cosas en segundo plano. Una explicación trivial para programadores experimentados, pero podría ser interesante para todos los demás.

¿De qué estaba hecho Social Beaver?

En primer lugar, debo mencionar que la plataforma estaba codificada teniendo en cuenta principalmente los dispositivos móviles. Quería que fuera receptiva. Creo que hoy en día más personas utilizan las redes sociales en sus dispositivos móviles que en sus computadoras de escritorio.

Registro/Inicio de sesión

No es una red social sin usuarios, y no puedes tener usuarios sin que creen una cuenta y puedan iniciar sesión con ella.

Una llamada al servicio de autenticación de Firebase en el backend, y listo, el usuario está conectado. Ya no soy fan de Firebase, pero realmente lo hicieron simple para los desarrolladores.

Aplicación completamente receptiva - página de autenticación

Aplicación completamente receptiva - página de autenticación

Página de inicio

Los usuarios podían acceder a la página de inicio sin estar conectados, pero debían estar conectados para interactuar con la plataforma. Esto se hizo tanto por la seguridad de los usuarios como por mi billetera.

Los datos vinculados a las cuentas podían ser moderados rápidamente, y una moderación rápida me evitaba dolores de cabeza si Social Beaver fuera objetivo de un ataque de spam de contenido. El almacenamiento y las solicitudes se vuelven costosos rápidamente en Firebase. De hecho, he establecido el límite máximo mensual de mis gastos allí, pero prefiero evitar alcanzarlo si es posible.

Página de inicio (versión de escritorio)

Página de inicio (versión móvil)

Perfil

Los usuarios podían cambiar su foto, su biografía, su ubicación y el enlace a su sitio web. Esto es solo una fracción de lo que los usuarios deberían poder hacer con sus perfiles, ya que no tenían forma de cambiar su contraseña, solicitar sus datos de usuario o configurar una seguridad adicional para la autenticación como la autenticación de dos factores (2FA) y frases contra el phishing, etc.

Pero como este era un proyecto de aprendizaje, sin que tuviera la intención de llevarlo a las masas, puedes entender por qué faltan las características mencionadas anteriormente.

Editor de perfil

Editor de perfil

Vista de publicaciones y comentarios

No se llamaría una red social si los usuarios no pudieran crear publicaciones, leer publicaciones y expresar sus opiniones en forma de comentarios o "me gusta". Realmente no hay nada innovador en estas funciones, pero necesitaban ser mencionadas por las razones mencionadas anteriormente.

Agregar un comentario

Agregar un comentario

Un poco más

Has llegado al final del artículo. ¡Gracias! Como prometí, te mostraré lo que desencadena en segundo plano un comentario en una publicación.

La foto de arriba te muestra la interfaz de usuario para agregar un comentario. Cuando presionas el botón de enviar, el frontend envía una solicitud POST al 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,
            });
        });
}

Enviar un comentario en la interfaz de usuario

Usando Express.js, el backend escucha los eventos POST de la ruta.

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

Punto de finalización para agregar un comentario

Cuando se recibe el evento en la ruta monitoreada, el middleware FBAuth primero se asegura de que el usuario esté autenticado y pueda enviar dichos eventos.

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 de autenticación

Si la autenticación es exitosa, el middleware FBAuth agrega el nombre de usuario y la URL de la imagen de perfil del usuario a la solicitud y la envía a la función que actualiza la base de datos de Firebase (Firestore) con el comentario recién agregado.

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" });
        });
}

Agregar comentario a la base de datos

Finalmente, se desencadena un escucha con un comentario recién agregado, que, en respuesta, envía una notificación por el comentario recibido al usuario cuya publicación lo recibió.

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);
            })
    });

Agregando notificación

En este punto, un WebSocket podría enviar una actualización de notificación en tiempo real al usuario mientras recibe el comentario y la notificación, pero por simplicidad, opté por no hacerlo.

Nueva notificación de comentario

Al hacer clic en la notificación se muestra el comentario

Lo anterior es solo uno de los ejemplos. Muchos escuchas se están disparando en segundo plano. Aquí tienes algunos ejemplos:

  • Crear una notificación cuando un usuario le da "me gusta" a una publicación
  • Eliminar una notificación cuando un usuario quita su "me gusta" de una publicación
  • Actualizar todas las publicaciones con la nueva imagen de perfil del usuario cuando el autor cambia su foto de perfil
  • Actualizar todos los comentarios con la nueva imagen de perfil del usuario cuando el autor cambia su foto de perfil
  • Eliminar la foto antigua del almacenamiento cuando un usuario cambia su foto de perfil
  • Eliminar todos los comentarios, "me gusta" y notificaciones cuando un usuario elimina una publicación
  • ...
Return to top button