websockets implemented

This commit is contained in:
Torsten Schulz
2024-12-04 19:08:26 +01:00
parent d46a51db38
commit 069c97fa90
64 changed files with 2488 additions and 562 deletions

View File

@@ -7,9 +7,11 @@ import navigationRouter from './routers/navigationRouter.js';
import settingsRouter from './routers/settingsRouter.js';
import adminRouter from './routers/adminRouter.js';
import contactRouter from './routers/contactRouter.js';
import cors from 'cors';
import socialnetworkRouter from './routers/socialnetworkRouter.js';
import forumRouter from './routers/forumRouter.js';
import falukantRouter from './routers/falukantRouter.js';
import friendshipRouter from './routers/friendshipRouter.js';
import cors from 'cors';
import './jobs/sessionCleanup.js';
const __filename = fileURLToPath(import.meta.url);
@@ -36,6 +38,8 @@ app.use('/images', express.static(path.join(__dirname, '../frontend/public/image
app.use('/api/contact', contactRouter);
app.use('/api/socialnetwork', socialnetworkRouter);
app.use('/api/forum', forumRouter);
app.use('/api/falukant', falukantRouter);
app.use('/api/friendships', friendshipRouter);
app.use((req, res) => {
res.status(404).send('404 Not Found');

View File

@@ -24,6 +24,7 @@ class AuthController {
const { username, password } = req.body;
try {
const result = await userService.loginUser({ username, password });
console.log('User logged in successfully', result);
res.status(200).json(result);
} catch (error) {
if (error.message === 'credentialsinvalid') {
@@ -37,6 +38,7 @@ class AuthController {
async logout(req, res) {
const { userid: hashedUserId } = req.headers;
await userService.logoutUser(hashedUserId);
res.status(200).json({ result: 'loggedout' });
}
async forgotPassword(req, res) {

View File

@@ -1,11 +1,4 @@
import {
getMessages as getMessagesService,
findMatch,
registerUser as registerUserService,
addMessage,
endChat,
removeUser as removeUserService
} from '../services/chatService.js';
import chatService from '../services/chatService.js';
class ChatController {
constructor() {
@@ -15,12 +8,15 @@ class ChatController {
this.sendMessage = this.sendMessage.bind(this);
this.stopChat = this.stopChat.bind(this);
this.removeUser = this.removeUser.bind(this);
this.initOneToOne = this.initOneToOne.bind(this);
this.sendOneToOneMessage = this.sendOneToOneMessage.bind(this);
this.getOneToOneMessageHistory = this.getOneToOneMessageHistory.bind(this);
}
async getMessages(req, res) {
const { to, from } = req.body;
try {
const messages = await getMessagesService(to, from);
const messages = await chatService.getMessages(to, from);
res.status(200).json(messages);
} catch (error) {
res.status(500).json({ error: error.message });
@@ -30,7 +26,7 @@ class ChatController {
async findRandomChatMatch(req, res) {
const { genders, age, id } = req.body;
try {
const match = await findMatch(genders, age, id);
const match = await chatService.findMatch(genders, age, id);
if (match) {
res.status(200).json({ status: 'matched', user: match });
} else {
@@ -44,7 +40,7 @@ class ChatController {
async registerUser(req, res) {
const { gender, age } = req.body;
try {
const userId = await registerUserService(gender, age);
const userId = await chatService.registerUser(gender, age);
res.status(200).json({ id: userId });
} catch (error) {
res.status(500).json({ error: error.message });
@@ -54,7 +50,7 @@ class ChatController {
async sendMessage(req, res) {
const { from, to, text } = req.body;
try {
const message = await addMessage(from, to, text);
const message = await chatService.addMessage(from, to, text);
res.status(200).json(message);
} catch (error) {
res.status(500).json({ error: error.message });
@@ -64,7 +60,7 @@ class ChatController {
async removeUser(req, res) {
const { id } = req.body;
try {
await removeUserService(id);
await chatService.removeUser(id);
res.sendStatus(200);
} catch (error) {
res.status(500).json({ error: error.message });
@@ -74,12 +70,43 @@ class ChatController {
async stopChat(req, res) {
const { id } = req.body;
try {
await endChat(id);
await chatService.endChat(id);
res.sendStatus(200);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async initOneToOne(req, res) {
const { userid: hashedUserId } = req.headers;
const { partnerHashId } = req.body;
try {
await chatService.initOneToOne(hashedUserId, partnerHashId);
res.status(200).json({ message: 'One-to-one chat initialization is pending implementation.' });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async sendOneToOneMessage(req, res) {
const { user1HashId, user2HashId, message } = req.body;
try {
await chatService.sendOneToOneMessage(user1HashId, user2HashId, message);
res.status(200).json({ status: 'message sent' });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async getOneToOneMessageHistory(req, res) {
const { user1HashId, user2HashId } = req.query;
try {
const history = await chatService.getOneToOneMessageHistory(user1HashId, user2HashId);
res.status(200).json({ history });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
}
export default ChatController;

View File

@@ -0,0 +1,18 @@
import * as falukantService from '../services/falukantService.js';
class FalukantController {
constructor() {
this.exampleMethod = this.exampleMethod.bind(this);
}
async exampleMethod(req, res) {
try {
const result = await falukantService.exampleMethod();
res.status(200).json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
}
export default FalukantController;

View File

@@ -0,0 +1,69 @@
import friendshipService from '../services/friendshipService.js';
const friendshipController = {
async endFriendship(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const { friendUserId } = req.body;
await friendshipService.endFriendship(hashedUserId, friendUserId);
res.status(200).json({ message: 'Friendship ended successfully' });
} catch (error) {
console.error('Error in endFriendship:', error);
res.status(400).json({ error: error.message });
}
},
async acceptFriendship(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const { friendUserId } = req.body;
await friendshipService.acceptFriendship(hashedUserId, friendUserId);
res.status(200).json({ message: 'Friendship accepted successfully' });
} catch (error) {
console.error('Error in acceptFriendship:', error);
res.status(400).json({ error: error.message });
}
},
async rejectFriendship(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const { friendUserId } = req.body;
await friendshipService.rejectFriendship(hashedUserId, friendUserId);
res.status(200).json({ message: 'Friendship rejected successfully' });
} catch (error) {
console.error('Error in rejectFriendship:', error);
res.status(400).json({ error: error.message });
}
},
async withdrawRequest(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const { friendUserId } = req.body;
await friendshipService.withdrawRequest(hashedUserId, friendUserId);
res.status(200).json({ message: 'Friendship request withdrawn successfully' });
} catch (error) {
console.error('Error in withdrawRequest:', error);
res.status(400).json({ error: error.message });
}
},
async getFriendships(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const { acceptedOnly } = req.query;
console.log('Friendships:', acceptedOnly);
const friendships = await friendshipService.getFriendships(hashedUserId, acceptedOnly === 'true');
res.status(200).json(friendships);
} catch (error) {
console.error('Error in getFriendships:', error);
res.status(400).json({ error: error.message });
}
},
};
export default friendshipController;

View File

@@ -16,7 +16,7 @@ const menuStructure = {
children: {
manageFriends: {
visible: ["all"],
path: "/socialnetwork/friends",
path: "/friends",
icon: "friends24.png"
}
},

View File

@@ -26,12 +26,14 @@ class SocialNetworkController {
this.addFriend = this.addFriend.bind(this);
this.removeFriend = this.removeFriend.bind(this);
this.acceptFriendship = this.acceptFriendship.bind(this);
this.getLoggedInFriends = this.getLoggedInFriends.bind(this);
}
async userSearch(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const { username, ageFrom, ageTo, genders } = req.body;
const users = await this.socialNetworkService.searchUsers({ username, ageFrom, ageTo, genders });
const users = await this.socialNetworkService.searchUsers({ hashedUserId, username, ageFrom, ageTo, genders });
res.status(200).json(users);
} catch (error) {
console.error('Error in userSearch:', error);
@@ -295,6 +297,7 @@ class SocialNetworkController {
try {
const { userid: hashedUserid } = req.headers;
const { friendUserid } = req.body;
console.log('--------', friendUserid, hashedUserid);
await this.socialNetworkService.addFriend(hashedUserid, friendUserid);
res.status(201).json({ message: 'added' });
} catch (error) {
@@ -326,6 +329,20 @@ class SocialNetworkController {
res.status(500).json({ error: error.message });
}
}
async getLoggedInFriends(req, res) {
try {
const { userid: userId } = req.headers;
if (!userId) {
return res.status(400).json({ error: 'Missing user ID' });
}
const loggedInFriends = await this.socialNetworkService.getLoggedInFriends(userId);
res.status(200).json(loggedInFriends);
} catch (error) {
console.error('Error in getLoggedInFriends:', error);
res.status(500).json({ error: error.message });
}
}
}
export default SocialNetworkController;

View File

@@ -2,8 +2,7 @@ import User from '../models/community/user.js';
import { updateUserTimestamp } from '../utils/redis.js';
export const authenticate = async (req, res, next) => {
const userId = req.headers.userid;
const authCode = req.headers.authcode;
const { userid: userId, authcode: authCode } = req.headers;
if (!userId || !authCode) {
return res.status(401).json({ error: 'Unauthorized: Missing credentials' });
}
@@ -12,7 +11,7 @@ export const authenticate = async (req, res, next) => {
return res.status(401).json({ error: 'Unauthorized: Invalid credentials' });
}
try {
await updateUserTimestamp(userId);
await updateUserTimestamp(user.id);
} catch (error) {
console.error('Fehler beim Aktualisieren des Zeitstempels:', error);
}

View File

@@ -28,6 +28,9 @@ import ForumPermission from './forum/forum_permission.js';
import ForumUserPermission from './forum/forum_user_permission.js';
import ForumForumPermission from './forum/forum_forum_permission.js';
import Friendship from './community/friendship.js';
import FalukantUser from './falukant/data/user.js';
import RegionType from './falukant/type/region.js';
import RegionData from './falukant/data/region.js';
export default function setupAssociations() {
// UserParam related associations
@@ -40,12 +43,13 @@ export default function setupAssociations() {
User.hasMany(UserParam, { foreignKey: 'userId', as: 'user_params' });
UserParam.belongsTo(User, { foreignKey: 'userId', as: 'user' });
// UserRight related associations
UserParamValue.belongsTo(UserParamType, { foreignKey: 'userParamTypeId', as: 'user_param_value_type' });
UserParamType.hasMany(UserParamValue, { foreignKey: 'userParamTypeId', as: 'user_param_type_value' });
UserRight.belongsTo(User, { foreignKey: 'userId', as: 'user_with_rights' });
UserRight.belongsTo(UserRightType, { foreignKey: 'rightTypeId', as: 'rightType' });
UserRightType.hasMany(UserRight, { foreignKey: 'rightTypeId', as: 'user_rights' });
// UserParamVisibility related associations
UserParam.hasMany(UserParamVisibility, { foreignKey: 'param_id', as: 'param_visibilities' });
UserParamVisibility.belongsTo(UserParam, { foreignKey: 'param_id', as: 'param' });
@@ -162,4 +166,19 @@ export default function setupAssociations() {
Friendship.belongsTo(User, { foreignKey: 'user2Id', as: 'friendReceiver' });
User.hasMany(Friendship, { foreignKey: 'user1Id', as: 'friendSender' });
User.hasMany(Friendship, { foreignKey: 'user2Id', as: 'friendReceiver' });
User.hasMany(FalukantUser, { foreignKey: 'userId', as: 'falukantData' });
FalukantUser.belongsTo(User, { foreignKey: 'userId', as: 'user' });
RegionType.hasMany(RegionType, { foreignKey: 'parentId', as: 'children' });
RegionType.belongsTo(RegionType, { foreignKey: 'parentId', as: 'parent' });
RegionData.hasMany(RegionData, { foreignKey: 'parentId', as: 'children' });
RegionData.belongsTo(RegionData, { foreignKey: 'parentId', as: 'parent' });
RegionData.belongsTo(RegionType, { foreignKey: 'regionTypeId', as: 'regionType' });
RegionType.hasMany(RegionData, { foreignKey: 'regionTypeId', as: 'regions' });
FalukantUser.belongsTo(RegionData, { foreignKey: 'mainBranchRegionId', as: 'mainBranchRegion' });
RegionData.hasMany(FalukantUser, { foreignKey: 'mainBranchRegionId', as: 'users' });
}

View File

@@ -5,14 +5,27 @@ import crypto from 'crypto';
const User = sequelize.define('user', {
email: {
type: DataTypes.BLOB,
type: DataTypes.BLOB,
allowNull: false,
unique: true,
set(value) {
if (value) {
this.setDataValue('email', Buffer.from(encrypt(value), 'hex'));
const encrypted = encrypt(value);
this.setDataValue('email', encrypted);
}
},
get() {
const encrypted = this.getDataValue('email');
if (encrypted) {
return decrypt(encrypted);
}
return null;
}
},
salt: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: () => crypto.randomBytes(16).toString('hex')
},
username: {
type: DataTypes.STRING,
@@ -58,11 +71,6 @@ const User = sequelize.define('user', {
user.hashedId = hashedId;
await user.save();
}
},
getterMethods: {
email() {
return decrypt(this.getDataValue('email').toString('hex'));
}
}
});

View File

@@ -2,7 +2,7 @@ import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
import User from './user.js';
import UserParamType from '../type/user_param.js';
import { encrypt, decrypt, generateIv } from '../../utils/encryption.js';
import { encrypt, decrypt } from '../../utils/encryption.js';
const UserParam = sequelize.define('user_param', {
userId: {
@@ -10,45 +10,43 @@ const UserParam = sequelize.define('user_param', {
allowNull: false,
references: {
model: User,
key: 'id'
}
key: 'id',
},
},
paramTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: UserParamType,
key: 'id'
}
key: 'id',
},
},
value: {
type: DataTypes.STRING,
allowNull: false,
set(value) {
console.log('.... [set param value]', value);
if (value) {
try {
const iv = generateIv();
this.setDataValue('iv', iv.toString('hex'));
this.setDataValue('value', encrypt(value.toString(), iv));
const encrypted = encrypt(value.toString());
console.log('.... [encrypted param value]', encrypted);
this.setDataValue('value', encrypted);
} catch (error) {
this.setDataValue('value', '');
console.error('.... Error encrypting param value:', error);
this.setDataValue('value', '');
}
}
},
get() {
try {
const value = this.getDataValue('value');
const iv = Buffer.from(this.getDataValue('iv'), 'hex');
return decrypt(value, iv);
const value = this.getDataValue('value');
return decrypt(value);
} catch (error) {
console.error('.... Error decrypting param value:', error);
return '';
}
}
},
},
iv: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'user_param',
schema: 'community',
@@ -56,9 +54,9 @@ const UserParam = sequelize.define('user_param', {
indexes: [
{
unique: true,
fields: ['user_id', 'param_type_id']
}
]
fields: ['user_id', 'param_type_id'],
},
],
});
export default UserParam;

View File

@@ -0,0 +1,39 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import RegionType from '../type/region.js';
class RegionData extends Model { }
RegionData.init({
name: {
type: DataTypes.STRING,
allowNull: false,
},
regionTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: RegionType,
key: 'id',
schema: 'falukant_type'
}
},
parentId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'region',
key: 'id',
schema: 'falukant_data',
}
}
}, {
sequelize,
modelName: 'RegionData',
tableName: 'region',
schema: 'falukant_data',
timestamps: false,
underscored: true,
});
export default RegionData;

View File

@@ -0,0 +1,57 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
import RegionData from './region.js';
class FalukantUser extends Model { }
FalukantUser.init({
userId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'user',
schema: 'community'
},
key: 'id'
}
},
money: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 0.00,
},
creditAmount: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 0.00,
},
todayCreditTaken: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
defaultValue: 0.00,
},
creditInterestRate: {
type: DataTypes.DECIMAL(5, 2),
allowNull: false,
defaultValue: 0.00,
},
mainBranchRegionId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: RegionData,
key: 'id',
schema: 'falukant_data'
}
}
}, {
sequelize,
modelName: 'FalukantUser',
tableName: 'falukant_user',
schema: 'falukant_data',
timestamps: true,
underscored: true,
});
export default FalukantUser;

View File

@@ -0,0 +1,29 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class RegionType extends Model { }
RegionType.init({
labelTr: {
type: DataTypes.STRING,
allowNull: false,
},
parentId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'region',
key: 'id',
schema: 'falukant_type',
}
}
}, {
sequelize,
modelName: 'RegionType',
tableName: 'region',
schema: 'falukant_type',
timestamps: false,
underscored: true,
});
export default RegionType;

View File

@@ -32,6 +32,9 @@ import MessageHistory from './forum/message_history.js';
import MessageImage from './forum/message_image.js';
import ForumForumPermission from './forum/forum_forum_permission.js';
import Friendship from './community/friendship.js';
import FalukantUser from './falukant/data/user.js';
import RegionType from './falukant/type/region.js';
import RegionData from './falukant/data/region.js';
const models = {
SettingsType,
@@ -40,7 +43,7 @@ const models = {
UserRightType,
User,
UserParam,
Login,
Login,
UserRight,
InterestType,
InterestTranslationType,
@@ -68,6 +71,9 @@ const models = {
MessageHistory,
MessageImage,
Friendship,
RegionType,
RegionData,
FalukantUser,
};
export default models;

View File

@@ -3,6 +3,11 @@ import { DataTypes } from 'sequelize';
import UserParam from './user_param.js';
const UserParamValue = sequelize.define('user_param_value', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
userParamTypeId: {
type: DataTypes.INTEGER,
allowNull: false

View File

@@ -29,7 +29,8 @@
"sequelize": "^6.37.3",
"sharp": "^0.33.5",
"socket.io": "^4.7.5",
"uuid": "^10.0.0"
"uuid": "^10.0.0",
"ws": "^8.18.0"
},
"devDependencies": {
"sequelize-cli": "^6.6.2"
@@ -1200,9 +1201,9 @@
}
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -1481,16 +1482,16 @@
}
},
"node_modules/engine.io": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
"integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
@@ -1509,13 +1510,33 @@
}
},
"node_modules/engine.io/node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -1646,16 +1667,16 @@
}
},
"node_modules/express": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -2497,26 +2518,6 @@
"node": ">=18"
}
},
"node_modules/jsdom/node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -3647,15 +3648,15 @@
}
},
"node_modules/socket.io": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
"integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.5.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
@@ -3672,6 +3673,26 @@
"ws": "~8.17.1"
}
},
"node_modules/socket.io-adapter/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
@@ -4120,9 +4141,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"engines": {
"node": ">=10.0.0"
},

View File

@@ -31,7 +31,8 @@
"sequelize": "^6.37.3",
"sharp": "^0.33.5",
"socket.io": "^4.7.5",
"uuid": "^10.0.0"
"uuid": "^10.0.0",
"ws": "^8.18.0"
},
"devDependencies": {
"sequelize-cli": "^6.6.2"

View File

@@ -1,5 +1,6 @@
import { Router } from 'express';
import ChatController from '../controllers/chatController.js';
import { authenticate } from '../middleware/authMiddleware.js';
const router = Router();
const chatController = new ChatController();
@@ -10,5 +11,8 @@ router.post('/register', chatController.registerUser);
router.post('/sendMessage', chatController.sendMessage);
router.post('/leave', chatController.stopChat);
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
export default router;

View File

@@ -0,0 +1,9 @@
import express from 'express';
import FalukantController from '../controllers/falukantController.js';
const router = express.Router();
const falukantController = new FalukantController();
router.get('/example', falukantController.exampleMethod);
export default router;

View File

@@ -0,0 +1,15 @@
import { Router } from 'express';
import friendshipController from '../controllers/friendshipController.js';
import { authenticate } from '../middleware/authMiddleware.js';
const friendshipRouter = Router();
friendshipRouter.use(authenticate);
friendshipRouter.post('/end', friendshipController.endFriendship);
friendshipRouter.post('/accept', friendshipController.acceptFriendship);
friendshipRouter.post('/reject', friendshipController.rejectFriendship);
friendshipRouter.post('/withdraw', friendshipController.withdrawRequest);
friendshipRouter.get('/', friendshipController.getFriendships);
export default friendshipRouter;

View File

@@ -32,5 +32,6 @@ router.get('/diary/:page', socialNetworkController.getDiaryEntries);
router.post('/friend', socialNetworkController.addFriend);
router.delete('/friend/:friendUserId', socialNetworkController.removeFriend);
router.put('/friend/:friendUserId', socialNetworkController.acceptFriendship);
router.get('/friends/loggedin', socialNetworkController.getLoggedInFriends);
export default router;

View File

@@ -1,44 +1,17 @@
import http from 'http';
import { Server } from 'socket.io';
import amqp from 'amqplib/callback_api.js';
import app from './app.js';
import { setupWebSocket } from './utils/socket.js';
import { syncDatabase } from './utils/syncDatabase.js';
const server = http.createServer(app);
const io = new Server(server);
const RABBITMQ_URL = 'amqp://localhost';
const QUEUE = 'chat_messages';
amqp.connect(RABBITMQ_URL, (err, connection) => {
if (err) {
throw err;
}
connection.createChannel((err, channel) => {
if (err) {
throw err;
}
channel.assertQueue(QUEUE, { durable: false });
io.on('connection', (socket) => {
channel.consume(QUEUE, (msg) => {
const message = JSON.parse(msg.content.toString());
io.emit('newMessage', message);
}, { noAck: true });
socket.on('newMessage', (message) => {
channel.sendToQueue(QUEUE, Buffer.from(JSON.stringify(message)));
});
socket.on('disconnect', () => {
});
});
syncDatabase().then(() => {
server.listen(3001, () => {
console.log('Server is running on port 3001');
});
}).catch(err => {
console.error('Failed to sync database:', err);
process.exit(1);
});
setupWebSocket(server);
syncDatabase().then(() => {
server.listen(3001, () => {
console.log('Server is running on port 3001');
});
}).catch(err => {
console.error('Failed to sync database:', err);
process.exit(1);
});

View File

@@ -9,9 +9,11 @@ import UserRightType from '../models/type/user_right.js';
class BaseService {
async getUserByHashedId(hashedId) {
const user = await User.findOne({ where: { hashedId } });
console.log('async getUserByHashedId: ', hashedId);
const user = await User.findOne({ where: { hashedId: hashedId } });
if (!user) {
throw new Error('User not found');
console.log('User not found: ', hashedId);
throw new Error('User not found: ', hashedId);
}
return user;
}

View File

@@ -6,20 +6,64 @@ import UserParam from '../models/community/user_param.js';
import UserParamType from '../models/type/user_param.js';
import { sendAccountActivationEmail, sendPasswordResetEmail } from './emailService.js';
import { sequelize } from '../utils/sequelize.js';
import { Op } from 'sequelize';
import { setUserSession, deleteUserSession } from '../utils/redis.js';
import { encrypt } from '../utils/encryption.js';
import { notifyUser } from '../utils/socket.js';
import Friendship from '../models/community/friendship.js';
const saltRounds = 10;
const getFriends = async (userId) => {
console.log('getFriends', userId);
try {
const friendships = await Friendship.findAll({
where: {
[Op.or]: [
{ user1Id: userId },
{ user2Id: userId },
],
accepted: true,
},
include: [
{
model: User,
as: 'friendSender',
attributes: ['hashedId', 'username'],
},
{
model: User,
as: 'friendReceiver',
attributes: ['hashedId', 'username'],
},
],
});
console.log('did read out friends');
return friendships.map((friendship) => {
if (friendship.user1Id === userId) {
return friendship.friendReceiver;
} else {
return friendship.friendSender;
}
});
} catch (error) {
console.error('Error fetching friends:', error);
throw error;
}
};
export const registerUser = async ({ email, username, password, language }) => {
const encryptionKey = process.env.SECRET_KEY;
const results = await sequelize.query(
`SELECT * FROM community.user WHERE pgp_sym_decrypt(email::bytea, :key) = :email`,
{
replacements: { key: encryptionKey, email },
type: sequelize.QueryTypes.SELECT
}
);
if (results.length > 0) {
const encryptedEmail = encrypt(email);
const query = `
SELECT id FROM community.user
WHERE email = :encryptedEmail
`;
const existingUser = await sequelize.query(query, {
replacements: { encryptedEmail },
type: sequelize.QueryTypes.SELECT,
});
if (existingUser.length > 0) {
throw new Error('emailinuse');
}
const hashedPassword = await bcrypt.hash(password, saltRounds);
@@ -59,6 +103,13 @@ export const loginUser = async ({ username, password }) => {
const authCode = crypto.randomBytes(20).toString('hex');
user.authCode = authCode;
await user.save();
const friends = await getFriends(user.id);
for (const friend of friends) {
await notifyUser(friend.hashedId, 'friendloginchanged', {
userId: user.hashedId,
status: 'online',
});
}
const sessionData = {
id: user.hashedId,
username: user.username,
@@ -86,14 +137,14 @@ export const loginUser = async ({ username, password }) => {
id: user.hashedId,
username: user.username,
active: user.active,
param: mappedParams,
param: mappedParams,
authCode
};
};
export const logoutUser = async (hashedUserId) => {
try {
const user = User.findOne({
const user = await User.findOne({
where: {
hashedId: hashedUserId
}
@@ -101,8 +152,14 @@ export const logoutUser = async (hashedUserId) => {
if (!user) {
return;
}
const friends = await getFriends(user.id);
for (const friend of friends) {
await notifyUser(friend.hashedId, 'friendloginchanged', {
userId: user.hashedId,
status: 'online',
});
}
await deleteUserSession(user.id);
console.log('Benutzer erfolgreich aus Redis entfernt:', userId);
} catch (error) {
console.error('Fehler beim Logout:', error);
throw new Error('logoutfailed');

View File

@@ -1,76 +1,137 @@
import { v4 as uuidv4 } from 'uuid';
import amqp from 'amqplib/callback_api.js';
let messages = [];
let searchQueue = [];
let users = [];
let currentChats = [];
const RABBITMQ_URL = 'amqp://localhost';
const QUEUE = 'oneToOne_messages';
export const getMessages = (toId, fromId) => {
const userChats = currentChats.filter(chat => chat.includes(toId) && chat.includes(fromId));
if (userChats.length === 0) {
fromId = '';
class ChatService {
constructor() {
this.messages = [];
this.searchQueue = [];
this.users = [];
this.randomChats = [];
this.oneToOneChats = [];
amqp.connect(RABBITMQ_URL, (err, connection) => {
if (err) throw err;
connection.createChannel((err, channel) => {
if (err) throw err;
this.channel = channel;
channel.assertQueue(QUEUE, { durable: false });
});
});
}
const userMessages = messages.filter(message => message.to = toId && ["system", fromId].includes(message.from));
messages = messages.filter(message => message.to === toId && ["system", fromId].includes(message.from));
return userMessages;
};
export const addMessage = (from, to, text) => {
const userChats = currentChats.filter(chat => chat.includes(from) && chat.includes(to));
if (userChats.length === 0) {
return;
getMessages(toId, fromId) {
const userChats = this.randomChats.filter(chat => chat.includes(toId) && chat.includes(fromId));
if (userChats.length === 0) {
fromId = '';
}
const userMessages = this.messages.filter(message => message.to === toId && ["system", fromId].includes(message.from));
this.messages = this.messages.filter(message => message.to === toId && ["system", fromId].includes(message.from));
return userMessages;
}
messages.push({ from: from, to: to, text: text });
return { text: text };
};
export const findMatch = (genders, age, id) => {
const currentUsersChat = currentChats.filter(chat => chat.includes(id));
if (currentUsersChat.length > 0) {
return findUser(currentUsersChat[0][0] === id ? currentUsersChat[0][1] : currentUsersChat[0][0]);
async addMessage(from, to, text) {
const userChats = this.randomChats.filter(chat => chat.includes(from) && chat.includes(to));
if (userChats.length === 0) {
return;
}
this.messages.push({ from: from, to: to, text: text });
return { text: text };
}
let filteredSearchQueue = users.filter(user =>
searchQueue.some(sq => sq.id === user.id) && user.id !== id
&& currentChats.filter(chat => chat.includes(user.id)).length === 0
).sort(() => Math.random() - 0.5);
for (let i = 0; i < filteredSearchQueue.length; i++) {
const user = filteredSearchQueue[i];
const ageMatch = user.age >= age.min && user.age <= age.max;
const genderMatch = genders.includes(user.gender);
if (ageMatch && genderMatch) {
for (let j = searchQueue.length - 1; j >= 0; j--) {
if ([id, user.id].includes(searchQueue[j].id)) {
searchQueue.splice(j, 1);
findMatch(genders, age, id) {
const currentUsersChat = this.randomChats.filter(chat => chat.includes(id));
if (currentUsersChat.length > 0) {
return this.findUser(currentUsersChat[0][0] === id ? currentUsersChat[0][1] : currentUsersChat[0][0]);
}
let filteredSearchQueue = this.users.filter(user =>
this.searchQueue.some(sq => sq.id === user.id) && user.id !== id
&& this.randomChats.filter(chat => chat.includes(user.id)).length === 0
).sort(() => Math.random() - 0.5);
for (let i = 0; i < filteredSearchQueue.length; i++) {
const user = filteredSearchQueue[i];
const ageMatch = user.age >= age.min && user.age <= age.max;
const genderMatch = genders.includes(user.gender);
if (ageMatch && genderMatch) {
for (let j = this.searchQueue.length - 1; j >= 0; j--) {
if ([id, user.id].includes(this.searchQueue[j].id)) {
this.searchQueue.splice(j, 1);
}
}
this.randomChats.push([user.id, id]);
return user;
}
currentChats.push([user.id, id]);
return user;
}
if (!this.searchQueue.find(user => user.id === id)) {
this.searchQueue.push({ id, genders, age });
}
return null;
}
findUser(id) {
return this.users.find(user => user.id === id);
}
async registerUser(gender, age) {
const id = uuidv4();
this.users.push({ gender, age, id });
return id;
}
async removeUser(id) {
this.searchQueue = this.searchQueue.filter(user => user.id !== id);
this.users = this.users.filter(user => user.id !== id);
this.randomChats = this.randomChats.filter(pair => pair[0] === id || pair[1] === id);
this.messages = this.messages.filter(message => message.from === id || message.to === id);
}
async endChat(userId) {
this.randomChats = this.randomChats.filter(chat => !chat.includes(userId));
this.messages.push({ to: userId, from: 'system', activity: 'otheruserleft' });
}
async initOneToOne(user1HashId, user2HashId) {
const chat = this.searchOneToOneChat(user1HashId, user2HashId);
if (!chat) {
this.oneToOneChats.push({ user1Id: user1HashId, user2Id: user2HashId, history: [] });
}
}
if (!searchQueue.find(user => user.id === id)) {
searchQueue.push({ id, genders, age });
async sendOneToOneMessage(user1HashId, user2HashId, message) {
const messageBundle = {
timestamp: Date.now(),
sender: user1HashId,
recipient: user2HashId,
message: message,
};
const chat = this.searchOneToOneChat(user1HashId, user2HashId);
if (chat) {
chat.history.push(messageBundle);
} else {
this.oneToOneChats.push({
user1Id: user1HashId,
user2Id: user2HashId,
history: [messageBundle],
});
}
if (this.channel) {
this.channel.sendToQueue(QUEUE, Buffer.from(JSON.stringify(messageBundle)));
}
}
return null;
};
const findUser = (id) => {
return users.find(user => user.id === id);
};
async getOneToOneMessageHistory(user1HashId, user2HashId) {
const chat = this.searchOneToOneChat(user1HashId, user2HashId);
return chat ? chat.history : [];
}
export const registerUser = (gender, age) => {
const id = uuidv4();
users.push({ gender, age, id });
return id;
};
searchOneToOneChat(user1HashId, user2HashId) {
return this.oneToOneChats.find(chat =>
(chat.user1Id === user1HashId && chat.user2Id === user2HashId) ||
(chat.user1Id === user2HashId && chat.user2Id === user1HashId)
);
}
}
export const removeUser = (id) => {
searchQueue = searchQueue.filter(user => user.id !== id);
users = users.filter(user => user.id !== id);
currentChats = currentChats.filter(pair => pair[0] === id || pair[1] === id);
messages = messages.filter(message => message.from === id || message.to === id);
};
export const endChat = (userId) => {
currentChats = currentChats.filter(chat => !chat.includes(userId));
messages.push({ to: userId, from: 'system', activity: 'otheruserleft'})
}
export default new ChatService();

View File

@@ -0,0 +1,7 @@
class FalukantService {
async exampleMethod() {
// Logik für die Methode
}
}
export default new FalukantService();

View File

@@ -10,7 +10,8 @@ import User from '../models/community/user.js';
import ForumForumPermission from '../models/forum/forum_forum_permission.js';
import Title from '../models/forum/title.js';
import Message from '../models/forum/message.js';
import { notifyAllUsers } from '../utils/socket.js';
class ForumService extends BaseService {
async createForum(hashedUserId, name, permissions) {
@@ -30,6 +31,7 @@ class ForumService extends BaseService {
}
}
}
await notifyAllUsers('forumschanged', {});
return newForum;
}
@@ -60,6 +62,7 @@ class ForumService extends BaseService {
await forum.destroy({ transaction });
await transaction.commit();
await notifyAllUsers('forumschanged', {});
return forum;
} catch (error) {
await transaction.rollback();
@@ -193,6 +196,7 @@ class ForumService extends BaseService {
}
const newTopic = await Title.create({ title, forumId, createdBy: user.id });
await Message.create({ titleId: newTopic.id, text: content, createdBy: user.id})
await notifyAllUsers('topicschanged', { forumId, topic: newTopic });
return this.getForum(hashedUserId, forumId, 1);
}
@@ -289,6 +293,7 @@ class ForumService extends BaseService {
}
console.log('[ForumService.addMessage] - create new message');
await Message.create({ titleId: topicId, text: content, createdBy: user.id });
await notifyAllUsers('messageschanged', { topicId, message });
console.log('[ForumService.addMessage] - return topic');
return this.getTopic(hashedUserId, topicId);
}

View File

@@ -0,0 +1,180 @@
import BaseService from './BaseService.js';
import Friendship from '../models/community/friendship.js';
import User from '../models/community/user.js';
import { Op } from 'sequelize';
import UserParam from '../models/community/user_param.js';
import UserParamType from '../models/type/user_param.js';
import UserParamValue from '../models/type/user_param_value.js';
import { notifyUser } from '../utils/socket.js';
class FriendshipService extends BaseService {
genders = {};
async endFriendship(hashedUserId, friendUserId) {
const user = await this.getUserByHashedId(hashedUserId);
const friend = await this.getUserByHashedId(friendUserId);
if (!user) throw new Error('User not found.');
const friendship = await Friendship.findOne({
where: {
[Op.or]: [
{ user1Id: user.id, user2Id: friend.id },
{ user1Id: friend.id, user2Id: user.id },
],
},
});
if (!friendship) throw new Error('Friendship not found.');
await friendship.destroy();
notifyUser(friend.hashedId, 'friendshipChanged', { userId: user.hashedId });
}
async acceptFriendship(hashedUserId, friendUserId) {
const user = await this.getUserByHashedId(hashedUserId);
const friend = await this.getUserByHashedId(friendUserId);
if (!user) throw new Error('User not found.');
const friendship = await Friendship.findOne({
where: { user1Id: friend.id, user2Id: user.id, accepted: false },
});
if (!friendship) throw new Error('Cannot accept this friendship.');
friendship.accepted = true;
await friendship.save();
notifyUser(friend.hashedId, 'friendshipChanged', { userId: user.hashedId });
}
async rejectFriendship(hashedUserId, friendUserId) {
const user = await this.getUserByHashedId(hashedUserId);
const friend = await this.getUserByHashedId(friendUserId);
if (!user) throw new Error('User not found.');
const friendship = await Friendship.findOne({
where: { user1Id: friend.id, user2Id: user.id, accepted: false },
});
if (!friendship) throw new Error('Cannot reject this friendship.');
friendship.denied = true;
await friendship.save();
notifyUser(friend.hashedId, 'friendshipChanged', { userId: user.hashedId });
}
async withdrawRequest(hashedUserId, friendUserId) {
const user = await this.getUserByHashedId(hashedUserId);
const friend = await this.getUserByHashedId(friendUserId);
if (!user) throw new Error('User not found.');
const friendship = await Friendship.findOne({
where: { user1Id: user.id, user2Id: friend.id, accepted: false },
});
if (!friendship) throw new Error('Cannot withdraw this request.');
await friendship.destroy();
notifyUser(friend.hashedId, 'friendshipChanged', { userId: user.hashedId });
}
async getFriendships(hashedUserId, acceptedOnly) {
const user = await this.getUserByHashedId(hashedUserId);
if (!user) throw new Error('User not found.');
const whereCondition = acceptedOnly
? { accepted: true, withdrawn: false, denied: false }
: {};
const friendships = await Friendship.findAll({
where: {
...whereCondition,
[Op.or]: [
{ user1Id: user.id },
{ user2Id: user.id },
],
},
include: [
{
model: User,
as: 'friendSender',
attributes: ['username', 'hashedId'],
include: [
{
model: UserParam,
as: 'user_params',
required: false,
include: [
{
model: UserParamType,
as: 'paramType',
required: true,
where: { description: 'gender' },
attributes: ['description'],
},
],
attributes: ['value'],
},
],
},
{
model: User,
as: 'friendReceiver',
attributes: ['username', 'hashedId'],
include: [
{
model: UserParam,
as: 'user_params',
required: false,
include: [
{
model: UserParamType,
as: 'paramType',
required: true,
where: { description: 'gender' },
attributes: ['description'],
},
],
attributes: ['value'],
},
],
},
],
});
const processedFriendships = await Promise.all(
friendships.map(async (friendship) => {
const isInitiator = friendship.user1Id === user.id;
const otherUser = isInitiator ? friendship.friendReceiver : friendship.friendSender;
const genderParam = otherUser.user_params?.find(param => param.paramType.description === 'gender');
const gender = genderParam ? await this.getGender(genderParam) : null;
return {
id: friendship.id,
user: {
username: otherUser.username,
hashedId: otherUser.hashedId,
gender,
},
accepted: friendship.accepted,
denied: friendship.denied,
withdrawn: friendship.withdrawn,
isInitiator,
};
})
);
return processedFriendships;
}
async getGender(genderParam) {
if (!this.genders) {
this.genders = {};
}
if (this.genders[genderParam.value]) return this.genders[genderParam.value];
const genderObject = await UserParamValue.findOne({
where: { id: genderParam.value },
});
if (genderObject) {
this.genders[genderParam.value] = genderObject.value;
return genderObject.value;
}
return '';
}
}
export default new FriendshipService();

View File

@@ -156,7 +156,7 @@ class SettingsService extends BaseService{
include: [
{
model: UserParamType,
as: 'user_param_type',
as: 'user_param_value_type',
where: { description: type }
}
]
@@ -264,7 +264,6 @@ class SettingsService extends BaseService{
async updateVisibility(hashedUserId, userParamTypeId, visibilityId) {
try {
const user = await this.getUserByHashedId(hashedUserId);
console.log(JSON.stringify(user));
if (!user) {
throw new Error('User not found');
}

View File

@@ -24,15 +24,22 @@ import DOMPurify from 'dompurify';
import sharp from 'sharp';
import Diary from '../models/community/diary.js';
import Friendship from '../models/community/friendship.js';
import { getUserSession } from '../utils/redis.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class SocialNetworkService extends BaseService {
async searchUsers({ username, ageFrom, ageTo, genders }) {
async searchUsers({ hashedUserId, username, ageFrom, ageTo, genders }) {
const whereClause = this.buildSearchWhereClause(username);
const user = await this.loadUserByHash(hashedUserId);
console.log(hashedUserId, user);
if (!user) {
throw new Error('User not found');
}
whereClause.id = { [Op.ne]: user.id };
const users = await User.findAll({ where: whereClause, include: this.getUserParamsInclude() });
return this.filterUsersByCriteria(users, ageFrom, ageTo, genders);
return await this.filterUsersByCriteria(users, ageFrom, ageTo, genders);
}
async getProfile(hashedUserId, requestingUserId) {
@@ -48,16 +55,18 @@ class SocialNetworkService extends BaseService {
if (!user) {
throw new Error('User not found');
}
console.log('given data', data, folderId);
const parentFolder = data.parentId ? await Folder.findOne({
where: { id: data.parentId, userId: user.id }
}) : null;
if (data.parentId && !parentFolder) {
throw new Error('Parent folder not found');
}
console.log('parentFolder', parentFolder);
let newFolder;
if (folderId === 0) {
if (parentFolder) {
newFolder = await Folder.create({
parentId: data.parentId || null,
parentId: parentFolder.id || null,
userId: user.id,
name: data.name
});
@@ -267,7 +276,8 @@ class SocialNetworkService extends BaseService {
}
async loadUserByHash(hashedId) {
return await User.findOne({ hashedId });
console.log('Loading user by hashedId:', hashedId);
return await User.findOne({ where: { hashedId: hashedId } });
}
async loadUserByName(userName) {
@@ -300,10 +310,10 @@ class SocialNetworkService extends BaseService {
];
}
filterUsersByCriteria(users, ageFrom, ageTo, genders) {
async filterUsersByCriteria(users, ageFrom, ageTo, genders) {
const results = [];
for (const user of users) {
const userDetails = this.extractUserDetails(user);
const userDetails = await this.extractUserDetails(user);
if (this.isUserValid(userDetails, ageFrom, ageTo, genders)) {
results.push(userDetails);
}
@@ -311,11 +321,11 @@ class SocialNetworkService extends BaseService {
return results;
}
extractUserDetails(user) {
async extractUserDetails(user) {
const birthdateParam = user.user_params.find(param => param.paramType.description === 'birthdate');
const genderParam = user.user_params.find(param => param.paramType.description === 'gender');
const age = birthdateParam ? this.calculateAge(birthdateParam.value) : null;
const gender = genderParam ? this.getGenderValue(genderParam.value) : null;
const gender = genderParam ? await this.getGenderValue(genderParam.value) : null;
return {
id: user.hashedId,
username: user.username,
@@ -735,8 +745,9 @@ class SocialNetworkService extends BaseService {
}
async addFriend(hashedUserid, friendUserid) {
console.log('--------', friendUserid, hashedUserid);
const requestingUserId = await this.checkUserAccess(hashedUserid);
const friend = await this.loadUserByHash(friendUserid);
const friend = await User.findOne({ where: { hashedId: friendUserid } });
if (!friend) {
throw new Error('notfound');
}
@@ -748,10 +759,10 @@ class SocialNetworkService extends BaseService {
]
}
});
console.log('friendship', friend, requestingUserId);
if (friendship) {
if (friendship.withdrawn) {
friendship.withdrawn = false;
if (friendship.withdrawn && friendship.user1Id === requestingUserId) {
friendship.update({ withdrawn: false });
} else {
throw new Error('alreadyexists');
}
@@ -811,5 +822,37 @@ class SocialNetworkService extends BaseService {
throw new Error('notfound');
}
}
async getLoggedInFriends(hashedUserId) {
const userId = await this.checkUserAccess(hashedUserId);
const activeFriendships = await Friendship.findAll({
where: {
accepted: true,
denied: false,
withdrawn: false,
[Op.or]: [
{ user1Id: userId },
{ user2Id: userId }
]
}
});
const friendIds = activeFriendships.map(friendship =>
friendship.user1Id === userId ? friendship.user2Id : friendship.user1Id
);
const loggedInFriends = [];
for (const friendId of friendIds) {
const session = await getUserSession(friendId);
if (session && session.id) {
const friend = await User.findOne({ where: { hashedId: session.id } });
if (friend) {
loggedInFriends.push({
id: friend.hashedId,
username: friend.username,
});
}
}
}
return loggedInFriends;
}
}
export default SocialNetworkService;

View File

@@ -0,0 +1,39 @@
// services/webSocketService.js
import { Server } from 'socket.io';
import amqp from 'amqplib/callback_api.js';
const RABBITMQ_URL = 'amqp://localhost';
const QUEUE = 'chat_messages';
export function setupWebSocket(server) {
const io = new Server(server);
amqp.connect(RABBITMQ_URL, (err, connection) => {
if (err) throw err;
connection.createChannel((err, channel) => {
if (err) throw err;
channel.assertQueue(QUEUE, { durable: false });
io.on('connection', (socket) => {
console.log('Client connected via WebSocket');
// Konsumiert Nachrichten aus RabbitMQ und sendet sie an den WebSocket-Client
channel.consume(QUEUE, (msg) => {
const message = JSON.parse(msg.content.toString());
io.emit('newMessage', message); // Broadcast an alle Clients
}, { noAck: true });
// Empfangt eine Nachricht vom WebSocket-Client und sendet sie an die RabbitMQ-Warteschlange
socket.on('newMessage', (message) => {
channel.sendToQueue(QUEUE, Buffer.from(JSON.stringify(message)));
});
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
});
});
}

View File

@@ -1,22 +1,28 @@
import crypto from 'crypto';
const algorithm = 'aes-256-ecb';
const algorithm = 'aes-256-ecb';
const key = crypto.scryptSync(process.env.SECRET_KEY, 'salt', 32);
export const generateIv = () => {
return crypto.randomBytes(16);
return crypto.randomBytes(16).toString('base64');
};
export const encrypt = (text) => {
const cipher = crypto.createCipheriv(algorithm, key, null);
let encrypted = cipher.update(text, 'utf8', 'hex');
const cipher = crypto.createCipheriv(algorithm, key, null);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
};
export const decrypt = (text) => {
const decipher = crypto.createDecipheriv(algorithm, key, null);
let decrypted = decipher.update(text, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
try {
const decipher = crypto.createDecipheriv(algorithm, key, null);
let decrypted = decipher.update(text, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
console.log(error);
return null;
}
};

View File

@@ -0,0 +1,81 @@
import RegionData from "../../models/falukant/data/region.js";
import RegionType from "../../models/falukant/type/region.js";
const regionTypes = [];
const regionTypeTrs = [
"country",
"duchy",
"markgravate",
"shire",
"county",
"city"
];
const regions = [
{ labelTr: "falukant", regionType: "country", parentTr: null },
{ labelTr: "duchy1", regionType: "duchy", parentTr: "falukant" },
{ labelTr: "markgravate", regionType: "markgravate", parentTr: "duchy1" },
{ labelTr: "shire1", regionType: "shire", parentTr: "markgravate" },
{ labelTr: "county1", regionType: "county", parentTr: "shire1" },
{ labelTr: "town1", regionType: "city", parentTr: "county1" },
{ labelTr: "town2", regionType: "city", parentTr: "county1" },
{ labelTr: "town3", regionType: "city", parentTr: "county1" },
{ labelTr: "town4", regionType: "city", parentTr: "county1" },
];
// Step 1: Initialize region types
export const initializeFalukantTypes = async () => {
await initializeFalukantTypeRegions();
}
const initializeFalukantTypeRegions = async () => {
for (const regionType of regionTypeTrs) {
const [regionTypeRecord] = await RegionType.findOrCreate({
where: { labelTr: regionType },
defaults: {
parentId: regionTypes[regionTypes.length - 1]?.id
}
});
regionTypes.push(regionTypeRecord);
}
};
// Utility: Find region type object by region type
const findRegionTypeObjectByRegionType = async (regionType) => {
return regionTypes.find(region => region.labelTr === regionType) || null;
};
// Utility: Find region object by label
const findRegionObjectByLabelTr = async (labelTr) => {
return await RegionData.findOne({ where: { name: labelTr } });
};
// Step 2: Initialize regions
export const initializeFalukantRegions = async () => {
for (const region of regions) {
const regionType = await findRegionTypeObjectByRegionType(region.regionType);
if (!regionType) {
console.error(`Region type not found for: ${region.regionType}`);
continue;
}
const parentRegion = region.parentTr
? await findRegionObjectByLabelTr(region.parentTr)
: null;
if (region.parentTr && !parentRegion) {
console.error(`Parent region not found for: ${region.parentTr}`);
continue;
}
console.log('Creating/Fetching Region:', region.labelTr, 'Type:', regionType.labelTr, 'Parent:', parentRegion?.name);
await RegionData.findOrCreate({
where: { name: region.labelTr },
defaults: {
regionTypeId: regionType.id,
parentId: parentRegion?.id || null
}
});
}
};

View File

@@ -0,0 +1,8 @@
import { initializeFalukantTypes, initializeFalukantRegions } from './falukant/initializeFalukantTypes.js';
const initializeFalukant = async () => {
await initializeFalukantTypes();
await initializeFalukantRegions();
}
export default initializeFalukant;

View File

@@ -153,7 +153,7 @@ const initializeTypes = async () => {
};
for (const key of Object.keys(interestsList)) {
try {
try {
const value = interestsList[key];
const [item, created] = await Interest.findOrCreate({
where: { name: key },

View File

@@ -16,38 +16,54 @@ redisClient.connect().catch(console.error);
const setUserSession = async (userId, sessionData) => {
try {
await redisClient.hSet(`user:${userId}`, sessionData);
await redisClient.sendCommand(['JSON.SET', `user:${userId}`, '.', JSON.stringify(sessionData)]);
console.log(userId, sessionData);
const sessionDataStr = await redisClient.sendCommand(['JSON.GET', `user:${userId}`]);
console.log(sessionDataStr);
} catch (error) {
console.error('Fehler beim Setzen der Benutzersitzung:', error);
}
};
};
const deleteUserSession = async (userId) => {
try {
await redisClient.del(`user:${userId}`);
const result = await redisClient.del(`user:${userId}`);
if (result === 1) {
console.log(`Benutzersitzung für Benutzer ${userId} erfolgreich gelöscht.`);
} else {
console.warn(`Benutzersitzung für Benutzer ${userId} war nicht vorhanden.`);
}
} catch (error) {
console.error('Fehler beim Löschen der Benutzersitzung:', error);
}
};
const convertToOriginalType = (value) => {
if (value === 'true') return true;
if (value === 'false') return false;
if (!isNaN(value) && value.trim() !== '') return Number(value);
return value;
};
const getUserSession = async (userId) => {
try {
return await redisClient.hGetAll(`user:${userId}`);
const sessionData = await redisClient.sendCommand(['JSON.GET', `user:${userId}`]);
return JSON.parse(sessionData);
} catch (error) {
console.error('Fehler beim Abrufen der Benutzersitzung:', error);
return null;
}
};
const updateUserTimestamp = async (hashedId) => {
const updateUserTimestamp = async (userId) => {
try {
const userKey = `user:${hashedId}`;
const userKey = `user:${userId}`;
const userExists = await redisClient.exists(userKey);
if (userExists) {
await redisClient.hSet(userKey, 'timestamp', Date.now());
console.log(`Zeitstempel für Benutzer ${hashedId} aktualisiert.`);
} else {
console.warn(`Benutzer mit der hashedId ${hashedId} wurde nicht gefunden.`);
const sessionDataString = await redisClient.sendCommand(['JSON.GET', `user:${userId}`]);
const sessionData = JSON.parse(sessionDataString);
sessionData.timestamp = Date.now();
await redisClient.sendCommand(['JSON.SET', `user:${userId}`, '.', JSON.stringify(sessionData)]);
}
} catch (error) {
console.error('Fehler beim Aktualisieren des Zeitstempels:', error);
@@ -58,17 +74,29 @@ const cleanupExpiredSessions = async () => {
try {
const keys = await redisClient.keys('user:*');
const now = Date.now();
for (const key of keys) {
const session = await redisClient.hGetAll(key);
if (session.timestamp && now - parseInt(session.timestamp) > EXPIRATION_TIME) {
const userId = key.split(':')[1];
await redisClient.del(key);
await User.update({ authCode: '' }, { where: { hashedId: userId } });
console.log(`Abgelaufene Sitzung für Benutzer ${userId} gelöscht.`);
try {
const sessionStr = await redisClient.sendCommand(['JSON.GET', key]);
if (sessionStr) {
const session = JSON.parse(sessionStr);
if (session.timestamp && now - parseInt(session.timestamp) > EXPIRATION_TIME) {
const userId = key.split(':')[1];
await redisClient.del(key);
await User.update({ authCode: '' }, { where: { hashedId: userId } });
console.log(`Abgelaufene Sitzung für Benutzer ${userId} mit RedisJSON gelöscht.`);
}
}
} catch (error) {
if (error.message.includes('WRONGTYPE')) {
console.warn(`Schlüssel ${key} ist kein JSON-Objekt, wird übersprungen.`);
} else {
console.error(`Fehler beim Bereinigen für Schlüssel ${key}:`, error);
}
}
}
} catch (error) {
console.error('Fehler beim Bereinigen abgelaufener Sitzungen:', error);
console.error('Fehler beim Bereinigen abgelaufener Sitzungen mit RedisJSON:', error);
}
};

View File

@@ -17,6 +17,8 @@ const createSchemas = async () => {
await sequelize.query('CREATE SCHEMA IF NOT EXISTS type');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS service');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS forum');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_data');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_type');
};
const initializeDatabase = async () => {

73
backend/utils/socket.js Normal file
View File

@@ -0,0 +1,73 @@
import { Server } from 'socket.io';
import BaseService from '../services/BaseService.js';
const baseService = new BaseService();
let io;
const userSockets = {};
export function setupWebSocket(server) {
io = new Server(server, {
cors: {
origin: '*',
},
});
io.on('connection', (socket) => {
socket.on('setUserId', (userId) => {
if (userId) {
socket.userId = userId;
userSockets[userId] = socket.id;
}
});
socket.on('disconnect', () => {
if (socket.userId) {
delete userSockets[socket.userId];
}
});
});
}
export function getIo() {
if (!io) {
throw new Error('Socket.io ist nicht initialisiert!');
}
return io;
}
export function getUserSockets() {
return userSockets;
}
export async function notifyUser(recipientHashedUserId, event, data) {
const io = getIo();
const userSockets = getUserSockets();
try {
const recipientUser = await baseService.getUserByHashedId(recipientHashedUserId);
if (recipientUser) {
const socketId = userSockets[recipientUser.hashedId];
if (socketId) {
io.to(socketId).emit(event, data);
}
} else {
console.log(`Benutzer mit gehashter ID ${recipientHashedUserId} nicht gefunden.`);
}
} catch (err) {
console.error('Fehler beim Senden der Benachrichtigung:', err);
}
}
export async function notifyAllUsers(event, data) {
const io = getIo();
const userSockets = getUserSockets();
try {
for (const [userId, socketId] of Object.entries(userSockets)) {
io.to(socketId).emit(event, data);
console.log(`Benachrichtigung an Benutzer mit ID ${userId} gesendet.`);
}
} catch (err) {
console.error('Fehler beim Senden der Benachrichtigung an alle Benutzer:', err);
}
}

View File

@@ -5,6 +5,7 @@ import initializeTypes from './initializeTypes.js';
import initializeSettings from './initializeSettings.js';
import initializeUserRights from './initializeUserRights.js';
import initializeImageTypes from './initializeImageTypes.js';
import initializeFalukant from './initializeFalukant.js';
import setupAssociations from '../models/associations.js';
import models from '../models/index.js';
import { createTriggers } from '../models/trigger.js';
@@ -13,32 +14,35 @@ import initializeForum from './initializeForum.js';
const syncDatabase = async () => {
try {
console.log("Initializing database schemas...");
await initializeDatabase(); // Stellt sicher, dass alle Schemas erstellt sind
await initializeDatabase();
console.log("Synchronizing models...");
await syncModels(models); // Modelle synchronisieren
await syncModels(models);
console.log("Setting up associations...");
setupAssociations(); // Assoziationen definieren
setupAssociations();
console.log("Creating triggers...");
await createTriggers(); // Trigger erstellen
await createTriggers();
console.log("Initializing settings...");
await initializeSettings(); // Einstellungsdaten initialisieren
await initializeSettings();
console.log("Initializing types...");
await initializeTypes(); // Typen initialisieren
await initializeTypes();
console.log("Initializing user rights...");
await initializeUserRights(); // Benutzerrechte initialisieren
await initializeUserRights();
console.log("Initializing image types...");
await initializeImageTypes(); // Bildtypen initialisieren
await initializeImageTypes();
console.log("Initializing forums...");
await initializeForum(); // Foren initialisieren
await initializeForum();
console.log("Initializing Falukant...");
await initializeFalukant();
console.log('Database synchronization complete.');
} catch (error) {
console.error('Unable to synchronize the database:', error);