Bugs in settings fixed, profile added

This commit is contained in:
Torsten Schulz
2024-09-21 00:25:42 +02:00
parent c5a72d57d8
commit e494fe41db
65 changed files with 3121 additions and 7478 deletions

View File

@@ -8,6 +8,7 @@ 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';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -31,6 +32,7 @@ app.use('/api/settings', settingsRouter);
app.use('/api/admin', adminRouter);
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
app.use('/api/contact', contactRouter);
app.use('/api/socialnetwork', socialnetworkRouter);
app.use((req, res) => {
res.status(404).send('404 Not Found');

View File

@@ -1,4 +1,5 @@
import AdminService from '../services/adminService.js';
import Joi from 'joi';
export const getOpenInterests = async (req, res) => {
try {
@@ -51,4 +52,28 @@ export const getOpenContacts = async (req, res) => {
} catch (error) {
res.status(403).json({ error: error.message });
}
}
}
export const answerContact = async (req, res) => {
try {
const schema = Joi.object({
id: Joi.number().integer().required(),
answer: Joi.string().min(1).required()
});
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
const { id, answer } = value;
await AdminService.answerContact(id, answer);
res.status(200).json({ status: 'ok' });
} catch (error) {
console.error('Error in answerContact:', error);
res.status(error.status || 500).json({ error: error.message || 'Internal Server Error' });
}
};

View File

@@ -162,6 +162,10 @@ const menuStructure = {
visible: ["all"],
path: "/settings/interests"
},
flirt: {
visible: ["over14"],
path: "/settings/flirt"
},
sexuality: {
visible: ["over14"],
path: "/settings/sexuality"

View File

@@ -133,3 +133,25 @@ export const removeInterest = async (req, res) => {
res.status(500).json({ error: 'Internal server error' });
}
}
export const getVisibilities = async (req, res) => {
try {
const visibilities = await settingsService.getVisibilities();
res.status(200).json(visibilities);
} catch (error) {
console.error('Error retrieving visibilities:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
export const updateVisibility = async (req, res) => {
const { userParamTypeId, visibilityId } = req.body;
const hashedUserId = req.headers.userid;
try {
await settingsService.updateVisibility(hashedUserId, userParamTypeId, visibilityId);
res.status(200).json({ message: 'Visibility updated successfully' });
} catch (error) {
console.error('Error updating visibility:', error);
res.status(500).json({ error: 'Internal server error' });
}
};

View File

@@ -0,0 +1,37 @@
import SocialNetworkService from '../services/socialnetworkService.js';
class SocialNetworkController {
constructor() {
this.socialNetworkService = new SocialNetworkService();
this.userSearch = this.userSearch.bind(this);
this.profile = this.profile.bind(this);
}
async userSearch(req, res) {
try {
const { username, ageFrom, ageTo, genders } = req.body;
const users = await this.socialNetworkService.searchUsers({ username, ageFrom, ageTo, genders });
res.status(200).json(users);
} catch (error) {
console.error('Error in userSearch:', error);
res.status(500).json({ error: error.message });
}
}
async profile(req, res) {
try {
const { userId } = req.params;
const requestingUserId = req.headers.userid;
if (!userId || !requestingUserId) {
return res.status(400).json({ error: 'Invalid user or requesting user ID.' });
}
const profile = await this.socialNetworkService.getProfile(userId, requestingUserId);
res.status(200).json(profile);
} catch (error) {
console.error('Error in profile:', error);
res.status(500).json({ error: error.message });
}
}
}
export default SocialNetworkController;

View File

@@ -2,5 +2,19 @@
"account_activation_subject": "Aktivierung Deines yourPart-Zugangs",
"account_activation_html": "<p>Hallo {{username}},</p><p>Herzlichen Dank für Deine Registrierung bei yourPart. Um Deinen Zugang zu erhalten, musst Du Ihn noch aktivieren. Dazu klicke bitte folgenden Link an:</p><p><a href='{{activationLink}}'>{{activationLink}}</a></p><p>Alternativ kannst Du auch nachfolgenden Code eingeben, wenn Du danach gefragt wirst:</p><p>{{resetToken}}</p><p>Dein yourPart-Team</p>",
"account_activation_text": "Hallo {{username}},\n\nHerzlichen Dank für Deine Registrierung bei yourPart. Um Deinen Zugang zu erhalten, musst Du Ihn noch aktivieren. Dazu klicke bitte folgenden Link an:\n\n{{activationLink}}\n\nAlternativ kannst Du auch nachfolgenden Code eingeben, wenn Du danach gefragt wirst:\n\n{{resetToken}}\n\nDein yourPart-Team",
"welcome": "welcome"
"welcome": "Willkommen",
"your_contact_request_answered_subject": "Ihre Kontaktanfrage wurde beantwortet",
"your_contact_request_answered_text": "Hallo,\n\nIhre Kontaktanfrage wurde beantwortet:\n\n{{answer}}\n\nMit freundlichen Grüßen,\nSupport Team",
"error": {
"emailrequired": "E-Mail ist erforderlich.",
"Contact not found": "Kontaktanfrage nicht gefunden.",
"id and answer are required": "ID und Antwort sind erforderlich.",
"Validation error": "Validierungsfehler: {{message}}"
},
"admin": {
"editcontactrequest": {
"title": "Kontaktanfrage bearbeiten"
}
},
"error.title": "Fehler"
}

View File

@@ -2,5 +2,7 @@
"account_activation_subject": "account_activation_subject",
"account_activation_text": "account_activation_text",
"welcome": "welcome",
"account_activation_html": "account_activation_html"
"account_activation_html": "account_activation_html",
"your_contact_request_answered_subject": "your_contact_request_answered_subject",
"your_contact_request_answered_text": "your_contact_request_answered_text"
}

View File

@@ -0,0 +1,43 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn({
tableName: 'contact_message',
schema: 'service'
}, 'answer', {
type: Sequelize.TEXT,
allowNull: true
});
await queryInterface.addColumn({
tableName: 'contact_message',
schema: 'service'
}, 'answered_at', {
type: Sequelize.DATE,
allowNull: true
});
await queryInterface.addColumn({
tableName: 'contact_message',
schema: 'service'
}, 'is_answered', {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn({
tableName: 'contact_message',
schema: 'service'
}, 'answer');
await queryInterface.removeColumn({
tableName: 'contact_message',
schema: 'service'
}, 'answered_at');
await queryInterface.removeColumn({
tableName: 'contact_message',
schema: 'service'
}, 'is_answered');
}
};

View File

@@ -8,6 +8,8 @@ import UserParamValue from './type/user_param_value.js';
import InterestType from './type/interest.js';
import InterestTranslationType from './type/interest_translation.js';
import Interest from './community/interest.js';
import UserParamVisibilityType from './type/user_param_visibility.js';
import UserParamVisibility from './community/user_param_visibility.js';
export default function setupAssociations() {
SettingsType.hasMany(UserParamType, { foreignKey: 'settingsId', as: 'user_param_types' });
@@ -16,11 +18,12 @@ export default function setupAssociations() {
UserParamType.hasMany(UserParam, { foreignKey: 'paramTypeId', as: 'user_params' });
UserParam.belongsTo(UserParamType, { foreignKey: 'paramTypeId', as: 'paramType' });
User.hasMany(UserParam, { foreignKey: 'userId', as: 'user_params' });
UserParam.belongsTo(User, { foreignKey: 'userId', as: 'user' });
UserRight.belongsTo(User, { foreignKey: 'userId' });
UserRight.belongsTo(User, { foreignKey: 'userId', as: 'user_with_rights' });
UserRight.belongsTo(UserRightType, { foreignKey: 'rightTypeId', as: 'rightType' });
UserRightType.hasMany(UserRight, { foreignKey: 'rightTypeId', as: 'rightType' });
UserRightType.hasMany(UserRight, { foreignKey: 'rightTypeId', as: 'user_rights' });
UserParamType.hasMany(UserParamValue, { foreignKey: 'userParamTypeId', as: 'user_param_values' });
UserParamValue.belongsTo(UserParamType, { foreignKey: 'userParamTypeId', as: 'user_param_type' });
@@ -28,15 +31,16 @@ export default function setupAssociations() {
InterestType.hasMany(InterestTranslationType, { foreignKey: 'interestsId', as: 'interest_translations' });
InterestTranslationType.belongsTo(InterestType, { foreignKey: 'interestsId', as: 'interest_translations' });
InterestType.hasMany(Interest, { foreignKey: 'userinterestId', as: 'user_interest_type'} );
User.hasMany(Interest, { foreignKey: 'userId', as: 'user_interest' });
Interest.belongsTo(InterestType, { foreignKey: 'userinterestId', as: 'user_interest_type' });
Interest.belongsTo(User, { foreignKey: 'userId', as: 'user_interest' });
InterestType.hasMany(Interest, { foreignKey: 'userinterestId', as: 'user_interest_type' });
User.hasMany(Interest, { foreignKey: 'userId', as: 'user_interests' });
Interest.belongsTo(InterestType, { foreignKey: 'userinterestId', as: 'interest_type' });
Interest.belongsTo(User, { foreignKey: 'userId', as: 'interest_owner' });
InterestTranslationType.belongsTo(UserParamValue, {
foreignKey: 'language',
targetKey: 'id',
as: 'user_param_value'
});
InterestTranslationType.belongsTo(UserParamValue, { foreignKey: 'language', targetKey: 'id', as: 'user_param_value' });
UserParam.hasMany(UserParamVisibility, { foreignKey: 'param_id', as: 'param_visibilities' });
UserParamVisibility.belongsTo(UserParam, { foreignKey: 'param_id', as: 'param' });
UserParamVisibility.belongsTo(UserParamVisibilityType, { foreignKey: 'visibility', as: 'visibility_type' });
UserParamVisibilityType.hasMany(UserParamVisibility, { foreignKey: 'visibility', as: 'user_param_visibilities' });
}

View File

@@ -5,7 +5,7 @@ import crypto from 'crypto';
const User = sequelize.define('user', {
email: {
type: DataTypes.BLOB, // Verwende BLOB, um die E-Mail als bytea zu speichern
type: DataTypes.BLOB,
allowNull: false,
unique: true,
set(value) {

View File

@@ -61,22 +61,4 @@ const UserParam = sequelize.define('user_param', {
]
});
UserParam.upsertParam = async function (userId, paramTypeId, value) {
try {
const val = value !== null && value !== undefined ? value.toString() : '';
const [userParam, created] = await UserParam.findOrCreate({
where: { userId, paramTypeId },
defaults: { value: val }
});
if (!created) {
userParam.value = value !== null && value !== undefined ? value.toString() : '';
await userParam.save();
}
} catch (error) {
console.error('Error in upsertParam:', error);
throw error;
}
};
export default UserParam;

View File

@@ -0,0 +1,26 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const UserParamVisibility = sequelize.define('user_param_visibility', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
param_id: {
type: DataTypes.INTEGER,
allowNull: false
},
visibility: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
tableName: 'user_param_visibility',
timestamps: false,
underscored: true,
schema: 'community'
});
export default UserParamVisibility;

View File

@@ -10,6 +10,8 @@ import InterestType from './type/interest.js';
import InterestTranslationType from './type/interest_translation.js';
import Interest from './community/interest.js';
import ContactMessage from './service/contactmessage.js';
import UserParamVisibilityType from './type/user_param_visibility.js';
import UserParamVisibility from './community/user_param_visibility.js';
const models = {
SettingsType,
@@ -24,6 +26,8 @@ const models = {
InterestTranslationType,
Interest,
ContactMessage,
UserParamVisibilityType,
UserParamVisibility,
};
export default models;

View File

@@ -59,6 +59,32 @@ const ContactMessage = sequelize.define('contact_message', {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
// Neue Felder für die Antwort
answer: {
type: DataTypes.TEXT,
allowNull: true,
set(value) {
if (value) {
const encryptedValue = encrypt(value);
this.setDataValue('answer', encryptedValue.toString('hex'));
}
},
get() {
const value = this.getDataValue('answer');
if (value) {
return decrypt(Buffer.from(value, 'hex'));
}
}
},
answeredAt: {
type: DataTypes.DATE,
allowNull: true
},
isAnswered: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
}
}, {
tableName: 'contact_message',
@@ -68,4 +94,3 @@ const ContactMessage = sequelize.define('contact_message', {
});
export default ContactMessage;

47
backend/models/trigger.js Normal file
View File

@@ -0,0 +1,47 @@
import { sequelize } from '../utils/sequelize.js';
export async function createTriggers() {
const createTriggerFunction = `
CREATE OR REPLACE FUNCTION create_user_param_visibility_trigger()
RETURNS TRIGGER AS $$
BEGIN
-- Check if UserParamVisibility already exists for this UserParam
IF NOT EXISTS (
SELECT 1 FROM community.user_param_visibility
WHERE param_id = NEW.id
) THEN
-- Insert the default visibility (Invisible)
INSERT INTO community.user_param_visibility (param_id, visibility)
VALUES (NEW.id, (
SELECT id FROM type.user_param_visibility WHERE description = 'Invisible'
));
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`;
const createInsertTrigger = `
CREATE TRIGGER trigger_create_user_param_visibility
AFTER INSERT ON community.user_param
FOR EACH ROW
EXECUTE FUNCTION create_user_param_visibility_trigger();
`;
const createUpdateTrigger = `
CREATE TRIGGER trigger_update_user_param_visibility
AFTER UPDATE ON community.user_param
FOR EACH ROW
EXECUTE FUNCTION create_user_param_visibility_trigger();
`;
try {
await sequelize.query(createTriggerFunction);
await sequelize.query(createInsertTrigger);
await sequelize.query(createUpdateTrigger);
console.log('Triggers created successfully');
} catch (error) {
console.error('Error creating triggers:', error);
}
}

View File

@@ -26,6 +26,15 @@ const UserParamType = sequelize.define('user_param_type', {
model: 'settings',
key: 'id'
}
},
orderId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
unit: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'user_param',

View File

@@ -10,6 +10,11 @@ const UserParamValue = sequelize.define('user_param_value', {
value: {
type: DataTypes.STRING,
allowNull: false
},
orderId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
}
},
{

View File

@@ -0,0 +1,22 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const UserParamVisibilityType = sequelize.define('user_param_visibility_type', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
description: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'user_param_visibility_type',
timestamps: false,
underscored: true,
schema: 'type'
});
export default UserParamVisibilityType;

View File

@@ -15,6 +15,7 @@
"dotenv": "^16.4.5",
"express": "^4.19.2",
"i18n": "^0.15.1",
"joi": "^17.13.3",
"mysql2": "^3.10.3",
"nodemailer": "^6.9.14",
"pg": "^8.12.0",
@@ -40,6 +41,19 @@
"node": ">=0.8"
}
},
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="
},
"node_modules/@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -210,6 +224,24 @@
"node": ">=14"
}
},
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
"integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@sideway/formula": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="
},
"node_modules/@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
@@ -432,9 +464,9 @@
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -444,7 +476,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -842,9 +874,9 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
@@ -1004,36 +1036,36 @@
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -1097,12 +1129,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -1515,6 +1547,18 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/joi": {
"version": "17.13.3",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
"integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
"dependencies": {
"@hapi/hoek": "^9.3.0",
"@hapi/topo": "^5.1.0",
"@sideway/address": "^4.1.5",
"@sideway/formula": "^3.0.1",
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/js-beautify": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
@@ -1722,9 +1766,12 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
@@ -2075,9 +2122,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
"node_modules/pg": {
"version": "8.12.0",
@@ -2225,11 +2272,11 @@
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -2354,9 +2401,9 @@
}
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -2389,6 +2436,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2499,14 +2554,14 @@
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"

View File

@@ -17,6 +17,7 @@
"dotenv": "^16.4.5",
"express": "^4.19.2",
"i18n": "^0.15.1",
"joi": "^17.13.3",
"mysql2": "^3.10.3",
"nodemailer": "^6.9.14",
"pg": "^8.12.0",

View File

@@ -1,6 +1,6 @@
import { Router } from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { getOpenInterests, changeInterest, deleteInterest, changeTranslation, getOpenContacts } from '../controllers/adminController.js';
import { getOpenInterests, changeInterest, deleteInterest, changeTranslation, getOpenContacts, answerContact } from '../controllers/adminController.js';
const router = Router();
@@ -10,5 +10,7 @@ router.post('/interest/translation', authenticate, changeTranslation);
router.delete('/interest/:id', authenticate, deleteInterest);
router.get('/opencontacts', authenticate, getOpenContacts);
router.post('/contacts/answer', answerContact);
export default router;

View File

@@ -1,6 +1,7 @@
import { Router } from 'express';
import { filterSettings, updateSetting, getTypeParamValueId, getTypeParamValues, getTypeParamValue, getAccountSettings,
getPossibleInterests, getInterests, addInterest, addUserInterest, removeInterest } from '../controllers/settingsController.js';
getPossibleInterests, getInterests, addInterest, addUserInterest, removeInterest, getVisibilities, updateVisibility }
from '../controllers/settingsController.js';
import { authenticate } from '../middleware/authMiddleware.js';
const router = Router();
@@ -17,5 +18,7 @@ router.get('/getuserinterests', authenticate, getInterests);
router.post('/addinterest', authenticate, addInterest);
router.post('/setinterest', authenticate, addUserInterest);
router.get('/removeinterest/:id', authenticate, removeInterest);
router.get('/visibilities', authenticate, getVisibilities);
router.post('/update-visibility', authenticate, updateVisibility);
export default router;

View File

@@ -0,0 +1,11 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import SocialNetworkController from '../controllers/socialnetworkController.js';
const router = express.Router();
const socialNetworkController = new SocialNetworkController();
router.post('/usersearch', authenticate, socialNetworkController.userSearch);
router.get('/profile/:userId', authenticate, socialNetworkController.profile);
export default router;

View File

@@ -32,7 +32,6 @@ amqp.connect(RABBITMQ_URL, (err, connection) => {
});
});
// Sync database before starting the server
syncDatabase().then(() => {
server.listen(3001, () => {
console.log('Server is running on port 3001');

View File

@@ -0,0 +1,56 @@
import User from '../models/community/user.js';
import UserParam from '../models/community/user_param.js';
import UserParamType from '../models/type/user_param.js';
import UserParamVisibility from '../models/community/user_param_visibility.js';
import UserParamVisibilityType from '../models/type/user_param_visibility.js';
import { Op } from 'sequelize';
class BaseService {
async getUserByHashedId(hashedId) {
const user = await User.findOne({ where: { hashedId } });
if (!user) {
throw new Error('User not found');
}
return user;
}
async getUserById(userId) {
const user = await User.findOne({ where: { id: userId } });
if (!user) {
throw new Error('User not found');
}
return user;
}
async getUserParams(userId, paramDescriptions) {
return await UserParam.findAll({
where: { userId },
include: [
{
model: UserParamType,
as: 'paramType',
where: { description: { [Op.in]: paramDescriptions } }
},
{
model: UserParamVisibility,
as: 'param_visibilities',
include: [
{
model: UserParamVisibilityType,
as: 'visibility_type'
}
]
}
]
});
}
calculateAge(birthdate) {
const birthDate = new Date(birthdate);
const ageDifMs = Date.now() - birthDate.getTime();
const ageDate = new Date(ageDifMs);
return Math.abs(ageDate.getUTCFullYear() - 1970);
}
}
export default BaseService;

View File

@@ -10,13 +10,30 @@ class ContactService {
name = '';
email = '';
}
ContactMessage.create({
await ContactMessage.create({
email,
name,
message,
allowDataSave: acceptDataSave
});
}
async getContactById(id) {
const contact = await ContactMessage.findByPk(id);
if (!contact) {
const error = new Error('Contact not found');
error.status = 404;
throw error;
}
return contact;
}
async saveAnswer(contact, answer) {
contact.answer = answer;
contact.answeredAt = new Date();
contact.isFinished = true;
await contact.save();
}
}
export default new ContactService();
export default new ContactService();

View File

@@ -5,6 +5,8 @@ import InterestTranslationType from "../models/type/interest_translation.js"
import User from "../models/community/user.js";
import UserParamValue from "../models/type/user_param_value.js";
import ContactMessage from "../models/service/contactmessage.js";
import ContactService from "./ContactService.js";
import { sendAnswerEmail } from './emailService.js';
class AdminService {
async hasUserAccess(userId, section) {
@@ -18,7 +20,7 @@ class AdminService {
},
{
model: User,
as: 'user',
as: 'user_with_rights',
where: {
hashedId: userId,
}
@@ -129,6 +131,13 @@ class AdminService {
})
return openContacts;
}
async answerContact(contactId, answer) {
const contact = await ContactService.getContactById(contactId);
await ContactService.saveAnswer(contact, answer);
await sendAnswerEmail(contact.email, answer, contact.language || 'en');
}
}
export default new AdminService();

View File

@@ -42,3 +42,16 @@ export const sendAccountActivationEmail = async (email, activationLink, username
await transporter.sendMail(mailOptions);
};
export const sendAnswerEmail = async (toEmail, answer, language) => {
i18n.setLocale(language);
const mailOptions = {
from: process.env.SMTP_FROM,
to: toEmail,
subject: 'yourPart',
text: answer,
html: `<p>${ answer }</p>`
};
await transporter.sendMail(mailOptions);
};

View File

@@ -1,23 +1,18 @@
import BaseService from './BaseService.js';
import UserParamType from '../models/type/user_param.js';
import SettingsType from '../models/type/settings.js';
import UserParam from '../models/community/user_param.js';
import User from '../models/community/user.js';
import UserParamValue from '../models/type/user_param_value.js';
import Interest from '../models/type/interest.js';
import UserInterest from '../models/community/interest.js'
import UserInterest from '../models/community/interest.js';
import InterestTranslation from '../models/type/interest_translation.js';
import { calculateAge } from '../utils/userdata.js';
import { Op } from 'sequelize';
import UserParamVisibilityType from '../models/type/user_param_visibility.js';
import UserParamVisibility from '../models/community/user_param_visibility.js';
import { generateIv } from '../utils/encryption.js';
class SettingsService {
async getUser(userId) {
const user = await User.findOne({ where: { hashedId: userId } });
if (!user) {
throw new Error('User not found');
}
return user;
}
class SettingsService extends BaseService{
async getUserParams(userId, paramDescriptions) {
return await UserParam.findAll({
where: { userId },
@@ -25,7 +20,8 @@ class SettingsService {
{
model: UserParamType,
as: 'paramType',
where: { description: { [Op.in]: paramDescriptions } }
where: { description: { [Op.in]: paramDescriptions } },
order: [[ 'order_id', 'asc' ]]
}
]
});
@@ -34,16 +30,16 @@ class SettingsService {
async getFieldOptions(field) {
if (['singleselect', 'multiselect'].includes(field.datatype)) {
return await UserParamValue.findAll({
where: { userParamTypeId: field.id }
where: { userParamTypeId: field.id },
order: [[ 'order_id', 'asc' ]]
});
}
return [];
}
}
async filterSettings(userId, type) {
const user = await this.getUser(userId);
async filterSettings(hashedUserId, type) {
const user = await this.getUserByHashedId(hashedUserId);
const userParams = await this.getUserParams(user.id, ['birthdate', 'gender']);
let birthdate = null;
let gender = null;
for (const param of userParams) {
@@ -55,8 +51,7 @@ class SettingsService {
gender = genderResult ? genderResult.dataValues?.value : null;
}
}
const age = birthdate ? calculateAge(birthdate) : null;
const age = birthdate ? this.calculateAge(birthdate) : null;
const fields = await UserParamType.findAll({
include: [
{
@@ -72,7 +67,18 @@ class SettingsService {
{
model: User,
as: 'user',
where: { hashedId: userId }
where: { id: user.id }
},
{
model: UserParamVisibility,
as: 'param_visibilities',
required: false,
include: [
{
model: UserParamVisibilityType,
as: 'visibility_type'
}
]
}
]
}
@@ -84,28 +90,55 @@ class SettingsService {
]
}
});
return await Promise.all(fields.map(async (field) => {
const options = await this.getFieldOptions(field);
const visibilityData = field.user_params[0]?.param_visibilities?.[0];
const visibility = visibilityData
? { id: visibilityData.visibility_type?.id, description: visibilityData.visibility_type?.description }
: { id: null, description: 'Invisible' };
return {
id: field.id,
name: field.description,
minAge: field.minAge,
gender: field.gender,
datatype: field.datatype,
unit: field.unit,
value: field.user_params.length > 0 ? field.user_params[0].value : null,
options: options.map(opt => ({ id: opt.id, value: opt.value }))
options: options.map(opt => ({ id: opt.id, value: opt.value })),
visibility
};
}));
}
async updateSetting(userId, settingId, value) {
const user = await this.getUser(userId);
const paramType = await UserParamType.findOne({ where: { id: settingId } });
if (!paramType) {
throw new Error('Parameter type not found');
async updateSetting(hashedUserId, settingId, value) {
try {
const user = await this.getUserByHashedId(hashedUserId);
const paramType = await UserParamType.findOne({ where: { id: settingId } });
if (!paramType) {
throw new Error('Parameter type not found');
}
const userParam = await UserParam.findOne({
where: { userId: user.id, paramTypeId: settingId }
});
if (userParam) {
console.log('update param with ', value)
if (typeof value === 'boolean') {
value = value ? 'true' : 'false';
}
await userParam.update({value: value});
} else {
await UserParam.create(
{
userId: user.id,
paramTypeId: settingId,
value: value
}
);
}
} catch (error) {
console.error('Error updating setting:', hashedUserId, settingId, value, error);
throw error;
}
await UserParam.upsertParam(user.id, paramType.id, value);
}
async getTypeParamValueId(paramValue) {
@@ -141,171 +174,123 @@ class SettingsService {
return userParamValueObject.value;
}
async getAccountSettings(userId) {
const user = await this.getUser(userId);
const email = user.email;
return { username: user.username, email, showinsearch: user.searchable };
async addInterest(hashedUserId, name) {
try {
const user = await this.getUserByHashedId(hashedUserId);
const existingInterests = await Interest.findAll({ where: { name: name.toLowerCase() } });
if (existingInterests.length > 0) {
throw new Error('Interest already exists');
}
const userParam = await this.getUserParams(user.id, ['language']);
let language = 'en';
if (userParam.length > 0) {
const userParamValue = await UserParamValue.findOne({
where: { id: userParam[0].value }
});
language = userParamValue ? userParamValue.value : 'en';
}
const languageParam = await UserParamValue.findOne({ where: { value: language } });
const languageId = languageParam.id;
const interest = await Interest.create({ name: name.toLowerCase(), allowed: false, adultOnly: true });
await InterestTranslation.create({ interestsId: interest.id, language: languageId, translation: name });
return interest;
} catch (error) {
console.error('Error adding interest:', error);
throw error;
}
}
async setAccountSettings(data) {
const { userId, username, email, searchable, oldpassword, newpassword, newpasswordrepeat } = data;
const user = await this.getUser(userId);
async addUserInterest(hashedUserId, interestId) {
try {
const user = await this.getUserByHashedId(hashedUserId);
user.searchable = searchable;
if (user.password !== oldpassword) {
throw new Error('Wrong password');
}
const updateUser = {};
if (username.toLowerCase() !== user.username.toLowerCase()) {
const isUsernameTaken = (await User.findAll({ where: { username: username } })).length > 0;
if (isUsernameTaken) {
throw new Error('Username already taken');
const userParams = await this.getUserParams(user.id, ['birthdate']);
let birthdate = null;
for (const param of userParams) {
if (param.paramType.description === 'birthdate') {
birthdate = param.value;
}
}
updateUser.username = username;
}
if (newpassword.trim().length > 0) {
if (newpassword.length < 6) {
throw new Error('Password too short');
}
if (newpassword !== newpasswordrepeat) {
throw new Error('Passwords do not match');
}
updateUser.password = newpassword;
}
await user.update(updateUser);
}
async getPossibleInterests(userId) {
const user = await this.getUser(userId);
const userParams = await this.getUserParams(user.id, ['birthdate']);
let birthdate = null;
for (const param of userParams) {
if (param.paramType.description === 'birthdate') {
birthdate = param.value;
}
}
const age = birthdate ? calculateAge(birthdate) : 0;
const filter = {
where: age >= 18 ? {
allowed: true,
} : {
allowed: true,
[Op.or]: [
const age = birthdate ? this.calculateAge(birthdate) : 0;
const interestsFilter = { id: interestId, allowed: true };
if (age < 18) {
interestsFilter[Op.or] = [
{ adultOnly: false },
{ adultOnly: { [Op.eq]: null } }
]
},
include: [
{
model: InterestTranslation,
as: 'interest_translations',
required: false,
include: [
{
model: UserParamValue,
as: 'user_param_value',
required: false
}
]
}
]
};
return await Interest.findAll(filter);
}
async getInterests(userId) {
const user = await this.getUser(userId);
return await UserInterest.findAll({
where: { userId: user.id },
include: [
{
model: Interest,
as: 'user_interest_type',
include: [
{
model: InterestTranslation,
as: 'interest_translations',
include: [
{
model: UserParamValue,
as: 'user_param_value',
required: false
}
]
}
]
}
]
});
}
async addInterest(userId, name) {
const user = await this.getUser(userId);
const existingInterests = await Interest.findAll({ where: { name: name.toLowerCase() } });
if (existingInterests.length > 0) {
throw new Error('Interest already exists');
}
const userParam = await this.getUserParams(user.id, ['language']);
let language = 'en';
if (userParam) {
const userParamValue = await UserParamValue.findOne({
where: {
id: userParam[0].value
}
});
language = userParamValue && userParamValue.value ? userParamValue.value : 'en';
}
const languageParam = await UserParamValue.findOne({ where: { value: language } });
const languageId = languageParam.id;
const interest = await Interest.create({ name: name.toLowerCase(), allowed: false, adultOnly: true });
await InterestTranslation.create({ interestsId: interest.id, language: languageId, translation: name });
return interest;
}
async addUserInterest(userId, interestId) {
const user = await this.getUser(userId);
const interestsFilter = {
id: interestId,
allowed: true,
};
const userParams = await this.getUserParams(user.id, ['birthdate']);
let birthdate = null;
for (const param of userParams) {
if (param.paramType.description === 'birthdate') {
birthdate = param.value;
];
}
const existingInterests = await Interest.findAll({ where: interestsFilter });
if (existingInterests.length === 0) {
throw new Error('Interest not found');
};
const interest = await UserInterest.findAll({
where: { userId: user.id, userinterestId: interestId }
});
if (interest.length > 0) {
throw new Error('Interest already exists');
}
await UserInterest.create({ userId: user.id, userinterestId: interestId });
} catch (error) {
console.error('Error adding user interest:', error);
throw error;
}
const age = birthdate ? calculateAge(birthdate) : 0;
if (age < 18) {
interestsFilter[Op.or] = [
{ adultOnly: false },
{ adultOnly: { [Op.eq]: null } }
];
}
const existingInterests = await Interest.findAll({ where: interestsFilter });
if (existingInterests.length === 0) {
throw new Error('Interest not found');
};
const interest = await UserInterest.findAll({
where: { userId: user.id, userinterestId: interestId }
});
if (interest.length > 0) {
throw new Error('Interest already exists');
}
await UserInterest.create({ userId: user.id, userinterestId: interestId });
}
async removeInterest(userId, interestId) {
const user = await this.getUser(userId);
const interests = await UserInterest.findAll({
where: { userId: user.id, userinterestId: interestId }
});
for (const interest of interests) {
await interest.destroy();
async removeInterest(hashedUserId, interestId) {
try {
const user = await this.getUserByHashedId(hashedUserId);
const interests = await UserInterest.findAll({
where: { userId: user.id, userinterestId: interestId }
});
for (const interest of interests) {
await interest.destroy();
}
} catch (error) {
console.error('Error removing interest:', error);
throw error;
}
}
async getVisibilities() {
return UserParamVisibilityType.findAll();
}
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');
}
const userParam = await UserParam.findOne({
where: { paramTypeId: userParamTypeId, userId: user.id }
});
if (!userParam) {
console.error(`UserParam not found for settingId: ${userParamTypeId} and userId: ${user.id}`);
throw new Error('User parameter not found or does not belong to the user');
}
let userParamVisibility = await UserParamVisibility.findOne({
where: { param_id: userParam.id }
});
if (userParamVisibility) {
userParamVisibility.visibility = visibilityId;
await userParamVisibility.save();
} else {
await UserParamVisibility.create({
param_id: userParam.id,
visibility: visibilityId
});
}
console.log(`Visibility updated for settingId: ${userParamTypeId} with visibilityId: ${visibilityId}`);
} catch (error) {
console.error('Error updating visibility:', error.message);
throw error;
}
}
}

View File

@@ -0,0 +1,187 @@
import BaseService from './BaseService.js';
import { Op, col } from 'sequelize';
import User from '../models/community/user.js';
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 UserParamVisibility from '../models/community/user_param_visibility.js';
import UserParamVisibilityType from '../models/type/user_param_visibility.js';
class SocialNetworkService extends BaseService {
async searchUsers({ username, ageFrom, ageTo, genders }) {
try {
const whereClause = {
active: true,
searchable: true
};
if (username) {
whereClause.username = { [Op.iLike]: `%${username}%` };
}
const users = await User.findAll({
where: whereClause,
include: [
{
model: UserParam,
as: 'user_params',
include: [
{
model: UserParamType,
as: 'paramType',
where: {
description: {
[Op.in]: ['gender', 'birthdate']
}
},
required: true
}
],
required: true
}
]
});
const results = [];
for (const user of users) {
const id = user.hashedId;
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 decryptedGenderValue = genderParam ? genderParam.value : null;
let gender = null;
if (decryptedGenderValue) {
const genderValue = await UserParamValue.findOne({
where: {
id: decryptedGenderValue
}
});
gender = genderValue ? genderValue.value : null;
}
const isWithinAgeRange = (!ageFrom || age >= ageFrom) && (!ageTo || age <= ageTo);
if (isWithinAgeRange && (!genders || !genders.length || (gender && genders.includes(gender))) && age >= 14) {
results.push({
id: id,
username: user.username,
email: user.email,
gender: gender,
age: age
});
}
}
return results;
} catch (error) {
console.error('Error in searchUsers:', error);
throw new Error('Error searching users');
}
}
async getProfile(hashedUserId, requestingUserId) {
try {
const requestingUser = await this.getUserByHashedId(requestingUserId);
const requestingUserParams = await this.getUserParams(requestingUser.id, ['birthdate']);
let requestingUserAge = 0;
for (const param of requestingUserParams) {
if (param.paramType.description === 'birthdate') {
requestingUserAge = this.calculateAge(param.value);
break;
}
}
const user = await User.findOne({
where: {
hashedId: hashedUserId,
active: true,
searchable: true,
},
include: [
{
model: UserParam,
as: 'user_params',
include: [
{
model: UserParamType,
as: 'paramType',
},
{
model: UserParamVisibility,
as: 'param_visibilities',
include: [
{
model: UserParamVisibilityType,
as: 'visibility_type'
}
]
}
],
order: [[ 'order_id', 'asc']]
}
]
});
if (user) {
const userParams = {};
await Promise.all(user.user_params.map(async (param) => {
const visibilityData = param.param_visibilities?.[0]?.visibility_type;
const visibility = visibilityData ? visibilityData.description : 'Invisible';
let paramValue = param.value;
let paramValueChanged = false;
try {
const parsedValue = JSON.parse(paramValue);
if (Array.isArray(parsedValue)) {
paramValue = await Promise.all(parsedValue.map(async (value) => {
if (/^\d+$/.test(value)) {
const userParamValue = await UserParamValue.findOne({
where: {
id: parseInt(value, 10),
userParamTypeId: param.paramTypeId
}
});
paramValueChanged = true;
return userParamValue ? userParamValue.value : value;
}
return value;
}));
}
} catch (e) {
}
if (!paramValueChanged) {
if (/^\d+$/.test(paramValue)) {
const userParamValue = await UserParamValue.findOne({
where: {
id: parseInt(paramValue, 10),
userParamTypeId: param.paramTypeId
}
});
if (userParamValue) {
paramValue = userParamValue.value;
}
}
}
const paramTypeDescription = param.paramType.description;
if (visibility === 'Invisible') {
return;
}
if (visibility === 'All' || (visibility === 'FriendsAndAdults' && requestingUserAge >= 18) || (visibility === 'AdultsOnly' && requestingUserAge >= 18)) {
userParams[paramTypeDescription] = {
type: param.paramType.datatype,
value: paramValue
};
if (paramTypeDescription === 'birthdate') {
userParams['age'] = { value: this.calculateAge(Date.parse(paramValue)), type: "int"};
}
}
}));
const userProfile = {
username: user.username,
registrationDate: user.registrationDate,
params: userParams
};
return userProfile;
}
return null;
} catch (error) {
console.error('Error in getProfile:', error);
throw new Error('Error getting profile');
}
}
}
export default SocialNetworkService;

View File

@@ -13,6 +13,10 @@ const initializeSettings = async () => {
where: { name: 'sexuality' },
defaults: { name: 'sexuality' }
});
await SettingsType.findOrCreate({
where: { name: 'flirt' },
defaults: { name: 'flirt' }
});
};
export default initializeSettings;

View File

@@ -4,7 +4,7 @@ import UserParamValue from '../models/type/user_param_value.js';
import Interest from '../models/type/interest.js';
import { Op, } from 'sequelize';
import InterestTranslation from '../models/type/interest_translation.js';
import { sequelize } from '../utils/sequelize.js';
import UserParamVisibilityType from '../models/type/user_param_visibility.js';
const initializeTypes = async () => {
const settingsTypes = await SettingsType.findAll();
@@ -27,8 +27,8 @@ const initializeTypes = async () => {
birthdate: { type: 'date', setting: 'personal' },
zip: { type: 'string', setting: 'personal' },
town: { type: 'string', setting: 'personal' },
bodyheight: { type: 'float', setting: 'view' },
weight: { type: 'float', setting: 'view' },
bodyheight: { type: 'float', setting: 'view', unit: 'cm' },
weight: { type: 'float', setting: 'view', unit: 'kg' },
eyecolor: { type: 'singleselect', setting: 'view' },
haircolor: { type: 'singleselect', setting: 'view' },
hairlength: { type: 'singleselect', setting: 'view' },
@@ -39,19 +39,26 @@ const initializeTypes = async () => {
sexualpreference: { type: 'singleselect', 'setting': 'sexuality', minAge: 14 },
gender: { type: 'singleselect', setting: 'personal' },
pubichair: { type: 'singleselect', setting: 'sexuality', minAge: 14 },
penislenght: { type: 'int', setting: 'sexuality', minAge: 14, gender: 'male' },
brasize: { type: 'string', setting: 'sexuality', minAge: 14, gender: 'female' }
penislength: { type: 'int', setting: 'sexuality', minAge: 14, gender: 'male', unit: 'cm' },
brasize: { type: 'string', setting: 'sexuality', minAge: 14, gender: 'female' },
interestedInGender: { type: 'multiselect', setting: 'flirt', minAge: 14},
hasChildren: { type: 'bool', setting: 'flirt', minAge: 14 },
willChildren: { type: 'bool', setting: 'flirt', minAge: 14 },
smokes: { type: 'singleselect', setting: 'flirt', minAge: 14},
drinks: { type: 'singleselect', setting: 'flirt', minAge: 14 },
};
Object.keys(userParams).forEach(async (key) => {
let orderId = 1;
for (const key of Object.keys(userParams)) {
const item = userParams[key];
const createItem = { description: key, datatype: item.type, settingsId: getSettingsTypeId(item.setting) };
const createItem = { description: key, datatype: item.type, settingsId: getSettingsTypeId(item.setting), orderId: orderId++ };
if (item.minAge) createItem.minAge = item.minAge;
if (item.gender) createItem.gender = item.gender;
await UserParamType.findOrCreate({
if (item.unit) createItem.unit = item.unit;
await UserParamType.findOrCreate({
where: { description: key },
defaults: createItem
});
});
}
const valuesList = {
gender: ['male', 'female', 'transfemale', 'transmale', 'nonbinary'],
language: ['de', 'en'],
@@ -62,17 +69,22 @@ const initializeTypes = async () => {
freckles: ['much', 'medium', 'less', 'none'],
sexualpreference: ['straight', 'gay', 'bi', 'pan', 'asexual'],
pubichair: ['none', 'short', 'medium', 'long', 'hairy', 'waxed', 'landingstrip', 'bikinizone', 'other'],
interestedInGender: ['male', 'female'],
smokes: ['never', 'socially', 'often', 'daily'],
drinks: ['never', 'socially', 'often', 'daily'],
brasize: ['Keine', 'AA', 'A', 'B', 'C', 'D', 'E (DD)', 'F (E)', 'G (F)', 'H (FF)', 'I (G)', 'J (GG)', 'K (H)']
};
Object.keys(valuesList).forEach(async (key) => {
const values = valuesList[key];
const userParamTypeId = await getUserParamTypeId(key);
let orderId = 1;
values.forEach(async (value) => {
await UserParamValue.findOrCreate({
where: {
userParamTypeId: userParamTypeId,
value: value
},
defaults: { userParamTypeId: userParamTypeId, value: value }
defaults: { userParamTypeId: userParamTypeId, value: value, orderId: orderId++ }
})
});
});
@@ -161,6 +173,18 @@ const initializeTypes = async () => {
throw error;
}
}
const visibilityTypes = ['Invisible', 'OnlyFriends', 'FriendsAndAdults', 'AdultsOnly', 'All'];
for (const type of visibilityTypes) {
try {
await UserParamVisibilityType.findOrCreate({
where: { description: type },
defaults: { description: type }
});
} catch (error) {
throw error;
}
}
};
export default initializeTypes;

View File

@@ -4,6 +4,7 @@ import initializeSettings from './initializeSettings.js';
import initializeUserRights from './initializeUserRights.js';
import setupAssociations from '../models/associations.js';
import models from '../models/index.js';
import { createTriggers } from '../models/trigger.js';
const syncDatabase = async () => {
try {
@@ -12,6 +13,7 @@ const syncDatabase = async () => {
for (const model of Object.values(models)) {
await model.sync();
}
createTriggers();
await initializeSettings();
await initializeTypes();