aboutsummaryrefslogtreecommitdiffstats
path: root/src/file/service/file.service.ts
blob: 24cb55ddc51fbe3470a51d6e4ee9795c45fe9c94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { FileMetadata } from '../entity/file.entity';
import { FilePurpose } from '../entity/purpose.entity';
import { DataSource, Repository } from 'typeorm';
import { Session } from '../../session/entity/session.entity';
import { StorageService } from 'src/storage/service/storage.service';

@Injectable()
export class FileService {
  constructor(
    @InjectRepository(FileMetadata)
    private fileMetadataRepository: Repository<FileMetadata>,

    @InjectRepository(FilePurpose)
    private filePurposeRepository: Repository<FilePurpose>,

    private readonly storageService: StorageService,
    private readonly dataSource: DataSource,
  ) {}

  async createFile(
    b64Contents: string,
    purpose: string,
    validTo: Date,
    session: Session,
  ): Promise<FileMetadata | null> {
    const filePurpose = await this.filePurposeRepository.findOneBy({ name: purpose });
    if (!filePurpose) {
      throw new NotFoundException(`Unknown file purpose: ${filePurpose}`);
    }

    const file = new FileMetadata();
    file.purpose = filePurpose;
    file.session = session;
    file.validUntil = validTo;

    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();
    try {
      await this.fileMetadataRepository.save(file);
      await this.storageService.createFile(b64Contents, file);

      await queryRunner.commitTransaction();
    } catch (err) {
      await queryRunner.rollbackTransaction();

      throw err;
    } finally {
      queryRunner.release();
    }

    return file;
  }

  async markFileAsUsed(fileKey: string): Promise<boolean> {
    const update = await this.fileMetadataRepository
      .createQueryBuilder()
      .update({
        used: true,
      })
      .where('validUntil > :date', {
        date: new Date(Date.now()),
      })
      .andWhere({
        key: fileKey,
        used: false,
      })
      .execute();

    return update.affected > 0;
  }

  async fetchFileContents(fileKey: string): Promise<string | null> {
    const file = await this.fileMetadataRepository.findOne({
      relations: {
        contents: true,
      },
      where: {
        key: fileKey,
      },
    });

    return file?.contents?.b64Contents;
  }

  async findManyValid(sessionToken: string): Promise<FileMetadata[]> {
    return this.fileMetadataRepository
      .createQueryBuilder('file')
      .innerJoinAndSelect('file.session', 'session', 'session.token = :token', { token: sessionToken })
      .leftJoinAndSelect('file.purpose', 'purpose')
      .where('file.used = :used', { used: false })
      .getMany();
  }
}