const express = require("express") const router = express.Router() const Errors = require("../lib/errors.js") const auth = require("../lib/authorize.js") const { User, Message, ChatAssociation, Chat, Attachment, Friend } = require("../models") router.all("*", auth, async (req, res, next) => { try { if (!req.user.emailVerified && req.app.locals.config.emailVerification) { throw Errors.emailVerificationRequired } else { next() } } catch (e) { next(e) } }) router.delete("/:id/:associationId", auth, async (req, res, next) => { try { const io = req.app.get("io") const chat = await ChatAssociation.findOne({ where: { id: req.params.id, userId: req.user.id, rank: "admin" }, include: [ { model: Chat, as: "chat", include: [ { model: User, as: "users", attributes: ["id", "username", "createdAt", "updatedAt"] } ] } ] }) const association = await ChatAssociation.findOne({ where: { id: req.params.associationId, chatId: chat.chat.id }, include: [ { model: User, as: "user", attributes: ["id", "username", "createdAt", "updatedAt"] } ] }) if (!chat) { throw Errors.chatNotFoundOrNotAdmin } if (association?.rank === "admin" || !association) { throw Errors.customMessage("User is admin and cannot be removed.") } await association.destroy() res.sendStatus(204) const message = await Message.create({ userId: 0, chatId: chat.chat.id, content: `${association.user.username} has been removed by ${req.user.username}.`, type: "leave" }) const associations = await ChatAssociation.findAll({ where: { chatId: chat.chat.id }, include: [ { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }) const messageLookup = await Message.findOne({ where: { id: message.id }, include: [ { model: ChatAssociation, as: "readReceipts", attributes: ["id"], include: [ { model: User, as: "user", attributes: ["username", "name", "avatar", "id"] } ] }, { model: Attachment, as: "attachments" }, { model: Message, as: "reply", include: [ { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }, { model: Chat, as: "chat", include: [ { model: User, as: "users", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }, { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }) associations.forEach((association) => { io.to(association.userId).emit("message", { ...messageLookup.dataValues, associationId: association.id, keyId: `${message.id}-${message.updatedAt.toISOString()}` }) io.to(association.userId).emit("memberListUpdate", { chatId: chat.chatId }) }) } catch (err) { next(err) } }) router.put("/:id/:associationId", auth, async (req, res, next) => { try { const chat = await ChatAssociation.findOne({ where: { id: req.params.id, userId: req.user.id, rank: "admin" }, include: [ { model: Chat, as: "chat", include: [ { model: User, as: "users", attributes: ["id", "username", "createdAt", "updatedAt"] } ] } ] }) const association = await ChatAssociation.findOne({ where: { id: req.params.associationId, chatId: chat.chat.id } }) if (!chat) { throw Errors.chatNotFoundOrNotAdmin } if (!association) { throw Errors.chatNotFoundOrNotAdmin } if (association.rank === "admin") { throw Errors.chatNotFoundOrNotAdmin } await association.update({ rank: req.body.rank }) res.sendStatus(204) const io = req.app.get("io") const associations = await ChatAssociation.findAll({ where: { chatId: chat.chat.id }, include: [ { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }) associations.forEach((association) => { io.to(association.userId).emit("memberListUpdate", { chatId: chat.chatId }) }) } catch (err) { next(err) } }) router.post("/:id", auth, async (req, res, next) => { try { const io = req.app.get("io") const chat = await ChatAssociation.findOne({ where: { userId: req.user.id, chatId: req.params.id, rank: "admin" }, include: [ { model: Chat, as: "chat", include: [ { model: User, as: "users", attributes: ["id", "username", "createdAt", "updatedAt"] } ] } ] }) if (chat) { if (req.body.users.length > 10) { throw Errors.invalidParameter( "User", "The maximum number of users is 10" ) } if (!req.body.users.length) { throw Errors.invalidParameter( "User", "You need at least 1 user to create a chat" ) } if (req.body.users.includes(req.user.id)) { throw Errors.invalidParameter( "User", "You cannot create a DM with yourself" ) } const friends = await Friend.findAll({ where: { userId: req.user.id, friendId: req.body.users, status: "accepted" } }) if (friends.length !== req.body.users.length) { throw Errors.invalidParameter( "User", "You are not friends with this user" ) } const users = await ChatAssociation.findAll({ where: { userId: req.body.users, chatId: req.params.id } }) if (users.length > 0) { throw Errors.invalidParameter( "User", "One or more users are already in this chat" ) } const associations = await ChatAssociation.findAll({ where: { chatId: chat.chatId }, include: [ { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }) for (let i = 0; i < req.body.users.length; i++) { const c1 = await ChatAssociation.create({ chatId: chat.chat.id, userId: req.body.users[i], rank: "member" }) const association = await ChatAssociation.findOne({ where: { id: c1.id }, include: [ { model: Chat, as: "chat", include: [ { model: User, as: "users", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt", "status" ] } ] }, { model: User, as: "user", attributes: ["id", "username", "createdAt", "updatedAt"] } ] }) io.to(req.body.users[i]).emit("chatAdded", association) const message = await Message.create({ userId: 0, chatId: chat.chatId, content: `${association.user.username} has been added by ${req.user.username}.`, type: "join" }) const messageLookup = await Message.findOne({ where: { id: message.id }, include: [ { model: ChatAssociation, as: "readReceipts", attributes: ["id"], include: [ { model: User, as: "user", attributes: ["username", "name", "avatar", "id"] } ] }, { model: Attachment, as: "attachments" }, { model: Message, as: "reply", include: [ { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }, { model: Chat, as: "chat", include: [ { model: User, as: "users", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }, { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }) associations.forEach((association) => { io.to(association.userId).emit("message", { ...messageLookup.dataValues, associationId: association.id, keyId: `${message.id}-${message.updatedAt.toISOString()}` }) io.to(association.userId).emit("memberListUpdate", { chatId: chat.chatId }) }) } res.sendStatus(204) } else { throw Errors.chatNotFoundOrNotAdmin } } catch (err) { next(err) } }) router.delete("/:id", auth, async (req, res, next) => { try { const io = req.app.get("io") const chat = await ChatAssociation.findOne({ where: { userId: req.user.id, id: req.params.id }, include: [ { model: Chat, as: "chat" } ] }) if (chat) { if (chat.chat.type === "direct") { throw Errors.leavingDirectChat } await chat.destroy() res.sendStatus(204) const message = await Message.create({ userId: 0, chatId: chat.chatId, content: `${req.user.username} has left the group.`, type: "leave" }) const associations = await ChatAssociation.findAll({ where: { chatId: chat.chatId }, include: [ { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }) const messageLookup = await Message.findOne({ where: { id: message.id }, include: [ { model: ChatAssociation, as: "readReceipts", attributes: ["id"], include: [ { model: User, as: "user", attributes: ["username", "name", "avatar", "id"] } ] }, { model: Attachment, as: "attachments" }, { model: Message, as: "reply", include: [ { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }, { model: Chat, as: "chat", include: [ { model: User, as: "users", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }, { model: User, as: "user", attributes: [ "username", "name", "avatar", "id", "createdAt", "updatedAt" ] } ] }) associations.forEach((association) => { io.to(association.userId).emit("message", { ...messageLookup.dataValues, associationId: association.id, keyId: `${message.id}-${message.updatedAt.toISOString()}` }) io.to(association.userId).emit("memberListUpdate", { chatId: chat.chatId }) }) } else { throw Errors.invalidParameter("chat association id") } } catch (err) { next(err) } }) module.exports = router