Add reputation attribute to FalukantCharacter model and update related services and views
- Introduced a new 'reputation' attribute in the FalukantCharacter model with a default value and validation. - Updated FalukantService to include 'reputation' in character attributes for API responses. - Enhanced ReputationView component to display current reputation and load it from the API. - Added translations for reputation in both German and English locales.
This commit is contained in:
@@ -0,0 +1,79 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
// falukant_data.character.reputation (integer, default random 20..80)
|
||||||
|
// Wichtig: Schema explizit angeben
|
||||||
|
// Vorgehen:
|
||||||
|
// - Spalte anlegen (falls noch nicht vorhanden)
|
||||||
|
// - bestehende Zeilen initialisieren (random 20..80)
|
||||||
|
// - DEFAULT setzen (random 20..80)
|
||||||
|
// - NOT NULL + CHECK 0..100 erzwingen
|
||||||
|
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 = 'reputation'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE falukant_data."character"
|
||||||
|
ADD COLUMN reputation integer;
|
||||||
|
END IF;
|
||||||
|
END$$;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Backfill: nur NULLs initialisieren (damit bestehende Werte nicht überschrieben werden)
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
UPDATE falukant_data."character"
|
||||||
|
SET reputation = (floor(random()*61)+20)::int
|
||||||
|
WHERE reputation IS NULL;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// DEFAULT + NOT NULL (nach Backfill)
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
ALTER TABLE falukant_data."character"
|
||||||
|
ALTER COLUMN reputation SET DEFAULT (floor(random()*61)+20)::int;
|
||||||
|
`);
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
ALTER TABLE falukant_data."character"
|
||||||
|
ALTER COLUMN reputation SET NOT NULL;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Enforce 0..100 at DB level (percent)
|
||||||
|
// (IF NOT EXISTS pattern, because deployments can be re-run)
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint c
|
||||||
|
JOIN pg_class t ON t.oid = c.conrelid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||||
|
WHERE c.conname = 'character_reputation_0_100_chk'
|
||||||
|
AND n.nspname = 'falukant_data'
|
||||||
|
AND t.relname = 'character'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE falukant_data."character"
|
||||||
|
ADD CONSTRAINT character_reputation_0_100_chk
|
||||||
|
CHECK (reputation >= 0 AND reputation <= 100);
|
||||||
|
END IF;
|
||||||
|
END$$;
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async down(queryInterface, Sequelize) {
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
ALTER TABLE falukant_data."character"
|
||||||
|
DROP CONSTRAINT IF EXISTS character_reputation_0_100_chk;
|
||||||
|
`);
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
ALTER TABLE falukant_data."character"
|
||||||
|
DROP COLUMN IF EXISTS reputation;
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +34,18 @@ FalukantCharacter.init(
|
|||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 1}
|
defaultValue: 1}
|
||||||
|
,
|
||||||
|
reputation: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
// Initialisierung: zufällig 20..80 (Prozent)
|
||||||
|
// DB-seitig per DEFAULT umgesetzt, damit es auch ohne App-Logic gilt.
|
||||||
|
defaultValue: sequelize.literal('(floor(random()*61)+20)'),
|
||||||
|
validate: {
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequelize,
|
sequelize,
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ class FalukantService extends BaseService {
|
|||||||
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr', 'id'] },
|
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr', 'id'] },
|
||||||
{ model: CharacterTrait, as: 'traits', attributes: ['id', 'tr'] }
|
{ model: CharacterTrait, as: 'traits', attributes: ['id', 'tr'] }
|
||||||
],
|
],
|
||||||
attributes: ['id', 'birthdate', 'gender', 'moodId', 'health']
|
attributes: ['id', 'birthdate', 'gender', 'moodId', 'health', 'reputation']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: UserHouse,
|
model: UserHouse,
|
||||||
@@ -496,7 +496,7 @@ class FalukantService extends BaseService {
|
|||||||
{
|
{
|
||||||
model: FalukantCharacter,
|
model: FalukantCharacter,
|
||||||
as: 'character',
|
as: 'character',
|
||||||
attributes: ['birthdate', 'health'],
|
attributes: ['birthdate', 'health', 'reputation'],
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: Relationship,
|
model: Relationship,
|
||||||
|
|||||||
@@ -742,7 +742,8 @@
|
|||||||
"reputation": {
|
"reputation": {
|
||||||
"title": "Reputation",
|
"title": "Reputation",
|
||||||
"overview": {
|
"overview": {
|
||||||
"title": "Übersicht"
|
"title": "Übersicht",
|
||||||
|
"current": "Deine aktuelle Reputation"
|
||||||
},
|
},
|
||||||
"party": {
|
"party": {
|
||||||
"title": "Feste",
|
"title": "Feste",
|
||||||
|
|||||||
@@ -198,6 +198,16 @@
|
|||||||
"nobility": {
|
"nobility": {
|
||||||
"cooldown": "You can only advance again on {date}."
|
"cooldown": "You can only advance again on {date}."
|
||||||
},
|
},
|
||||||
|
"reputation": {
|
||||||
|
"title": "Reputation",
|
||||||
|
"overview": {
|
||||||
|
"title": "Overview",
|
||||||
|
"current": "Your current reputation"
|
||||||
|
},
|
||||||
|
"party": {
|
||||||
|
"title": "Parties"
|
||||||
|
}
|
||||||
|
},
|
||||||
"branchProduction": {
|
"branchProduction": {
|
||||||
"storageAvailable": "Free storage"
|
"storageAvailable": "Free storage"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div v-if="activeTab === 'overview'">
|
<div v-if="activeTab === 'overview'">
|
||||||
<p>Deine aktuelle Reputation: …</p>
|
<p>
|
||||||
|
{{ $t('falukant.reputation.overview.current') }}:
|
||||||
|
<strong>{{ reputationDisplay }}</strong>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="activeTab === 'party'">
|
<div v-else-if="activeTab === 'party'">
|
||||||
@@ -169,7 +172,8 @@ export default {
|
|||||||
selectedNobilityIds: [],
|
selectedNobilityIds: [],
|
||||||
servantRatio: 50,
|
servantRatio: 50,
|
||||||
inProgressParties: [],
|
inProgressParties: [],
|
||||||
completedParties: []
|
completedParties: [],
|
||||||
|
reputation: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -198,6 +202,15 @@ export default {
|
|||||||
return partyDate <= twentyFourHoursAgo;
|
return partyDate <= twentyFourHoursAgo;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
async loadReputation() {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.get('/api/falukant/info');
|
||||||
|
this.reputation = data?.character?.reputation ?? null;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load reputation', e);
|
||||||
|
this.reputation = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
async loadNobilityTitles() {
|
async loadNobilityTitles() {
|
||||||
this.nobilityTitles = await apiClient.get('/api/falukant/nobility/titels').then(r => r.data)
|
this.nobilityTitles = await apiClient.get('/api/falukant/nobility/titels').then(r => r.data)
|
||||||
},
|
},
|
||||||
@@ -219,6 +232,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
reputationDisplay() {
|
||||||
|
if (this.reputation == null) return '—';
|
||||||
|
return String(this.reputation);
|
||||||
|
},
|
||||||
formattedCost() {
|
formattedCost() {
|
||||||
const type = this.partyTypes.find(t => t.id === this.newPartyTypeId) || {};
|
const type = this.partyTypes.find(t => t.id === this.newPartyTypeId) || {};
|
||||||
const music = this.musicTypes.find(m => m.id === this.musicId) || {};
|
const music = this.musicTypes.find(m => m.id === this.musicId) || {};
|
||||||
@@ -245,6 +262,7 @@ export default {
|
|||||||
await this.loadPartyTypes();
|
await this.loadPartyTypes();
|
||||||
await this.loadNobilityTitles();
|
await this.loadNobilityTitles();
|
||||||
await this.loadParties();
|
await this.loadParties();
|
||||||
|
await this.loadReputation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user