feat(admin): implement pregnancy and birth management features
Some checks failed
Deploy to production / deploy (push) Failing after 2m6s
Some checks failed
Deploy to production / deploy (push) Failing after 2m6s
- Added new admin functionalities to force pregnancy, clear pregnancy, and trigger birth for characters. - Introduced corresponding routes and controller methods in adminRouter and adminController. - Enhanced the FalukantCharacter model to include pregnancy-related fields. - Created database migration for adding pregnancy columns to the character table. - Updated frontend views and internationalization files to support new pregnancy and birth management features. - Improved user feedback and error handling for these new actions.
This commit is contained in:
@@ -13,6 +13,9 @@ class AdminController {
|
||||
this.searchUser = this.searchUser.bind(this);
|
||||
this.getFalukantUserById = this.getFalukantUserById.bind(this);
|
||||
this.changeFalukantUser = this.changeFalukantUser.bind(this);
|
||||
this.adminForceFalukantPregnancy = this.adminForceFalukantPregnancy.bind(this);
|
||||
this.adminClearFalukantPregnancy = this.adminClearFalukantPregnancy.bind(this);
|
||||
this.adminForceFalukantBirth = this.adminForceFalukantBirth.bind(this);
|
||||
this.getFalukantUserBranches = this.getFalukantUserBranches.bind(this);
|
||||
this.updateFalukantStock = this.updateFalukantStock.bind(this);
|
||||
this.addFalukantStock = this.addFalukantStock.bind(this);
|
||||
@@ -372,6 +375,50 @@ class AdminController {
|
||||
}
|
||||
}
|
||||
|
||||
async adminForceFalukantPregnancy(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { characterId, fatherCharacterId, dueInDays } = req.body;
|
||||
const response = await AdminService.adminForceFalukantPregnancy(userId, characterId, {
|
||||
fatherCharacterId,
|
||||
dueInDays,
|
||||
});
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async adminClearFalukantPregnancy(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { characterId } = req.body;
|
||||
const response = await AdminService.adminClearFalukantPregnancy(userId, characterId);
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async adminForceFalukantBirth(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { motherCharacterId, fatherCharacterId, birthContext, legitimacy, gender } = req.body;
|
||||
const response = await AdminService.adminForceFalukantBirth(userId, motherCharacterId, {
|
||||
fatherCharacterId,
|
||||
birthContext,
|
||||
legitimacy,
|
||||
gender,
|
||||
});
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getFalukantUserBranches(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
|
||||
/** Schwangerschaft (Admin / Spiel): erwarteter Geburtstermin + optionaler Vater-Charakter */
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'falukant_data' AND table_name = 'character' AND column_name = 'pregnancy_due_at'
|
||||
) THEN
|
||||
ALTER TABLE falukant_data."character"
|
||||
ADD COLUMN pregnancy_due_at TIMESTAMPTZ NULL;
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'falukant_data' AND table_name = 'character' AND column_name = 'pregnancy_father_character_id'
|
||||
) THEN
|
||||
ALTER TABLE falukant_data."character"
|
||||
ADD COLUMN pregnancy_father_character_id INTEGER NULL
|
||||
REFERENCES falukant_data."character"(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END$$;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data."character" DROP COLUMN IF EXISTS pregnancy_father_character_id;
|
||||
`);
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TABLE falukant_data."character" DROP COLUMN IF EXISTS pregnancy_due_at;
|
||||
`);
|
||||
},
|
||||
};
|
||||
@@ -379,6 +379,8 @@ export default function setupAssociations() {
|
||||
FalukantCharacter.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
|
||||
RegionData.hasMany(FalukantCharacter, { foreignKey: 'regionId', as: 'charactersInRegion' });
|
||||
|
||||
FalukantCharacter.belongsTo(FalukantCharacter, { foreignKey: 'pregnancyFatherCharacterId', as: 'pregnancyFather' });
|
||||
|
||||
FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' });
|
||||
FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' });
|
||||
|
||||
|
||||
@@ -45,6 +45,14 @@ FalukantCharacter.init(
|
||||
min: 0,
|
||||
max: 100
|
||||
}
|
||||
},
|
||||
pregnancyDueAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
pregnancyFatherCharacterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -43,6 +43,9 @@ 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);
|
||||
router.post('/falukant/character/force-pregnancy', authenticate, adminController.adminForceFalukantPregnancy);
|
||||
router.post('/falukant/character/clear-pregnancy', authenticate, adminController.adminClearFalukantPregnancy);
|
||||
router.post('/falukant/character/force-birth', authenticate, adminController.adminForceFalukantBirth);
|
||||
router.get('/falukant/branches/:falukantUserId', authenticate, adminController.getFalukantUserBranches);
|
||||
router.put('/falukant/stock/:stockId', authenticate, adminController.updateFalukantStock);
|
||||
router.post('/falukant/stock', authenticate, adminController.addFalukantStock);
|
||||
|
||||
@@ -28,6 +28,7 @@ import Image from '../models/community/image.js';
|
||||
import EroticVideo from '../models/community/erotic_video.js';
|
||||
import EroticContentReport from '../models/community/erotic_content_report.js';
|
||||
import TitleOfNobility from "../models/falukant/type/title_of_nobility.js";
|
||||
import ChildRelation from "../models/falukant/data/child_relation.js";
|
||||
import { sequelize } from '../utils/sequelize.js';
|
||||
import npcCreationJobService from './npcCreationJobService.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -669,7 +670,6 @@ class AdminService {
|
||||
include: [{
|
||||
model: FalukantCharacter,
|
||||
as: 'character',
|
||||
attributes: ['birthdate', 'health', 'title_of_nobility'],
|
||||
include: [{
|
||||
model: FalukantPredefineFirstname,
|
||||
as: 'definedFirstName',
|
||||
@@ -945,6 +945,145 @@ class AdminService {
|
||||
await character.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin: Charakter als schwanger markieren (erwarteter Geburtstermin).
|
||||
* @param {number} fatherCharacterId - optional; Vater-Charakter-ID
|
||||
* @param {number} dueInDays - Tage bis zur „Geburt“ (Default 21)
|
||||
*/
|
||||
async adminForceFalukantPregnancy(userId, characterId, { fatherCharacterId = null, dueInDays = 21 } = {}) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const mother = await FalukantCharacter.findByPk(characterId);
|
||||
if (!mother) throw new Error('notfound');
|
||||
if (fatherCharacterId != null) {
|
||||
const father = await FalukantCharacter.findByPk(Number(fatherCharacterId));
|
||||
if (!father) throw new Error('fatherNotFound');
|
||||
}
|
||||
const days = Math.max(1, Math.min(365, Number(dueInDays) || 21));
|
||||
const due = new Date();
|
||||
due.setDate(due.getDate() + days);
|
||||
await mother.update({
|
||||
pregnancyDueAt: due,
|
||||
pregnancyFatherCharacterId: fatherCharacterId != null ? Number(fatherCharacterId) : null,
|
||||
});
|
||||
const fu = mother.userId ? await FalukantUser.findByPk(mother.userId) : null;
|
||||
if (fu) {
|
||||
const u = await User.findByPk(fu.userId);
|
||||
if (u?.hashedId) await notifyUser(u.hashedId, 'familychanged', {});
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
pregnancyDueAt: due.toISOString(),
|
||||
pregnancyFatherCharacterId: fatherCharacterId != null ? Number(fatherCharacterId) : null,
|
||||
};
|
||||
}
|
||||
|
||||
async adminClearFalukantPregnancy(userId, characterId) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const mother = await FalukantCharacter.findByPk(characterId);
|
||||
if (!mother) throw new Error('notfound');
|
||||
await mother.update({
|
||||
pregnancyDueAt: null,
|
||||
pregnancyFatherCharacterId: null,
|
||||
});
|
||||
const fu = mother.userId ? await FalukantUser.findByPk(mother.userId) : null;
|
||||
if (fu) {
|
||||
const u = await User.findByPk(fu.userId);
|
||||
if (u?.hashedId) await notifyUser(u.hashedId, 'familychanged', {});
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin: Geburt auslösen – Kind-Charakter + child_relation; setzt Schwangerschaft zurück.
|
||||
*/
|
||||
async adminForceFalukantBirth(userId, motherCharacterId, {
|
||||
fatherCharacterId,
|
||||
birthContext = 'marriage',
|
||||
legitimacy = 'legitimate',
|
||||
gender = null,
|
||||
} = {}) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
if (fatherCharacterId == null || fatherCharacterId === '') {
|
||||
throw new Error('fatherRequired');
|
||||
}
|
||||
const mother = await FalukantCharacter.findByPk(motherCharacterId);
|
||||
if (!mother) throw new Error('notfound');
|
||||
const father = await FalukantCharacter.findByPk(Number(fatherCharacterId));
|
||||
if (!father) throw new Error('fatherNotFound');
|
||||
if (Number(fatherCharacterId) === Number(motherCharacterId)) {
|
||||
throw new Error('invalidParents');
|
||||
}
|
||||
const ctx = ['marriage', 'lover'].includes(birthContext) ? birthContext : 'marriage';
|
||||
const leg = ['legitimate', 'acknowledged_bastard', 'hidden_bastard'].includes(legitimacy)
|
||||
? legitimacy
|
||||
: 'legitimate';
|
||||
const childGender = gender === 'male' || gender === 'female'
|
||||
? gender
|
||||
: (Math.random() < 0.5 ? 'male' : 'female');
|
||||
|
||||
const nobility = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } });
|
||||
if (!nobility) throw new Error('titleNotFound');
|
||||
|
||||
const fnObj = await FalukantPredefineFirstname.findOne({
|
||||
where: { gender: childGender },
|
||||
order: Sequelize.fn('RANDOM'),
|
||||
});
|
||||
if (!fnObj) throw new Error('firstNameNotFound');
|
||||
|
||||
let childCharacterId;
|
||||
await sequelize.transaction(async (t) => {
|
||||
const baby = await FalukantCharacter.create({
|
||||
userId: null,
|
||||
regionId: mother.regionId,
|
||||
firstName: fnObj.id,
|
||||
lastName: mother.lastName,
|
||||
gender: childGender,
|
||||
birthdate: new Date(),
|
||||
titleOfNobility: nobility.id,
|
||||
health: 100,
|
||||
moodId: 1,
|
||||
}, { transaction: t });
|
||||
childCharacterId = baby.id;
|
||||
|
||||
await ChildRelation.create({
|
||||
fatherCharacterId: father.id,
|
||||
motherCharacterId: mother.id,
|
||||
childCharacterId: baby.id,
|
||||
nameSet: false,
|
||||
isHeir: false,
|
||||
legitimacy: leg,
|
||||
birthContext: ctx,
|
||||
publicKnown: ctx === 'marriage',
|
||||
}, { transaction: t });
|
||||
|
||||
await mother.update({
|
||||
pregnancyDueAt: null,
|
||||
pregnancyFatherCharacterId: null,
|
||||
}, { transaction: t });
|
||||
});
|
||||
|
||||
const notifyParent = async (char) => {
|
||||
if (!char?.userId) return;
|
||||
const fu = await FalukantUser.findByPk(char.userId);
|
||||
if (!fu) return;
|
||||
const u = await User.findByPk(fu.userId);
|
||||
if (u?.hashedId) {
|
||||
await notifyUser(u.hashedId, 'familychanged', {});
|
||||
await notifyUser(u.hashedId, 'falukantUpdateStatus', {});
|
||||
}
|
||||
};
|
||||
await notifyParent(mother);
|
||||
await notifyParent(father);
|
||||
|
||||
return { success: true, childCharacterId, gender: childGender };
|
||||
}
|
||||
|
||||
// --- User Administration ---
|
||||
async searchUsers(requestingHashedUserId, query) {
|
||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
||||
|
||||
@@ -3518,7 +3518,13 @@ class FalukantService extends BaseService {
|
||||
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
|
||||
children: children.map(({ _createdAt, ...rest }) => rest),
|
||||
possiblePartners: [],
|
||||
possibleLovers: []
|
||||
possibleLovers: [],
|
||||
pregnancy: character.pregnancyDueAt
|
||||
? {
|
||||
dueAt: character.pregnancyDueAt,
|
||||
fatherCharacterId: character.pregnancyFatherCharacterId,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
const ownAge = calcAge(character.birthdate);
|
||||
if (ownAge >= 12) {
|
||||
|
||||
6
backend/sql/add_character_pregnancy.sql
Normal file
6
backend/sql/add_character_pregnancy.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- Optional: manuell ausführen, falls Migration nicht genutzt wird
|
||||
ALTER TABLE falukant_data."character"
|
||||
ADD COLUMN IF NOT EXISTS pregnancy_due_at TIMESTAMPTZ NULL;
|
||||
ALTER TABLE falukant_data."character"
|
||||
ADD COLUMN IF NOT EXISTS pregnancy_father_character_id INTEGER NULL
|
||||
REFERENCES falukant_data."character"(id) ON DELETE SET NULL;
|
||||
Reference in New Issue
Block a user