feat(renown): add Reputation/Renown/TurnIn entities and update Character/ItemDefinition/CombatReward

This commit is contained in:
Bastian Wagner
2026-08-21 09:03:37 +02:00
parent 3662029ec9
commit 4fa1923525
10 changed files with 289 additions and 14 deletions

View File

@@ -17,11 +17,8 @@ export class Character {
@Column({ name: 'name', type: 'varchar', length: 150 }) @Column({ name: 'name', type: 'varchar', length: 150 })
name!: string; name!: string;
@Column({ name: 'level', type: 'integer' }) @Column({ name: 'renown', type: 'integer' })
level!: number; renown!: number;
@Column({ name: 'experience', type: 'integer' })
experience!: number;
@Column({ name: 'silver', type: 'integer' }) @Column({ name: 'silver', type: 'integer' })
silver!: number; silver!: number;

View File

@@ -0,0 +1,71 @@
import 'reflect-metadata';
import { getMetadataArgsStorage } from 'typeorm';
import { Character } from '../../characters/entities/character.entity';
import { ItemDefinition } from '../../items/entities/item-definition.entity';
import { ItemType } from '../../items/item-type.enum';
import { CombatReward } from '../../rewards/entities/combat-reward.entity';
import { ReputationFaction } from '../../reputation/entities/reputation-faction.entity';
import { CharacterReputation } from '../../reputation/entities/character-reputation.entity';
import { RenownMilestoneDefinition } from '../../renown/entities/renown-milestone-definition.entity';
import { CharacterRenownMilestone } from '../../renown/entities/character-renown-milestone.entity';
import { TurnInDefinition } from '../../turn-in/entities/turn-in-definition.entity';
function columnNames(target: unknown): string[] {
return getMetadataArgsStorage()
.columns.filter((column) => column.target === target)
.map((column) => column.propertyName);
}
function uniqueIndexFor(target: unknown, columns: string[]): boolean {
const index = getMetadataArgsStorage().indices.find(
(candidate) =>
candidate.target === target && columns.every((column) => candidate.columns?.includes(column)),
);
const meta = index as typeof index & { options?: { unique?: boolean }; unique?: boolean };
return (meta?.options?.unique ?? meta?.unique) === true;
}
describe('Slice 0.6.5 entity metadata', () => {
it('Character exposes renown and no longer exposes level/experience', () => {
const names = columnNames(Character);
expect(names).toContain('renown');
expect(names).not.toContain('level');
expect(names).not.toContain('experience');
});
it('ItemDefinition no longer exposes requiredLevel', () => {
expect(columnNames(ItemDefinition)).not.toContain('requiredLevel');
});
it('ItemType has exactly the five Slice 0.6.5 values', () => {
expect(Object.values(ItemType).sort()).toEqual(
['CONSUMABLE', 'EQUIPMENT', 'QUEST_ITEM', 'TRADE_GOOD', 'TROPHY'].sort(),
);
});
it('CombatReward no longer exposes experienceGranted', () => {
expect(columnNames(CombatReward)).not.toContain('experienceGranted');
});
it('ReputationFaction has a unique key', () => {
expect(uniqueIndexFor(ReputationFaction, ['key'])).toBe(true);
});
it('CharacterReputation enforces one row per character per faction', () => {
expect(uniqueIndexFor(CharacterReputation, ['characterId', 'factionId'])).toBe(true);
});
it('RenownMilestoneDefinition has a unique key', () => {
expect(uniqueIndexFor(RenownMilestoneDefinition, ['key'])).toBe(true);
});
it('CharacterRenownMilestone enforces one row per character per milestone', () => {
expect(
uniqueIndexFor(CharacterRenownMilestone, ['characterId', 'milestoneId']),
).toBe(true);
});
it('TurnInDefinition has a unique key', () => {
expect(uniqueIndexFor(TurnInDefinition, ['key'])).toBe(true);
});
});

View File

@@ -43,9 +43,6 @@ export class ItemDefinition {
@Column({ name: 'tier', type: 'integer' }) @Column({ name: 'tier', type: 'integer' })
tier!: number; tier!: number;
@Column({ name: 'required_level', type: 'integer' })
requiredLevel!: number;
@Column({ name: 'weapon_damage', type: 'integer' }) @Column({ name: 'weapon_damage', type: 'integer' })
weaponDamage!: number; weaponDamage!: number;

View File

@@ -1,6 +1,7 @@
export enum ItemType { export enum ItemType {
WEAPON = 'WEAPON', EQUIPMENT = 'EQUIPMENT',
ARMOR = 'ARMOR', TRADE_GOOD = 'TRADE_GOOD',
MATERIAL = 'MATERIAL', TROPHY = 'TROPHY',
QUEST_ITEM = 'QUEST_ITEM',
CONSUMABLE = 'CONSUMABLE', CONSUMABLE = 'CONSUMABLE',
} }

View File

@@ -0,0 +1,39 @@
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Character } from '../../characters/entities/character.entity';
import { RenownMilestoneDefinition } from './renown-milestone-definition.entity';
@Entity({ name: 'character_renown_milestones' })
@Index('IDX_character_renown_milestones_character_milestone', ['characterId', 'milestoneId'], {
unique: true,
})
export class CharacterRenownMilestone {
@PrimaryGeneratedColumn('uuid', { name: 'id' })
id!: string;
@Column({ name: 'character_id', type: 'uuid' })
characterId!: string;
@Column({ name: 'milestone_id', type: 'uuid' })
milestoneId!: string;
@Column({ name: 'completed_at', type: 'timestamptz' })
completedAt!: Date;
@Column({ name: 'times_completed', type: 'integer' })
timesCompleted!: number;
@ManyToOne(() => Character, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'character_id' })
character!: Character;
@ManyToOne(() => RenownMilestoneDefinition, { onDelete: 'RESTRICT' })
@JoinColumn({ name: 'milestone_id' })
milestone!: RenownMilestoneDefinition;
}

View File

@@ -0,0 +1,39 @@
import {
Column,
CreateDateColumn,
Entity,
Index,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({ name: 'renown_milestone_definitions' })
@Index('IDX_renown_milestone_definitions_key', ['key'], { unique: true })
export class RenownMilestoneDefinition {
@PrimaryGeneratedColumn('uuid', { name: 'id' })
id!: string;
@Column({ name: 'key', type: 'varchar', length: 100 })
key!: string;
@Column({ name: 'name', type: 'varchar', length: 150 })
name!: string;
@Column({ name: 'description', type: 'text' })
description!: string;
@Column({ name: 'renown_reward', type: 'integer' })
renownReward!: number;
@Column({ name: 'repeatable', type: 'boolean' })
repeatable!: boolean;
@Column({ name: 'enabled', type: 'boolean' })
enabled!: boolean;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
updatedAt!: Date;
}

View File

@@ -0,0 +1,44 @@
import {
Column,
CreateDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Character } from '../../characters/entities/character.entity';
import { ReputationFaction } from './reputation-faction.entity';
@Entity({ name: 'character_reputation' })
@Index('IDX_character_reputation_character_faction', ['characterId', 'factionId'], {
unique: true,
})
export class CharacterReputation {
@PrimaryGeneratedColumn('uuid', { name: 'id' })
id!: string;
@Column({ name: 'character_id', type: 'uuid' })
characterId!: string;
@Column({ name: 'faction_id', type: 'uuid' })
factionId!: string;
@Column({ name: 'reputation', type: 'integer' })
reputation!: number;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
updatedAt!: Date;
@ManyToOne(() => Character, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'character_id' })
character!: Character;
@ManyToOne(() => ReputationFaction, { onDelete: 'RESTRICT' })
@JoinColumn({ name: 'faction_id' })
faction!: ReputationFaction;
}

View File

@@ -0,0 +1,36 @@
import {
Column,
CreateDateColumn,
Entity,
Index,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({ name: 'reputation_factions' })
@Index('IDX_reputation_factions_key', ['key'], { unique: true })
export class ReputationFaction {
@PrimaryGeneratedColumn('uuid', { name: 'id' })
id!: string;
@Column({ name: 'key', type: 'varchar', length: 100 })
key!: string;
@Column({ name: 'name', type: 'varchar', length: 150 })
name!: string;
@Column({ name: 'description', type: 'text' })
description!: string;
@Column({ name: 'region_key', type: 'varchar', length: 100 })
regionKey!: string;
@Column({ name: 'enabled', type: 'boolean' })
enabled!: boolean;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
updatedAt!: Date;
}

View File

@@ -29,9 +29,6 @@ export class CombatReward {
@Column({ name: 'character_id', type: 'uuid' }) @Column({ name: 'character_id', type: 'uuid' })
characterId!: string; characterId!: string;
@Column({ name: 'experience_granted', type: 'integer' })
experienceGranted!: number;
@Column({ name: 'silver_granted', type: 'integer' }) @Column({ name: 'silver_granted', type: 'integer' })
silverGranted!: number; silverGranted!: number;

View File

@@ -0,0 +1,54 @@
import {
Column,
CreateDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { ItemDefinition } from '../../items/entities/item-definition.entity';
import { ReputationFaction } from '../../reputation/entities/reputation-faction.entity';
@Entity({ name: 'turn_in_definitions' })
@Index('IDX_turn_in_definitions_key', ['key'], { unique: true })
export class TurnInDefinition {
@PrimaryGeneratedColumn('uuid', { name: 'id' })
id!: string;
@Column({ name: 'key', type: 'varchar', length: 100 })
key!: string;
@Column({ name: 'item_definition_id', type: 'uuid' })
itemDefinitionId!: string;
@Column({ name: 'faction_id', type: 'uuid' })
factionId!: string;
@Column({ name: 'silver_reward_per_item', type: 'integer' })
silverRewardPerItem!: number;
@Column({ name: 'reputation_reward_per_item', type: 'integer' })
reputationRewardPerItem!: number;
@Column({ name: 'repeatable', type: 'boolean' })
repeatable!: boolean;
@Column({ name: 'enabled', type: 'boolean' })
enabled!: boolean;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
updatedAt!: Date;
@ManyToOne(() => ItemDefinition, { onDelete: 'RESTRICT' })
@JoinColumn({ name: 'item_definition_id' })
itemDefinition!: ItemDefinition;
@ManyToOne(() => ReputationFaction, { onDelete: 'RESTRICT' })
@JoinColumn({ name: 'faction_id' })
faction!: ReputationFaction;
}