import { Injectable } from '@nestjs/common'; import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; import { EntityManager, Repository, TreeRepository } from 'typeorm'; import assert from 'assert'; import { NewItemInput } from './dto/new-item.input'; import { EditItemInput } from './dto/edit-item.input'; import { EANService } from './ean/ean.service'; import { Item } from './items.entity'; import { ItemModel, PaginatedItems } from './items.model'; @Injectable() export class ItemsService { constructor( @InjectRepository(Item) private itemRepository: Repository, @InjectEntityManager() private entityManager: EntityManager, private eans: EANService, ) { this.treeRepository = this.entityManager.getTreeRepository(Item); } treeRepository: TreeRepository; async getItem(id: string): Promise { return this.itemRepository.findOne(this.eans.toID(id)); } async getItemParent(item: ItemModel): Promise { const childItem = await this.itemRepository.findOneOrFail( this.eans.toID(item.id), { relations: ['parent'], }, ); return childItem.parent; } async getItemAncestors(item: ItemModel): Promise { const id = this.eans.toID(item.id); const childItem = await this.itemRepository.findOneOrFail(id, { relations: ['parent'], }); return (await this.treeRepository.findAncestors(childItem)).filter( (anc) => anc.id !== id, ); } async getItemChildren(item: ItemModel): Promise { return this.itemRepository.find({ where: { parent: this.eans.toID(item.id), }, }); } async getItemDescendants(item: ItemModel): Promise { const id = this.eans.toID(item.id); const parentItem = await this.itemRepository.findOneOrFail(id, { relations: ['parent'], }); return (await this.treeRepository.findDescendants(parentItem)).filter( (des) => des.id !== id, ); } async getItemList(cursor: number = 0): Promise { const [items, count] = await this.itemRepository.findAndCount({ order: { id: 'ASC' }, take: 25, skip: cursor, }); return { nodes: items, cursor: cursor + items.length, totalCount: count, hasNextPage: count > cursor + items.length, }; } async createItem(input: NewItemInput): Promise { // must use Repository.save() for the closure table to work, // so we have to check whether the provided ID exists in the first place if ( input.id && (await this.itemRepository.count({ id: this.eans.toID(input.id) })) !== 0 ) { throw new Error('Item with this ID already exists'); } const highestId = input.id ? // nobody cares in this case null : ( await this.itemRepository.findOne({ select: ['id'], order: { id: 'DESC' }, }) )?.id || '139999999999'; const item = this.itemRepository.create({ ...input, // if id not provided, use the highest one in db +1 id: highestId ? (BigInt(highestId) + 1n).toString(10) : // @ts-ignore input.id must exist here this.eans.toID(input.id), name: input.name.trim(), notes: input.notes ? input.notes.trim() || undefined : undefined, parent: input.parent ? await this.itemRepository.findOneOrFail(this.eans.toID(input.parent)) : undefined, }); assert(item.name.length > 0, 'You must provide item name'); await this.itemRepository.save(item); return item; } async editItem(input: EditItemInput) { const item = await this.itemRepository.findOneOrFail( this.eans.toID(input.id), ); const edited: Item = { ...item, ...input, // input.id can be an EAN-13 id: item.id, parent: input.parent ? await this.itemRepository.findOneOrFail(this.eans.toID(input.parent)) : undefined, }; await this.itemRepository.save(edited); return edited; } async deleteItem(id: string) { const item = await this.itemRepository.findOne(this.eans.toID(id)); if (!item) { return null; } // will fail if item has children await this.itemRepository.remove(item); return item; } }