Bereinigen und Entfernen von nicht mehr benötigten TinyMCE-Dateien und -Plugins; Aktualisierung der Internationalisierung für Deutsch und Englisch in den Falukant- und Navigationsmodulen; Verbesserung der Statusleiste und Router-Implementierung.

This commit is contained in:
Torsten Schulz (local)
2025-08-21 16:10:21 +02:00
parent 53c748a074
commit 3eb7ae4e93
170 changed files with 3850 additions and 7924 deletions

View File

@@ -298,10 +298,79 @@ class FalukantService extends BaseService {
]
},
],
attributes: ['money']
attributes: ['id', 'money']
});
if (!falukantUser) throw new Error('User not found');
if (falukantUser.character?.birthdate) falukantUser.character.setDataValue('age', calcAge(falukantUser.character.birthdate));
// Aggregate status additions: children counts and unread notifications
try {
const bm = (step, payload = {}) => {
try { console.log(`[BLOCKMARKER][falukant.getInfo] ${step}`, payload); } catch (_) { /* ignore */ }
};
bm('aggregate.start', { userId: user.id, falukantUserId: falukantUser.id });
// Determine all character IDs belonging to the user
if (!falukantUser.id) {
bm('aggregate.noFalukantUserId');
throw new Error('Missing falukantUser.id in getInfo aggregation');
}
const userCharacterIdsRows = await FalukantCharacter.findAll({
attributes: ['id'],
where: { userId: falukantUser.id },
raw: true
});
const userCharacterIds = userCharacterIdsRows.map(r => r.id);
bm('aggregate.userCharacters', { count: userCharacterIds.length, ids: userCharacterIds.slice(0, 5) });
// Count distinct children for any of the user's characters (as father or mother)
let childrenCount = 0;
let unbaptisedChildrenCount = 0;
if (userCharacterIds.length > 0) {
const childRels = await ChildRelation.findAll({
attributes: ['childCharacterId'],
where: {
[Op.or]: [
{ fatherCharacterId: { [Op.in]: userCharacterIds } },
{ motherCharacterId: { [Op.in]: userCharacterIds } },
]
},
raw: true
});
const distinctChildIds = new Set(childRels.map(r => r.childCharacterId));
childrenCount = distinctChildIds.size;
bm('aggregate.children', { relations: childRels.length, distinct: childrenCount, sample: Array.from(distinctChildIds).slice(0, 5) });
const unbaptised = await ChildRelation.findAll({
attributes: ['childCharacterId'],
where: {
nameSet: false,
[Op.or]: [
{ fatherCharacterId: { [Op.in]: userCharacterIds } },
{ motherCharacterId: { [Op.in]: userCharacterIds } },
]
},
raw: true
});
const distinctUnbaptisedIds = new Set(unbaptised.map(r => r.childCharacterId));
unbaptisedChildrenCount = distinctUnbaptisedIds.size;
bm('aggregate.unbaptised', { relations: unbaptised.length, distinct: unbaptisedChildrenCount, sample: Array.from(distinctUnbaptisedIds).slice(0, 5) });
}
// Unread notifications count
const unreadNotifications = await Notification.count({ where: { userId: falukantUser.id, shown: false } });
bm('aggregate.unread', { unreadNotifications });
falukantUser.setDataValue('childrenCount', childrenCount);
falukantUser.setDataValue('unbaptisedChildrenCount', unbaptisedChildrenCount);
falukantUser.setDataValue('unreadNotifications', unreadNotifications);
bm('aggregate.done', { childrenCount, unbaptisedChildrenCount });
} catch (e) {
console.error('Error aggregating status info:', e);
falukantUser.setDataValue('childrenCount', 0);
falukantUser.setDataValue('unbaptisedChildrenCount', 0);
falukantUser.setDataValue('unreadNotifications', 0);
}
return falukantUser;
}
@@ -898,12 +967,9 @@ class FalukantService extends BaseService {
await this.deleteExpiredProposals();
const existingProposals = await this.fetchProposals(falukantUserId, regionId);
if (existingProposals.length > 0) {
console.log('Existing proposals:', existingProposals);
return this.formatProposals(existingProposals);
}
console.log('No existing proposals, generating new ones');
await this.generateProposals(falukantUserId, regionId);
console.log('Fetch new proposals');
const newProposals = await this.fetchProposals(falukantUserId, regionId);
return this.formatProposals(newProposals);
}
@@ -1320,7 +1386,7 @@ class FalukantService extends BaseService {
}
]
});
const children = [];
const children = [];
for (const parentChar of charsWithChildren) {
const allRels = [
...(parentChar.childrenFather || []),
@@ -1332,16 +1398,19 @@ class FalukantService extends BaseService {
name: kid.definedFirstName?.name || 'Unknown',
gender: kid.gender,
age: calcAge(kid.birthdate),
hasName: rel.nameSet,
hasName: rel.nameSet,
_createdAt: rel.createdAt,
});
}
}
// Sort children globally by relation createdAt ascending (older first)
children.sort((a, b) => new Date(a._createdAt) - new Date(b._createdAt));
const inProgress = ['wooing', 'engaged', 'married'];
const family = {
relationships: relationships.filter(r => inProgress.includes(r.relationshipType)),
lovers: relationships.filter(r => r.relationshipType === 'lover'),
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
children,
children: children.map(({ _createdAt, ...rest }) => rest),
possiblePartners: []
};
const ownAge = calcAge(character.birthdate);
@@ -1983,62 +2052,66 @@ class FalukantService extends BaseService {
}
async baptise(hashedUserId, childId, firstName) {
const falukantUser = await getFalukantUserOrFail(hashedUserId);
const parentCharacter = await FalukantCharacter.findOne({
where: {
userId: falukantUser.id,
},
});
if (!parentCharacter) {
throw new Error('Parent character not found');
}
const child = await FalukantCharacter.findOne({
where: {
id: childId,
},
});
if (!child) {
throw new Error('Child not found');
}
const childRelation = await ChildRelation.findOne({
where: {
[Op.or]: [
{
fatherCharacterId: parentCharacter.id,
childCharacterId: child.id,
},
{
motherCharacterId: parentCharacter.id,
childCharacterId: child.id,
}
]
}
});
if (!childRelation) {
throw new Error('Child relation not found');
}
await childRelation.update({
nameSet: true,
});
let firstNameObject = FalukantPredefineFirstname.findOne({
where: {
name: firstName,
gender: child.gender,
},
});
if (!firstNameObject) {
firstNameObject = await FalukantPredefineFirstname.create({
name: firstName,
gender: child.gender,
try {
const falukantUser = await getFalukantUserOrFail(hashedUserId);
const parentCharacter = await FalukantCharacter.findOne({
where: {
userId: falukantUser.id,
},
});
if (!parentCharacter) {
throw new Error('Parent character not found');
}
const child = await FalukantCharacter.findOne({
where: {
id: childId,
},
});
if (!child) {
throw new Error('Child not found');
}
const childRelation = await ChildRelation.findOne({
where: {
[Op.or]: [
{
fatherCharacterId: parentCharacter.id,
childCharacterId: child.id,
},
{
motherCharacterId: parentCharacter.id,
childCharacterId: child.id,
}
]
}
});
if (!childRelation) {
throw new Error('Child relation not found');
}
await childRelation.update({
nameSet: true,
});
let firstNameObject = await FalukantPredefineFirstname.findOne({
where: {
name: firstName,
gender: child.gender,
},
});
if (!firstNameObject) {
firstNameObject = await FalukantPredefineFirstname.create({
name: firstName,
gender: child.gender,
});
}
await child.update({
firstName: firstNameObject.id,
});
updateFalukantUserMoney(falukantUser.id, -50, 'Baptism', falukantUser.id);
// Trigger status bar refresh for the user after baptism
notifyUser(hashedUserId, 'falukantUpdateStatus', {});
return { success: true };
} catch (error) {
throw new Error(error.message);
}
await child.update({
firstName: firstNameObject.id,
});
updateFalukantUserMoney(falukantUser.id, -50, 'Baptism', falukantUser.id);
return { success: true };
} catch(error) {
throw new Error(error.message);
}
async getEducation(hashedUserId) {
@@ -2795,7 +2868,29 @@ class FalukantService extends BaseService {
where: { userId: user.id, shown: false },
order: [['createdAt', 'DESC']]
});
return user.notifications;
return notifications;
}
async getAllNotifications(hashedUserId, page = 1, size = 10) {
const user = await getFalukantUserOrFail(hashedUserId);
const limit = Math.max(1, Math.min(Number(size) || 10, 100));
const offset = Math.max(0, ((Number(page) || 1) - 1) * limit);
const { rows, count } = await Notification.findAndCountAll({
where: { userId: user.id },
order: [['createdAt', 'DESC']],
offset,
limit,
});
return { items: rows, total: count, page: Number(page) || 1, size: limit };
}
async markNotificationsShown(hashedUserId) {
const user = await getFalukantUserOrFail(hashedUserId);
const [count] = await Notification.update(
{ shown: true },
{ where: { userId: user.id, shown: false } }
);
return { updated: count };
}
async getPoliticalOfficeHolders(hashedUserId) {

View File

@@ -0,0 +1,313 @@
import Match3Campaign from '../models/match3/campaign.js';
import Match3Level from '../models/match3/level.js';
import Match3Objective from '../models/match3/objective.js';
import Match3UserProgress from '../models/match3/userProgress.js';
import Match3UserLevelProgress from '../models/match3/userLevelProgress.js';
class Match3Service {
/**
* Lädt alle aktiven Kampagnen
*/
async getActiveCampaigns() {
try {
const campaigns = await Match3Campaign.findAll({
where: { isActive: true },
include: [
{
model: Match3Level,
as: 'levels',
where: { isActive: true },
required: false,
include: [
{
model: Match3Objective,
as: 'objectives',
required: false,
order: [['order', 'ASC']]
}
],
order: [['order', 'ASC']]
}
],
order: [['order', 'ASC']]
});
return campaigns;
} catch (error) {
console.error('Error loading active campaigns:', error);
throw error;
}
}
/**
* Lädt eine spezifische Kampagne mit allen Leveln
*/
async getCampaign(campaignId) {
try {
const campaign = await Match3Campaign.findByPk(campaignId, {
include: [
{
model: Match3Level,
as: 'levels',
where: { isActive: true },
required: false,
include: [
{
model: Match3Objective,
as: 'objectives',
required: false,
order: [['order', 'ASC']]
}
],
order: [['order', 'ASC']]
}
]
});
return campaign;
} catch (error) {
console.error('Error loading campaign:', error);
throw error;
}
}
/**
* Lädt den Benutzerfortschritt für eine Kampagne
*/
async getUserProgress(userId, campaignId) {
try {
let userProgress = await Match3UserProgress.findOne({
where: { userId, campaignId },
include: [
{
model: Match3UserLevelProgress,
as: 'levelProgress',
include: [
{
model: Match3Level,
as: 'level'
}
]
}
]
});
if (!userProgress) {
// Erstelle neuen Fortschritt wenn noch nicht vorhanden
userProgress = await Match3UserProgress.create({
userId,
campaignId,
totalScore: 0,
totalStars: 0,
levelsCompleted: 0,
currentLevel: 1,
isCompleted: false
});
} else {
// Validiere und korrigiere bestehende currentLevel-Werte
if (userProgress.currentLevel < 1 || userProgress.currentLevel > 1000) {
console.warn(`Invalid currentLevel detected for user ${userId}: ${userProgress.currentLevel}, correcting to ${userProgress.levelsCompleted + 1}`);
// Korrigiere den ungültigen Wert
await userProgress.update({
currentLevel: userProgress.levelsCompleted + 1
});
// Lade den aktualisierten Datensatz
userProgress = await Match3UserProgress.findByPk(userProgress.id, {
include: [
{
model: Match3UserLevelProgress,
as: 'levelProgress',
include: [
{
model: Match3Level,
as: 'level'
}
]
}
]
});
}
}
return userProgress;
} catch (error) {
console.error('Error loading user progress:', error);
throw error;
}
}
/**
* Aktualisiert den Level-Fortschritt eines Benutzers
*/
async updateLevelProgress(userId, campaignId, levelId, levelData) {
try {
// Lade oder erstelle Benutzerfortschritt
let userProgress = await Match3UserProgress.findOne({
where: { userId, campaignId }
});
if (!userProgress) {
userProgress = await Match3UserProgress.create({
userId,
campaignId,
totalScore: 0,
totalStars: 0,
levelsCompleted: 0,
currentLevel: 1,
isCompleted: false
});
}
// Lade oder erstelle Level-Fortschritt
let levelProgress = await Match3UserLevelProgress.findOne({
where: { userProgressId: userProgress.id, levelId }
});
if (!levelProgress) {
levelProgress = await Match3UserLevelProgress.create({
userProgressId: userProgress.id,
levelId,
score: 0,
moves: 0,
time: 0,
stars: 0,
isCompleted: false,
attempts: 0
});
}
// Aktualisiere Level-Fortschritt
const updateData = {
score: Math.max(levelProgress.bestScore, levelData.score),
moves: levelData.moves,
time: levelData.time || 0,
stars: Math.max(levelProgress.stars, levelData.stars),
isCompleted: levelData.isCompleted || false,
attempts: levelProgress.attempts + 1
};
if (levelData.isCompleted) {
updateData.completedAt = new Date();
}
await levelProgress.update(updateData);
// Aktualisiere Bestwerte
if (levelData.score > levelProgress.bestScore) {
await levelProgress.update({ bestScore: levelData.score });
}
if (levelData.moves < levelProgress.bestMoves || levelProgress.bestMoves === 0) {
await levelProgress.update({ bestMoves: levelData.moves });
}
if (levelData.time < levelProgress.bestTime || levelProgress.bestTime === 0) {
await levelProgress.update({ bestTime: levelData.time });
}
// Aktualisiere Kampagnen-Fortschritt
if (levelData.isCompleted) {
const totalScore = await Match3UserLevelProgress.sum('score', {
where: { userProgressId: userProgress.id, isCompleted: true }
});
const totalStars = await Match3UserLevelProgress.sum('stars', {
where: { userProgressId: userProgress.id, isCompleted: true }
});
const levelsCompleted = await Match3UserLevelProgress.count({
where: { userProgressId: userProgress.id, isCompleted: true }
});
// Korrigiere currentLevel: Es sollte immer levelsCompleted + 1 sein
const correctCurrentLevel = levelsCompleted + 1;
await userProgress.update({
totalScore,
totalStars,
levelsCompleted,
currentLevel: correctCurrentLevel, // Verwende den korrigierten Wert
lastPlayed: new Date()
});
// Prüfe ob Kampagne abgeschlossen ist
const totalLevels = await Match3Level.count({
where: { campaignId, isActive: true }
});
if (levelsCompleted >= totalLevels) {
await userProgress.update({ isCompleted: true });
}
}
return { userProgress, levelProgress };
} catch (error) {
console.error('Error updating level progress:', error);
throw error;
}
}
/**
* Lädt die Bestenliste für eine Kampagne
*/
async getLeaderboard(campaignId, limit = 10) {
try {
const leaderboard = await Match3UserProgress.findAll({
where: { campaignId },
include: [
{
model: Match3UserLevelProgress,
as: 'levelProgress',
where: { isCompleted: true },
required: false
}
],
order: [
['totalScore', 'DESC'],
['totalStars', 'DESC'],
['levelsCompleted', 'DESC']
],
limit
});
return leaderboard;
} catch (error) {
console.error('Error loading leaderboard:', error);
throw error;
}
}
/**
* Lädt Statistiken für einen Benutzer
*/
async getUserStats(userId) {
try {
const stats = await Match3UserProgress.findAll({
where: { userId },
include: [
{
model: Match3Campaign,
as: 'campaign'
},
{
model: Match3UserLevelProgress,
as: 'levelProgress',
include: [
{
model: Match3Level,
as: 'level'
}
]
}
]
});
return stats;
} catch (error) {
console.error('Error loading user stats:', error);
throw error;
}
}
}
export default new Match3Service();

View File

@@ -0,0 +1,53 @@
import BaseService from './BaseService.js';
import models from '../models/index.js';
import { Op } from 'sequelize';
const { MinigameCampaign, MinigameCampaignLevel, MinigameUserProgress, User } = models;
class MinigamesService extends BaseService {
async listCampaigns() {
const campaigns = await MinigameCampaign.findAll({ order: [['id', 'ASC']] });
return campaigns;
}
async getCampaign(code) {
const campaign = await MinigameCampaign.findOne({ where: { code }, include: [{ model: MinigameCampaignLevel, as: 'levels', order: [['index', 'ASC']] }] });
if (!campaign) throw new Error('campaign_not_found');
return campaign;
}
async getProgress(hashedUserId, code) {
const user = await this.getUserByHashedId(hashedUserId);
if (!user) throw new Error('user_not_found');
const campaign = await MinigameCampaign.findOne({ where: { code } });
if (!campaign) throw new Error('campaign_not_found');
const progress = await MinigameUserProgress.findOne({ where: { userId: user.id, campaignId: campaign.id } });
if (!progress) {
return { levelIndex: 1, stars: 0, bestScore: 0 };
}
return progress;
}
async saveProgress(hashedUserId, code, payload) {
const user = await this.getUserByHashedId(hashedUserId);
if (!user) throw new Error('user_not_found');
const campaign = await MinigameCampaign.findOne({ where: { code } });
if (!campaign) throw new Error('campaign_not_found');
const { levelIndex, stars, bestScore } = payload;
const [progress, created] = await MinigameUserProgress.findOrCreate({
where: { userId: user.id, campaignId: campaign.id },
defaults: { levelIndex, stars, bestScore }
});
if (!created) {
await progress.update({
levelIndex: Math.max(progress.levelIndex, levelIndex),
stars: Math.max(progress.stars, stars),
bestScore: Math.max(progress.bestScore, bestScore),
});
}
return { success: true };
}
}
export default new MinigamesService();