139 lines
3.7 KiB
TypeScript
139 lines
3.7 KiB
TypeScript
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { UserWork, ListStatus } from './user-work.entity';
|
|
import { WorkService } from '../work/work.service';
|
|
import { UserService } from '../user/user.service';
|
|
|
|
const XP_ADD_WORK = 10;
|
|
const XP_PROGRESS = 5;
|
|
const XP_COMPLETE = 100;
|
|
const XP_SCORE = 15;
|
|
|
|
@Injectable()
|
|
export class ListService {
|
|
constructor(
|
|
@InjectRepository(UserWork)
|
|
private readonly userWorkRepo: Repository<UserWork>,
|
|
private readonly workService: WorkService,
|
|
private readonly userService: UserService,
|
|
) {}
|
|
|
|
async getUserList(userId: number, status?: ListStatus) {
|
|
const where: any = { userId };
|
|
if (status) where.status = status;
|
|
|
|
return this.userWorkRepo.find({
|
|
where,
|
|
relations: ['work'],
|
|
order: { updatedAt: 'DESC' },
|
|
});
|
|
}
|
|
|
|
async addToList(
|
|
userId: number,
|
|
anilistId: number,
|
|
status: ListStatus,
|
|
): Promise<UserWork> {
|
|
const work = await this.workService.findOrCreateFromAniList(anilistId);
|
|
|
|
const existing = await this.userWorkRepo.findOne({
|
|
where: { userId, workId: work.id },
|
|
});
|
|
if (existing) {
|
|
existing.status = status;
|
|
return this.userWorkRepo.save(existing);
|
|
}
|
|
|
|
const userWork = this.userWorkRepo.create({
|
|
userId,
|
|
workId: work.id,
|
|
status,
|
|
progress: 0,
|
|
});
|
|
const saved = await this.userWorkRepo.save(userWork);
|
|
await this.userService.addXp(userId, XP_ADD_WORK);
|
|
return saved;
|
|
}
|
|
|
|
async updateProgress(
|
|
userId: number,
|
|
userWorkId: number,
|
|
progress: number,
|
|
): Promise<UserWork> {
|
|
const uw = await this.userWorkRepo.findOne({
|
|
where: { id: userWorkId, userId },
|
|
relations: ['work'],
|
|
});
|
|
if (!uw) throw new NotFoundException('Entry not found');
|
|
|
|
const oldProgress = uw.progress;
|
|
uw.progress = progress;
|
|
|
|
const total = uw.work.totalEpisodes || uw.work.totalChapters;
|
|
if (total && progress >= total && uw.status !== ListStatus.COMPLETED) {
|
|
uw.status = ListStatus.COMPLETED;
|
|
uw.completedAt = new Date();
|
|
await this.userService.addXp(userId, XP_COMPLETE);
|
|
}
|
|
|
|
const saved = await this.userWorkRepo.save(uw);
|
|
|
|
if (progress > oldProgress) {
|
|
const delta = progress - oldProgress;
|
|
await this.userService.addXp(userId, delta * XP_PROGRESS);
|
|
}
|
|
|
|
return saved;
|
|
}
|
|
|
|
async updateStatus(
|
|
userId: number,
|
|
userWorkId: number,
|
|
status: ListStatus,
|
|
): Promise<UserWork> {
|
|
const uw = await this.userWorkRepo.findOne({
|
|
where: { id: userWorkId, userId },
|
|
});
|
|
if (!uw) throw new NotFoundException('Entry not found');
|
|
|
|
const wasAlreadyCompleted = uw.status === ListStatus.COMPLETED;
|
|
uw.status = status;
|
|
if (status === ListStatus.COMPLETED && !wasAlreadyCompleted) {
|
|
uw.completedAt = uw.completedAt || new Date();
|
|
await this.userService.addXp(userId, XP_COMPLETE);
|
|
}
|
|
|
|
return this.userWorkRepo.save(uw);
|
|
}
|
|
|
|
async setScore(
|
|
userId: number,
|
|
userWorkId: number,
|
|
score: number,
|
|
): Promise<UserWork> {
|
|
const uw = await this.userWorkRepo.findOne({
|
|
where: { id: userWorkId, userId },
|
|
});
|
|
if (!uw) throw new NotFoundException('Entry not found');
|
|
|
|
const hadScore = uw.score !== null;
|
|
uw.score = score;
|
|
const saved = await this.userWorkRepo.save(uw);
|
|
|
|
if (!hadScore) {
|
|
await this.userService.addXp(userId, XP_SCORE);
|
|
}
|
|
|
|
return saved;
|
|
}
|
|
|
|
async removeFromList(userId: number, userWorkId: number): Promise<void> {
|
|
const uw = await this.userWorkRepo.findOne({
|
|
where: { id: userWorkId, userId },
|
|
});
|
|
if (!uw) throw new NotFoundException('Entry not found');
|
|
await this.userWorkRepo.remove(uw);
|
|
}
|
|
}
|