This commit is contained in:
Bastian Wagner
2026-06-15 10:10:47 +02:00
parent cb938d3dc8
commit c2d2157de8
8 changed files with 100 additions and 14 deletions

View File

@@ -17,12 +17,14 @@ describe('ListsService', () => {
const originalApiKey = process.env.MISTRAL_API_KEY;
const originalAgentId = process.env.MISTRAL_AGENT_ID;
let service: ListsService;
let listsRepository: InMemoryRepository<UserListEntity>;
let usersRepository: InMemoryRepository<UserEntity>;
beforeEach(() => {
listsRepository = new InMemoryRepository<UserListEntity>();
usersRepository = new InMemoryRepository<UserEntity>();
service = new ListsService(
new InMemoryRepository<UserListEntity>() as never,
listsRepository as never,
new InMemoryRepository<UserListItemEntity>() as never,
new InMemoryRepository<UserListShareEntity>() as never,
usersRepository as never,
@@ -67,6 +69,16 @@ describe('ListsService', () => {
expect(updatedList.description).toBe('Fokusliste');
expect(deleteResponse.message).toBe('List deleted.');
await expect(service.listLists('user-1')).resolves.toHaveLength(0);
await expect(service.getList('user-1', list.id)).rejects.toThrow(
NotFoundException,
);
await expect(
service.updateList('user-1', list.id, { name: 'Wieder da' }),
).rejects.toThrow(NotFoundException);
expect(
(await listsRepository.find()).find((storedList) => storedList.id === list.id)
?.deletedAt,
).toBeInstanceOf(Date);
});
it('publishes realtime snapshots and deletions for the owning user', async () => {
@@ -155,6 +167,12 @@ describe('ListsService', () => {
await expect(service.deleteList('user-2', list.id)).rejects.toThrow(
ForbiddenException,
);
await service.deleteList('user-1', list.id);
await expect(service.listLists('user-2')).resolves.toHaveLength(0);
await expect(service.getList('user-2', list.id)).rejects.toThrow(
NotFoundException,
);
});
it('adds, updates, checks and deletes list items', async () => {

View File

@@ -8,7 +8,7 @@ import {
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { randomUUID } from 'crypto';
import { Repository } from 'typeorm';
import { IsNull, Repository } from 'typeorm';
import { AuditLogService } from '../audit/audit-log.service';
import {
ListTemplate,
@@ -78,6 +78,7 @@ export class ListsService {
name: this.requireName(createDto.name),
description: this.normalizeOptionalText(createDto.description),
kind: this.normalizeKind(createDto.kind),
deletedAt: null,
items: [],
});
@@ -119,6 +120,7 @@ export class ListsService {
? this.normalizeOptionalText(createDto.description)
: template.description,
kind: template.kind,
deletedAt: null,
items: template.items.map((item) =>
this.listItemsRepository.create({
id: randomUUID(),
@@ -157,7 +159,7 @@ export class ListsService {
async listLists(ownerId: string): Promise<UserList[]> {
const ownedLists = await this.listsRepository.find({
where: { ownerId },
where: { ownerId, deletedAt: IsNull() },
relations: { items: true, owner: true, shares: { user: true } },
order: { name: 'ASC', items: { position: 'ASC' } },
});
@@ -176,12 +178,12 @@ export class ListsService {
const sharedList =
share.list ??
(await this.listsRepository.findOne({
where: { id: share.listId },
where: { id: share.listId, deletedAt: IsNull() },
relations: { items: true, owner: true, shares: { user: true } },
order: { items: { position: 'ASC' } },
}));
if (sharedList) {
if (sharedList && !sharedList.deletedAt) {
await this.hydrateListAccessRelations(sharedList);
listsById.set(sharedList.id, sharedList);
}
@@ -245,7 +247,8 @@ export class ListsService {
kind: list.kind,
itemCount: list.items.length,
};
await this.listsRepository.remove(list);
list.deletedAt = new Date();
await this.listsRepository.save(list);
await this.auditLogService?.record({
actorUserId: ownerId,
@@ -503,7 +506,7 @@ export class ListsService {
listId: string,
): Promise<UserListEntity> {
const list = await this.listsRepository.findOne({
where: { id: listId },
where: { id: listId, deletedAt: IsNull() },
relations: { items: true, owner: true, shares: { user: true } },
order: { items: { position: 'ASC' } },
});
@@ -693,7 +696,7 @@ export class ListsService {
}
const list = await this.listsRepository.findOne({
where: { id: listId },
where: { id: listId, deletedAt: IsNull() },
relations: { items: true, owner: true, shares: { user: true } },
order: { items: { position: 'ASC' } },
});

View File

@@ -50,6 +50,10 @@ export class UserListEntity {
})
updatedAt!: Date;
@Index('IDX_user_lists_deleted_at')
@Column({ type: 'datetime', precision: 3, nullable: true })
deletedAt?: Date | null;
@ManyToOne(() => UserEntity, (user) => user.lists, {
onDelete: 'CASCADE',
})