diff --git a/backend/controllers/adminController.js b/backend/controllers/adminController.js index 506686d..f65fc10 100644 --- a/backend/controllers/adminController.js +++ b/backend/controllers/adminController.js @@ -89,6 +89,43 @@ class AdminController { res.status(error.status || 500).json({ error: error.message || 'Internal Server Error' }); } } + + async searchUser(req, res) { + try { + const { userid: userId } = req.headers; + const { userName, characterName } = req.body; + const response = await AdminService.getFalukantUser(userId, userName, characterName); + res.status(200).json(response); + } catch (error) { + console.log(error); + res.status(403).json({ error: error.message }); + } + } + + async getFalukantUserById(req, res) { + try { + const { userid: userId } = req.headers; + const { id: hashedId } = req.params; + const response = await AdminService.getFalukantUserById(userId, hashedId); + res.status(200).json(response); + } catch (error) { + console.log(error); + res.status(403).json({ error: error.message }); + } + } + + async changeFalukantUser(req, res) { + try { + const { userid: userId } = req.headers; + const data = req.body; + const { id: falukantUserId, } = req.body; + const response = await AdminService.changeFalukantUser(userId, falukantUserId, data); + res.status(200).json(response); + } catch (error) { + console.log(error); + res.status(403).json({ error: error.message }); + } + } } export default AdminController; diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index d7598a0..7169d3c 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -21,7 +21,10 @@ class FalukantController { this.convertProposalToDirector = this.convertProposalToDirector.bind(this); this.getDirectorForBranch = this.getDirectorForBranch.bind(this); this.setSetting = this.setSetting.bind(this); - this.getMarriageProposals = this.getMarriageProposals.bind(this); + this.getFamily = this.getFamily.bind(this); + this.acceptMarriageProposal = this.acceptMarriageProposal.bind(this); + this.getGifts = this.getGifts.bind(this); + this.sendGift = this.sendGift.bind(this); } async getUser(req, res) { @@ -308,9 +311,6 @@ class FalukantController { const { userid: hashedUserId } = req.headers; const { branchId } = req.params; const result = await FalukantService.getDirectorForBranch(hashedUserId, branchId); - if (!result) { - return res.status(404).json({ message: 'No director found for this branch' }); - } res.status(200).json(result); } catch (error) { res.status(500).json({ error: error.message }); @@ -329,13 +329,74 @@ class FalukantController { } } - async getMarriageProposals(req, res) { + async getFamily(req, res) { try { const { userid: hashedUserId } = req.headers; - const result = await FalukantService.getMarriageProposals(hashedUserId); + const result = await FalukantService.getFamily(hashedUserId); + if (!result) { + res.status(404).json({ error: 'No family data found' }); + } res.status(200).json(result); } catch (error) { res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async acceptMarriageProposal(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { proposalId } = req.body; + const result = await FalukantService.acceptMarriageProposal(hashedUserId, proposalId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getGifts(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getGifts(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async sendGift(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { giftId} = req.body; + const result = await FalukantService.sendGift(hashedUserId, giftId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getTitelsOfNobility(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getTitlesOfNobility(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getHouseTypes(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getHouseTypes(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); } } } diff --git a/backend/controllers/navigationController.js b/backend/controllers/navigationController.js index e69ca88..7ea5daf 100644 --- a/backend/controllers/navigationController.js +++ b/backend/controllers/navigationController.js @@ -93,10 +93,6 @@ const menuStructure = { visible: ["hasfalukantaccount"], path: "/falukant/directors" }, - factory: { - visible: ["hasfalukantaccount"], - path: "/falukant/factory" - }, family: { visible: ["hasfalukantaccount"], path: "/falukant/family" diff --git a/backend/models/associations.js b/backend/models/associations.js index 81368aa..75877ca 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -52,13 +52,16 @@ import TownProductWorth from './falukant/data/town_product_worth.js'; import DayProduction from './falukant/log/dayproduction.js'; import DaySell from './falukant/log/daysell.js'; import MarriageProposal from './falukant/data/marriage_proposal.js'; -import Notification from './falukant/log/notification'; +import Notification from './falukant/log/notification.js'; import CharacterTrait from './falukant/type/character_trait.js'; import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js'; import Mood from './falukant/type/mood.js'; import PromotionalGift from './falukant/type/promotional_gift.js'; import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js'; import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js'; +import RelationshipType from './falukant/type/relationship.js'; +import Relationship from './falukant/data/relationship.js'; +import PromotionalGiftLog from './falukant/log/promotional_gift.js'; export default function setupAssociations() { // UserParam related associations @@ -326,4 +329,27 @@ export default function setupAssociations() { PromotionalGift.belongsToMany(Mood, {through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods',}); Mood.belongsToMany(PromotionalGift, {through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts',}); + + Relationship.belongsTo(RelationshipType, { foreignKey: 'relationshipTypeId', as: 'relationshipType' }); + RelationshipType.hasMany(Relationship, { foreignKey: 'relationshipTypeId', as: 'relationships' }); + + Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character1Id', as: 'character1', }); + Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character2Id', as: 'character2', }); + FalukantCharacter.hasMany(Relationship, {foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', }); + FalukantCharacter.hasMany(Relationship, {foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', }); + + PromotionalGiftLog.belongsTo(PromotionalGift, { foreignKey: 'giftId', as: 'gift' }); + PromotionalGift.hasMany(PromotionalGiftLog, { foreignKey: 'giftId', as: 'logs' }); + + PromotionalGiftLog.belongsTo(FalukantCharacter, { foreignKey: 'senderCharacterId', as: 'character' }); + FalukantCharacter.hasMany(PromotionalGiftLog, { foreignKey: 'senderCharacterId', as: 'logs' }); + + PromotionalGiftLog.belongsTo(FalukantCharacter, { foreignKey: 'recipientCharacterId', as: 'recipient' }); + FalukantCharacter.hasMany(PromotionalGiftLog, { foreignKey: 'recipientCharacterId', as: 'giftlogs' }); + + PromotionalGift.hasMany(PromotionalGiftCharacterTrait, { foreignKey: 'gift_id', as: 'characterTraits' }); + PromotionalGift.hasMany(PromotionalGiftMood, { foreignKey: 'gift_id', as: 'promotionalgiftmoods' }); + + PromotionalGiftCharacterTrait.belongsTo(PromotionalGift, { foreignKey: 'gift_id', as: 'promotionalgiftcharactertrait' }); + PromotionalGiftMood.belongsTo(PromotionalGift, { foreignKey: 'gift_id', as: 'promotionalgiftcharactermood' }); } diff --git a/backend/models/falukant/data/character.js b/backend/models/falukant/data/character.js index 0778c33..7a48e55 100644 --- a/backend/models/falukant/data/character.js +++ b/backend/models/falukant/data/character.js @@ -5,19 +5,19 @@ class FalukantCharacter extends Model {} FalukantCharacter.init( { - user_id: { + userId: { type: DataTypes.INTEGER, allowNull: true, }, - region_id: { + regionId: { type: DataTypes.INTEGER, allowNull: false, }, - first_name: { + firstName: { type: DataTypes.INTEGER, allowNull: false, }, - last_name: { + lastName: { type: DataTypes.INTEGER, allowNull: false, }, diff --git a/backend/models/falukant/data/director.js b/backend/models/falukant/data/director.js index 558920d..8ae2e99 100644 --- a/backend/models/falukant/data/director.js +++ b/backend/models/falukant/data/director.js @@ -35,6 +35,11 @@ Director.init({ type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, + }, + lastSalaryPayout: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: new Date(0) } }, { sequelize, diff --git a/backend/models/falukant/data/relationship.js b/backend/models/falukant/data/relationship.js new file mode 100644 index 0000000..f874f7d --- /dev/null +++ b/backend/models/falukant/data/relationship.js @@ -0,0 +1,56 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; +import FalukantCharacter from './character.js'; + +class Relationship extends Model {} + +Relationship.init( + { + character1Id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: FalukantCharacter, + key: 'id', + }, + onDelete: 'CASCADE', + }, + character2Id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: FalukantCharacter, + key: 'id', + }, + onDelete: 'CASCADE', + }, + relationshipTypeId: { + type: DataTypes.INTEGER, + allowNull: false, + onDelete: 'CASCADE', + }, + widowFirstName1: { + type: DataTypes.STRING, + allowNull: true, + }, + widowFirstName2: { + type: DataTypes.STRING, + allowNull: true, + }, + nextStepProgress: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0, + }, + }, + { + sequelize, + modelName: 'Relationship', + tableName: 'relationship', + schema: 'falukant_data', + timestamps: true, + underscored: true, + } +); + +export default Relationship; diff --git a/backend/models/falukant/log/dayproduction.js b/backend/models/falukant/log/dayproduction.js index a0686d6..0adc013 100644 --- a/backend/models/falukant/log/dayproduction.js +++ b/backend/models/falukant/log/dayproduction.js @@ -24,6 +24,11 @@ DayProduction.init({ type: DataTypes.DATE, allowNull: false, defaultValue: sequelize.literal('CURRENT_TIMESTAMP'), + }, + productionDate: { + type: DataTypes.DATEONLY, + allowNull: false, + defaultValue: sequelize.literal('CURRENT_DATE'), } }, { sequelize, @@ -35,10 +40,9 @@ DayProduction.init({ indexes: [ { unique: true, - fields: ['producer_id', 'product_id', 'region_id'] + fields: ['producer_id', 'product_id', 'region_id', 'production_date'] } ] - }); export default DayProduction; diff --git a/backend/models/falukant/log/notification b/backend/models/falukant/log/notification.js similarity index 100% rename from backend/models/falukant/log/notification rename to backend/models/falukant/log/notification.js diff --git a/backend/models/falukant/log/promotional_gift.js b/backend/models/falukant/log/promotional_gift.js new file mode 100644 index 0000000..5dd1272 --- /dev/null +++ b/backend/models/falukant/log/promotional_gift.js @@ -0,0 +1,32 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class PromotionalGiftLog extends Model { }; + +PromotionalGiftLog.init({ + senderCharacterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + recipientCharacterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + giftId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + changeValue: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'PromotionalGiftLog', + tableName: 'promotional_gift', + schema: 'falukant_log', + timestamps: true, + underscored: true, +}); + +export default PromotionalGiftLog; \ No newline at end of file diff --git a/backend/models/falukant/type/promotional_gift.js b/backend/models/falukant/type/promotional_gift.js index d2ce4e7..b806368 100644 --- a/backend/models/falukant/type/promotional_gift.js +++ b/backend/models/falukant/type/promotional_gift.js @@ -16,7 +16,7 @@ PromotionalGift.init( value: { type: DataTypes.INTEGER, allowNull: false, - defaultValue: 0, // Wert des Geschenks + defaultValue: 0, }, }, { diff --git a/backend/models/falukant/type/relationship.js b/backend/models/falukant/type/relationship.js index ea4214f..881782b 100644 --- a/backend/models/falukant/type/relationship.js +++ b/backend/models/falukant/type/relationship.js @@ -1,9 +1,9 @@ import { Model, DataTypes } from 'sequelize'; import { sequelize } from '../../../utils/sequelize.js'; -class Relationship extends Model {} +class RelationshipType extends Model {} -Relationship.init( +RelationshipType.init( { tr: { type: DataTypes.STRING, @@ -12,7 +12,7 @@ Relationship.init( }, { sequelize, - modelName: 'Relationship', + modelName: 'RelationshipType', tableName: 'relationship', schema: 'falukant_type', timestamps: false, @@ -20,4 +20,4 @@ Relationship.init( } ); -export default Relationship; +export default RelationshipType; diff --git a/backend/models/falukant/type/stock.js b/backend/models/falukant/type/stock.js index 1fde918..ba1aaa4 100644 --- a/backend/models/falukant/type/stock.js +++ b/backend/models/falukant/type/stock.js @@ -7,6 +7,7 @@ FalukantStockType.init({ labelTr: { type: DataTypes.STRING, allowNull: false, + unique: true, }, cost: { type: DataTypes.INTEGER, diff --git a/backend/models/index.js b/backend/models/index.js index 0cf9a5b..8928ab6 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -55,15 +55,17 @@ import DirectorProposal from './falukant/data/director_proposal.js'; import TownProductWorth from './falukant/data/town_product_worth.js'; import DayProduction from './falukant/log/dayproduction.js'; import DaySell from './falukant/log/daysell.js'; -import Notification from './falukant/log/notification'; +import Notification from './falukant/log/notification.js'; import MarriageProposal from './falukant/data/marriage_proposal.js'; -import Relationship from './falukant/type/relationship.js'; +import RelationshipType from './falukant/type/relationship.js'; import CharacterTrait from './falukant/type/character_trait.js'; import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js'; import Mood from './falukant/type/mood.js'; import PromotionalGift from './falukant/type/promotional_gift.js'; import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js'; import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js'; +import Relationship from './falukant/data/relationship.js'; +import PromotionalGiftLog from './falukant/log/promotional_gift.js'; const models = { SettingsType, @@ -125,6 +127,7 @@ const models = { DaySell, Notification, MarriageProposal, + RelationshipType, Relationship, CharacterTrait, FalukantCharacterTrait, @@ -132,6 +135,7 @@ const models = { PromotionalGift, PromotionalGiftCharacterTrait, PromotionalGiftMood, + PromotionalGiftLog, }; export default models; diff --git a/backend/models/trigger.js b/backend/models/trigger.js index c298082..9adc1e1 100644 --- a/backend/models/trigger.js +++ b/backend/models/trigger.js @@ -193,6 +193,7 @@ export async function createTriggers() { await sequelize.query(createKnowledgeTriggerMethod); await sequelize.query(createKnowledgeTrigger); await sequelize.query(updateMoney); + await initializeCharacterTraitTrigger(); console.log('Triggers created successfully'); } catch (error) { @@ -200,3 +201,53 @@ export async function createTriggers() { } } +export const initializeCharacterTraitTrigger = async () => { + try { + const triggerCheckQuery = ` + SELECT tgname + FROM pg_trigger + WHERE tgname = 'trigger_assign_traits'; + `; + const [existingTrigger] = await sequelize.query(triggerCheckQuery, { type: sequelize.QueryTypes.SELECT }); + if (!existingTrigger) { + console.log('⚡ Erstelle den Trigger für zufällige Traits...'); + const createTriggerFunctionQuery = ` + CREATE OR REPLACE FUNCTION falukant_data.assign_random_traits() + RETURNS TRIGGER AS $$ + DECLARE + trait_ids INTEGER[]; + i INTEGER; + BEGIN + -- Zufällig 5 Trait-IDs auswählen + SELECT ARRAY( + SELECT id FROM falukant_type.character_trait + ORDER BY RANDOM() + LIMIT 5 + ) INTO trait_ids; + + -- Die 5 Traits dem neuen Charakter zuweisen + FOR i IN 1..array_length(trait_ids, 1) LOOP + INSERT INTO falukant_data.falukant_character_trait (character_id, trait_id) + VALUES (NEW.id, trait_ids[i]); + END LOOP; + + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + `; + const createTriggerQuery = ` + CREATE TRIGGER trigger_assign_traits + AFTER INSERT ON falukant_data.character + FOR EACH ROW EXECUTE FUNCTION falukant_data.assign_random_traits(); + `; + await sequelize.query(createTriggerFunctionQuery); + await sequelize.query(createTriggerQuery); + console.log('✅ Trigger erfolgreich erstellt.'); + } else { + console.log('🔹 Trigger existiert bereits. Keine Aktion erforderlich.'); + } + } catch (error) { + console.error('❌ Fehler beim Erstellen des Triggers:', error); + } +}; + \ No newline at end of file diff --git a/backend/routers/adminRouter.js b/backend/routers/adminRouter.js index d99b3fc..5b5ed15 100644 --- a/backend/routers/adminRouter.js +++ b/backend/routers/adminRouter.js @@ -11,5 +11,8 @@ router.post('/interest/translation', authenticate, adminController.changeTransla router.delete('/interest/:id', authenticate, adminController.deleteInterest); router.get('/opencontacts', authenticate, adminController.getOpenContacts); router.post('/contacts/answer', authenticate, adminController.answerContact); +router.post('/falukant/searchuser', authenticate, adminController.searchUser); +router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById); +router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser); export default router; diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index 0650f84..83d15ca 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -30,5 +30,11 @@ router.post('/director/proposal', falukantController.getDirectorProposals); router.post('/director/convertproposal', falukantController.convertProposalToDirector); router.post('/director/settings', falukantController.setSetting); router.get('/director/:branchId', falukantController.getDirectorForBranch); -router.get('/marriage/proposals', falukantController.getMarriageProposals); +router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal); +router.get('/family/gifts', falukantController.getGifts); +router.post('/family/gift', falukantController.sendGift); +router.get('/family', falukantController.getFamily); +router.get('/nobility/titels', falukantController.getTitelsOfNobility); +router.get('/houses/types', falukantController.getHouseTypes); + export default router; diff --git a/backend/services/adminService.js b/backend/services/adminService.js index 4458059..b45abc0 100644 --- a/backend/services/adminService.js +++ b/backend/services/adminService.js @@ -7,6 +7,11 @@ 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'; +import { Op } from 'sequelize'; +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"; class AdminService { async hasUserAccess(userId, section) { @@ -18,7 +23,7 @@ class AdminService { title: [section, 'mainadmin'], } }, - { + { model: User, as: 'user_with_rights', where: { @@ -26,9 +31,9 @@ class AdminService { } } ] - + }); - return userRights.length > 0; + return userRights.length > 0; } async getOpenInterests(userId) { @@ -55,7 +60,7 @@ class AdminService { where: { id: interestId } - }); + }); if (interest) { interest.allowed = active; interest.adultOnly = adultOnly; @@ -69,7 +74,7 @@ class AdminService { } const interest = await InterestType.findOne({ where: { - id: interestId + id: interestId } }); if (interest) { @@ -114,10 +119,10 @@ class AdminService { interestsId: interestId, language: languageObject.id, translation: translations[languageId] - + }); } - } + } } async getOpenContacts(userId) { @@ -138,6 +143,149 @@ class AdminService { await sendAnswerEmail(contact.email, answer, contact.language || 'en'); } + async getFalukantUser(userId, userName, characterName) { + if (!(await this.hasUserAccess(userId, 'falukantusers'))) { + throw new Error('noaccess'); + } + let users; + if (userName) { + users = await User.findAll({ + where: { + username: { + [Op.like]: '%' + userName + '%' + } + }, + include: [{ + model: FalukantUser, + as: 'falukantData', + required: true, + include: [{ + model: FalukantCharacter, + as: 'character', + required: true, + include: [{ + model: FalukantPredefineFirstname, + as: 'definedFirstName', + required: true + }, { + model: FalukantPredefineLastname, + as: 'definedLastName', + required: true + }] + }] + }] + }); + } else if (characterName) { + const [firstname, lastname] = characterName.split(' '); + users = await User.findAll({ + include: [{ + model: FalukantUser, + as: 'falukantData', + required: true, + include: [{ + model: FalukantCharacter, + as: 'character', + required: true, + include: [{ + model: FalukantPredefineFirstname, + as: 'definedFirstName', + required: true, + where: { + name: firstname + } + }, { + model: FalukantPredefineLastname, + as: 'definedLastName', + required: true, + where: { + name: lastname + } + }] + }] + }] + }); + } else { + throw new Error('no search parameter'); + } + return users.map(user => { + return { + id: user.hashedId, + username: user.username, + falukantUser: user.falukantData + } + }); + } + + async getFalukantUserById(userId, hashedId) { + if (!(await this.hasUserAccess(userId, 'falukantusers'))) { + throw new Error('noaccess'); + } + const user = await User.findOne({ + where: { + hashedId: hashedId + }, + attributes: ['hashedId', 'username'], + include: [{ + model: FalukantUser, + as: 'falukantData', + required: true, + attributes: ['money', 'certificate', 'id'], + include: [{ + model: FalukantCharacter, + as: 'character', + attributes: ['birthdate', 'health', 'title_of_nobility'], + include: [{ + model: FalukantPredefineFirstname, + as: 'definedFirstName', + }, { + model: FalukantPredefineLastname, + as: 'definedLastName', + }] + }] + }] + }); + return user; + } + + async changeFalukantUser(userId, falukantUserId, falukantData) { + if (!(await this.hasUserAccess(userId, 'falukantusers'))) { + throw new Error('noaccess'); + } + const falukantUser = await FalukantUser.findOne({ + where: { + id: falukantUserId + } + }); + if (!falukantUser) { + throw new Error('notfound'); + } + const character = await FalukantCharacter.findOne({ + where: { + userId: falukantUserId + } + }); + if (!character) { + throw new Error('notfound'); + } + if (Object.keys(falukantData).indexOf('age') >= 0) { + const birthDate = (new Date()) - (falukantData.age * 24 * 3600000); + await character.update({ + birthdate: birthDate + }); + } + if (Object.keys(falukantData).indexOf('money') >= 0) { + await falukantUser.update({ + money: falukantData.money + }); + } + if (Object.keys(falukantData).indexOf('title_of_nobility') >= 0) { + await character.update({ + titleOfNobility: falukantData.title_of_nobility + }); + } + await falukantUser.save(); + await character.save(); + } } export default new AdminService(); \ No newline at end of file diff --git a/backend/services/authService.js b/backend/services/authService.js index be329b8..252e282 100644 --- a/backend/services/authService.js +++ b/backend/services/authService.js @@ -104,7 +104,6 @@ export const loginUser = async ({ username, password }) => { user.authCode = authCode; await user.save(); const friends = await getFriends(user.id); - console.log('send login to friends'); for (const friend of friends) { await notifyUser(friend.hashedId, 'friendloginchanged', { userId: user.hashedId, diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 96c8848..d5418e7 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -1,5 +1,5 @@ import BaseService from './BaseService.js'; -import { Sequelize, Op } from 'sequelize'; +import { Sequelize, Op, where } from 'sequelize'; import FalukantPredefineFirstname from '../models/falukant/predefine/firstname.js'; import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js'; @@ -25,7 +25,14 @@ import BuyableStock from '../models/falukant/data/buyable_stock.js'; import DirectorProposal from '../models/falukant/data/director_proposal.js'; import Director from '../models/falukant/data/director.js'; import DaySell from '../models/falukant/log/daysell.js'; - +import MarriageProposal from '../models/falukant/data/marriage_proposal.js'; +import RelationshipType from '../models/falukant/type/relationship.js'; +import Relationship from '../models/falukant/data/relationship.js'; +import PromotionalGift from '../models/falukant/type/promotional_gift.js'; +import PromotionalGiftCharacterTrait from '../models/falukant/predefine/promotional_gift_character_trait.js'; +import PromotionalGiftMood from '../models/falukant/predefine/promotional_gift_mood.js'; +import PromotionalGiftLog from '../models/falukant/log/promotional_gift.js'; +import CharacterTrait from '../models/falukant/type/character_trait.js'; function calcAge(birthdate) { const b = new Date(birthdate); b.setHours(0, 0); @@ -53,11 +60,33 @@ function calcSellPrice(product, knowledgeFactor = 0) { return min + (max - min) * (knowledgeFactor / 100); } +function calculateMarriageCost(titleOfNobility, age) { + const minTitle = 1; + const adjustedTitle = titleOfNobility - minTitle + 1; + const baseCost = 500; + return baseCost * Math.pow(adjustedTitle, 1.3) - (age - 12) * 20; +} + class FalukantService extends BaseService { async getFalukantUserByHashedId(hashedId) { - return FalukantUser.findOne({ - include: [{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }] + const user = await FalukantUser.findOne({ + include: [ + { model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }, + { + model: FalukantCharacter, + as: 'character', + include: [ + { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, + { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }, + { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }, + { model: CharacterTrait, as: 'traits', attributes: ['id', 'tr'] } + ], + attributes: ['id', 'birthdate', 'gender'] + }, + ] }); + if (!user) throw new Error('User not found'); + return user; } async getUser(hashedUserId) { @@ -137,7 +166,7 @@ class FalukantService extends BaseService { await FalukantStock.create({ userId: falukantUser.id, regionId: region.id, stockTypeId: stType.id, quantity: 10 }); falukantUser.character = ch; const bType = await BranchType.findOne({ where: { labelTr: 'fullstack' } }); - await Branch.create({ userId: falukantUser.id, regionId: region.id, branchTypeId: bType.id }); + await Branch.create({ falukantUserId: falukantUser.id, regionId: region.id, branchTypeId: bType.id }); notifyUser(user.hashedId, 'reloadmenu', {}); return falukantUser; } @@ -209,7 +238,12 @@ class FalukantService extends BaseService { const u = await getFalukantUserOrFail(hashedUserId); const b = await getBranchOrFail(u.id, branchId); const p = await ProductType.findOne({ where: { id: productId } }); + const runningProductions = await Production.findAll({ where: { branchId: b.id } }); + if (runningProductions.length >= 2) { + throw new Error('Too many productions'); + } if (!p) throw new Error('Product not found'); + quantity = Math.min(100, quantity); const cost = quantity * p.category * 6; if (u.money < cost) throw new Error('notenoughmoney'); const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id); @@ -293,7 +327,7 @@ class FalukantService extends BaseService { const stock = await FalukantStock.findOne({ where: { branchId: branch.id } }); if (!stock) throw new Error('Stock not found'); const inventory = await Inventory.findAll({ - where: { stockId: stock.id, quality }, + where: { quality }, include: [ { model: ProductType, @@ -311,8 +345,12 @@ class FalukantService extends BaseService { } ] }); - if (!inventory.length) throw new Error('No inventory found'); + if (!inventory.length) { + throw new Error('No inventory found'); + } + console.log(inventory); const available = inventory.reduce((sum, i) => sum + i.quantity, 0); + console.log(available); if (available < quantity) throw new Error('Not enough inventory available'); const item = inventory[0].productType; const knowledgeVal = item.knowledges?.[0]?.knowledge || 0; @@ -330,7 +368,7 @@ class FalukantService extends BaseService { break; } } - await this.addSellItem(branchId, falukantUser.id, productId, quantity); + await this.addSellItem(branchId, user.id, productId, quantity); notifyUser(user.user.hashedId, 'falukantUpdateStatus', {}); notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id }); return { success: true }; @@ -384,7 +422,7 @@ class FalukantService extends BaseService { for (const item of inventory) { const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0; total += item.quantity * calcSellPrice(item.productType, knowledgeVal); - await this.addSellItem(item.stock[0].branch[0].id, falukantUser.id, item.productType.id, item.quantity); + await this.addSellItem(item.stock.branch.id, falukantUser.id, item.productType.id, item.quantity); } const moneyResult = await updateFalukantUserMoney( falukantUser.id, @@ -397,7 +435,7 @@ class FalukantService extends BaseService { await Inventory.destroy({ where: { id: item.id } }); } notifyUser(falukantUser.user.hashedId, 'falukantUpdateStatus', {}); - notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', {}); + notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', { branchId }); return { success: true, revenue: total }; } @@ -408,7 +446,7 @@ class FalukantService extends BaseService { ; const daySell = await DaySell.findOne({ where: { - regionId: regionId, + regionId: branch.regionId, productId: productId, sellerId: userId, } @@ -418,7 +456,7 @@ class FalukantService extends BaseService { await daySell.save(); } else { await DaySell.create({ - regionId: regionId, + regionId: branch.regionId, productId: productId, sellerId: userId, quantity: quantity, @@ -528,13 +566,22 @@ class FalukantService extends BaseService { if (!moneyResult.success) throw new Error('Failed to update money'); buyable.quantity -= amount; await buyable.save(); - const stock = await FalukantStock.findOne({ + let stock = await FalukantStock.findOne({ where: { branchId: branch.id, stockTypeId }, include: [{ model: FalukantStockType, as: 'stockType' }] }); - if (!stock) throw new Error('No stock record found for this branch and stockType'); + if (!stock) { + stock = await FalukantStock.create({ + branchId: branch.id, + stockTypeId, + quantity: amount, + }); + return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr }; + } stock.quantity += amount; await stock.save(); + notifyUser(user.user.hashedId, 'falukantUpdateStatus', {}); + notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId }); return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr }; } @@ -775,10 +822,13 @@ class FalukantService extends BaseService { } async convertProposalToDirector(hashedUserId, proposalId) { + console.log('convert proposal to director - start'); const user = await getFalukantUserOrFail(hashedUserId); + console.log('convert proposal to director - check user'); if (!user) { throw new Error('User not found'); } + console.log('convert proposal to director - find proposal', proposalId); const proposal = await DirectorProposal.findOne( { where: { id: proposalId }, @@ -787,10 +837,12 @@ class FalukantService extends BaseService { ] } ); + console.log('convert proposal to director - check proposal'); if (!proposal || proposal.employerUserId !== user.id) { throw new Error('Proposal does not belong to the user'); } + console.log('convert proposal to director - check existing director', user, proposal); const existingDirector = await Director.findOne({ where: { employerUserId: user.id @@ -808,6 +860,7 @@ class FalukantService extends BaseService { if (existingDirector) { throw new Error('A director already exists for this region'); } + console.log('convert proposal to director - create new director'); const newDirector = await Director.create({ directorCharacterId: proposal.directorCharacterId, employerUserId: proposal.employerUserId, @@ -827,11 +880,13 @@ class FalukantService extends BaseService { }, ] }); + console.log('convert proposal to director - remove propsals'); if (regionUserDirectorProposals.length > 0) { for (const proposal of regionUserDirectorProposals) { await DirectorProposal.destroy(); } } + console.log('convert proposal to director - notify user'); notifyUser(hashedUserId, 'directorchanged'); return newDirector; } @@ -932,59 +987,268 @@ class FalukantService extends BaseService { return { result: 'ok' }; } - async getMarriageProposals(hashedUserId) { + async getFamily(hashedUserId) { const user = await this.getFalukantUserByHashedId(hashedUserId); + if (!user) { + throw new Error('User not found'); + } const character = await FalukantCharacter.findOne({ where: { userId: user.id } }); if (!character) { throw new Error('Character not found for this user'); } - const midnight = new Date(); - midnight.setHours(0, 0, 0, 0); - await MarriageProposal.destroy({ - where: { - [Op.or]: [ - { requesterCharacterId: character.id }, - { proposedCharacterId: character.id }, - ], - createdAt: { - [Op.lt]: midnight, - }, - }, - }); - let proposals = await MarriageProposal.findAll({ - where: { - [Op.or]: [ - { requesterCharacterId: character.id }, - { proposedCharacterId: character.id }, - ], - }, - }); - if (proposals.length === 0) { - const proposalCount = Math.floor(Math.random() * 4) + 3; // 3–6 - const thirteenDaysAgo = new Date(Date.now() - 13 * 24 * 60 * 60 * 1000); - const possiblePartners = await FalukantCharacter.findAll({ + const family = { + relationships: [], + deathPartners: [], + children: [], + lovers: [], + possiblePartners: [], + }; + let relationships = await Relationship.findAll( + { where: { - id: { [Op.ne]: character.id }, - createdAt: { [Op.lt]: thirteenDaysAgo }, + character1Id: character.id, }, - order: [sequelize.fn('RANDOM')], - }); - if (possiblePartners.length === 0) { - return []; + attributes: ['createdAt', 'widowFirstName2'], + include: [ + { + model: FalukantCharacter, + as: 'character2', + attributes: ['id', 'birthdate', 'gender'], + include: [ + { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, + { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }, + ], + }, + { + model: RelationshipType, + as: 'relationshipType', + attributes: ['tr'], + }, + ], } - const newProposals = []; - for (let i = 0; i < proposalCount; i++) { - const partner = possiblePartners[i % possiblePartners.length]; - const createdProposal = await MarriageProposal.create({ - requesterCharacterId: character.id, - proposedCharacterId: partner.id, - courtingProgress: 0, - }); - newProposals.push(createdProposal); + ); + relationships = relationships.map((relationship) => ({ + createdAt: relationship.createdAt, + widowFirstName2: relationship.widowFirstName2, + character2: { + id: relationship.character2.id, + age: calcAge(relationship.character2.birthdate), + gender: relationship.character2.gender, + firstName: relationship.character2.definedFirstName?.name || 'Unknown', + nobleTitle: relationship.character2.nobleTitle?.labelTr || '', + }, + relationshipType: relationship.relationshipType.tr, + })); + family.relationships = relationships.filter((relationship) => ['wooing', 'engaged', 'married'].includes(relationship.relationshipType)); + family.lovers = relationships.filter((relationship) => ['lover'].includes(relationship.relationshipType.tr)); + family.deathPartners = relationships.filter((relationship) => ['widowed'].includes(relationship.relationshipType.tr)); + if (family.relationships.length === 0 ) { + family.possiblePartners = await this.getPossiblePartners(character.id); + if (family.possiblePartners.length === 0) { + await this.createPossiblePartners(character.id, character.gender, character.regionId, character.titleOfNobility); + family.possiblePartners = await this.getPossiblePartners(character.id); } - proposals = newProposals; } - return proposals; + return family; + } + + async getPossiblePartners(requestingCharacterId) { + const proposals = await MarriageProposal.findAll({ + where: { + requesterCharacterId: requestingCharacterId, + }, + include: [ + { + model: FalukantCharacter, + as: 'proposedCharacter', + attributes: ['id', 'firstName', 'lastName', 'gender', 'regionId', 'birthdate'], + include: [ + { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, + { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }, + { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }, + ], + }, + ], + }); + return proposals.map(proposal => { + const birthdate = new Date(proposal.proposedCharacter.birthdate); + const age = calcAge(birthdate); + console.log(proposal.proposedCharacter); + return { + id: proposal.id, + requesterCharacterId: proposal.requesterCharacterId, + proposedCharacterId: proposal.proposedCharacter.id, + proposedCharacterName: `${proposal.proposedCharacter.definedFirstName?.name} ${proposal.proposedCharacter.definedLastName?.name}`, + proposedCharacterGender: proposal.proposedCharacter.gender, + proposedCharacterRegionId: proposal.proposedCharacter.regionId, + proposedCharacterAge: age, + proposedCharacterNobleTitle: proposal.proposedCharacter.nobleTitle.labelTr, + cost: proposal.cost, + }; + }); + } + + async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility) { + try { + const minTitleResult = await TitleOfNobility.findOne({ + order: [['id', 'ASC']], + attributes: ['id'], + }); + if (!minTitleResult) { + throw new Error('No title of nobility found'); + } + const minTitle = minTitleResult.id; + const potentialPartners = await FalukantCharacter.findAll({ + where: { + id: { [Op.ne]: requestingCharacterId }, + gender: { [Op.ne]: requestingCharacterGender }, + regionId: requestingRegionId, + createdAt: { [Op.lt]: new Date(new Date() - 12 * 24 * 60 * 60 * 1000) }, + titleOfNobility: { [Op.between]: [requestingCharacterTitleOfNobility - 1, requestingCharacterTitleOfNobility + 1] } + }, + limit: 5, + }); + const proposals = potentialPartners.map(partner => { + const age = calcAge(partner.birthdate); + return { + requesterCharacterId: requestingCharacterId, + proposedCharacterId: partner.id, + cost: calculateMarriageCost(partner.titleOfNobility, age, minTitle), + }; + }); + await MarriageProposal.bulkCreate(proposals); + } catch (error) { + console.error('Error creating possible partners:', error); + throw error; + } + } + + async acceptMarriageProposal(hashedUserId, proposedCharacterId) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + const character = await FalukantCharacter.findOne({ where: { userId: user.id } }); + if (!user) { + throw new Error('User not found'); + } + const proposal = await MarriageProposal.findOne({ + where: { + requesterCharacterId: character.id, + proposedCharacterId: proposedCharacterId, + }, + }); + if (!proposal) { + throw new Error('Proposal not found'); + } + if (user.money < proposal.cost) { + console.log(user, proposal); + throw new Error('Not enough money to accept the proposal'); + } + const moneyResult = await updateFalukantUserMoney(user.id, -proposal.cost, 'Marriage cost', user.id); + if (!moneyResult.success) { + throw new Error('Failed to update money'); + } + const marriedType = await RelationshipType.findOne({ + where: { tr: 'wooing' }, + }); + if (!marriedType) { + throw new Error('Relationship type "married" not found'); + } + await Relationship.create({ + character1Id: proposal.requesterCharacterId, + character2Id: proposal.proposedCharacterId, + relationshipTypeId: marriedType.id, + }); + await MarriageProposal.destroy({ + where: { character1Id: character.id }, + }) + ; + return { success: true, message: 'Marriage proposal accepted' }; + } + + async getGifts(hashedUserId) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + const character = await FalukantCharacter.findOne({ + where: { userId: user.id }, + }); + if (!character) { + throw new Error('Character not found'); + } + let gifts = await PromotionalGift.findAll(); + const lowestTitleOfNobility = await TitleOfNobility.findOne({ + order: [['id', 'ASC']], + }); + return await Promise.all(gifts.map(async (gift) => { + return { + id: gift.id, + name: gift.name, + cost: await this.getGiftCost(gift.value, character.titleOfNobility, lowestTitleOfNobility.id), + }; + })); + } + + async sendGift(hashedUserId, giftId) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + const lowestTitleOfNobility = await TitleOfNobility.findOne({ + order: [['id', 'ASC']], + }); + const relation = Relationship.findOne({ + where: { + character1Id: user.character.id, + }, + include: [ + { + model: RelationshipType, + as: 'relationshipType', + where: { tr: 'wooing' }, + } + ], + }); + if (!relation) { + throw new Error('User and character are not related'); + } + console.log(user); + const gift = await PromotionalGift.findOne({ + where: { id: giftId }, + include: [ + { + model: PromotionalGiftCharacterTrait, + as: 'characterTraits', + where: { trait_id: { [Op.in]: user.character.characterTraits.map(trait => trait.id) }, }, + }, + { + model: PromotionalGiftMood, + as: 'promotionalgiftmoods', + }, + ] + }); + const cost = await this.getGiftCost(gift.value, user.character.titleOfNobility, lowestTitleOfNobility.id); + if (user.money < cost) { + console.log(user, user.money, cost); + throw new Error('Not enough money to send the gift'); + } + console.log(JSON.stringify(gift)); + const changeValue = gift.characterTraits.suitability + gift.promotionalgiftmoods.suitability - 4; + this.updateFalukantUserMoney(user.id, -cost, 'Gift cost', user.id); + await relation.update({ value: relation.value + changeValue }); + await PromotionalGiftLog.create({ + senderCharacterId: user.character.id, + recipientCharacterId: relation.character2Id, + giftId: giftId, + changeValue: changeValue, + }); + return { success: true, message: 'Gift sent' }; + } + + async getGiftCost(value, titleOfNobility, lowestTitleOfNobility) { + const titleLevel = titleOfNobility - lowestTitleOfNobility + 1; + return Math.round(value * Math.pow(1 + titleLevel * 0.3, 1.3) * 100) / 100; + } + + async getTitlesOfNobility() { + return TitleOfNobility.findAll(); + } + + async getHouseTypes() { +// return House } } diff --git a/backend/utils/falukant/initializeFalukantPredefines.js b/backend/utils/falukant/initializeFalukantPredefines.js index aa441dc..482b1e3 100644 --- a/backend/utils/falukant/initializeFalukantPredefines.js +++ b/backend/utils/falukant/initializeFalukantPredefines.js @@ -231,16 +231,16 @@ async function initializeFalukantProducts() { await ProductType.bulkCreate([ { labelTr: 'wheat', category: 1, productionTime: 2, sellCost: 7 }, { labelTr: 'grain', category: 1, productionTime: 2, sellCost: 7 }, - { labelTr: 'carrot', category: 1, productionTime: 1, sellCost: 4 }, + { labelTr: 'carrot', category: 1, productionTime: 1, sellCost: 46}, { labelTr: 'fish', category: 1, productionTime: 2, sellCost: 7 }, { labelTr: 'meat', category: 1, productionTime: 2, sellCost: 7 }, { labelTr: 'leather', category: 1, productionTime: 2, sellCost: 7 }, { labelTr: 'wood', category: 1, productionTime: 2, sellCost: 7 }, { labelTr: 'stone', category: 1, productionTime: 2, sellCost: 7 }, - { labelTr: 'milk', category: 1, productionTime: 1, sellCost: 4 }, - { labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 4 }, - { labelTr: 'bread', category: 1, productionTime: 1, sellCost: 4 }, - { labelTr: 'beer', category: 2, productionTime: 3, sellCost: 4 }, + { labelTr: 'milk', category: 1, productionTime: 1, sellCost: 6 }, + { labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 6 }, + { labelTr: 'bread', category: 1, productionTime: 1, sellCost: 6 }, + { labelTr: 'beer', category: 2, productionTime: 3, sellCost: 6 }, { labelTr: 'iron', category: 2, productionTime: 4, sellCost: 15 }, { labelTr: 'copper', category: 2, productionTime: 4, sellCost: 15 }, { labelTr: 'spices', category: 2, productionTime: 8, sellCost: 30 }, diff --git a/backend/utils/falukant/initializeFalukantTypes.js b/backend/utils/falukant/initializeFalukantTypes.js index 8d63dbb..1d89894 100644 --- a/backend/utils/falukant/initializeFalukantTypes.js +++ b/backend/utils/falukant/initializeFalukantTypes.js @@ -7,6 +7,15 @@ import PromotionalGift from "../../models/falukant/type/promotional_gift.js"; import PromotionalGiftCharacterTrait from "../../models/falukant/predefine/promotional_gift_character_trait.js"; import PromotionalGiftMood from "../../models/falukant/predefine/promotional_gift_mood.js"; +export const initializeFalukantTypes = async () => { + await initializeFalukantTypeRegions(); + await initializeFalukantRelationships(); + await initializeFalukantMoods(); + await initializeFalukantCharacterTraits(); + await initializeFalukantPromotionalGifts(); + await initializePromotionalGiftMoodLinks(); +}; + const regionTypes = []; const regionTypeTrs = [ "country", @@ -33,7 +42,8 @@ const relationships = [ { tr: "wooing" }, { tr: "engaged" }, { tr: "married" }, - { tr: "widowed" } + { tr: "widowed" }, + { tr: "lover" }, ]; const moods = [ @@ -194,15 +204,6 @@ const promotionalGiftMoodLinks = [ { gift: "Horse", mood: "nervous", suitability: 4 }, ]; -export const initializeFalukantTypes = async () => { - await initializeFalukantTypeRegions(); - await initializeFalukantRelationships(); - await initializeFalukantMoods(); - await initializeFalukantCharacterTraits(); - await initializeFalukantPromotionalGifts(); - await initializePromotionalGiftMoodLinks(); -}; - const initializeFalukantTypeRegions = async () => { for (const regionType of regionTypeTrs) { const [regionTypeRecord] = await RegionType.findOrCreate({ @@ -326,3 +327,4 @@ export const initializePromotionalGiftMoodLinks = async () => { }); } }; + diff --git a/backend/utils/syncDatabase.js b/backend/utils/syncDatabase.js index f441b06..be13356 100644 --- a/backend/utils/syncDatabase.js +++ b/backend/utils/syncDatabase.js @@ -22,9 +22,6 @@ const syncDatabase = async () => { console.log("Setting up associations..."); setupAssociations(); - console.log("Creating triggers..."); - await createTriggers(); - console.log("Initializing settings..."); await initializeSettings(); @@ -43,6 +40,9 @@ const syncDatabase = async () => { console.log("Initializing Falukant..."); await initializeFalukant(); + console.log("Creating triggers..."); + await createTriggers(); + console.log('Database synchronization complete.'); } catch (error) { console.error('Unable to synchronize the database:', error); diff --git a/dump.rdb b/dump.rdb index bfc94cf..b057372 100644 Binary files a/dump.rdb and b/dump.rdb differ diff --git a/frontend/public/images/falukant/avatar/female00.jpg b/frontend/public/images/falukant/avatar/female00.jpg new file mode 100644 index 0000000..dda3f16 Binary files /dev/null and b/frontend/public/images/falukant/avatar/female00.jpg differ diff --git a/frontend/public/images/falukant/avatar/female01.jpg b/frontend/public/images/falukant/avatar/female01.jpg new file mode 100644 index 0000000..256cde7 Binary files /dev/null and b/frontend/public/images/falukant/avatar/female01.jpg differ diff --git a/frontend/public/images/falukant/avatar/female02.jpg b/frontend/public/images/falukant/avatar/female02.jpg new file mode 100644 index 0000000..6c7b5d7 Binary files /dev/null and b/frontend/public/images/falukant/avatar/female02.jpg differ diff --git a/frontend/public/images/falukant/avatar/female03.png b/frontend/public/images/falukant/avatar/female03.png new file mode 100644 index 0000000..af8452a Binary files /dev/null and b/frontend/public/images/falukant/avatar/female03.png differ diff --git a/frontend/public/images/falukant/avatar/male00.jpg b/frontend/public/images/falukant/avatar/male00.jpg new file mode 100644 index 0000000..dc2a7c0 Binary files /dev/null and b/frontend/public/images/falukant/avatar/male00.jpg differ diff --git a/frontend/public/images/falukant/avatar/male01.jpg b/frontend/public/images/falukant/avatar/male01.jpg new file mode 100644 index 0000000..5b6cd2a Binary files /dev/null and b/frontend/public/images/falukant/avatar/male01.jpg differ diff --git a/frontend/public/images/falukant/avatar/male02.jpg b/frontend/public/images/falukant/avatar/male02.jpg new file mode 100644 index 0000000..49ec5f0 Binary files /dev/null and b/frontend/public/images/falukant/avatar/male02.jpg differ diff --git a/frontend/public/images/falukant/avatar/male03.png b/frontend/public/images/falukant/avatar/male03.png new file mode 100644 index 0000000..fcb4388 Binary files /dev/null and b/frontend/public/images/falukant/avatar/male03.png differ diff --git a/frontend/public/images/icons/falukant/bank.jpg b/frontend/public/images/icons/falukant/bank.jpg new file mode 100644 index 0000000..c96ff97 Binary files /dev/null and b/frontend/public/images/icons/falukant/bank.jpg differ diff --git a/frontend/public/images/icons/falukant/darknet.jpg b/frontend/public/images/icons/falukant/darknet.jpg new file mode 100644 index 0000000..7d6101b Binary files /dev/null and b/frontend/public/images/icons/falukant/darknet.jpg differ diff --git a/frontend/public/images/icons/falukant/directors.jpg b/frontend/public/images/icons/falukant/directors.jpg new file mode 100644 index 0000000..921fef9 Binary files /dev/null and b/frontend/public/images/icons/falukant/directors.jpg differ diff --git a/frontend/public/images/icons/falukant/education.jpg b/frontend/public/images/icons/falukant/education.jpg new file mode 100644 index 0000000..e327e6d Binary files /dev/null and b/frontend/public/images/icons/falukant/education.jpg differ diff --git a/frontend/public/images/icons/falukant/family.jpg b/frontend/public/images/icons/falukant/family.jpg new file mode 100644 index 0000000..2a23df6 Binary files /dev/null and b/frontend/public/images/icons/falukant/family.jpg differ diff --git a/frontend/public/images/icons/falukant/health.jpg b/frontend/public/images/icons/falukant/health.jpg new file mode 100644 index 0000000..c610138 Binary files /dev/null and b/frontend/public/images/icons/falukant/health.jpg differ diff --git a/frontend/public/images/icons/falukant/house.jpg b/frontend/public/images/icons/falukant/house.jpg new file mode 100644 index 0000000..49fb244 Binary files /dev/null and b/frontend/public/images/icons/falukant/house.jpg differ diff --git a/frontend/public/images/icons/falukant/moneyhistory.jpg b/frontend/public/images/icons/falukant/moneyhistory.jpg new file mode 100644 index 0000000..899d0b4 Binary files /dev/null and b/frontend/public/images/icons/falukant/moneyhistory.jpg differ diff --git a/frontend/public/images/icons/falukant/nobility.jpg b/frontend/public/images/icons/falukant/nobility.jpg new file mode 100644 index 0000000..b394e79 Binary files /dev/null and b/frontend/public/images/icons/falukant/nobility.jpg differ diff --git a/frontend/public/images/icons/falukant/overview.jpg b/frontend/public/images/icons/falukant/overview.jpg new file mode 100644 index 0000000..449cf45 Binary files /dev/null and b/frontend/public/images/icons/falukant/overview.jpg differ diff --git a/frontend/public/images/icons/falukant/politics.jpg b/frontend/public/images/icons/falukant/politics.jpg new file mode 100644 index 0000000..e4cd49b Binary files /dev/null and b/frontend/public/images/icons/falukant/politics.jpg differ diff --git a/frontend/public/images/icons/falukant/product-carrot.png b/frontend/public/images/icons/falukant/product-carrot.png new file mode 100644 index 0000000..c7a8957 Binary files /dev/null and b/frontend/public/images/icons/falukant/product-carrot.png differ diff --git a/frontend/public/images/icons/falukant/product-fish.png b/frontend/public/images/icons/falukant/product-fish.png new file mode 100644 index 0000000..f1daf2c Binary files /dev/null and b/frontend/public/images/icons/falukant/product-fish.png differ diff --git a/frontend/public/images/icons/falukant/product-grain.png b/frontend/public/images/icons/falukant/product-grain.png new file mode 100644 index 0000000..58dd0d2 Binary files /dev/null and b/frontend/public/images/icons/falukant/product-grain.png differ diff --git a/frontend/public/images/icons/falukant/product-leather.png b/frontend/public/images/icons/falukant/product-leather.png new file mode 100644 index 0000000..53646ee Binary files /dev/null and b/frontend/public/images/icons/falukant/product-leather.png differ diff --git a/frontend/public/images/icons/falukant/product-meat.png b/frontend/public/images/icons/falukant/product-meat.png new file mode 100644 index 0000000..26beef0 Binary files /dev/null and b/frontend/public/images/icons/falukant/product-meat.png differ diff --git a/frontend/public/images/icons/falukant/product-wheat.png b/frontend/public/images/icons/falukant/product-wheat.png new file mode 100644 index 0000000..73a03b2 Binary files /dev/null and b/frontend/public/images/icons/falukant/product-wheat.png differ diff --git a/frontend/public/images/icons/falukant/reputation.jpg b/frontend/public/images/icons/falukant/reputation.jpg new file mode 100644 index 0000000..1e6fe5f Binary files /dev/null and b/frontend/public/images/icons/falukant/reputation.jpg differ diff --git a/frontend/public/images/icons/falukant/storage-field.png b/frontend/public/images/icons/falukant/storage-field.png new file mode 100644 index 0000000..07e209c Binary files /dev/null and b/frontend/public/images/icons/falukant/storage-field.png differ diff --git a/frontend/public/images/icons/falukant/storage-iron.png b/frontend/public/images/icons/falukant/storage-iron.png new file mode 100644 index 0000000..d7ed9ae Binary files /dev/null and b/frontend/public/images/icons/falukant/storage-iron.png differ diff --git a/frontend/public/images/icons/falukant/storage-stone.png b/frontend/public/images/icons/falukant/storage-stone.png new file mode 100644 index 0000000..2e3f219 Binary files /dev/null and b/frontend/public/images/icons/falukant/storage-stone.png differ diff --git a/frontend/public/images/icons/falukant/storage-wood.png b/frontend/public/images/icons/falukant/storage-wood.png new file mode 100644 index 0000000..26df13d Binary files /dev/null and b/frontend/public/images/icons/falukant/storage-wood.png differ diff --git a/frontend/public/images/icons/falukant/towns.jpg b/frontend/public/images/icons/falukant/towns.jpg new file mode 100644 index 0000000..199a58e Binary files /dev/null and b/frontend/public/images/icons/falukant/towns.jpg differ diff --git a/frontend/src/components/AppFooter.vue b/frontend/src/components/AppFooter.vue index 75b6edd..399e0c4 100644 --- a/frontend/src/components/AppFooter.vue +++ b/frontend/src/components/AppFooter.vue @@ -1,6 +1,6 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/falukant/DirectorInfo.vue b/frontend/src/components/falukant/DirectorInfo.vue new file mode 100644 index 0000000..98cf922 --- /dev/null +++ b/frontend/src/components/falukant/DirectorInfo.vue @@ -0,0 +1,120 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/components/falukant/ProductionSection.vue b/frontend/src/components/falukant/ProductionSection.vue new file mode 100644 index 0000000..48e4c5e --- /dev/null +++ b/frontend/src/components/falukant/ProductionSection.vue @@ -0,0 +1,182 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/components/falukant/RevenueSection.vue b/frontend/src/components/falukant/RevenueSection.vue new file mode 100644 index 0000000..6ce6466 --- /dev/null +++ b/frontend/src/components/falukant/RevenueSection.vue @@ -0,0 +1,98 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/components/falukant/SaleSection.vue b/frontend/src/components/falukant/SaleSection.vue new file mode 100644 index 0000000..3cff4b4 --- /dev/null +++ b/frontend/src/components/falukant/SaleSection.vue @@ -0,0 +1,101 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/components/falukant/StatusBar.vue b/frontend/src/components/falukant/StatusBar.vue index 0b91699..cb59efb 100644 --- a/frontend/src/components/falukant/StatusBar.vue +++ b/frontend/src/components/falukant/StatusBar.vue @@ -5,11 +5,16 @@ {{ item.icon }}: {{ item.value }} + + + @@ -92,4 +122,11 @@ export default { .status-icon { font-size: 14px; } + +.menu-icon { + width: 30px; + height: 30px; + cursor: pointer; + padding: 4px 2px 0 0; +} diff --git a/frontend/src/components/falukant/StorageSection.vue b/frontend/src/components/falukant/StorageSection.vue new file mode 100644 index 0000000..f412007 --- /dev/null +++ b/frontend/src/components/falukant/StorageSection.vue @@ -0,0 +1,220 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/i18n/locales/de/admin.json b/frontend/src/i18n/locales/de/admin.json index 636df4c..86a339f 100644 --- a/frontend/src/i18n/locales/de/admin.json +++ b/frontend/src/i18n/locales/de/admin.json @@ -42,6 +42,12 @@ "selectPermissions": "Bitte auswählen", "confirmDeleteMessage": "Soll das Forum wirklich gelöscht werden?", "confirmDeleteTitle": "Forum löschen" + }, + "falukant": { + "edituser": { + "success": "Die Änderungen wurden gespeichert.", + "error": "Die Änderungen konnten nicht gespeichert werden." + } } } } \ No newline at end of file diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index 8d553f1..7a64ef9 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -30,7 +30,8 @@ "name": "Name", "money": "Vermögen", "age": "Alter", - "mainbranch": "Heimatstadt" + "mainbranch": "Heimatstadt", + "nobleTitle": "Stand" }, "productions": { "title": "Produktionen" @@ -194,6 +195,55 @@ "field": "Feldlager" } }, + "family": { + "title": "Familie", + "spouse": { + "title": "Beziehung", + "name": "Name", + "age": "Alter", + "status": "Status", + "none": "Kein Ehepartner vorhanden.", + "search": "Ehepartner suchen", + "found": "Ehepartner gefunden", + "select": "Verloben mit", + "marriagecost": "Verlobungskosten", + "notice": "Hinweis. Die beiden Ehepartner bekommen beide den Titel, der höher ist.", + "accept": "Werbung mit diesem Partner starten", + "wooing": { + "gifts": "Werbegeschenke", + "sendGift": "Werbegeschenk senden" + } + }, + "relationships": { + "name": "Name" + }, + "children": { + "title": "Kinder", + "name": "Name", + "age": "Alter", + "actions": "Aktionen", + "none": "Keine Kinder vorhanden.", + "detailButton": "Details anzeigen", + "addChild": "Kind hinzufügen" + }, + "lovers": { + "title": "Liebhaber", + "none": "Keine Liebhaber vorhanden.", + "affection": "Zuneigung" + }, + "statuses": { + "wooing": "In Werbung", + "engaged": "Verlobt", + "married": "Verheiratet", + "single": "Ledig", + "widowed": "Verwitwet" + }, + "actions": { + "addSpouse": "Ehepartner hinzufügen", + "viewDetails": "Details anzeigen", + "remove": "Entfernen" + } + }, "product": { "wheat": "Weizen", "grain": "Getreide", @@ -262,6 +312,21 @@ "low": "Schlecht", "verylow": "Sehr schlecht", "none": "Kein Wissen" + }, + "gifts": { + "Gold Coin": "Goldmünze", + "Silk Scarf": "Seidenschale", + "Exotic Perfume": "Exotisches Parfum", + "Crystal Pendant": "Kristallanhänger", + "Leather Journal": "Lederjournal", + "Fine Wine": "Feiner Wein", + "Artisan Chocolate": "Kunsthandwerkliche Schokolade", + "Pearl Necklace": "Perlenanhänger", + "Rare Painting": "Seltenes Gemälde", + "Silver Watch": "Silberuhr", + "Cat": "Katze", + "Dog": "Hund", + "Horse": "Pferd" } } } \ No newline at end of file diff --git a/frontend/src/i18n/locales/de/navigation.json b/frontend/src/i18n/locales/de/navigation.json index 1a54d5d..d5a7a77 100644 --- a/frontend/src/i18n/locales/de/navigation.json +++ b/frontend/src/i18n/locales/de/navigation.json @@ -38,7 +38,12 @@ "forum": "Forum", "userrights": "Benutzerrechte", "interests": "Interessen", - "falukant": "Falukant" + "falukant": "Falukant", + "m-falukant": { + "logentries": "Log-Einträge", + "edituser": "Benutzer bearbeiten", + "database": "Datenbank" + } }, "m-friends": { "manageFriends": "Freunde verwalten", diff --git a/frontend/src/router/adminRoutes.js b/frontend/src/router/adminRoutes.js index eae4dbc..e884a9d 100644 --- a/frontend/src/router/adminRoutes.js +++ b/frontend/src/router/adminRoutes.js @@ -1,6 +1,7 @@ import AdminInterestsView from '../views/admin/InterestsView.vue'; import AdminContactsView from '../views/admin/ContactsView.vue'; import ForumAdminView from '../dialogues/admin/ForumAdminView.vue'; +import AdminFalukantEditUserView from '../views/admin/falukant/EditUserView.vue' const adminRoutes = [ { @@ -21,6 +22,12 @@ const adminRoutes = [ component: ForumAdminView, meta: { requiresAuth: true } }, + { + path: '/admin/falukant/edituser', + name: 'AdminFalukantEditUserView', + component: AdminFalukantEditUserView, + meta: { requiresAuth: true } + } ]; export default adminRoutes; diff --git a/frontend/src/router/falukantRoutes.js b/frontend/src/router/falukantRoutes.js index f620398..dfd8c71 100644 --- a/frontend/src/router/falukantRoutes.js +++ b/frontend/src/router/falukantRoutes.js @@ -2,6 +2,7 @@ import BranchView from '../views/falukant/BranchView.vue'; import Createview from '../views/falukant/CreateView.vue'; import FalukantOverviewView from '../views/falukant/OverviewView.vue'; import MoneyHistoryView from '../views/falukant/MoneyHistoryView.vue'; +import FamilyView from '../views/falukant/FamilyView.vue'; const falukantRoutes = [ { @@ -28,6 +29,12 @@ const falukantRoutes = [ component: MoneyHistoryView, meta: { requiresAuth: true }, }, + { + path: '/falukant/family', + name: 'FalukantFamily', + component: FamilyView, + meta: { requiresAuth: true } + }, ]; export default falukantRoutes; diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 3cfb346..d6dacca 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -159,9 +159,9 @@ const store = createStore({ setTimeout(() => { console.log('Retrying Daemon WebSocket connection...'); reconnectFn(); - }, 1000); // Retry every second + }, 1000); + console.log('Retrying Daemon WebSocket connection...'); }; - connectDaemonSocket(); } }, diff --git a/frontend/src/views/admin/falukant/EditUserView.vue b/frontend/src/views/admin/falukant/EditUserView.vue new file mode 100644 index 0000000..2210a88 --- /dev/null +++ b/frontend/src/views/admin/falukant/EditUserView.vue @@ -0,0 +1,116 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/falukant/BranchView.vue b/frontend/src/views/falukant/BranchView.vue index 3bc0c31..0c8c288 100644 --- a/frontend/src/views/falukant/BranchView.vue +++ b/frontend/src/views/falukant/BranchView.vue @@ -1,476 +1,110 @@ + \ No newline at end of file diff --git a/frontend/src/views/falukant/FamilyView.vue b/frontend/src/views/falukant/FamilyView.vue new file mode 100644 index 0000000..06d4161 --- /dev/null +++ b/frontend/src/views/falukant/FamilyView.vue @@ -0,0 +1,287 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/falukant/MoneyHistoryView.vue b/frontend/src/views/falukant/MoneyHistoryView.vue index 8684826..fc43292 100644 --- a/frontend/src/views/falukant/MoneyHistoryView.vue +++ b/frontend/src/views/falukant/MoneyHistoryView.vue @@ -1,106 +1,118 @@ - - - - - \ No newline at end of file + }, +}; + + + \ No newline at end of file diff --git a/frontend/src/views/falukant/OverviewView.vue b/frontend/src/views/falukant/OverviewView.vue index 659a61c..736c95e 100644 --- a/frontend/src/views/falukant/OverviewView.vue +++ b/frontend/src/views/falukant/OverviewView.vue @@ -11,9 +11,17 @@ {{ falukantUser?.character.definedFirstName.name }} {{ falukantUser?.character.definedLastName.name }} + + {{ $t('falukant.overview.metadata.nobleTitle') }} + {{ $t('falukant.titles.' + falukantUser?.character.gender + '.' + falukantUser?.character.nobleTitle.labelTr) }} + {{ $t('falukant.overview.metadata.money') }} - {{ falukantUser?.money }} + + {{ moneyValue != null + ? moneyValue.toLocaleString(locale, { style: 'currency', currency: 'EUR' }) + : '---' }} + {{ $t('falukant.overview.metadata.age') }} @@ -150,7 +158,7 @@ export default { const ageGroup = this.getAgeGroup(age); const genderData = AVATAR_POSITIONS[gender] || {}; const position = genderData.positions?.[ageGroup] || { x: 0, y: 0 }; - const width = genderData.width || 100; + const width = genderData.width || 100; const height = genderData.height || 100; return { backgroundImage: `url(${imageUrl})`, @@ -160,6 +168,13 @@ export default { height: `${height}px`, }; }, + moneyValue() { + const m = this.falukantUser?.money; + return typeof m === 'string' ? parseFloat(m) : m; + }, + locale() { + return window.navigator.language || 'en-US'; + }, }, async mounted() { await this.fetchFalukantUser(); @@ -171,7 +186,6 @@ export default { } if (this.daemonSocket) { this.daemonSocket.addEventListener('message', (event) => { - console.log('incoming event', event); try { if (event.data === "ping") return; const message = JSON.parse(event.data); @@ -182,6 +196,8 @@ export default { console.error('Error processing WebSocket message in FalukantOverviewView:', error); } }); + } else { + console.log('no daemon socket'); } }, beforeUnmount() { @@ -281,4 +297,8 @@ export default { background-size: cover; image-rendering: crisp-edges; } + +h2 { + padding-top: 20px; +} diff --git a/outputs/generated/000000_985862166_ddim50_PS7.5_13_years_old_red_haired_girl_in_puberty_with_violet_eyes_and_a_lot_of_freckles_on_face_and_chest_stays_on_beach_of_a_lake_and_wash_[generated].jpg b/outputs/generated/000000_985862166_ddim50_PS7.5_13_years_old_red_haired_girl_in_puberty_with_violet_eyes_and_a_lot_of_freckles_on_face_and_chest_stays_on_beach_of_a_lake_and_wash_[generated].jpg new file mode 100644 index 0000000..3079013 Binary files /dev/null and b/outputs/generated/000000_985862166_ddim50_PS7.5_13_years_old_red_haired_girl_in_puberty_with_violet_eyes_and_a_lot_of_freckles_on_face_and_chest_stays_on_beach_of_a_lake_and_wash_[generated].jpg differ