diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index b6ddb7f..923912e 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -126,6 +126,8 @@ class FalukantController { this.getTitlesOfNobility = this._wrapWithUser((userId) => this.service.getTitlesOfNobility(userId)); this.getReputationActions = this._wrapWithUser((userId) => this.service.getReputationActions(userId)); + this.executeReputationAction = this._wrapWithUser((userId, req) => + this.service.executeReputationAction(userId, req.body?.actionTypeId), { successStatus: 201 }); this.getHouseTypes = this._wrapWithUser((userId) => this.service.getHouseTypes(userId)); this.getMoodAffect = this._wrapWithUser((userId) => this.service.getMoodAffect(userId)); this.getCharacterAffect = this._wrapWithUser((userId) => this.service.getCharacterAffect(userId)); diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index a85f309..8b96c68 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -48,6 +48,7 @@ router.post('/family/gift', falukantController.sendGift); router.get('/family', falukantController.getFamily); router.get('/nobility/titels', falukantController.getTitlesOfNobility); router.get('/reputation/actions', falukantController.getReputationActions); +router.post('/reputation/actions', falukantController.executeReputationAction); router.get('/houses/types', falukantController.getHouseTypes); router.get('/houses/buyable', falukantController.getBuyableHouses); router.get('/houses', falukantController.getUserHouse); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 3d5fc70..74041c8 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -3285,6 +3285,101 @@ class FalukantService extends BaseService { }; } + async executeReputationAction(hashedUserId, actionTypeId) { + const user = await getFalukantUserOrFail(hashedUserId); + const character = await FalukantCharacter.findOne({ + where: { userId: user.id }, + attributes: ['id', 'reputation'] + }); + if (!character) { + throw new Error('Character not found'); + } + + const actionType = await ReputationActionType.findByPk(actionTypeId); + if (!actionType) { + throw new Error('Action type not found'); + } + + const todayStart = new Date(); + todayStart.setHours(0, 0, 0, 0); + const todayEnd = new Date(); + todayEnd.setHours(23, 59, 59, 999); + const dailyCap = 10; + const todayActions = await ReputationActionLog.count({ + where: { + falukantUserId: user.id, + actionTimestamp: { [Op.between]: [todayStart, todayEnd] } + } + }); + if (todayActions >= dailyCap) { + throw new Error('Daily limit reached'); + } + + const lastAction = await ReputationActionLog.findOne({ + where: { falukantUserId: user.id }, + order: [['actionTimestamp', 'DESC']], + attributes: ['actionTimestamp'] + }); + const cooldownMinutes = 60; + if (lastAction?.actionTimestamp) { + const cooldownSec = cooldownMinutes * 60; + const secondsSinceLast = Math.floor((Date.now() - new Date(lastAction.actionTimestamp)) / 1000); + if (secondsSinceLast < cooldownSec) { + throw new Error('Cooldown active'); + } + } + + const reloadedUser = await FalukantUser.findByPk(user.id, { attributes: ['id', 'money'] }); + if (reloadedUser.money < actionType.cost) { + throw new Error('notenoughmoney'); + } + + const windowStart = new Date(); + windowStart.setDate(windowStart.getDate() - actionType.decayWindowDays); + const timesUsedBefore = await ReputationActionLog.count({ + where: { + falukantUserId: user.id, + actionTypeId: actionType.id, + actionTimestamp: { [Op.gte]: windowStart } + } + }); + + let gain = actionType.baseGain; + for (let i = 0; i < timesUsedBefore; i++) { + gain = Math.max(actionType.minGain, Math.floor(gain * actionType.decayFactor)); + } + + const transaction = await sequelize.transaction(); + try { + await updateFalukantUserMoney( + user.id, + -actionType.cost, + 'Reputation action: ' + actionType.tr, + user.id, + transaction + ); + await ReputationActionLog.create( + { + falukantUserId: user.id, + actionTypeId: actionType.id, + cost: actionType.cost, + baseGain: actionType.baseGain, + gain, + timesUsedBefore + }, + { transaction } + ); + const newReputation = Math.min(100, Math.max(0, (character.reputation ?? 0) + gain)); + await character.update({ reputation: newReputation }, { transaction }); + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + + return { gain, cost: actionType.cost }; + } + async getHouseTypes() { // return House }