DTP Base provides a scalable and secure Node.js application development harness ready for production service.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

262 lines
6.9 KiB

// chat.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
import mongoose from 'mongoose';
// const AuthToken = mongoose.model('AuthToken');
const ChatRoom = mongoose.model('ChatRoom');
const ChatMessage = mongoose.model('ChatMessage');
// const ChatRoomInvite = mongoose.model('ChatRoomInvite');
import numeral from 'numeral';
import { SiteService, SiteError } from '../../lib/site-lib.js';
import { MAX_ROOM_CAPACITY } from '../models/lib/constants.js';
export default class ChatService extends SiteService {
static get name ( ) { return 'ChatService'; }
static get slug () { return 'chat'; }
constructor (dtp) {
super(dtp, ChatService);
}
async start ( ) {
const { user: userService } = this.dtp.services;
this.templates = {
memberListItem: this.loadViewTemplate('chat/components/member-list-item-standalone.pug'),
};
this.populateChatRoom = [
{
path: 'owner',
select: userService.USER_SELECT,
},
];
}
async createRoom (owner, roomDefinition) {
const { text: textService } = this.dtp.services;
const NOW = new Date();
const room = new ChatRoom();
room.created = NOW;
room.owner = owner._id;
room.name = textService.filter(roomDefinition.name);
if (roomDefinition.topic) {
room.topic = textService.filter(roomDefinition.topic);
}
room.capacity = MAX_ROOM_CAPACITY;
room.members = [owner._id];
room.stats.memberCount = 1;
await room.save();
return room.toObject();
}
async destroyRoom (user, room) {
if (user._id.equals(room.owner._id)) {
throw new SiteError(401, 'This is not your chat room');
}
await this.removeMessagesForChannel(room);
await ChatRoom.deleteOne({ _id: room._id });
}
async joinRoom (room, user) {
const roomData = await ChatRoom.findOne({ _id: room._id, banned: user._id }).lean();
if (roomData) {
throw new SiteError(401, 'You are banned from this chat room');
}
const response = await ChatRoom.updateOne(
{ _id: room._id },
{
$push: { members: user._id },
},
);
this.log.debug('joinRoom complete', { response });
return response;
}
async chatRoomCheckIn (room, member) {
const NOW = new Date();
// indicate presence in the chat room's Mongo document
const roomData = await ChatRoom.findOneAndUpdate(
{ _id: room._id },
{
$addToSet: { present: member._id },
$inc: { 'stats.presentCount': 1 },
},
{
new: true,
},
);
this.log.debug('member checking into chat room', {
room: {
_id: room._id,
name: room.name,
presentCount: roomData.stats.presentCount,
},
member: {
_id: member._id,
username: member.username,
},
});
/*
* Broadcast a control message to all room members that a new member has
* joined the room.
*/
const displayList = this.createDisplayList('chat-control');
displayList.removeElement(`ul#present-members li[data-member-id="${member._id}"]`);
displayList.addElement(
`ul#chat-active-members[data-room-id="${room._id}"]`,
'afterBegin',
this.templates.memberListItem({ room, member }),
);
displayList.setTextContent(
`.chat-present-count`,
numeral(roomData.stats.presentCount).format('0,0'),
);
const systemMessage = {
created: NOW.toISOString(),
content: `<a href="/member/${member.username}", uk-tooltip="Visit ${member.username}">@${member.username}</a> has connected to the room.`,
};
this.dtp.emitter.to(room._id.toString()).emit('chat-control', { displayList, systemMessages: [systemMessage] });
}
async chatRoomCheckOut (room, member) {
const NOW = new Date();
const roomData = await ChatRoom.findOneAndUpdate(
{ _id: room._id },
{
$pull: { present: member._id },
$inc: { 'stats.presentCount': -1 },
},
{
new: true,
},
);
this.log.debug('member checking out of chat room', {
room: {
_id: room._id,
name: room.name,
presentCount: roomData.stats.presentCount,
},
member: {
_id: member._id,
username: member.username,
},
});
/*
* Broadcast a control message to all room members that a new member has
* joined the room.
*/
const displayList = this.createDisplayList('chat-control');
displayList.removeElement(`ul#chat-active-members li[data-member-id="${member._id}"]`);
displayList.setTextContent(
`.chat-present-count`,
numeral(roomData.stats.presentCount).format('0,0'),
);
const systemMessage = {
created: NOW.toISOString(),
content: `<a href="/member/${member.username}", uk-tooltip="Visit ${member.username}">@${member.username}</a> has connected to the room.`,
};
this.dtp.emitter.to(room._id.toString()).emit('chat-control', { displayList, systemMessages: [systemMessage] });
}
async checkRoomMember (room, member) {
if (room.owner._id.equals(member._id)) {
return true;
}
const search = { _id: room._id, members: member._id };
const checkRoom = await ChatRoom.findOne(search).select('name').lean();
if (!checkRoom) {
throw new SiteError(403, `You are not a member of ${checkRoom.name}`);
}
return true;
}
async isRoomMember (room, member) {
if (room.owner._id.equals(member._id)) {
return true;
}
const search = { _id: room._id, members: member._id };
const checkRoom = await ChatRoom.findOne(search).select('name').lean();
return !!checkRoom;
}
async leaveRoom (room, user) {
await ChatRoom.updateOne(
{ _id: room._id },
{
$pull: { members: user._id },
},
);
}
async getRoomMemberList (room) {
const roomData = await ChatRoom.findOne({ _id: room._id }).select('members');
if (!roomData) {
throw new SiteError(404, 'Room not found');
}
return roomData.members;
}
async getRoomBlockList (room) {
const roomData = await ChatRoom.findOne({ _id: room._id }).select('members');
if (!roomData) {
throw new SiteError(404, 'Room not found');
}
return roomData.banned;
}
async getRoomById (roomId) {
const room = await ChatRoom
.findOne({ _id: roomId })
.populate(this.populateChatRoom)
.lean();
return room;
}
async getRoomsForOwner (owner) {
const rooms = await ChatRoom
.find({ owner: owner._id })
.populate(this.populateChatRoom)
.lean();
return rooms;
}
async getRoomsForMember (member, pagination) {
const rooms = await ChatRoom
.find({ members: member._id })
.populate(this.populateChatRoom)
.skip(pagination.skip)
.limit(pagination.cpp)
.lean();
return rooms;
}
async removeMessagesForChannel (channel) {
await ChatMessage.deleteMany({ channel: channel._id });
}
}