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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user