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.searchUser = this.searchUser.bind(this);
|
||||||
this.getFalukantUserById = this.getFalukantUserById.bind(this);
|
this.getFalukantUserById = this.getFalukantUserById.bind(this);
|
||||||
this.changeFalukantUser = this.changeFalukantUser.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.getFalukantUserBranches = this.getFalukantUserBranches.bind(this);
|
||||||
this.updateFalukantStock = this.updateFalukantStock.bind(this);
|
this.updateFalukantStock = this.updateFalukantStock.bind(this);
|
||||||
this.addFalukantStock = this.addFalukantStock.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) {
|
async getFalukantUserBranches(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userid: userId } = req.headers;
|
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' });
|
FalukantCharacter.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
|
||||||
RegionData.hasMany(FalukantCharacter, { foreignKey: 'regionId', as: 'charactersInRegion' });
|
RegionData.hasMany(FalukantCharacter, { foreignKey: 'regionId', as: 'charactersInRegion' });
|
||||||
|
|
||||||
|
FalukantCharacter.belongsTo(FalukantCharacter, { foreignKey: 'pregnancyFatherCharacterId', as: 'pregnancyFather' });
|
||||||
|
|
||||||
FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' });
|
FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' });
|
||||||
FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' });
|
FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' });
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ FalukantCharacter.init(
|
|||||||
min: 0,
|
min: 0,
|
||||||
max: 100
|
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.post('/falukant/searchuser', authenticate, adminController.searchUser);
|
||||||
router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById);
|
router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById);
|
||||||
router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser);
|
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.get('/falukant/branches/:falukantUserId', authenticate, adminController.getFalukantUserBranches);
|
||||||
router.put('/falukant/stock/:stockId', authenticate, adminController.updateFalukantStock);
|
router.put('/falukant/stock/:stockId', authenticate, adminController.updateFalukantStock);
|
||||||
router.post('/falukant/stock', authenticate, adminController.addFalukantStock);
|
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 EroticVideo from '../models/community/erotic_video.js';
|
||||||
import EroticContentReport from '../models/community/erotic_content_report.js';
|
import EroticContentReport from '../models/community/erotic_content_report.js';
|
||||||
import TitleOfNobility from "../models/falukant/type/title_of_nobility.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 { sequelize } from '../utils/sequelize.js';
|
||||||
import npcCreationJobService from './npcCreationJobService.js';
|
import npcCreationJobService from './npcCreationJobService.js';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
@@ -669,7 +670,6 @@ class AdminService {
|
|||||||
include: [{
|
include: [{
|
||||||
model: FalukantCharacter,
|
model: FalukantCharacter,
|
||||||
as: 'character',
|
as: 'character',
|
||||||
attributes: ['birthdate', 'health', 'title_of_nobility'],
|
|
||||||
include: [{
|
include: [{
|
||||||
model: FalukantPredefineFirstname,
|
model: FalukantPredefineFirstname,
|
||||||
as: 'definedFirstName',
|
as: 'definedFirstName',
|
||||||
@@ -945,6 +945,145 @@ class AdminService {
|
|||||||
await character.save();
|
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 ---
|
// --- User Administration ---
|
||||||
async searchUsers(requestingHashedUserId, query) {
|
async searchUsers(requestingHashedUserId, query) {
|
||||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
||||||
|
|||||||
@@ -3518,7 +3518,13 @@ class FalukantService extends BaseService {
|
|||||||
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
|
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
|
||||||
children: children.map(({ _createdAt, ...rest }) => rest),
|
children: children.map(({ _createdAt, ...rest }) => rest),
|
||||||
possiblePartners: [],
|
possiblePartners: [],
|
||||||
possibleLovers: []
|
possibleLovers: [],
|
||||||
|
pregnancy: character.pregnancyDueAt
|
||||||
|
? {
|
||||||
|
dueAt: character.pregnancyDueAt,
|
||||||
|
fatherCharacterId: character.pregnancyFatherCharacterId,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
};
|
};
|
||||||
const ownAge = calcAge(character.birthdate);
|
const ownAge = calcAge(character.birthdate);
|
||||||
if (ownAge >= 12) {
|
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;
|
||||||
@@ -168,7 +168,40 @@
|
|||||||
"errorLoadingStockTypes": "Fehler beim Laden der Lagertypen.",
|
"errorLoadingStockTypes": "Fehler beim Laden der Lagertypen.",
|
||||||
"errorAddingStock": "Fehler beim Hinzufügen des Lagers.",
|
"errorAddingStock": "Fehler beim Hinzufügen des Lagers.",
|
||||||
"stockAdded": "Lager erfolgreich hinzugefügt.",
|
"stockAdded": "Lager erfolgreich hinzugefügt.",
|
||||||
"invalidStockData": "Bitte gültige Lagertyp- und Mengenangaben eingeben."
|
"invalidStockData": "Bitte gültige Lagertyp- und Mengenangaben eingeben.",
|
||||||
|
"pregnancy": {
|
||||||
|
"title": "Schwangerschaft (Admin)",
|
||||||
|
"characterId": "Charakter-ID",
|
||||||
|
"status": "Status",
|
||||||
|
"statusActive": "Schwanger bis",
|
||||||
|
"statusNone": "Nicht schwanger",
|
||||||
|
"fatherId": "Vater-Charakter-ID (optional)",
|
||||||
|
"dueDays": "Tage bis zum Termin",
|
||||||
|
"force": "Schwangerschaft setzen",
|
||||||
|
"clear": "Schwangerschaft entfernen",
|
||||||
|
"successForce": "Schwangerschaft wurde gesetzt.",
|
||||||
|
"successClear": "Schwangerschaft wurde entfernt.",
|
||||||
|
"error": "Aktion fehlgeschlagen."
|
||||||
|
},
|
||||||
|
"birth": {
|
||||||
|
"title": "Geburt erzwingen (Admin)",
|
||||||
|
"motherHint": "Es wird der oben genannte Charakter (Mutter) verwendet.",
|
||||||
|
"fatherId": "Vater-Charakter-ID",
|
||||||
|
"context": "Kontext",
|
||||||
|
"contextMarriage": "Ehe",
|
||||||
|
"contextLover": "Liebschaft",
|
||||||
|
"legitimacy": "Legitimität",
|
||||||
|
"legitimate": "Legitim",
|
||||||
|
"ackBastard": "Anerkannt unehelich",
|
||||||
|
"hiddenBastard": "Verborgen unehelich",
|
||||||
|
"gender": "Kind-Geschlecht",
|
||||||
|
"genderRandom": "Zufällig",
|
||||||
|
"male": "Männlich",
|
||||||
|
"female": "Weiblich",
|
||||||
|
"force": "Geburt auslösen",
|
||||||
|
"success": "Kind wurde angelegt (Taufe ausstehend).",
|
||||||
|
"error": "Geburt konnte nicht ausgelöst werden."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"title": "Falukant Karten-Editor (Regionen)",
|
"title": "Falukant Karten-Editor (Regionen)",
|
||||||
|
|||||||
@@ -540,6 +540,10 @@
|
|||||||
"familyWarning": "Anhaltender Kreditverzug belastet Ehe, Haushalt und Liebschaften.",
|
"familyWarning": "Anhaltender Kreditverzug belastet Ehe, Haushalt und Liebschaften.",
|
||||||
"familyImpact": "Der Schuldturm schadet Ehe, Hausfrieden und der Stabilität von Liebschaften."
|
"familyImpact": "Der Schuldturm schadet Ehe, Hausfrieden und der Stabilität von Liebschaften."
|
||||||
},
|
},
|
||||||
|
"pregnancy": {
|
||||||
|
"banner": "Du erwartest ein Kind.",
|
||||||
|
"dueHint": "Voraussichtlicher Geburtstermin"
|
||||||
|
},
|
||||||
"spouse": {
|
"spouse": {
|
||||||
"title": "Beziehung",
|
"title": "Beziehung",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
|||||||
@@ -223,7 +223,40 @@
|
|||||||
"errorLoadingStockTypes": "Error loading warehouse types.",
|
"errorLoadingStockTypes": "Error loading warehouse types.",
|
||||||
"errorAddingStock": "Error adding warehouse.",
|
"errorAddingStock": "Error adding warehouse.",
|
||||||
"stockAdded": "Warehouse successfully added.",
|
"stockAdded": "Warehouse successfully added.",
|
||||||
"invalidStockData": "Please enter valid warehouse type and quantity."
|
"invalidStockData": "Please enter valid warehouse type and quantity.",
|
||||||
|
"pregnancy": {
|
||||||
|
"title": "Pregnancy (admin)",
|
||||||
|
"characterId": "Character ID",
|
||||||
|
"status": "Status",
|
||||||
|
"statusActive": "Expecting until",
|
||||||
|
"statusNone": "Not pregnant",
|
||||||
|
"fatherId": "Father character ID (optional)",
|
||||||
|
"dueDays": "Days until due date",
|
||||||
|
"force": "Set pregnancy",
|
||||||
|
"clear": "Clear pregnancy",
|
||||||
|
"successForce": "Pregnancy has been set.",
|
||||||
|
"successClear": "Pregnancy has been cleared.",
|
||||||
|
"error": "Action failed."
|
||||||
|
},
|
||||||
|
"birth": {
|
||||||
|
"title": "Force birth (admin)",
|
||||||
|
"motherHint": "The character listed above is used as the mother.",
|
||||||
|
"fatherId": "Father character ID",
|
||||||
|
"context": "Context",
|
||||||
|
"contextMarriage": "Marriage",
|
||||||
|
"contextLover": "Affair",
|
||||||
|
"legitimacy": "Legitimacy",
|
||||||
|
"legitimate": "Legitimate",
|
||||||
|
"ackBastard": "Acknowledged bastard",
|
||||||
|
"hiddenBastard": "Hidden bastard",
|
||||||
|
"gender": "Child gender",
|
||||||
|
"genderRandom": "Random",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"force": "Trigger birth",
|
||||||
|
"success": "Child created (baptism pending).",
|
||||||
|
"error": "Could not trigger birth."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"createNPC": {
|
"createNPC": {
|
||||||
"title": "Create NPCs",
|
"title": "Create NPCs",
|
||||||
|
|||||||
@@ -611,6 +611,10 @@
|
|||||||
"familyWarning": "Ongoing debt delinquency puts strain on marriage, household and affairs.",
|
"familyWarning": "Ongoing debt delinquency puts strain on marriage, household and affairs.",
|
||||||
"familyImpact": "Debtors' prison damages marriage, household peace and the stability of affairs."
|
"familyImpact": "Debtors' prison damages marriage, household peace and the stability of affairs."
|
||||||
},
|
},
|
||||||
|
"pregnancy": {
|
||||||
|
"banner": "You are expecting a child.",
|
||||||
|
"dueHint": "Expected due date"
|
||||||
|
},
|
||||||
"children": {
|
"children": {
|
||||||
"title": "Children",
|
"title": "Children",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
|||||||
@@ -168,7 +168,40 @@
|
|||||||
"errorLoadingStockTypes": "Error al cargar los tipos de almacén.",
|
"errorLoadingStockTypes": "Error al cargar los tipos de almacén.",
|
||||||
"errorAddingStock": "Error al añadir el almacén.",
|
"errorAddingStock": "Error al añadir el almacén.",
|
||||||
"stockAdded": "Almacén añadido correctamente.",
|
"stockAdded": "Almacén añadido correctamente.",
|
||||||
"invalidStockData": "Por favor, introduce un tipo de almacén y una cantidad válidos."
|
"invalidStockData": "Por favor, introduce un tipo de almacén y una cantidad válidos.",
|
||||||
|
"pregnancy": {
|
||||||
|
"title": "Embarazo (admin)",
|
||||||
|
"characterId": "ID de personaje",
|
||||||
|
"status": "Estado",
|
||||||
|
"statusActive": "Embarazo hasta",
|
||||||
|
"statusNone": "No embarazada",
|
||||||
|
"fatherId": "ID del padre (opcional)",
|
||||||
|
"dueDays": "Días hasta el parto previsto",
|
||||||
|
"force": "Establecer embarazo",
|
||||||
|
"clear": "Quitar embarazo",
|
||||||
|
"successForce": "Embarazo establecido.",
|
||||||
|
"successClear": "Embarazo eliminado.",
|
||||||
|
"error": "La acción ha fallado."
|
||||||
|
},
|
||||||
|
"birth": {
|
||||||
|
"title": "Forzar nacimiento (admin)",
|
||||||
|
"motherHint": "Se usa el personaje indicado arriba como madre.",
|
||||||
|
"fatherId": "ID del padre",
|
||||||
|
"context": "Contexto",
|
||||||
|
"contextMarriage": "Matrimonio",
|
||||||
|
"contextLover": "Amante",
|
||||||
|
"legitimacy": "Legitimidad",
|
||||||
|
"legitimate": "Legítimo",
|
||||||
|
"ackBastard": "Bastardo reconocido",
|
||||||
|
"hiddenBastard": "Bastardo oculto",
|
||||||
|
"gender": "Sexo del niño",
|
||||||
|
"genderRandom": "Aleatorio",
|
||||||
|
"male": "Masculino",
|
||||||
|
"female": "Femenino",
|
||||||
|
"force": "Provocar nacimiento",
|
||||||
|
"success": "Niño creado (bautizo pendiente).",
|
||||||
|
"error": "No se pudo provocar el nacimiento."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"title": "Editor de mapas de Falukant (regiones)",
|
"title": "Editor de mapas de Falukant (regiones)",
|
||||||
|
|||||||
@@ -521,6 +521,10 @@
|
|||||||
"familyWarning": "La mora continuada perjudica el matrimonio, el hogar y las relaciones.",
|
"familyWarning": "La mora continuada perjudica el matrimonio, el hogar y las relaciones.",
|
||||||
"familyImpact": "La prisión por deudas daña el matrimonio, la paz del hogar y la estabilidad de las relaciones."
|
"familyImpact": "La prisión por deudas daña el matrimonio, la paz del hogar y la estabilidad de las relaciones."
|
||||||
},
|
},
|
||||||
|
"pregnancy": {
|
||||||
|
"banner": "Esperas un hijo.",
|
||||||
|
"dueHint": "Fecha prevista de nacimiento"
|
||||||
|
},
|
||||||
"spouse": {
|
"spouse": {
|
||||||
"title": "Relación",
|
"title": "Relación",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
|
|||||||
@@ -39,6 +39,67 @@
|
|||||||
<option v-for="house in houses" :value="house.id">{{ $t(`falukant.house.type.${house.labelTr}`) }}</option>
|
<option v-for="house in houses" :value="house.id">{{ $t(`falukant.house.type.${house.labelTr}`) }}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<div class="admin-family-tools">
|
||||||
|
<h4>{{ $t('admin.falukant.edituser.pregnancy.title') }}</h4>
|
||||||
|
<p class="admin-family-tools__meta">
|
||||||
|
{{ $t('admin.falukant.edituser.pregnancy.characterId') }}:
|
||||||
|
<code>{{ editableUser.falukantData[0].character.id }}</code>
|
||||||
|
</p>
|
||||||
|
<p class="admin-family-tools__meta">
|
||||||
|
{{ $t('admin.falukant.edituser.pregnancy.status') }}:
|
||||||
|
<template v-if="pregnancyDueDisplay">
|
||||||
|
{{ $t('admin.falukant.edituser.pregnancy.statusActive') }} {{ pregnancyDueDisplay }}
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ $t('admin.falukant.edituser.pregnancy.statusNone') }}</template>
|
||||||
|
</p>
|
||||||
|
<label class="form-field">
|
||||||
|
{{ $t('admin.falukant.edituser.pregnancy.fatherId') }}
|
||||||
|
<input type="number" v-model.number="adminPregnancyFatherId" min="1" placeholder="—" />
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
{{ $t('admin.falukant.edituser.pregnancy.dueDays') }}
|
||||||
|
<input type="number" v-model.number="adminDueInDays" min="1" max="365" />
|
||||||
|
</label>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button type="button" @click="adminForcePregnancy">{{ $t('admin.falukant.edituser.pregnancy.force') }}</button>
|
||||||
|
<button type="button" class="button-secondary" @click="adminClearPregnancy">{{ $t('admin.falukant.edituser.pregnancy.clear') }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{ $t('admin.falukant.edituser.birth.title') }}</h4>
|
||||||
|
<p class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.birth.motherHint') }}</p>
|
||||||
|
<label class="form-field">
|
||||||
|
{{ $t('admin.falukant.edituser.birth.fatherId') }} *
|
||||||
|
<input type="number" v-model.number="adminBirthFatherId" min="1" required />
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
{{ $t('admin.falukant.edituser.birth.context') }}
|
||||||
|
<select v-model="adminBirthContext">
|
||||||
|
<option value="marriage">{{ $t('admin.falukant.edituser.birth.contextMarriage') }}</option>
|
||||||
|
<option value="lover">{{ $t('admin.falukant.edituser.birth.contextLover') }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
{{ $t('admin.falukant.edituser.birth.legitimacy') }}
|
||||||
|
<select v-model="adminBirthLegitimacy">
|
||||||
|
<option value="legitimate">{{ $t('admin.falukant.edituser.birth.legitimate') }}</option>
|
||||||
|
<option value="acknowledged_bastard">{{ $t('admin.falukant.edituser.birth.ackBastard') }}</option>
|
||||||
|
<option value="hidden_bastard">{{ $t('admin.falukant.edituser.birth.hiddenBastard') }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
{{ $t('admin.falukant.edituser.birth.gender') }}
|
||||||
|
<select v-model="adminBirthGender">
|
||||||
|
<option value="">{{ $t('admin.falukant.edituser.birth.genderRandom') }}</option>
|
||||||
|
<option value="male">{{ $t('admin.falukant.edituser.birth.male') }}</option>
|
||||||
|
<option value="female">{{ $t('admin.falukant.edituser.birth.female') }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button type="button" @click="adminForceBirth">{{ $t('admin.falukant.edituser.birth.force') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button @click="saveUser" :disabled="!hasUserChanges">{{ $t('common.save') }}</button>
|
<button @click="saveUser" :disabled="!hasUserChanges">{{ $t('common.save') }}</button>
|
||||||
<button @click="deleteUser" class="button-secondary">{{ $t('common.delete') }}</button>
|
<button @click="deleteUser" class="button-secondary">{{ $t('common.delete') }}</button>
|
||||||
@@ -158,7 +219,13 @@ export default {
|
|||||||
loading: {
|
loading: {
|
||||||
branches: false,
|
branches: false,
|
||||||
stockTypes: false
|
stockTypes: false
|
||||||
}
|
},
|
||||||
|
adminPregnancyFatherId: null,
|
||||||
|
adminDueInDays: 21,
|
||||||
|
adminBirthFatherId: null,
|
||||||
|
adminBirthContext: 'marriage',
|
||||||
|
adminBirthLegitimacy: 'legitimate',
|
||||||
|
adminBirthGender: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -172,6 +239,20 @@ export default {
|
|||||||
|| this.editableUser.falukantData[0].character.title_of_nobility != this.originalUser.falukantData[0].character.title_of_nobility
|
|| this.editableUser.falukantData[0].character.title_of_nobility != this.originalUser.falukantData[0].character.title_of_nobility
|
||||||
|| this.originalAge != this.age;
|
|| this.originalAge != this.age;
|
||||||
},
|
},
|
||||||
|
pregnancyDueDisplay() {
|
||||||
|
const c = this.editableUser?.falukantData?.[0]?.character;
|
||||||
|
if (!c) return null;
|
||||||
|
const raw = c.pregnancyDueAt ?? c.pregnancy_due_at;
|
||||||
|
if (!raw) return null;
|
||||||
|
try {
|
||||||
|
return new Date(raw).toLocaleString(this.$i18n?.locale || undefined, {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'short'
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
return String(raw);
|
||||||
|
}
|
||||||
|
},
|
||||||
availableStockTypes() {
|
availableStockTypes() {
|
||||||
if (!this.newStock.branchId || !this.stockTypes.length) {
|
if (!this.newStock.branchId || !this.stockTypes.length) {
|
||||||
return this.stockTypes;
|
return this.stockTypes;
|
||||||
@@ -343,6 +424,59 @@ export default {
|
|||||||
!existingStockTypeIds.includes(stockType.id)
|
!existingStockTypeIds.includes(stockType.id)
|
||||||
);
|
);
|
||||||
return availableStockTypes.length > 0;
|
return availableStockTypes.length > 0;
|
||||||
|
},
|
||||||
|
async refreshEditableUser() {
|
||||||
|
if (!this.editableUser?.hashedId) return;
|
||||||
|
const userResult = await apiClient.get(`/api/admin/falukant/getuser/${this.editableUser.hashedId}`);
|
||||||
|
this.editableUser = userResult.data;
|
||||||
|
this.originalUser = JSON.parse(JSON.stringify(this.editableUser));
|
||||||
|
this.age = Math.floor((Date.now() - new Date(this.editableUser.falukantData[0].character.birthdate)) / (24 * 60 * 60 * 1000));
|
||||||
|
this.originalAge = this.age;
|
||||||
|
},
|
||||||
|
async adminForcePregnancy() {
|
||||||
|
const characterId = this.editableUser.falukantData[0].character.id;
|
||||||
|
const payload = { characterId, dueInDays: Number(this.adminDueInDays) || 21 };
|
||||||
|
if (this.adminPregnancyFatherId) payload.fatherCharacterId = Number(this.adminPregnancyFatherId);
|
||||||
|
try {
|
||||||
|
await apiClient.post('/api/admin/falukant/character/force-pregnancy', payload);
|
||||||
|
showSuccess(this, 'tr:admin.falukant.edituser.pregnancy.successForce');
|
||||||
|
await this.refreshEditableUser();
|
||||||
|
} catch (error) {
|
||||||
|
showApiError(this, error, 'tr:admin.falukant.edituser.pregnancy.error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async adminClearPregnancy() {
|
||||||
|
const characterId = this.editableUser.falukantData[0].character.id;
|
||||||
|
try {
|
||||||
|
await apiClient.post('/api/admin/falukant/character/clear-pregnancy', { characterId });
|
||||||
|
showSuccess(this, 'tr:admin.falukant.edituser.pregnancy.successClear');
|
||||||
|
await this.refreshEditableUser();
|
||||||
|
} catch (error) {
|
||||||
|
showApiError(this, error, 'tr:admin.falukant.edituser.pregnancy.error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async adminForceBirth() {
|
||||||
|
const motherCharacterId = this.editableUser.falukantData[0].character.id;
|
||||||
|
if (!this.adminBirthFatherId) {
|
||||||
|
showError(this, this.$t('admin.falukant.edituser.birth.fatherId'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const body = {
|
||||||
|
motherCharacterId,
|
||||||
|
fatherCharacterId: Number(this.adminBirthFatherId),
|
||||||
|
birthContext: this.adminBirthContext,
|
||||||
|
legitimacy: this.adminBirthLegitimacy
|
||||||
|
};
|
||||||
|
if (this.adminBirthGender === 'male' || this.adminBirthGender === 'female') {
|
||||||
|
body.gender = this.adminBirthGender;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await apiClient.post('/api/admin/falukant/character/force-birth', body);
|
||||||
|
showSuccess(this, 'tr:admin.falukant.edituser.birth.success');
|
||||||
|
await this.refreshEditableUser();
|
||||||
|
} catch (error) {
|
||||||
|
showApiError(this, error, 'tr:admin.falukant.edituser.birth.error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,6 +491,43 @@ export default {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-family-tools {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
padding: 1rem 1rem 0.5rem;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-family-tools h4 {
|
||||||
|
margin: 0.75rem 0 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-family-tools h4:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-family-tools__meta {
|
||||||
|
margin: 0.35rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-family-tools__hint {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-family-tools .form-field {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-family-tools .action-buttons {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.search-section {
|
.search-section {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|||||||
@@ -11,6 +11,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-if="pregnancy"
|
||||||
|
class="family-pregnancy surface-card"
|
||||||
|
>
|
||||||
|
<strong>{{ $t('falukant.family.pregnancy.banner') }}</strong>
|
||||||
|
<p>{{ $t('falukant.family.pregnancy.dueHint') }}: {{ formatPregnancyDue(pregnancy.dueAt) }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
v-if="debtorsPrison.active"
|
v-if="debtorsPrison.active"
|
||||||
class="family-debt-warning surface-card"
|
class="family-debt-warning surface-card"
|
||||||
@@ -407,6 +415,7 @@ export default {
|
|||||||
active: false,
|
active: false,
|
||||||
inDebtorsPrison: false
|
inDebtorsPrison: false
|
||||||
},
|
},
|
||||||
|
pregnancy: null,
|
||||||
selectedChild: null,
|
selectedChild: null,
|
||||||
pendingFamilyRefresh: null
|
pendingFamilyRefresh: null
|
||||||
}
|
}
|
||||||
@@ -563,11 +572,24 @@ export default {
|
|||||||
active: false,
|
active: false,
|
||||||
inDebtorsPrison: false
|
inDebtorsPrison: false
|
||||||
};
|
};
|
||||||
|
this.pregnancy = response.data.pregnancy || null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading family data:', error);
|
console.error('Error loading family data:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
formatPregnancyDue(iso) {
|
||||||
|
if (!iso) return '—';
|
||||||
|
try {
|
||||||
|
return new Date(iso).toLocaleString(this.$i18n?.locale || undefined, {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'short',
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
return String(iso);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async spendTimeWithSpouse() {
|
async spendTimeWithSpouse() {
|
||||||
try {
|
try {
|
||||||
await apiClient.post('/api/falukant/family/marriage/spend-time');
|
await apiClient.post('/api/falukant/family/marriage/spend-time');
|
||||||
@@ -904,6 +926,18 @@ export default {
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.family-pregnancy {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid rgba(120, 140, 200, 0.35);
|
||||||
|
background: linear-gradient(180deg, rgba(235, 240, 255, 0.95), rgba(248, 250, 255, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.family-pregnancy p {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.family-debt-warning {
|
.family-debt-warning {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 16px 18px;
|
padding: 16px 18px;
|
||||||
|
|||||||
@@ -1,113 +1,69 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
TARGET_DIR="${1:?Zielverzeichnis fehlt: ./update-backend.sh <z. B. /opt/yourpart-green> [STAGE]}"
|
TARGET_DIR="${1:?target dir required}"
|
||||||
# Zweiter Parameter: STAGE für DB-Sync (früher erster Parameter)
|
|
||||||
STAGE="${2:-production}"
|
STAGE="${2:-production}"
|
||||||
BACKEND_TARGET="$TARGET_DIR/backend"
|
|
||||||
|
BACKEND_DIR="$TARGET_DIR/backend"
|
||||||
|
CURRENT_LINK="/opt/yourpart"
|
||||||
|
CURRENT_BACKEND="$CURRENT_LINK/backend"
|
||||||
|
DATA_DIR="/opt/yourpart-data"
|
||||||
|
|
||||||
echo "Updating YourPart Backend..."
|
echo "Updating YourPart Backend..."
|
||||||
echo "Ziel: $BACKEND_TARGET"
|
echo "Ziel: $BACKEND_DIR"
|
||||||
echo "Using STAGE: $STAGE"
|
echo "Using STAGE: $STAGE"
|
||||||
echo "NOTE: .env files will NOT be overwritten"
|
echo "NOTE: .env files will NOT be overwritten"
|
||||||
|
|
||||||
# Zum Backend-Verzeichnis wechseln
|
cd "$BACKEND_DIR"
|
||||||
cd backend
|
|
||||||
|
|
||||||
# Alle generierten Verzeichnisse löschen
|
|
||||||
echo "Lösche alle generierten Verzeichnisse..."
|
echo "Lösche alle generierten Verzeichnisse..."
|
||||||
rm -rf node_modules/.cache/
|
rm -rf node_modules/.cache/
|
||||||
rm -rf logs/
|
rm -rf logs/
|
||||||
rm -rf tmp/
|
rm -rf tmp/
|
||||||
|
|
||||||
# Dependencies installieren
|
|
||||||
echo "Installing dependencies..."
|
echo "Installing dependencies..."
|
||||||
npm ci --production
|
npm ci --production
|
||||||
|
|
||||||
# Sichere .env-Dateien aus dem Ziel-Backend temporär
|
|
||||||
TEMP_ENV_DIR="/tmp/yourpart-env-backup-$$"
|
TEMP_ENV_DIR="/tmp/yourpart-env-backup-$$"
|
||||||
echo "Sichere .env-Dateien aus $BACKEND_TARGET..."
|
mkdir -p "$TEMP_ENV_DIR"
|
||||||
sudo mkdir -p "$TEMP_ENV_DIR"
|
|
||||||
if [ -d "$BACKEND_TARGET" ]; then
|
echo "Sichere .env-Dateien aus $CURRENT_BACKEND..."
|
||||||
sudo find "$BACKEND_TARGET" -maxdepth 1 -name '.env*' -type f -exec cp {} "$TEMP_ENV_DIR/" \; 2>/dev/null || true
|
if [ -d "$CURRENT_BACKEND" ]; then
|
||||||
if [ "$(ls -A $TEMP_ENV_DIR 2>/dev/null)" ]; then
|
find "$CURRENT_BACKEND" -maxdepth 1 -name '.env*' -type f -exec cp {} "$TEMP_ENV_DIR/" \; 2>/dev/null || true
|
||||||
echo "✓ .env-Dateien gesichert: $(ls $TEMP_ENV_DIR)"
|
if [ "$(ls -A "$TEMP_ENV_DIR" 2>/dev/null)" ]; then
|
||||||
|
echo "✓ .env-Dateien gesichert: $(ls "$TEMP_ENV_DIR")"
|
||||||
|
cp "$TEMP_ENV_DIR"/.env* "$BACKEND_DIR"/ 2>/dev/null || true
|
||||||
else
|
else
|
||||||
echo "⚠ Keine .env-Dateien in $BACKEND_TARGET gefunden"
|
echo "⚠ Keine .env-Dateien in $CURRENT_BACKEND gefunden"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Altes Backend löschen
|
sudo -n mkdir -p "$DATA_DIR/adult-verification"
|
||||||
echo "Lösche altes Backend..."
|
|
||||||
sudo rm -rf "$BACKEND_TARGET"
|
|
||||||
|
|
||||||
# Erstelle Backend-Verzeichnis
|
if [ -f "$BACKEND_DIR/.env" ]; then
|
||||||
echo "Erstelle Backend-Verzeichnis..."
|
|
||||||
sudo mkdir -p "$BACKEND_TARGET"
|
|
||||||
sudo mkdir -p /opt/yourpart-data/adult-verification
|
|
||||||
|
|
||||||
# Kopiere neues Backend (ohne .env-Dateien aus dem Quellverzeichnis)
|
|
||||||
echo "Kopiere neues Backend..."
|
|
||||||
if command -v rsync &> /dev/null; then
|
|
||||||
sudo rsync -av --exclude='.env*' --exclude='node_modules' . "$BACKEND_TARGET/" 2>/dev/null
|
|
||||||
if [ -d node_modules ]; then
|
|
||||||
sudo rsync -av --exclude='.env*' node_modules/ "$BACKEND_TARGET/node_modules/" 2>/dev/null || \
|
|
||||||
sudo cp -r node_modules "$BACKEND_TARGET/" 2>/dev/null
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Fallback: Kopiere alles außer .env
|
|
||||||
sudo cp -r * "$BACKEND_TARGET/" 2>/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Stelle .env-Dateien wieder her
|
|
||||||
echo "Stelle .env-Dateien wieder her..."
|
|
||||||
if [ -d "$TEMP_ENV_DIR" ] && [ "$(ls -A $TEMP_ENV_DIR 2>/dev/null)" ]; then
|
|
||||||
sudo cp "$TEMP_ENV_DIR"/.env* "$BACKEND_TARGET/" 2>/dev/null || true
|
|
||||||
echo "✓ .env-Dateien wiederhergestellt"
|
|
||||||
else
|
|
||||||
echo "⚠ Keine .env-Dateien zum Wiederherstellen vorhanden"
|
|
||||||
fi
|
|
||||||
sudo rm -rf "$TEMP_ENV_DIR"
|
|
||||||
|
|
||||||
# Prüfe ob .env vorhanden ist
|
|
||||||
if [ -f "$BACKEND_TARGET/.env" ]; then
|
|
||||||
echo "✓ .env-Datei ist vorhanden"
|
echo "✓ .env-Datei ist vorhanden"
|
||||||
else
|
else
|
||||||
echo "⚠ WARNUNG: Keine .env-Datei in $BACKEND_TARGET gefunden!"
|
echo "⚠ WARNUNG: Keine .env-Datei in $BACKEND_DIR gefunden!"
|
||||||
echo " Bitte manuell erstellen: sudo nano $BACKEND_TARGET/.env"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Berechtigungen setzen
|
|
||||||
echo "Setting permissions..."
|
echo "Setting permissions..."
|
||||||
sudo chown -R yourpart:yourpart "$BACKEND_TARGET"
|
sudo -n chown -R yourpart:yourpart "$BACKEND_DIR"
|
||||||
sudo chmod -R 755 "$BACKEND_TARGET"
|
sudo -n chmod -R 755 "$BACKEND_DIR"
|
||||||
sudo chown -R yourpart:yourpart /opt/yourpart-data
|
sudo -n chown -R yourpart:yourpart "$DATA_DIR"
|
||||||
sudo chmod -R 755 /opt/yourpart-data
|
sudo -n chmod -R 755 "$DATA_DIR"
|
||||||
# Stelle sicher, dass .env-Dateien die richtigen Berechtigungen haben
|
|
||||||
if [ -f "$BACKEND_TARGET/.env" ]; then
|
if [ -f "$BACKEND_DIR/.env" ]; then
|
||||||
sudo chmod 600 "$BACKEND_TARGET/.env"
|
sudo -n chmod 600 "$BACKEND_DIR/.env"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Systemd-Service aktualisieren und neu laden
|
|
||||||
echo "Updating systemd service..."
|
echo "Updating systemd service..."
|
||||||
sudo cp yourpart.service /etc/systemd/system/
|
sudo -n cp "$TARGET_DIR/yourpart.service" /etc/systemd/system/
|
||||||
sudo systemctl daemon-reload
|
sudo -n systemctl daemon-reload
|
||||||
|
|
||||||
# Datenbank-Synchronisation durchführen
|
|
||||||
echo "Running database synchronization..."
|
echo "Running database synchronization..."
|
||||||
cd "$BACKEND_TARGET"
|
cd "$BACKEND_DIR"
|
||||||
|
export STAGE="$STAGE"
|
||||||
# STAGE für Schema-Updates verwenden
|
npm run sync-db
|
||||||
echo "Running database sync with STAGE=$STAGE..."
|
|
||||||
export STAGE=$STAGE && npm run sync-db
|
|
||||||
|
|
||||||
# Service neu starten
|
|
||||||
echo "Restarting yourpart service..."
|
|
||||||
sudo systemctl restart yourpart
|
|
||||||
|
|
||||||
# Kurz warten und Status prüfen
|
|
||||||
sleep 2
|
|
||||||
echo "Checking service status..."
|
|
||||||
sudo systemctl status yourpart --no-pager
|
|
||||||
|
|
||||||
echo "Backend update completed!"
|
echo "Backend update completed!"
|
||||||
|
rm -rf "$TEMP_ENV_DIR"
|
||||||
@@ -1,35 +1,33 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
TARGET_DIR="${1:?Zielverzeichnis fehlt: ./update-frontend.sh <z. B. /opt/yourpart-green>}"
|
TARGET_DIR="${1:?target dir required}"
|
||||||
FRONTEND_TARGET="$TARGET_DIR/frontend"
|
|
||||||
|
FRONTEND_DIR="$TARGET_DIR/frontend"
|
||||||
|
CURRENT_LINK="/opt/yourpart"
|
||||||
|
CURRENT_FRONTEND="$CURRENT_LINK/frontend"
|
||||||
|
|
||||||
echo "=== YourPart Frontend Update ==="
|
echo "=== YourPart Frontend Update ==="
|
||||||
echo "Ziel: $FRONTEND_TARGET"
|
echo "Ziel: $FRONTEND_DIR"
|
||||||
echo "NOTE: .env files will NOT be overwritten"
|
echo "NOTE: .env files will NOT be overwritten"
|
||||||
|
|
||||||
# 1. Zum Frontend-Verzeichnis wechseln
|
cd "$FRONTEND_DIR"
|
||||||
cd frontend
|
|
||||||
|
|
||||||
# 2. Berechtigungen für dist-Verzeichnis korrigieren (falls vorhanden)
|
|
||||||
if [ -d "dist" ]; then
|
if [ -d "dist" ]; then
|
||||||
echo "Korrigiere Berechtigungen für dist-Verzeichnis..."
|
echo "Korrigiere Berechtigungen für dist-Verzeichnis..."
|
||||||
sudo chown -R $USER:$USER dist/ 2>/dev/null || true
|
sudo -n chown -R "$USER:$USER" dist/ 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3. Alle generierten Verzeichnisse löschen
|
|
||||||
echo "Lösche alle generierten Verzeichnisse..."
|
echo "Lösche alle generierten Verzeichnisse..."
|
||||||
rm -rf dist/
|
rm -rf dist/
|
||||||
rm -rf node_modules/.vite/
|
rm -rf node_modules/.vite/
|
||||||
rm -rf node_modules/.cache/
|
rm -rf node_modules/.cache/
|
||||||
|
|
||||||
# 4. Verwende bestehende .env-Dateien im Zielverzeichnis für Build
|
|
||||||
# Kopiere temporär die bestehende .env vom Ziel, falls vorhanden
|
|
||||||
TEMP_ENV="/tmp/yourpart-frontend-env-$$"
|
TEMP_ENV="/tmp/yourpart-frontend-env-$$"
|
||||||
if [ -f "$FRONTEND_TARGET/.env" ]; then
|
|
||||||
echo "Nutze bestehende .env-Datei vom Zielverzeichnis für Build..."
|
if [ -f "$CURRENT_FRONTEND/.env" ]; then
|
||||||
sudo cp "$FRONTEND_TARGET/.env" "$TEMP_ENV"
|
echo "Nutze bestehende .env-Datei vom Live-System für Build..."
|
||||||
sudo chown $USER:$USER "$TEMP_ENV"
|
cp "$CURRENT_FRONTEND/.env" "$TEMP_ENV"
|
||||||
cp "$TEMP_ENV" .env
|
cp "$TEMP_ENV" .env
|
||||||
elif [ -f .env.production ]; then
|
elif [ -f .env.production ]; then
|
||||||
echo "Nutze .env.production für Build..."
|
echo "Nutze .env.production für Build..."
|
||||||
@@ -39,84 +37,35 @@ elif [ -f .env.server ]; then
|
|||||||
cp .env.server .env
|
cp .env.server .env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 5. Frontend neu bauen – VITE_* aus Environment übernehmen oder Defaults setzen
|
|
||||||
echo "Baue Frontend neu..."
|
echo "Baue Frontend neu..."
|
||||||
export VITE_API_BASE_URL=${VITE_API_BASE_URL:-https://www.your-part.de}
|
export VITE_API_BASE_URL="${VITE_API_BASE_URL:-https://www.your-part.de}"
|
||||||
# Standard: Daemon direkt auf Port 4551, nicht über Apache-Proxy
|
export VITE_DAEMON_SOCKET="${VITE_DAEMON_SOCKET:-wss://www.your-part.de:4551}"
|
||||||
export VITE_DAEMON_SOCKET=${VITE_DAEMON_SOCKET:-wss://www.your-part.de:4551}
|
export VITE_CHAT_WS_URL="${VITE_CHAT_WS_URL:-wss://www.your-part.de:1235}"
|
||||||
export VITE_CHAT_WS_URL=${VITE_CHAT_WS_URL:-wss://www.your-part.de:1235}
|
|
||||||
|
|
||||||
echo "VITE_API_BASE_URL=$VITE_API_BASE_URL"
|
echo "VITE_API_BASE_URL=$VITE_API_BASE_URL"
|
||||||
echo "VITE_DAEMON_SOCKET=$VITE_DAEMON_SOCKET"
|
echo "VITE_DAEMON_SOCKET=$VITE_DAEMON_SOCKET"
|
||||||
echo "VITE_CHAT_WS_URL=$VITE_CHAT_WS_URL"
|
echo "VITE_CHAT_WS_URL=$VITE_CHAT_WS_URL"
|
||||||
|
|
||||||
# 5a. Dependencies installieren
|
|
||||||
echo "Installiere Dependencies..."
|
echo "Installiere Dependencies..."
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
echo "Baue Frontend..."
|
||||||
echo "❌ npm install fehlgeschlagen!"
|
|
||||||
# Temporäre .env-Datei aufräumen
|
|
||||||
rm -f "$TEMP_ENV"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 5b. Frontend neu bauen
|
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ Build fehlgeschlagen!"
|
|
||||||
# Temporäre .env-Datei aufräumen
|
|
||||||
rm -f "$TEMP_ENV"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Build erfolgreich!"
|
|
||||||
|
|
||||||
# Temporäre .env-Datei aufräumen
|
|
||||||
rm -f "$TEMP_ENV"
|
rm -f "$TEMP_ENV"
|
||||||
|
|
||||||
# 6. Zielverzeichnis erstellen (falls nicht vorhanden)
|
if [ -f "$FRONTEND_DIR/.env" ]; then
|
||||||
echo "Erstelle Zielverzeichnis..."
|
echo "✓ Bestehende .env-Datei wurde beibehalten"
|
||||||
sudo mkdir -p "$FRONTEND_TARGET/dist"
|
|
||||||
|
|
||||||
# 7. Altes Frontend löschen (außer .env-Dateien)
|
|
||||||
echo "Lösche altes Frontend (außer .env-Dateien)..."
|
|
||||||
sudo find "$FRONTEND_TARGET/dist" -mindepth 1 -exec rm -rf {} + 2>/dev/null || true
|
|
||||||
|
|
||||||
# 8. Neues Frontend kopieren
|
|
||||||
echo "Kopiere neues Frontend..."
|
|
||||||
sudo cp -r dist/* "$FRONTEND_TARGET/dist/"
|
|
||||||
|
|
||||||
# 9. .env-Dateien NICHT überschreiben - bestehende beibehalten
|
|
||||||
if [ -f "$FRONTEND_TARGET/.env" ]; then
|
|
||||||
echo "✓ Bestehende .env-Datei wurde beibehalten (nicht überschrieben)"
|
|
||||||
else
|
else
|
||||||
echo "⚠ Keine .env-Datei im Zielverzeichnis gefunden"
|
echo "⚠ Keine .env-Datei im Zielverzeichnis gefunden"
|
||||||
# Falls .env im Quellverzeichnis existiert, kopiere sie nur wenn sie im Ziel nicht existiert
|
|
||||||
if [ -f .env ]; then
|
|
||||||
echo "Kopiere .env-Datei (nur wenn nicht vorhanden)..."
|
|
||||||
sudo cp .env "$FRONTEND_TARGET/"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 10. Berechtigungen setzen
|
|
||||||
echo "Setze Berechtigungen..."
|
echo "Setze Berechtigungen..."
|
||||||
sudo chown -R www-data:www-data "$FRONTEND_TARGET/dist"
|
sudo -n chown -R www-data:www-data "$FRONTEND_DIR/dist"
|
||||||
if [ -f "$FRONTEND_TARGET/.env" ]; then
|
if [ -f "$FRONTEND_DIR/.env" ]; then
|
||||||
sudo chown www-data:www-data "$FRONTEND_TARGET/.env"
|
sudo -n chown www-data:www-data "$FRONTEND_DIR/.env"
|
||||||
sudo chmod 644 "$FRONTEND_TARGET/.env"
|
sudo -n chmod 644 "$FRONTEND_DIR/.env"
|
||||||
fi
|
fi
|
||||||
sudo chmod -R 755 "$FRONTEND_TARGET/dist"
|
sudo -n chmod -R 755 "$FRONTEND_DIR/dist"
|
||||||
|
|
||||||
# 11. Apache neu laden
|
echo "=== Frontend Update abgeschlossen! ==="
|
||||||
echo "Lade Apache neu..."
|
|
||||||
sudo systemctl reload apache2
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=== Frontend Update abgeschlossen! ==="
|
|
||||||
echo "✅ Frontend neu gebaut"
|
|
||||||
echo "✅ Frontend aktualisiert"
|
|
||||||
echo "✅ .env-Dateien wurden NICHT überschrieben"
|
|
||||||
echo "✅ Apache neu geladen"
|
|
||||||
echo ""
|
|
||||||
Reference in New Issue
Block a user