feat(chat): add chat room management functionality
- Created new chat schema in the database. - Implemented chat room model with necessary fields (title, ownerId, roomTypeId, etc.). - Added room type model and rights model for chat functionality. - Developed API endpoints for managing chat rooms, including create, edit, and delete operations. - Integrated chat room management into the admin interface with a dedicated view and dialog for room creation/editing. - Added internationalization support for chat room management UI. - Implemented autocomplete for victim selection in underground activities. - Enhanced the underground view with new activity types and political target selection.
This commit is contained in:
@@ -2,6 +2,7 @@ import AdminService from '../services/adminService.js';
|
||||
import Joi from 'joi';
|
||||
|
||||
class AdminController {
|
||||
// --- Chat Room Admin ---
|
||||
constructor() {
|
||||
this.getOpenInterests = this.getOpenInterests.bind(this);
|
||||
this.changeInterest = this.changeInterest.bind(this);
|
||||
@@ -9,6 +10,15 @@ class AdminController {
|
||||
this.changeTranslation = this.changeTranslation.bind(this);
|
||||
this.getOpenContacts = this.getOpenContacts.bind(this);
|
||||
this.answerContact = this.answerContact.bind(this);
|
||||
this.searchUser = this.searchUser.bind(this);
|
||||
this.getFalukantUserById = this.getFalukantUserById.bind(this);
|
||||
this.changeFalukantUser = this.changeFalukantUser.bind(this);
|
||||
this.getRoomTypes = this.getRoomTypes.bind(this);
|
||||
this.getGenderRestrictions = this.getGenderRestrictions.bind(this);
|
||||
this.getUserRights = this.getUserRights.bind(this);
|
||||
this.getRooms = this.getRooms.bind(this);
|
||||
this.createRoom = this.createRoom.bind(this);
|
||||
this.deleteRoom = this.deleteRoom.bind(this);
|
||||
}
|
||||
|
||||
async getOpenInterests(req, res) {
|
||||
@@ -126,6 +136,66 @@ class AdminController {
|
||||
res.status(403).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getRoomTypes(req, res) {
|
||||
try {
|
||||
const types = await AdminService.getRoomTypes();
|
||||
res.status(200).json(types);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getGenderRestrictions(req, res) {
|
||||
try {
|
||||
const restrictions = await AdminService.getGenderRestrictions();
|
||||
res.status(200).json(restrictions);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getUserRights(req, res) {
|
||||
try {
|
||||
const rights = await AdminService.getUserRights();
|
||||
res.status(200).json(rights);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getRooms(req, res) {
|
||||
try {
|
||||
const rooms = await AdminService.getRooms();
|
||||
res.status(200).json(rooms);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async createRoom(req, res) {
|
||||
try {
|
||||
const room = await AdminService.createRoom(req.body);
|
||||
res.status(201).json(room);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async deleteRoom(req, res) {
|
||||
try {
|
||||
await AdminService.deleteRoom(req.params.id);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminController;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -219,6 +219,10 @@ const menuStructure = {
|
||||
visible: ["mainadmin", "forum"],
|
||||
path: "/admin/forum"
|
||||
},
|
||||
chatrooms: {
|
||||
visible: ["mainadmin", "chatrooms"],
|
||||
path: "/admin/chatrooms"
|
||||
},
|
||||
userrights: {
|
||||
visible: ["mainadmin", "rights"],
|
||||
path: "/admin/rights"
|
||||
|
||||
@@ -3,3 +3,4 @@ import { cleanupExpiredSessions } from '../utils/redis.js';
|
||||
setInterval(async () => {
|
||||
await cleanupExpiredSessions();
|
||||
}, 5000);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import RoomType from './chat/room_type.js';
|
||||
import ChatRight from './chat/rights.js';
|
||||
import ChatUserRight from './chat/user_rights.js';
|
||||
import ChatUser from './chat/user.js';
|
||||
import Room from './chat/room.js';
|
||||
import User from './community/user.js';
|
||||
import UserParam from './community/user_param.js';
|
||||
import UserParamType from './type/user_param.js';
|
||||
@@ -92,6 +97,31 @@ import Underground from './falukant/data/underground.js';
|
||||
import UndergroundType from './falukant/type/underground.js';
|
||||
|
||||
export default function setupAssociations() {
|
||||
// RoomType 1:n Room
|
||||
RoomType.hasMany(Room, { foreignKey: 'roomTypeId', as: 'rooms' });
|
||||
Room.belongsTo(RoomType, { foreignKey: 'roomTypeId', as: 'roomType' });
|
||||
// ChatUser <-> ChatRight n:m
|
||||
ChatUser.belongsToMany(ChatRight, {
|
||||
through: ChatUserRight,
|
||||
foreignKey: 'chat_user_id',
|
||||
otherKey: 'chat_right_id',
|
||||
as: 'rights',
|
||||
});
|
||||
ChatRight.belongsToMany(ChatUser, {
|
||||
through: ChatUserRight,
|
||||
foreignKey: 'chat_right_id',
|
||||
otherKey: 'chat_user_id',
|
||||
as: 'users',
|
||||
});
|
||||
// ChatUser zu FalukantUser
|
||||
ChatUser.belongsTo(FalukantUser, { foreignKey: 'falukant_user_id', as: 'falukantUser' });
|
||||
FalukantUser.hasOne(ChatUser, { foreignKey: 'falukant_user_id', as: 'chatUser' });
|
||||
// Chat Room associations
|
||||
Room.belongsTo(User, { foreignKey: 'owner_id', as: 'owner' });
|
||||
User.hasMany(Room, { foreignKey: 'owner_id', as: 'ownedRooms' });
|
||||
|
||||
Room.belongsTo(UserParamValue, { foreignKey: 'gender_restriction_id', as: 'genderRestriction' });
|
||||
UserParamValue.hasMany(Room, { foreignKey: 'gender_restriction_id', as: 'roomsWithGenderRestriction' });
|
||||
// UserParam related associations
|
||||
SettingsType.hasMany(UserParamType, { foreignKey: 'settingsId', as: 'user_param_types' });
|
||||
UserParamType.belongsTo(SettingsType, { foreignKey: 'settingsId', as: 'settings_type' });
|
||||
|
||||
0
backend/models/chat/index.js
Normal file
0
backend/models/chat/index.js
Normal file
22
backend/models/chat/rights.js
Normal file
22
backend/models/chat/rights.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
const ChatRight = sequelize.define('ChatRight', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
tr: {
|
||||
type: DataTypes.STRING(32),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
}, {
|
||||
schema: 'chat',
|
||||
tableName: 'rights',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default ChatRight;
|
||||
69
backend/models/chat/room.js
Normal file
69
backend/models/chat/room.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
const Room = sequelize.define('Room', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
ownerId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true, // kann null sein, wenn system-owned
|
||||
},
|
||||
roomTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
isPublic: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
genderRestrictionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
minAge: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
maxAge: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
passwordHash: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
friendsOfOwnerOnly: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
requiredUserRightId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
schema: 'chat',
|
||||
tableName: 'room',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{
|
||||
name: 'idx_chat_room_owner',
|
||||
fields: ['owner_id'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default Room;
|
||||
22
backend/models/chat/room_type.js
Normal file
22
backend/models/chat/room_type.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
const RoomType = sequelize.define('RoomType', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
tr: {
|
||||
type: DataTypes.STRING(32),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
}, {
|
||||
schema: 'chat',
|
||||
tableName: 'room_type',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default RoomType;
|
||||
41
backend/models/chat/user.js
Normal file
41
backend/models/chat/user.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
const ChatUser = sequelize.define('ChatUser', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
falukant_user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: 'Verknüpfung zu community.falukant_user',
|
||||
},
|
||||
display_name: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: false,
|
||||
},
|
||||
color: {
|
||||
type: DataTypes.STRING(16), // z.B. Hex-Code
|
||||
allowNull: false,
|
||||
defaultValue: '#000000',
|
||||
},
|
||||
show_gender: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
show_age: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
}, {
|
||||
schema: 'chat',
|
||||
tableName: 'user',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default ChatUser;
|
||||
26
backend/models/chat/user_rights.js
Normal file
26
backend/models/chat/user_rights.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
import ChatUser from './user.js';
|
||||
import ChatRight from './rights.js';
|
||||
|
||||
const ChatUserRight = sequelize.define('ChatUserRight', {
|
||||
chatUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: null, // Assoziation wird separat gesetzt
|
||||
},
|
||||
chatRightId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: null, // Assoziation wird separat gesetzt
|
||||
},
|
||||
}, {
|
||||
schema: 'chat',
|
||||
tableName: 'user_rights',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default ChatUserRight;
|
||||
@@ -99,6 +99,12 @@ import ElectionHistory from './falukant/log/election_history.js';
|
||||
import UndergroundType from './falukant/type/underground.js';
|
||||
import Underground from './falukant/data/underground.js';
|
||||
|
||||
import Room from './chat/room.js';
|
||||
import ChatUser from './chat/user.js';
|
||||
import ChatRight from './chat/rights.js';
|
||||
import ChatUserRight from './chat/user_rights.js';
|
||||
import RoomType from './chat/room_type.js';
|
||||
|
||||
const models = {
|
||||
SettingsType,
|
||||
UserParamValue,
|
||||
@@ -197,6 +203,11 @@ const models = {
|
||||
ElectionHistory,
|
||||
UndergroundType,
|
||||
Underground,
|
||||
Room,
|
||||
ChatUser,
|
||||
ChatRight,
|
||||
ChatUserRight,
|
||||
RoomType,
|
||||
};
|
||||
|
||||
export default models;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
import { Router } from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import AdminController from '../controllers/adminController.js';
|
||||
@@ -5,6 +6,14 @@ import AdminController from '../controllers/adminController.js';
|
||||
const router = Router();
|
||||
const adminController = new AdminController();
|
||||
|
||||
// --- Chat Room Admin ---
|
||||
router.get('/chat/room-types', authenticate, adminController.getRoomTypes);
|
||||
router.get('/chat/gender-restrictions', authenticate, adminController.getGenderRestrictions);
|
||||
router.get('/chat/user-rights', authenticate, adminController.getUserRights);
|
||||
router.get('/chat/rooms', authenticate, adminController.getRooms);
|
||||
router.post('/chat/rooms', authenticate, adminController.createRoom);
|
||||
router.delete('/chat/rooms/:id', authenticate, adminController.deleteRoom);
|
||||
|
||||
router.get('/interests/open', authenticate, adminController.getOpenInterests);
|
||||
router.post('/interest', authenticate, adminController.changeInterest);
|
||||
router.post('/interest/translation', authenticate, adminController.changeTranslation);
|
||||
|
||||
@@ -41,7 +41,7 @@ router.get('/family/gifts', falukantController.getGifts);
|
||||
router.get('/family/children', falukantController.getChildren);
|
||||
router.post('/family/gift', falukantController.sendGift);
|
||||
router.get('/family', falukantController.getFamily);
|
||||
router.get('/nobility/titels', falukantController.getTitelsOfNobility);
|
||||
router.get('/nobility/titels', falukantController.getTitlesOfNobility);
|
||||
router.get('/houses/types', falukantController.getHouseTypes);
|
||||
router.get('/houses/buyable', falukantController.getBuyableHouses);
|
||||
router.get('/houses', falukantController.getUserHouse);
|
||||
@@ -71,5 +71,9 @@ router.post('/politics/open', falukantController.applyForElections);
|
||||
router.get('/cities', falukantController.getRegions);
|
||||
router.get('/underground/types', falukantController.getUndergroundTypes);
|
||||
router.get('/notifications', falukantController.getNotifications);
|
||||
router.get('/underground/targets', falukantController.getUndergroundTargets);
|
||||
router.post('/underground/activities', falukantController.createUndergroundActivity);
|
||||
router.get('/users/search', falukantController.searchUsers);
|
||||
router.get('/underground/attacks', falukantController.getUndergroundAttacks);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import RoomType from '../models/chat/room_type.js';
|
||||
import ChatRight from '../models/chat/rights.js';
|
||||
import UserRight from "../models/community/user_right.js";
|
||||
import UserRightType from "../models/type/user_right.js";
|
||||
import InterestType from "../models/type/interest.js"
|
||||
import InterestTranslationType from "../models/type/interest_translation.js"
|
||||
import InterestType from "../models/type/interest.js";
|
||||
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 UserParamType from "../models/type/user_param.js";
|
||||
import ContactMessage from "../models/service/contactmessage.js";
|
||||
import ContactService from "./ContactService.js";
|
||||
import { sendAnswerEmail } from './emailService.js';
|
||||
@@ -12,6 +15,7 @@ import FalukantUser from "../models/falukant/data/user.js";
|
||||
import FalukantCharacter from "../models/falukant/data/character.js";
|
||||
import FalukantPredefineFirstname from "../models/falukant/predefine/firstname.js";
|
||||
import FalukantPredefineLastname from "../models/falukant/predefine/lastname.js";
|
||||
import Room from '../models/chat/room.js';
|
||||
|
||||
class AdminService {
|
||||
async hasUserAccess(userId, section) {
|
||||
@@ -286,6 +290,39 @@ class AdminService {
|
||||
await falukantUser.save();
|
||||
await character.save();
|
||||
}
|
||||
|
||||
// --- Chat Room Admin ---
|
||||
async getRoomTypes() {
|
||||
return await RoomType.findAll();
|
||||
}
|
||||
|
||||
async getGenderRestrictions() {
|
||||
// Find the UserParamType for gender restriction (e.g. description = 'gender')
|
||||
const genderType = await UserParamType.findOne({ where: { description: 'gender' } });
|
||||
if (!genderType) return [];
|
||||
return await UserParamValue.findAll({ where: { userParamTypeId: genderType.id } });
|
||||
}
|
||||
|
||||
async getUserRights() {
|
||||
return await ChatRight.findAll();
|
||||
}
|
||||
|
||||
async getRooms() {
|
||||
return await Room.findAll({
|
||||
include: [
|
||||
{ model: RoomType, as: 'roomType' },
|
||||
{ model: UserParamValue, as: 'genderRestriction' },
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async createRoom(data) {
|
||||
return await Room.create(data);
|
||||
}
|
||||
|
||||
async deleteRoom(id) {
|
||||
return await Room.destroy({ where: { id } });
|
||||
}
|
||||
}
|
||||
|
||||
export default new AdminService();
|
||||
@@ -55,6 +55,8 @@ import PoliticalOfficePrerequisite from '../models/falukant/predefine/political_
|
||||
import PoliticalOfficeHistory from '../models/falukant/log/political_office_history.js';
|
||||
import UndergroundType from '../models/falukant/type/underground.js';
|
||||
import Notification from '../models/falukant/log/notification.js';
|
||||
import PoliticalOffice from '../models/falukant/data/political_office.js';
|
||||
import Underground from '../models/falukant/data/underground.js';
|
||||
|
||||
function calcAge(birthdate) {
|
||||
const b = new Date(birthdate); b.setHours(0, 0);
|
||||
@@ -1066,7 +1068,7 @@ class FalukantService extends BaseService {
|
||||
});
|
||||
if (regionUserDirectorProposals.length > 0) {
|
||||
for (const p of regionUserDirectorProposals) {
|
||||
await p.destroy();
|
||||
await p.destroy();
|
||||
}
|
||||
}
|
||||
notifyUser(hashedUserId, 'directorchanged');
|
||||
@@ -2795,6 +2797,297 @@ class FalukantService extends BaseService {
|
||||
});
|
||||
return user.notifications;
|
||||
}
|
||||
|
||||
async getPoliticalOfficeHolders(hashedUserId) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: {
|
||||
userId: user.id
|
||||
}
|
||||
});
|
||||
const relevantRegionIds = await this.getRegionAndParentIds(character.regionId);
|
||||
const now = new Date();
|
||||
const histories = await PoliticalOffice.findAll({
|
||||
where: {
|
||||
regionId: {
|
||||
[Op.in]: relevantRegionIds
|
||||
},
|
||||
},
|
||||
include: [{
|
||||
model: FalukantCharacter,
|
||||
as: 'holder',
|
||||
include: [
|
||||
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
|
||||
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }
|
||||
],
|
||||
attributes: ['id', 'gender']
|
||||
}, {
|
||||
model: PoliticalOfficeType,
|
||||
as: 'type',
|
||||
}]
|
||||
});
|
||||
|
||||
// Unikate nach character.id
|
||||
const map = new Map();
|
||||
histories.forEach(h => {
|
||||
const c = h.holder;
|
||||
if (c && c.id && !map.has(c.id)) {
|
||||
map.set(c.id, {
|
||||
id: c.id,
|
||||
name: `${c.definedFirstName.name} ${c.definedLastName.name}`,
|
||||
title: c.nobleTitle.labelTr,
|
||||
officeType: h.type.name,
|
||||
gender: c.gender
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
async getRegionAndParentIds(regionId) {
|
||||
const relevantRegionIds = new Set();
|
||||
let currentRegionId = regionId;
|
||||
|
||||
while (currentRegionId !== null) {
|
||||
relevantRegionIds.add(currentRegionId);
|
||||
const region = await RegionData.findByPk(currentRegionId, {
|
||||
attributes: ['parentId']
|
||||
});
|
||||
|
||||
if (region && region.parentId) {
|
||||
currentRegionId = region.parentId;
|
||||
} else {
|
||||
currentRegionId = null; // Keine weitere Elternregion gefunden
|
||||
}
|
||||
}
|
||||
return Array.from(relevantRegionIds);
|
||||
}
|
||||
|
||||
// vorher: async searchUsers(q) {
|
||||
async searchUsers(hashedUserId, q) {
|
||||
// User-Prüfung wie bei anderen Methoden
|
||||
await getFalukantUserOrFail(hashedUserId); // wir brauchen das Ergebnis hier nicht weiter, nur Validierung
|
||||
|
||||
const chars = await FalukantCharacter.findAll({
|
||||
where: {
|
||||
userId: { [Op.ne]: null },
|
||||
[Op.or]: [
|
||||
{ '$user.user.username$': { [Op.iLike]: `%${q}%` } },
|
||||
{ '$definedFirstName.name$': { [Op.iLike]: `%${q}%` } },
|
||||
{ '$definedLastName.name$': { [Op.iLike]: `%${q}%` } }
|
||||
]
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: FalukantUser,
|
||||
as: 'user',
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['username']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: FalukantPredefineFirstname,
|
||||
as: 'definedFirstName',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: FalukantPredefineLastname,
|
||||
as: 'definedLastName',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: RegionData,
|
||||
as: 'region',
|
||||
attributes: ['name']
|
||||
}
|
||||
],
|
||||
limit: 50,
|
||||
raw: false
|
||||
});
|
||||
|
||||
// Debug-Log (optional)
|
||||
console.log('FalukantService.searchUsers raw result for', q, chars);
|
||||
|
||||
const mapped = chars
|
||||
.map(c => ({
|
||||
username: c.user?.user?.username || null,
|
||||
firstname: c.definedFirstName?.name || null,
|
||||
lastname: c.definedLastName?.name || null,
|
||||
town: c.region?.name || null
|
||||
}))
|
||||
.filter(u => u.username);
|
||||
|
||||
console.log('FalukantService.searchUsers mapped result for', q, mapped);
|
||||
return mapped;
|
||||
}
|
||||
|
||||
async createUndergroundActivity(hashedUserId, payload) {
|
||||
const { typeId, victimUsername, target, goal, politicalTargets } = payload;
|
||||
|
||||
// 1) Performer auflösen
|
||||
const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
|
||||
if (!falukantUser || !falukantUser.character) {
|
||||
throw new Error('Performer not found');
|
||||
}
|
||||
const performerChar = falukantUser.character;
|
||||
|
||||
// 2) Victim auflösen über Username (inner join)
|
||||
const victimChar = await FalukantCharacter.findOne({
|
||||
include: [
|
||||
{
|
||||
model: FalukantUser,
|
||||
as: 'user',
|
||||
required: true, // inner join
|
||||
attributes: [],
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user',
|
||||
required: true, // inner join
|
||||
where: { username: victimUsername },
|
||||
attributes: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!victimChar) {
|
||||
throw new PreconditionError('Victim character not found');
|
||||
}
|
||||
|
||||
// 3) Selbstangriff verhindern
|
||||
if (victimChar.id === performerChar.id) {
|
||||
throw new PreconditionError('Cannot target yourself');
|
||||
}
|
||||
|
||||
// 4) Typ-spezifische Validierung
|
||||
const undergroundType = await UndergroundType.findByPk(typeId);
|
||||
if (!undergroundType) {
|
||||
throw new Error('Invalid underground type');
|
||||
}
|
||||
|
||||
if (undergroundType.tr === 'sabotage') {
|
||||
if (!target) {
|
||||
throw new PreconditionError('Sabotage target missing');
|
||||
}
|
||||
}
|
||||
|
||||
if (undergroundType.tr === 'corrupt_politician') {
|
||||
if (!goal) {
|
||||
throw new PreconditionError('Corrupt goal missing');
|
||||
}
|
||||
// politicalTargets kann optional sein, falls benötigt prüfen
|
||||
}
|
||||
|
||||
// 5) Eintrag anlegen (optional: in Transaction)
|
||||
const newEntry = await Underground.create({
|
||||
undergroundTypeId: typeId,
|
||||
performerId: performerChar.id,
|
||||
victimId: victimChar.id,
|
||||
result: null,
|
||||
parameters: {
|
||||
target: target || null,
|
||||
goal: goal || null,
|
||||
politicalTargets: politicalTargets || null
|
||||
}
|
||||
});
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
|
||||
async getUndergroundAttacks(hashedUserId) {
|
||||
const falukantUser = await getFalukantUserOrFail(hashedUserId);
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: falukantUser.id }
|
||||
});
|
||||
if (!character) throw new Error('Character not found');
|
||||
|
||||
const charId = character.id;
|
||||
|
||||
const attacks = await Underground.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ performerId: charId },
|
||||
{ victimId: charId }
|
||||
]
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'performer',
|
||||
include: [
|
||||
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
|
||||
{
|
||||
model: FalukantUser,
|
||||
as: 'user',
|
||||
include: [{ model: User, as: 'user', attributes: ['username'] }],
|
||||
attributes: []
|
||||
}
|
||||
],
|
||||
attributes: ['id', 'gender']
|
||||
},
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'victim',
|
||||
include: [
|
||||
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
|
||||
{
|
||||
model: FalukantUser,
|
||||
as: 'user',
|
||||
include: [{ model: User, as: 'user', attributes: ['username'] }],
|
||||
attributes: []
|
||||
}
|
||||
],
|
||||
attributes: ['id', 'gender']
|
||||
},
|
||||
{
|
||||
model: UndergroundType,
|
||||
as: 'undergroundType', // hier der korrekte Alias
|
||||
attributes: ['tr']
|
||||
}
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const formatCharacter = (c) => {
|
||||
if (!c) return null;
|
||||
return {
|
||||
id: c.id,
|
||||
username: c.user?.user?.username || null,
|
||||
firstname: c.definedFirstName?.name || null,
|
||||
lastname: c.definedLastName?.name || null,
|
||||
gender: c.gender
|
||||
};
|
||||
};
|
||||
|
||||
const mapped = attacks.map(a => ({
|
||||
id: a.id,
|
||||
result: a.result,
|
||||
createdAt: a.createdAt,
|
||||
updatedAt: a.updatedAt,
|
||||
type: a.undergroundType?.tr || null, // angepasst
|
||||
parameters: a.parameters,
|
||||
performer: formatCharacter(a.performer),
|
||||
victim: formatCharacter(a.victim),
|
||||
success: !!a.result
|
||||
}));
|
||||
|
||||
return {
|
||||
sent: mapped.filter(a => a.performer?.id === charId),
|
||||
received: mapped.filter(a => a.victim?.id === charId),
|
||||
all: mapped
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new FalukantService();
|
||||
|
||||
43
backend/utils/initializeChat.js
Normal file
43
backend/utils/initializeChat.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import ChatRight from "../models/chat/rights.js";
|
||||
import RoomType from "../models/chat/room_type.js";
|
||||
|
||||
const initializeChat = async () => {
|
||||
initializeChatRights();
|
||||
initializeRoomTypes();
|
||||
}
|
||||
|
||||
const RoomTypes = [
|
||||
'chat',
|
||||
'dice',
|
||||
'poker',
|
||||
'hangman'
|
||||
];
|
||||
|
||||
const ChatRights = [
|
||||
'talk',
|
||||
'scream',
|
||||
'whisper',
|
||||
'start game',
|
||||
'open room',
|
||||
'systemmessage'
|
||||
];
|
||||
|
||||
const initializeChatRights = async () => {
|
||||
for (const right of ChatRights) {
|
||||
await ChatRight.findOrCreate({
|
||||
where: { tr: right },
|
||||
defaults: { tr: right }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const initializeRoomTypes = async () => {
|
||||
for (const type of RoomTypes) {
|
||||
await RoomType.findOrCreate({
|
||||
where: { tr: type },
|
||||
defaults: { tr: type }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default initializeChat;
|
||||
@@ -21,6 +21,7 @@ const createSchemas = async () => {
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_type');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_predefine');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_log');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS chat');
|
||||
};
|
||||
|
||||
const initializeDatabase = async () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import setupAssociations from '../models/associations.js';
|
||||
import models from '../models/index.js';
|
||||
import { createTriggers } from '../models/trigger.js';
|
||||
import initializeForum from './initializeForum.js';
|
||||
import initializeChat from './initializeChat.js';
|
||||
|
||||
const syncDatabase = async () => {
|
||||
try {
|
||||
@@ -43,6 +44,9 @@ const syncDatabase = async () => {
|
||||
console.log("Creating triggers...");
|
||||
await createTriggers();
|
||||
|
||||
console.log("Initializing chat...");
|
||||
await initializeChat();
|
||||
|
||||
console.log('Database synchronization complete.');
|
||||
} catch (error) {
|
||||
console.error('Unable to synchronize the database:', error);
|
||||
|
||||
Reference in New Issue
Block a user