Add password reset localization and chat configuration
- Implemented German and English localization for password reset functionality. - Added WebSocket URL resolution logic in chat services to support various environments and configurations. - Created centralized chat configuration for event keys and payload mappings. - Developed RoomsView component for admin chat room management, including create, edit, and delete functionalities.
This commit is contained in:
4
backend/config/chatBridge.json
Normal file
4
backend/config/chatBridge.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"host": "localhost",
|
||||
"port": 1235
|
||||
}
|
||||
@@ -177,9 +177,60 @@ class AdminController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async updateRoom(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
if (!userId || !(await AdminService.hasUserAccess(userId, 'chatrooms'))) {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung.' });
|
||||
}
|
||||
const schema = Joi.object({
|
||||
title: Joi.string().min(1).max(255).required(),
|
||||
roomTypeId: Joi.number().integer().required(),
|
||||
isPublic: Joi.boolean().required(),
|
||||
genderRestrictionId: Joi.number().integer().allow(null),
|
||||
minAge: Joi.number().integer().min(0).allow(null),
|
||||
maxAge: Joi.number().integer().min(0).allow(null),
|
||||
password: Joi.string().allow('', null),
|
||||
friendsOfOwnerOnly: Joi.boolean().allow(null),
|
||||
requiredUserRightId: Joi.number().integer().allow(null)
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
const room = await AdminService.updateRoom(req.params.id, value);
|
||||
res.status(200).json(room);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async createRoom(req, res) {
|
||||
try {
|
||||
const room = await AdminService.createRoom(req.body);
|
||||
const userId = req.headers.userid;
|
||||
if (!userId || !(await AdminService.hasUserAccess(userId, 'chatrooms'))) {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung.' });
|
||||
}
|
||||
const schema = Joi.object({
|
||||
title: Joi.string().min(1).max(255).required(),
|
||||
roomTypeId: Joi.number().integer().required(),
|
||||
isPublic: Joi.boolean().required(),
|
||||
genderRestrictionId: Joi.number().integer().allow(null),
|
||||
minAge: Joi.number().integer().min(0).allow(null),
|
||||
maxAge: Joi.number().integer().min(0).allow(null),
|
||||
password: Joi.string().allow('', null),
|
||||
friendsOfOwnerOnly: Joi.boolean().allow(null),
|
||||
requiredUserRightId: Joi.number().integer().allow(null)
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
const room = await AdminService.createRoom(value);
|
||||
res.status(201).json(room);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -189,6 +240,10 @@ class AdminController {
|
||||
|
||||
async deleteRoom(req, res) {
|
||||
try {
|
||||
const userId = req.headers.userid;
|
||||
if (!userId || !(await AdminService.hasUserAccess(userId, 'chatrooms'))) {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung.' });
|
||||
}
|
||||
await AdminService.deleteRoom(req.params.id);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import chatService from '../services/chatService.js';
|
||||
import Joi from 'joi';
|
||||
|
||||
class ChatController {
|
||||
constructor() {
|
||||
@@ -11,12 +12,20 @@ class ChatController {
|
||||
this.initOneToOne = this.initOneToOne.bind(this);
|
||||
this.sendOneToOneMessage = this.sendOneToOneMessage.bind(this);
|
||||
this.getOneToOneMessageHistory = this.getOneToOneMessageHistory.bind(this);
|
||||
this.getRoomList = this.getRoomList.bind(this);
|
||||
}
|
||||
|
||||
async getMessages(req, res) {
|
||||
const { to, from } = req.body;
|
||||
const schema = Joi.object({
|
||||
to: Joi.string().required(),
|
||||
from: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const messages = await chatService.getMessages(to, from);
|
||||
const messages = await chatService.getMessages(value.to, value.from);
|
||||
res.status(200).json(messages);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -24,9 +33,17 @@ class ChatController {
|
||||
}
|
||||
|
||||
async findRandomChatMatch(req, res) {
|
||||
const { genders, age, id } = req.body;
|
||||
const schema = Joi.object({
|
||||
genders: Joi.array().items(Joi.string()).required(),
|
||||
age: Joi.number().integer().min(0).required(),
|
||||
id: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const match = await chatService.findMatch(genders, age, id);
|
||||
const match = await chatService.findMatch(value.genders, value.age, value.id);
|
||||
if (match) {
|
||||
res.status(200).json({ status: 'matched', user: match });
|
||||
} else {
|
||||
@@ -38,9 +55,16 @@ class ChatController {
|
||||
}
|
||||
|
||||
async registerUser(req, res) {
|
||||
const { gender, age } = req.body;
|
||||
const schema = Joi.object({
|
||||
gender: Joi.string().required(),
|
||||
age: Joi.number().integer().min(0).required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const userId = await chatService.registerUser(gender, age);
|
||||
const userId = await chatService.registerUser(value.gender, value.age);
|
||||
res.status(200).json({ id: userId });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -48,9 +72,17 @@ class ChatController {
|
||||
}
|
||||
|
||||
async sendMessage(req, res) {
|
||||
const { from, to, text } = req.body;
|
||||
const schema = Joi.object({
|
||||
from: Joi.string().required(),
|
||||
to: Joi.string().required(),
|
||||
text: Joi.string().min(1).max(2000).required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const message = await chatService.addMessage(from, to, text);
|
||||
const message = await chatService.addMessage(value.from, value.to, value.text);
|
||||
res.status(200).json(message);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -58,9 +90,15 @@ class ChatController {
|
||||
}
|
||||
|
||||
async removeUser(req, res) {
|
||||
const { id } = req.body;
|
||||
const schema = Joi.object({
|
||||
id: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
await chatService.removeUser(id);
|
||||
await chatService.removeUser(value.id);
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -68,9 +106,15 @@ class ChatController {
|
||||
}
|
||||
|
||||
async stopChat(req, res) {
|
||||
const { id } = req.body;
|
||||
const schema = Joi.object({
|
||||
id: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
await chatService.endChat(id);
|
||||
await chatService.endChat(value.id);
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -78,10 +122,16 @@ class ChatController {
|
||||
}
|
||||
|
||||
async initOneToOne(req, res) {
|
||||
const schema = Joi.object({
|
||||
partnerHashId: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { partnerHashId } = req.body;
|
||||
try {
|
||||
await chatService.initOneToOne(hashedUserId, partnerHashId);
|
||||
await chatService.initOneToOne(hashedUserId, value.partnerHashId);
|
||||
res.status(200).json({ message: 'One-to-one chat initialization is pending implementation.' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -89,9 +139,17 @@ class ChatController {
|
||||
}
|
||||
|
||||
async sendOneToOneMessage(req, res) {
|
||||
const { user1HashId, user2HashId, message } = req.body;
|
||||
const schema = Joi.object({
|
||||
user1HashId: Joi.string().required(),
|
||||
user2HashId: Joi.string().required(),
|
||||
message: Joi.string().min(1).max(2000).required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
await chatService.sendOneToOneMessage(user1HashId, user2HashId, message);
|
||||
await chatService.sendOneToOneMessage(value.user1HashId, value.user2HashId, value.message);
|
||||
res.status(200).json({ status: 'message sent' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -107,6 +165,16 @@ class ChatController {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getRoomList(req, res) {
|
||||
// Öffentliche Räume für Chat-Frontend
|
||||
try {
|
||||
const rooms = await chatService.getRoomList();
|
||||
res.status(200).json(rooms);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatController;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ContactService from '../services/ContactService.js';
|
||||
import Joi from 'joi';
|
||||
|
||||
class ContactController {
|
||||
constructor() {
|
||||
@@ -6,9 +7,18 @@ class ContactController {
|
||||
}
|
||||
|
||||
async addContactMessage(req, res) {
|
||||
const schema = Joi.object({
|
||||
email: Joi.string().email().required(),
|
||||
name: Joi.string().min(1).max(255).required(),
|
||||
message: Joi.string().min(1).max(2000).required(),
|
||||
acceptDataSave: Joi.boolean().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { email, name, message, acceptDataSave } = req.body;
|
||||
await ContactService.addContactMessage(email, name, message, acceptDataSave);
|
||||
await ContactService.addContactMessage(value.email, value.name, value.message, value.acceptDataSave);
|
||||
res.status(200).json({ status: 'ok' });
|
||||
} catch (error) {
|
||||
res.status(409).json({ error: error.message });
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import forumService from '../services/forumService.js';
|
||||
import Joi from 'joi';
|
||||
|
||||
const forumController = {
|
||||
async createForum(req, res) {
|
||||
const schema = Joi.object({
|
||||
name: Joi.string().min(1).max(255).required(),
|
||||
permissions: Joi.array().items(Joi.string().min(1).max(255)).required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { name, permissions } = req.body;
|
||||
const forum = await forumService.createForum(userId, name, permissions);
|
||||
const forum = await forumService.createForum(userId, value.name, value.permissions);
|
||||
res.status(201).json(forum);
|
||||
} catch (error) {
|
||||
console.error('Error in createForum:', error);
|
||||
@@ -49,10 +57,18 @@ const forumController = {
|
||||
},
|
||||
|
||||
async createTopic(req, res) {
|
||||
const schema = Joi.object({
|
||||
forumId: Joi.number().integer().required(),
|
||||
title: Joi.string().min(1).max(255).required(),
|
||||
content: Joi.string().min(1).max(5000).required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { forumId, title, content } = req.body;
|
||||
const result = await forumService.createTopic(userId, forumId, title, content);
|
||||
const result = await forumService.createTopic(userId, value.forumId, value.title, value.content);
|
||||
res.status(201).json(result);
|
||||
} catch (error) {
|
||||
console.error('Error in createTopic:', error);
|
||||
@@ -73,11 +89,17 @@ const forumController = {
|
||||
},
|
||||
|
||||
async addMessage(req, res) {
|
||||
const schema = Joi.object({
|
||||
content: Joi.string().min(1).max(5000).required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { id: topicId } = req.params;
|
||||
const { content } = req.body;
|
||||
const result = await forumService.addMessage(userId, topicId, content);
|
||||
const result = await forumService.addMessage(userId, topicId, value.content);
|
||||
res.status(201).json(result);
|
||||
} catch (error) {
|
||||
console.error('Error in addMessage:', error);
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import friendshipService from '../services/friendshipService.js';
|
||||
import Joi from 'joi';
|
||||
|
||||
const friendshipController = {
|
||||
async endFriendship(req, res) {
|
||||
const schema = Joi.object({
|
||||
friendUserId: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { friendUserId } = req.body;
|
||||
|
||||
await friendshipService.endFriendship(hashedUserId, friendUserId);
|
||||
await friendshipService.endFriendship(hashedUserId, value.friendUserId);
|
||||
res.status(200).json({ message: 'Friendship ended successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error in endFriendship:', error);
|
||||
@@ -15,11 +21,16 @@ const friendshipController = {
|
||||
},
|
||||
|
||||
async acceptFriendship(req, res) {
|
||||
const schema = Joi.object({
|
||||
friendUserId: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { friendUserId } = req.body;
|
||||
|
||||
await friendshipService.acceptFriendship(hashedUserId, friendUserId);
|
||||
await friendshipService.acceptFriendship(hashedUserId, value.friendUserId);
|
||||
res.status(200).json({ message: 'Friendship accepted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error in acceptFriendship:', error);
|
||||
@@ -28,11 +39,16 @@ const friendshipController = {
|
||||
},
|
||||
|
||||
async rejectFriendship(req, res) {
|
||||
const schema = Joi.object({
|
||||
friendUserId: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { friendUserId } = req.body;
|
||||
|
||||
await friendshipService.rejectFriendship(hashedUserId, friendUserId);
|
||||
await friendshipService.rejectFriendship(hashedUserId, value.friendUserId);
|
||||
res.status(200).json({ message: 'Friendship rejected successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error in rejectFriendship:', error);
|
||||
@@ -41,10 +57,16 @@ const friendshipController = {
|
||||
},
|
||||
|
||||
async withdrawRequest(req, res) {
|
||||
const schema = Joi.object({
|
||||
friendUserId: Joi.string().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { friendUserId } = req.body;
|
||||
await friendshipService.withdrawRequest(hashedUserId, friendUserId);
|
||||
await friendshipService.withdrawRequest(hashedUserId, value.friendUserId);
|
||||
res.status(200).json({ message: 'Friendship request withdrawn successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error in withdrawRequest:', error);
|
||||
|
||||
@@ -80,7 +80,8 @@ const menuStructure = {
|
||||
visible: ["over12"],
|
||||
action: "openMultiChat",
|
||||
view: "window",
|
||||
class: "multiChatWindow"
|
||||
class: "multiChatDialog",
|
||||
icon: "multichat24.png"
|
||||
},
|
||||
randomChat: {
|
||||
visible: ["over12"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import settingsService from '../services/settingsService.js';
|
||||
import Joi from 'joi';
|
||||
|
||||
class SettingsController {
|
||||
async filterSettings(req, res) {
|
||||
@@ -13,9 +14,17 @@ class SettingsController {
|
||||
}
|
||||
|
||||
async updateSetting(req, res) {
|
||||
const { userid, settingId, value } = req.body;
|
||||
const schema = Joi.object({
|
||||
userid: Joi.string().required(),
|
||||
settingId: Joi.number().integer().required(),
|
||||
value: Joi.any().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
await settingsService.updateSetting(userid, settingId, value);
|
||||
await settingsService.updateSetting(value.userid, value.settingId, value.value);
|
||||
res.status(200).json({ message: 'Setting updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error updating user setting:', error);
|
||||
@@ -68,8 +77,16 @@ class SettingsController {
|
||||
}
|
||||
|
||||
async setAccountSettings(req, res) {
|
||||
const schema = Joi.object({
|
||||
userId: Joi.string().required(),
|
||||
settings: Joi.object().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
await settingsService.setAccountSettings(req.body);
|
||||
await settingsService.setAccountSettings(value);
|
||||
res.status(200).json({ message: 'Account settings updated successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error updating account settings:', error);
|
||||
@@ -100,10 +117,16 @@ class SettingsController {
|
||||
}
|
||||
|
||||
async addInterest(req, res) {
|
||||
const schema = Joi.object({
|
||||
name: Joi.string().min(1).max(255).required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { name } = req.body;
|
||||
const interest = await settingsService.addInterest(userId, name);
|
||||
const interest = await settingsService.addInterest(userId, value.name);
|
||||
res.status(200).json({ interest });
|
||||
} catch (error) {
|
||||
console.error('Error adding interest:', error);
|
||||
@@ -112,10 +135,16 @@ class SettingsController {
|
||||
}
|
||||
|
||||
async addUserInterest(req, res) {
|
||||
const schema = Joi.object({
|
||||
interestid: Joi.number().integer().required()
|
||||
});
|
||||
const { error, value } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error.details[0].message });
|
||||
}
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { interestid: interestId } = req.body;
|
||||
await settingsService.addUserInterest(userId, interestId);
|
||||
await settingsService.addUserInterest(userId, value.interestid);
|
||||
res.status(200).json({ message: 'User interest added successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error adding user interest:', error);
|
||||
|
||||
@@ -12,6 +12,7 @@ router.get('/chat/gender-restrictions', authenticate, adminController.getGenderR
|
||||
router.get('/chat/user-rights', authenticate, adminController.getUserRights);
|
||||
router.get('/chat/rooms', authenticate, adminController.getRooms);
|
||||
router.post('/chat/rooms', authenticate, adminController.createRoom);
|
||||
router.put('/chat/rooms/:id', authenticate, adminController.updateRoom);
|
||||
router.delete('/chat/rooms/:id', authenticate, adminController.deleteRoom);
|
||||
|
||||
router.get('/interests/open', authenticate, adminController.getOpenInterests);
|
||||
|
||||
@@ -14,5 +14,6 @@ router.post('/exit', chatController.removeUser);
|
||||
router.post('/initOneToOne', authenticate, chatController.initOneToOne);
|
||||
router.post('/oneToOne/sendMessage', authenticate, chatController.sendOneToOneMessage); // Neue Route zum Senden einer Nachricht
|
||||
router.get('/oneToOne/messageHistory', authenticate, chatController.getOneToOneMessageHistory); // Neue Route zum Abrufen der Nachrichtengeschichte
|
||||
router.get('/rooms', chatController.getRoomList);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -308,7 +308,20 @@ class AdminService {
|
||||
}
|
||||
|
||||
async getRooms() {
|
||||
// Only return necessary fields to the frontend
|
||||
return await Room.findAll({
|
||||
attributes: [
|
||||
'id',
|
||||
'title',
|
||||
'roomTypeId',
|
||||
'isPublic',
|
||||
'genderRestrictionId',
|
||||
'minAge',
|
||||
'maxAge',
|
||||
'friendsOfOwnerOnly',
|
||||
'requiredUserRightId',
|
||||
'password' // only if needed for editing, otherwise remove
|
||||
],
|
||||
include: [
|
||||
{ model: RoomType, as: 'roomType' },
|
||||
{ model: UserParamValue, as: 'genderRestriction' },
|
||||
@@ -316,6 +329,13 @@ class AdminService {
|
||||
});
|
||||
}
|
||||
|
||||
async updateRoom(id, data) {
|
||||
const room = await Room.findByPk(id);
|
||||
if (!room) throw new Error('Room not found');
|
||||
await room.update(data);
|
||||
return room;
|
||||
}
|
||||
|
||||
async createRoom(data) {
|
||||
return await Room.create(data);
|
||||
}
|
||||
|
||||
@@ -132,6 +132,22 @@ class ChatService {
|
||||
(chat.user1Id === user2HashId && chat.user2Id === user1HashId)
|
||||
);
|
||||
}
|
||||
|
||||
async getRoomList() {
|
||||
// Nur öffentliche Räume, keine sensiblen Felder
|
||||
const { default: Room } = await import('../models/chat/room.js');
|
||||
const { default: RoomType } = await import('../models/chat/room_type.js');
|
||||
return Room.findAll({
|
||||
attributes: [
|
||||
'id', 'title', 'roomTypeId', 'isPublic', 'genderRestrictionId',
|
||||
'minAge', 'maxAge', 'friendsOfOwnerOnly', 'requiredUserRightId'
|
||||
],
|
||||
where: { isPublic: true },
|
||||
include: [
|
||||
{ model: RoomType, as: 'roomType' }
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new ChatService();
|
||||
|
||||
307
backend/services/chatTcpBridge.js
Normal file
307
backend/services/chatTcpBridge.js
Normal file
@@ -0,0 +1,307 @@
|
||||
import net from 'net';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const DEFAULT_CONFIG = { host: 'localhost', port: 1235 };
|
||||
|
||||
function loadBridgeConfig() {
|
||||
try {
|
||||
const cfgPath = path.resolve(process.cwd(), 'backend/config/chatBridge.json');
|
||||
if (fs.existsSync(cfgPath)) {
|
||||
const raw = fs.readFileSync(cfgPath, 'utf-8');
|
||||
const json = JSON.parse(raw);
|
||||
return { ...DEFAULT_CONFIG, ...json };
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load chatBridge.json, using defaults:', e.message);
|
||||
}
|
||||
return { ...DEFAULT_CONFIG };
|
||||
}
|
||||
|
||||
export default class ChatTcpBridge {
|
||||
constructor(ioSocket, user) {
|
||||
this.ioSocket = ioSocket;
|
||||
this.user = user || {};
|
||||
this.token = null;
|
||||
this.tcp = null;
|
||||
this.buffer = '';
|
||||
this.config = loadBridgeConfig();
|
||||
this.pending = [];
|
||||
this.objStart = -1;
|
||||
this.depthCurly = 0;
|
||||
this.depthSquare = 0;
|
||||
this.inString = false;
|
||||
this.escapeNext = false;
|
||||
}
|
||||
|
||||
connect(initialRoomName = '') {
|
||||
if (this.tcp) return;
|
||||
const { host, port } = this.config;
|
||||
|
||||
this.ioEmitStatus('connecting', `${host}:${port}`);
|
||||
console.log(`[ChatBridge] Connecting to ${host}:${port}`);
|
||||
this.tcp = new net.Socket();
|
||||
|
||||
this.tcp.on('connect', () => {
|
||||
this.ioEmitStatus('connected', `${host}:${port}`);
|
||||
console.log(`[ChatBridge] Connected to ${host}:${port}`);
|
||||
// Erstnachricht: init
|
||||
const initPayload = {
|
||||
type: 'init',
|
||||
name: this.user.username || '',
|
||||
room: initialRoomName || '',
|
||||
};
|
||||
this.sendRaw(initPayload);
|
||||
});
|
||||
|
||||
this.tcp.on('data', (data) => {
|
||||
this.processChunk(data.toString('utf-8'));
|
||||
});
|
||||
|
||||
this.tcp.on('error', (err) => {
|
||||
this.ioEmitStatus('error', err.message);
|
||||
console.error('[ChatBridge] Error:', err.message);
|
||||
});
|
||||
|
||||
this.tcp.on('close', () => {
|
||||
this.ioEmitStatus('disconnected');
|
||||
console.warn('[ChatBridge] Disconnected');
|
||||
});
|
||||
|
||||
this.tcp.connect(port, host);
|
||||
}
|
||||
|
||||
handleIncomingLine(line) {
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
this.handleIncoming(obj);
|
||||
} catch (e) {
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: line });
|
||||
}
|
||||
}
|
||||
|
||||
processChunk(chunk) {
|
||||
if (!chunk) return;
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
const ch = chunk[i];
|
||||
this.buffer += ch;
|
||||
if (this.escapeNext) {
|
||||
this.escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
if (this.inString) {
|
||||
if (ch === '\\') { this.escapeNext = true; }
|
||||
else if (ch === '"') { this.inString = false; }
|
||||
continue;
|
||||
}
|
||||
if (ch === '"') { this.inString = true; continue; }
|
||||
if (ch === '{') { this.depthCurly++; if (this.objStart === -1) this.objStart = this.buffer.length - 1; }
|
||||
else if (ch === '}') { this.depthCurly--; }
|
||||
else if (ch === '[') { this.depthSquare++; }
|
||||
else if (ch === ']') { this.depthSquare--; }
|
||||
|
||||
// Wenn ein komplettes Objekt abgeschlossen ist (außerhalb von Strings und beide Tiefen 0)
|
||||
if (this.objStart !== -1 && this.depthCurly === 0) {
|
||||
const jsonStr = this.buffer.slice(this.objStart, this.buffer.length);
|
||||
// Alles vor objStart (falls vorhanden) rausfiltern
|
||||
const prefix = this.buffer.slice(0, this.objStart);
|
||||
if (prefix.trim()) {
|
||||
// Unerwarteter Text, als System ausgeben
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: prefix.trim() });
|
||||
}
|
||||
this.buffer = '';
|
||||
this.objStart = -1;
|
||||
this.parseAndHandle(jsonStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseAndHandle(jsonStr) {
|
||||
try {
|
||||
const obj = JSON.parse(jsonStr);
|
||||
// Forward raw parsed inbound message to browser console
|
||||
this.ioSocket.emit('chat:debug', { dir: 'in', payload: obj });
|
||||
this.handleIncoming(obj);
|
||||
} catch (e) {
|
||||
// Wenn mehrere Objekte direkt aneinander hängen (}{), versuchen sie zu splitten
|
||||
const parts = jsonStr.split('}{').map((p, idx, arr) => (idx === 0 ? p + '}' : (idx === arr.length - 1 ? '{' + p : '{' + p + '}')));
|
||||
if (parts.length > 1) {
|
||||
parts.forEach(p => {
|
||||
try {
|
||||
const o = JSON.parse(p);
|
||||
this.ioSocket.emit('chat:debug', { dir: 'in', payload: o });
|
||||
this.handleIncoming(o);
|
||||
} catch (_) {
|
||||
this.ioSocket.emit('chat:debug', { dir: 'in', raw: p });
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: p });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.ioSocket.emit('chat:debug', { dir: 'in', raw: jsonStr });
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: jsonStr });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleIncoming(obj) {
|
||||
if (!obj) return;
|
||||
// Map numeric type codes
|
||||
if (typeof obj.type === 'number') {
|
||||
switch (obj.type) {
|
||||
case 1: // token
|
||||
if (typeof obj.message === 'string' && obj.message) {
|
||||
this.token = obj.message;
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: 'Token received' });
|
||||
const actions = this.pending; this.pending = [];
|
||||
actions.forEach(fn => { try { fn(); } catch (_) { } });
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 3: // room list
|
||||
if (Array.isArray(obj.message)) {
|
||||
const names = obj.message.map(r => r.name).filter(Boolean).join(', ');
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: names ? `Rooms: ${names}` : 'Rooms updated' });
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 5: {
|
||||
// generic server event (room entered, color changes, etc.)
|
||||
const msg = obj.message;
|
||||
if (!msg) break;
|
||||
if (typeof msg === 'string') {
|
||||
if (msg === 'room_entered') {
|
||||
const to = obj.to || obj.name || obj.room || '';
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', code: 'room_entered', tr: 'room_entered', to });
|
||||
return;
|
||||
}
|
||||
if (msg === 'color_changed' || msg === 'user_color_changed') {
|
||||
this.ioSocket.emit('chat:incoming', {
|
||||
type: 'system',
|
||||
code: msg,
|
||||
color: obj.color,
|
||||
userName: obj.userName || ''
|
||||
});
|
||||
return;
|
||||
}
|
||||
// unknown message string; fallthrough to text echo
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: msg });
|
||||
return;
|
||||
}
|
||||
if (typeof msg === 'object') {
|
||||
const tr = msg.tr || 'room_entered';
|
||||
const to = msg.to || msg.name || msg.room || '';
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', code: tr, tr, to });
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
// scream
|
||||
const userName = obj.userName || obj.user || obj.name || '';
|
||||
const message = obj.message || '';
|
||||
this.ioSocket.emit('chat:incoming', {
|
||||
type: 'scream',
|
||||
userName,
|
||||
message,
|
||||
color: obj.color || null
|
||||
});
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (obj.type === 'token' && obj.message) {
|
||||
this.token = obj.message;
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: 'Token received' });
|
||||
// Ausstehende Aktionen jetzt mit Token senden
|
||||
const actions = this.pending;
|
||||
this.pending = [];
|
||||
actions.forEach(fn => {
|
||||
try { fn(); } catch (_) { }
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Normalize generic messages
|
||||
if (typeof obj.message === 'string' && obj.message && (obj.userName || obj.user || obj.name)) {
|
||||
this.ioSocket.emit('chat:incoming', {
|
||||
type: 'message',
|
||||
message: obj.message,
|
||||
userName: obj.userName || obj.user || obj.name,
|
||||
color: obj.color || null
|
||||
});
|
||||
return;
|
||||
}
|
||||
// As system fallback
|
||||
this.ioSocket.emit('chat:incoming', { type: 'system', text: JSON.stringify(obj) });
|
||||
}
|
||||
|
||||
sendRaw(obj) {
|
||||
if (!this.tcp) return;
|
||||
try {
|
||||
const str = JSON.stringify(obj) + '\n';
|
||||
// Forward outbound payload to browser console
|
||||
this.ioSocket.emit('chat:debug', { dir: 'out', payload: obj });
|
||||
this.tcp.write(str);
|
||||
} catch (e) {
|
||||
this.ioEmitStatus('error', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
sendWithToken(obj) {
|
||||
if (this.token) obj.token = this.token;
|
||||
this.sendRaw(obj);
|
||||
}
|
||||
|
||||
withTokenOrQueue(fn) {
|
||||
if (this.token) {
|
||||
fn();
|
||||
} else {
|
||||
this.pending.push(fn);
|
||||
}
|
||||
}
|
||||
|
||||
joinRoom(roomName, password = '') {
|
||||
// Prefer keys name/room if server expects them for room switch; fallback to newroom if required elsewhere
|
||||
this.withTokenOrQueue(() => this.sendWithToken({ type: 'join', room: roomName, name: this.user.username || '', password }));
|
||||
}
|
||||
|
||||
sendMessage(text) {
|
||||
this.withTokenOrQueue(() => this.sendWithToken({ type: 'message', message: text, userName: this.user.username }));
|
||||
}
|
||||
|
||||
scream(text) {
|
||||
this.withTokenOrQueue(() => this.sendWithToken({ type: 'scream', message: text }));
|
||||
}
|
||||
|
||||
doAction(text) {
|
||||
this.withTokenOrQueue(() => this.sendWithToken({ type: 'do', message: text }));
|
||||
}
|
||||
|
||||
dice(expr) {
|
||||
this.withTokenOrQueue(() => this.sendWithToken({ type: 'dice', message: expr || '' }));
|
||||
}
|
||||
|
||||
color(hex) {
|
||||
let value = typeof hex === 'string' ? hex.trim() : '';
|
||||
if (!value) return;
|
||||
if (!value.startsWith('#')) value = '#' + value;
|
||||
// Normalize 3-digit to 6-digit when possible
|
||||
const m3 = /^#([0-9a-fA-F]{3})$/.exec(value);
|
||||
if (m3) {
|
||||
value = '#' + m3[1].split('').map(c => c + c).join('');
|
||||
}
|
||||
const m6 = /^#([0-9a-fA-F]{6})$/.exec(value);
|
||||
if (!m6) return;
|
||||
this.withTokenOrQueue(() => this.sendWithToken({ type: 'color', value }));
|
||||
}
|
||||
|
||||
close() {
|
||||
try { this.tcp?.destroy(); } catch (_) { }
|
||||
this.tcp = null;
|
||||
}
|
||||
|
||||
ioEmitStatus(type, detail) {
|
||||
this.ioSocket.emit('chat:status', { type, detail });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user