# Service logic

Before entering the <mark style="color:blue;">`ProductsService`</mark>, we should add the <mark style="color:blue;">`FilesModule`</mark> to the <mark style="color:blue;">`imports`</mark> of the <mark style="color:blue;">`ProductsModule`</mark>. This way, the <mark style="color:blue;">`StorageService`</mark> will be available for injection in its context.

Now, let's actually implement these methods, starting with <mark style="color:blue;">`uploadImages()`</mark>. Let's begin by simply searching for the entity, but not doing anything with it. This is done just to check its existence.

```typescript
await this.findOne(id);
```

We'll use certain path fragments many times, making it useful to have them as constants. So, let's create them in <mark style="color:purple;">file.constants</mark>.

```typescript
export const FilePath = {
  Products: {
    BASE: 'products',
    IMAGES: 'images',
  },
} as const satisfies Record<string, Record<string, string>>;
```

After that, back in the service, let's create the <mark style="color:blue;">`path`</mark> where the images will be saved.

```typescript
const { BASE, IMAGES } = FilePath.Products;
const path = join(BASE, id.toString(), IMAGES);
```

Then, as we can save many images, let's ensure that the amount of images that arrived, combined with the amount already present in the directory, don't exceed the limit. However, we should first verify that the folder actually exists, lest an error will be thrown.

```typescript
if (await pathExists(join(BASE_PATH, path))) {
  const incomingFilecount = files.length;
  const dirFilecount = await this.storageService.getDirFilecount(path);
  const totalFilecount = incomingFilecount + dirFilecount;
  
  this.storageService.validateFilecount(totalFilecount, MaxFileCount.PRODUCT_IMAGES);
}
```

We checked everything and have the <mark style="color:blue;">`path`</mark> in hand. Let's then create the directory where the files will be stored, in case it does not exist yet.

```typescript
await this.storageService.createDir(path);
```

Finally, let's save the images in parallel using <mark style="color:blue;">`Promise.all()`</mark>.

```typescript
await Promise.all(
  files.map((file) => this.storageService.saveFile(path, file)),
);
```

{% hint style="info" %}
Here, <mark style="color:blue;">`map()`</mark> is used instead of <mark style="color:blue;">`forEach()`</mark> due to the latter's unstable behavior with promises.
{% endhint %}

Now, the <mark style="color:blue;">`downloadImage()`</mark> method. The step of searching for the entity is identical. However, the <mark style="color:blue;">`path`</mark> also includes the name of the <mark style="color:blue;">`file`</mark> to be downloaded.

```typescript
const path = join(BASE, id.toString(), IMAGES, filename);
```

Then, it is checked if the <mark style="color:blue;">`file`</mark> actually exists.

```typescript
await this.storageService.validatePath(path);
```

Finally, the <mark style="color:blue;">`file`</mark> is returned.

```typescript
return this.storageService.getFile(path);
```

The last method is <mark style="color:blue;">`deleteImage()`</mark>, which is practically identical to <mark style="color:blue;">`downloadImage()`</mark>. The only difference is the last line, where the file is deleted.

```typescript
await this.storageService.delete(path);
```

One final improvement: let's make that, when removing a <mark style="color:blue;">`product`</mark>, its file-related folder is too. First, create an auxiliary method that performs this exclusion.

```typescript
private async deleteBaseDir(id: number) {
  const { BASE } = FilePath.Products;

  const path = join(BASE, id.toString());
  await this.storageService.delete(path);
}
```

Then, invoke it in the <mark style="color:blue;">`remove()`</mark> method. However, so that both can happen in atomicity, we should wrap this in a **transaction**.

```typescript
remove(id: number) {
  return this.dataSource.transaction(async (manager) => {
    const productsRepository = manager.getRepository(Product);

    const product = await productsRepository.findOneByOrFail({ id });
    await productsRepository.remove(product);

    await this.deleteBaseDir(id);

    return product;
  });
}
```

{% hint style="info" %}
Ensuring that both database and file system operations are in sync can be challenging, but as the nature of our operations is very simple (database operation followed by file system operation), a transaction is enough.
{% endhint %}

We have finished implementing the file-related methods in the service.

<mark style="color:green;">**Commit**</mark> - Implementing file related methods in service


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://kinesis-school-of-programming.gitbook.io/nestjs-unleashed/extra-module-4-file-management/file-logic/product-related-logic/service-logic.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
