• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bpatrik / pigallery2 / 17657946221

11 Sep 2025 09:35PM UTC coverage: 65.793% (+0.2%) from 65.569%
17657946221

push

github

bpatrik
Implementing projected album cache tests #1015

1306 of 2245 branches covered (58.17%)

Branch coverage included in aggregate %.

8 of 8 new or added lines in 2 files covered. (100.0%)

125 existing lines in 9 files now uncovered.

4791 of 7022 relevant lines covered (68.23%)

4354.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.61
/src/backend/model/database/AlbumManager.ts
1
import {SQLConnection} from './SQLConnection';
1✔
2
import {AlbumBaseEntity} from './enitites/album/AlbumBaseEntity';
1✔
3
import {AlbumBaseDTO} from '../../../common/entities/album/AlbumBaseDTO';
4
import {ObjectManagers} from '../ObjectManagers';
1✔
5
import {SearchQueryDTO} from '../../../common/entities/SearchQueryDTO';
6
import {SavedSearchEntity} from './enitites/album/SavedSearchEntity';
1✔
7
import {Logger} from '../../Logger';
1✔
8
import {IObjectManager} from './IObjectManager';
9
import {SessionContext} from '../SessionContext';
10
import {ProjectedAlbumCacheEntity} from './enitites/album/ProjectedAlbumCacheEntity';
1✔
11

12
const LOG_TAG = '[AlbumManager]';
1✔
13

14
export class AlbumManager implements IObjectManager {
1✔
15
  /**
16
   * Person table contains denormalized data that needs to update when isDBValid = false
17
   */
18
  private isDBValid:Record<string, boolean> = {};
237✔
19

20
  private static async updateAlbum(session: SessionContext, album: SavedSearchEntity): Promise<void> {
21
    const connection = await SQLConnection.getConnection();
34✔
22

23
    await ObjectManagers.getInstance().ProjectedCacheManager
34✔
24
      .setAndGetCacheForAlbum(connection, session, {
25
        id: album.id,
26
        searchQuery: album.searchQuery
27
      });
28

29
  }
30

31
  public async addIfNotExistSavedSearch(
32
    name: string,
33
    searchQuery: SearchQueryDTO,
34
    lockedAlbum: boolean
35
  ): Promise<void> {
36
    const connection = await SQLConnection.getConnection();
8✔
37
    const album = await connection
8✔
38
      .getRepository(SavedSearchEntity)
39
      .findOneBy({name, searchQuery});
40
    if (album) {
8!
UNCOV
41
      return;
×
42
    }
43
    await this.addSavedSearch(name, searchQuery, lockedAlbum);
8✔
44
  }
45

46
  public async addSavedSearch(
47
    name: string,
48
    searchQuery: SearchQueryDTO,
49
    lockedAlbum?: boolean
50
  ): Promise<void> {
51
    const connection = await SQLConnection.getConnection();
32✔
52
    const a = await connection
32✔
53
      .getRepository(SavedSearchEntity)
54
      .save({name, searchQuery, locked: lockedAlbum});
55
  }
56

57
  public async deleteAlbum(id: number): Promise<void> {
58
    const connection = await SQLConnection.getConnection();
4✔
59

60
    if (
4✔
61
      (await connection
62
        .getRepository(AlbumBaseEntity)
63
        .countBy({id, locked: false})) !== 1
64
    ) {
65
      throw new Error('Could not delete album, id:' + id);
2✔
66
    }
67

68
    await connection
2✔
69
      .getRepository(AlbumBaseEntity)
70
      .delete({id, locked: false});
71
  }
72

73
  public async getAlbums(session: SessionContext): Promise<AlbumBaseDTO[]> {
74
    await this.updateAlbums(session);
22✔
75
    const connection = await SQLConnection.getConnection();
22✔
76

77
    // Return albums with projected cache data
78
    return await connection
22✔
79
      .getRepository(AlbumBaseEntity)
80
      .createQueryBuilder('album')
81
      .leftJoin('album.cache', 'cache', 'cache.projectionKey = :pk AND cache.valid = 1', {pk: session.user.projectionKey})
82
      .leftJoin('cache.cover', 'cover')
83
      .leftJoin('cover.directory', 'directory')
84
      .select(['album', 'cache', 'cover.name', 'directory.name', 'directory.path'])
85
      .getMany();
86

87
  }
88

89
  public async onNewDataVersion(): Promise<void> {
90
    await this.resetCovers();
98✔
91
  }
92

93
  public async resetCovers(): Promise<void> {
94
    this.isDBValid = {};
100✔
95
    // Invalidate all album cache entries
96
    const connection = await SQLConnection.getConnection();
100✔
97
    await connection.getRepository(ProjectedAlbumCacheEntity)
100✔
98
      .createQueryBuilder()
99
      .update()
100
      .set({valid: false})
101
      .execute();
102
  }
103

104
  private async updateAlbums(session: SessionContext): Promise<void> {
105
    if (this.isDBValid[session.user.projectionKey] === true) {
22!
UNCOV
106
      return;
×
107
    }
108
    Logger.debug(LOG_TAG, 'Updating derived album data');
22✔
109
    const connection = await SQLConnection.getConnection();
22✔
110
    const albums = await connection
22✔
111
      .getRepository(SavedSearchEntity)
112
      .createQueryBuilder('album')
113
      .leftJoinAndSelect('album.cache', 'cache', 'cache.projectionKey = :pk AND cache.valid = 1', {pk: session.user.projectionKey})
114
      .getMany();
115

116
    for (const a of albums) {
22✔
117
      if (a.cache?.valid === true) {
34!
UNCOV
118
        continue;
×
119
      }
120
      await AlbumManager.updateAlbum(session, a);
34✔
121
      // giving back the control to the main event loop (Macrotask queue)
122
      // https://blog.insiderattack.net/promises-next-ticks-and-immediates-nodejs-event-loop-part-3-9226cbe7a6aa
123
      await new Promise(setImmediate);
34✔
124
    }
125
    this.isDBValid[session.user.projectionKey] = true;
22✔
126
  }
127

128
  async deleteAll() {
UNCOV
129
    const connection = await SQLConnection.getConnection();
×
UNCOV
130
    await connection
×
131
      .getRepository(AlbumBaseEntity)
132
      .createQueryBuilder('album')
133
      .delete()
134
      .execute();
135
  }
136
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc