feat: Sprint 1 — backend fondations TetaRdPG
Auth SuperOAuth (JWT validation + httpOnly cookie), entités users/characters/level_thresholds, lazy calculation endurance, seed 100 niveaux, config prod-ready (trust proxy, helmet, CORS, rate limit). Validé : health 200, auth flow, character CRUD, endurance lazy, 401 sans cookie.
This commit is contained in:
97
src/character/character.service.ts
Normal file
97
src/character/character.service.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
Injectable,
|
||||
ConflictException,
|
||||
NotFoundException,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Character } from './entities/character.entity';
|
||||
import { LevelThreshold } from './entities/level-threshold.entity';
|
||||
import { CreateCharacterDto } from './dto/create-character.dto';
|
||||
import { User } from '../user/user.entity';
|
||||
|
||||
const STAT_POOL = 10; // 5 stats × 1 base + 5 points à distribuer
|
||||
const ENDURANCE_REGEN_MINUTES = 6; // 1 pt d'endurance toutes les 6 min = 10 pts/heure
|
||||
|
||||
@Injectable()
|
||||
export class CharacterService {
|
||||
constructor(
|
||||
@InjectRepository(Character)
|
||||
private readonly characterRepository: Repository<Character>,
|
||||
@InjectRepository(LevelThreshold)
|
||||
private readonly levelThresholdRepository: Repository<LevelThreshold>,
|
||||
) {}
|
||||
|
||||
// Pattern lazy calculation — pas de timer actif
|
||||
private calculateEndurance(character: Character): number {
|
||||
const elapsedMinutes =
|
||||
(Date.now() - character.lastEnduranceTs.getTime()) / 60_000;
|
||||
const recharge = Math.floor(elapsedMinutes / ENDURANCE_REGEN_MINUTES);
|
||||
return Math.min(character.enduranceSaved + recharge, character.enduranceMax);
|
||||
}
|
||||
|
||||
async create(dto: CreateCharacterDto, user: User): Promise<Character & { enduranceCurrent: number }> {
|
||||
const totalStats =
|
||||
dto.force + dto.agilite + dto.intelligence + dto.chance + dto.vitalite;
|
||||
|
||||
if (totalStats !== STAT_POOL) {
|
||||
throw new BadRequestException(
|
||||
`La somme des stats doit être égale à ${STAT_POOL} (reçu : ${totalStats})`,
|
||||
);
|
||||
}
|
||||
|
||||
const existing = await this.characterRepository.findOne({
|
||||
where: { userId: user.id },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('Ce joueur possède déjà un personnage');
|
||||
}
|
||||
|
||||
const character = this.characterRepository.create({
|
||||
userId: user.id,
|
||||
name: dto.name,
|
||||
force: dto.force,
|
||||
agilite: dto.agilite,
|
||||
intelligence: dto.intelligence,
|
||||
chance: dto.chance,
|
||||
vitalite: dto.vitalite,
|
||||
enduranceSaved: 100,
|
||||
lastEnduranceTs: new Date(),
|
||||
enduranceMax: 100,
|
||||
});
|
||||
|
||||
const saved = await this.characterRepository.save(character);
|
||||
return { ...saved, enduranceCurrent: this.calculateEndurance(saved) };
|
||||
}
|
||||
|
||||
async findByUser(user: User): Promise<Character & { enduranceCurrent: number }> {
|
||||
const character = await this.characterRepository.findOne({
|
||||
where: { userId: user.id },
|
||||
});
|
||||
|
||||
if (!character) {
|
||||
throw new NotFoundException('Aucun personnage trouvé pour ce joueur');
|
||||
}
|
||||
|
||||
return { ...character, enduranceCurrent: this.calculateEndurance(character) };
|
||||
}
|
||||
|
||||
async getEndurance(
|
||||
user: User,
|
||||
): Promise<{ enduranceCurrent: number; enduranceMax: number; rechargeRatePerHour: number }> {
|
||||
const character = await this.characterRepository.findOne({
|
||||
where: { userId: user.id },
|
||||
});
|
||||
|
||||
if (!character) {
|
||||
throw new NotFoundException('Aucun personnage trouvé pour ce joueur');
|
||||
}
|
||||
|
||||
return {
|
||||
enduranceCurrent: this.calculateEndurance(character),
|
||||
enduranceMax: character.enduranceMax,
|
||||
rechargeRatePerHour: 60 / ENDURANCE_REGEN_MINUTES,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user