Apr 22nd, 2024
L'esperimento dell'app sociale,
Social Beaver
, va offline dopo più di tre anni di utilizzo continuo e ininterrotto. Quello che è iniziato come un mini progetto per padroneggiare il contenitore di gestione dello stato di React Redux si è trasformato in una completa rete sociale simile a Twitter che funziona su Firebase.
L'ambito di questo post non è né educativo né quello di dare una panoramica dettagliata del codice e spiegare tutte le funzionalità e le ragioni dietro la mia implementazione del codice.
No, terrò le cose brevi e semplici. Oggi tutti sanno come funzionano i social network e cosa ci si può aspettare usando uno. Non c'è bisogno per me di entrare nei dettagli. Il post è principalmente per me per ricordare la piattaforma e per chi legge per vedere cosa stavo facendo nei miei primi anni di codifica.
Nei paragrafi seguenti, menzionerò brevemente alcune delle principali funzionalità della piattaforma insieme alle loro schermate.
Se, per qualche motivo, decidi di arrivare fino alla fine, ti aspetta una sorpresa. Mostrerò come un semplice commento su una pubblicazione ha innescato una serie di cose sullo sfondo. Una spiegazione banale per i programmatori esperti, ma potrebbe essere interessante per tutti gli altri.
Innanzitutto, devo menzionare che la piattaforma è stata programmata pensando prima di tutto ai dispositivi mobili. Volevo che fosse reattiva. Credo che al giorno d'oggi più persone utilizzino i social network sui loro dispositivi mobili piuttosto che sui loro computer fissi.
Non è un social network senza utenti e non puoi avere utenti senza che creino un account e possano accedere con esso.
Una chiamata al servizio di autenticazione di Firebase sul backend, e voilà, l'utente è loggato. Non sono più un fan di Firebase, ma hanno reso davvero semplice per gli sviluppatori.
App completamente responsiva - pagina di autenticazione
Gli utenti potevano accedere alla homepage senza essere loggati, ma dovevano essere loggati per interagire con la piattaforma. Ciò è stato fatto sia per la sicurezza degli utenti che per il mio portafoglio.
I dati legati agli account potevano essere moderati rapidamente, e una moderazione veloce mi evitava mal di testa nel caso in cui Social Beaver diventasse un obiettivo di un attacco di spam di contenuti. Lo storage e le richieste diventano rapidamente costosi su Firebase. Ho effettivamente impostato il limite massimo mensile delle mie spese lì, ma preferisco evitarlo se possibile.
Homepage (versione desktop)
Homepage (versione mobile)
Gli utenti potevano cambiare la loro foto, la biografia, la posizione e il link al loro sito web. Questo è solo una frazione di ciò che gli utenti dovrebbero essere in grado di fare con i loro profili, poiché non avevano alcun modo per cambiare la loro password, richiedere i loro dati utente o impostare una sicurezza aggiuntiva per l'autenticazione come la doppia autenticazione (2FA) e frasi anti-phishing, ecc.
Ma poiché questo era un progetto di apprendimento, senza che io avessi l'intenzione di spingerlo alle masse, puoi capire perché le funzionalità sopra citate mancano.
Editor del profilo
Non sarebbe chiamato un social network se gli utenti non potessero creare post, leggere post ed esprimere le proprie opinioni sotto forma di commenti o mi piace. Non c'è davvero nulla di innovativo in queste funzionalità, ma dovevano essere menzionate per i motivi sopra indicati.
Aggiungi un commento
Sei arrivato alla fine dell'articolo. Grazie! Come promesso, ti mostrerò cosa provoca in background un commento a un post.
La foto qui sopra ti mostra l'interfaccia utente per aggiungere un commento. Quando premi il pulsante di invio, il frontend invia una richiesta 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,
});
});
}
Invia un commento sull'interfaccia utente
Utilizzando Express.js, il backend ascolta gli eventi POST della route.
app.post('/bites/:biteId/comment', FBAuth, commentBite)
Endpoint per aggiungere un commento
Quando l'evento viene ricevuto sulla route monitorata, il middleware
FBAuth
si assicura prima che l'utente sia autenticato e possa inviare tali eventi.
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 di autenticazione
Se l'autenticazione è riuscita, il middleware FBAuth aggiunge il nome utente e l'URL dell'immagine del profilo dell'utente alla richiesta e la inoltra alla funzione che aggiorna il database di Firebase (Firestore) con il commento appena aggiunto.
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" });
});
}
Aggiunta di un commento al database
Infine, viene attivato un listener con un commento appena aggiunto, che, in risposta, invia una notifica per il commento ricevuto all'utente il cui post l'ha ricevuto.
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);
})
});
Aggiunta di notifica
A questo punto, un WebSocket potrebbe inviare un aggiornamento delle notifiche in tempo reale all'utente mentre riceve il commento e la notifica, ma per semplicità ho optato per non farlo.
Notifica di nuovo commento
Cliccando sulla notifica, viene visualizzato il commento
Quanto sopra è solo uno degli esempi. Molti listener vengono attivati in background. Ecco alcuni esempi: