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

bpatrik / pigallery2 / 17595229310

09 Sep 2025 08:47PM UTC coverage: 65.569% (+0.5%) from 65.073%
17595229310

push

github

bpatrik
Fix tests #1015

1289 of 2225 branches covered (57.93%)

Branch coverage included in aggregate %.

4742 of 6973 relevant lines covered (68.01%)

4378.93 hits per line

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

84.34
/src/backend/model/database/SQLConnection.ts
1
import 'reflect-metadata';
1✔
2
import {Connection, createConnection, DataSourceOptions, getConnection, LoggerOptions,} from 'typeorm';
1✔
3
import {UserEntity} from './enitites/UserEntity';
1✔
4
import {UserRoles} from '../../../common/entities/UserDTO';
1✔
5
import {PhotoEntity} from './enitites/PhotoEntity';
1✔
6
import {DirectoryEntity} from './enitites/DirectoryEntity';
1✔
7
import {Config} from '../../../common/config/private/Config';
1✔
8
import {SharingEntity} from './enitites/SharingEntity';
1✔
9
import {PasswordHelper} from '../PasswordHelper';
1✔
10
import {ProjectPath} from '../../ProjectPath';
1✔
11
import {VersionEntity} from './enitites/VersionEntity';
1✔
12
import {Logger} from '../../Logger';
1✔
13
import {MediaEntity} from './enitites/MediaEntity';
1✔
14
import {VideoEntity} from './enitites/VideoEntity';
1✔
15
import {DataStructureVersion} from '../../../common/DataStructureVersion';
1✔
16
import {FileEntity} from './enitites/FileEntity';
1✔
17
import {PersonEntry} from './enitites/PersonEntry';
1✔
18
import {Utils} from '../../../common/Utils';
1✔
19
import * as path from 'path';
1✔
20
import {DatabaseType, ServerDataBaseConfig, SQLLogLevel,} from '../../../common/config/private/PrivateConfig';
1✔
21
import {AlbumBaseEntity} from './enitites/album/AlbumBaseEntity';
1✔
22
import {SavedSearchEntity} from './enitites/album/SavedSearchEntity';
1✔
23
import {NotificationManager} from '../NotifocationManager';
1✔
24
import {PersonJunctionTable} from './enitites/PersonJunctionTable';
1✔
25
import {MDFileEntity} from './enitites/MDFileEntity';
1✔
26
import { ProjectedDirectoryCacheEntity } from './enitites/ProjectedDirectoryCacheEntity';
1✔
27
import { ProjectedPersonCacheEntity } from './enitites/ProjectedPersonCacheEntity';
1✔
28

29
const LOG_TAG = '[SQLConnection]';
1✔
30

31
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
32

33
export class SQLConnection {
1✔
34
  // eslint-disable-next-line @typescript-eslint/ban-types
35
  public static getEntries(): Function[] {
36
    return this.entries;
×
37
  }
38

39
  // eslint-disable-next-line @typescript-eslint/ban-types
40
  public static async addEntries(tables: Function[]) {
41
    if (!tables?.length) {
×
42
      return;
×
43
    }
44
    await this.close();
×
45
    this.entries = Utils.getUnique(this.entries.concat(tables));
×
46
    await (await this.getConnection()).synchronize();
×
47
  }
48

49
  // eslint-disable-next-line @typescript-eslint/ban-types
50
  private static entries: Function[] = [
1✔
51
    UserEntity,
52
    FileEntity,
53
    MDFileEntity,
54
    PersonJunctionTable,
55
    PersonEntry,
56
    MediaEntity,
57
    PhotoEntity,
58
    VideoEntity,
59
    DirectoryEntity,
60
    SharingEntity,
61
    AlbumBaseEntity,
62
    SavedSearchEntity,
63
    VersionEntity,
64
    // projection-aware cache entries
65
    ProjectedDirectoryCacheEntity,
66
    ProjectedPersonCacheEntity
67
  ];
68

69
  private static connection: Connection = null;
1✔
70

71

72
  public static async getConnection(): Promise<Connection> {
73
    if (this.connection == null) {
2,247✔
74
      const options = this.getDriver(Config.Database);
424✔
75

76
      Logger.debug(
424✔
77
        LOG_TAG,
78
        'Creating connection: ' + DatabaseType[Config.Database.type],
79
        ', with driver:',
80
        options.type
81
      );
82
      this.connection = await this.createConnection(options);
424✔
83
      await SQLConnection.schemeSync(this.connection);
424✔
84
    }
85
    return this.connection;
2,247✔
86
  }
87

88
  public static async tryConnection(
89
    config: ServerDataBaseConfig
90
  ): Promise<boolean> {
91
    try {
31✔
92
      await getConnection('test').close();
31✔
93
      // eslint-disable-next-line no-empty
94
    } catch (err) {
95
    }
96
    const options = this.getDriver(config);
31✔
97
    options.name = 'test';
31✔
98
    const conn = await this.createConnection(options);
31✔
99
    await SQLConnection.schemeSync(conn);
31✔
100
    await conn.close();
31✔
101
    return true;
31✔
102
  }
103

104
  public static async init(): Promise<void> {
105
    const connection = await this.getConnection();
199✔
106

107
    if (Config.Users.authenticationRequired !== true) {
199✔
108
      return;
12✔
109
    }
110
    // Adding enforced users to the db
111
    const userRepository = connection.getRepository(UserEntity);
187✔
112
    if (
187✔
113
      Array.isArray(Config.Users.enforcedUsers) &&
374✔
114
      Config.Users.enforcedUsers.length > 0
115
    ) {
116
      for (let i = 0; i < Config.Users.enforcedUsers.length; ++i) {
62✔
117
        const uc = Config.Users.enforcedUsers[i];
62✔
118
        const user = await userRepository.findOneBy({name: uc.name});
62✔
119
        if (!user) {
62✔
120
          Logger.info(LOG_TAG, 'Saving enforced user: ' + uc.name);
62✔
121
          const a = new UserEntity();
62✔
122
          a.name = uc.name;
62✔
123
          a.password = uc.encryptedPassword;
62✔
124
          a.role = uc.role;
62✔
125
          await userRepository.save(a);
62✔
126
        }
127
      }
128
    }
129

130
    // Add dummy Admin to the db
131
    const admins = await userRepository.findBy({role: UserRoles.Admin});
187✔
132
    const devs = await userRepository.findBy({role: UserRoles.Developer});
187✔
133
    if (admins.length === 0 && devs.length === 0) {
187✔
134
      const a = new UserEntity();
165✔
135
      a.name = 'admin';
165✔
136
      a.password = PasswordHelper.cryptPassword('admin');
165✔
137
      a.role = UserRoles.Admin;
165✔
138
      await userRepository.save(a);
165✔
139
    }
140

141
    const defAdmin = await userRepository.findOneBy({
187✔
142
      name: 'admin',
143
      role: UserRoles.Admin,
144
    });
145
    if (
187✔
146
      defAdmin &&
374✔
147
      PasswordHelper.comparePassword('admin', defAdmin.password)
148
    ) {
149
      NotificationManager.error(
187✔
150
        'Using default admin user!',
151
        'You are using the default admin/admin user/password, please change or remove it.'
152
      );
153
    }
154
  }
155

156
  public static async close(): Promise<void> {
157
    try {
501✔
158
      if (this.connection != null) {
501✔
159
        await this.connection.close();
424✔
160
        this.connection = null;
424✔
161
      }
162
    } catch (err) {
163
      console.error('Error during closing sql db:');
×
164
      console.error(err);
×
165
    }
166
  }
167

168
  private static FIXED_SQL_TABLE = [
1✔
169
    'sqlite_sequence'
170
  ];
171

172
  /**
173
   * Clears up the DB from unused tables. use it when the entities list are up-to-date (extensions won't add any new)
174
   */
175
  public static async removeUnusedTables() {
176
    const conn = await this.getConnection();
×
177
    const validTableNames = this.entries.map(e => conn.getRepository(e).metadata.tableName).concat(this.FIXED_SQL_TABLE);
×
178
    let currentTables: string[];
179

180
    if (Config.Database.type === DatabaseType.sqlite) {
×
181
      currentTables = (await conn.query('SELECT name FROM sqlite_master  WHERE type=\'table\''))
×
182
        .map((r: { name: string }) => r.name);
×
183
    } else {
184
      currentTables = (await conn.query(`SELECT table_name
×
185
                                         FROM information_schema.tables ` +
186
        `WHERE table_schema = '${Config.Database.mysql.database}'`))
187
        .map((r: { table_name: string }) => r.table_name);
×
188
    }
189

190
    const tableToDrop = currentTables.filter(ct => !validTableNames.includes(ct));
×
191
    for (let i = 0; i < tableToDrop.length; ++i) {
×
192
      await conn.query('DROP TABLE ' + tableToDrop[i]);
×
193
    }
194
  }
195

196
  public static getSQLiteDB(config: ServerDataBaseConfig): string {
197
    return path.join(ProjectPath.getAbsolutePath(config.dbFolder), 'sqlite.db');
26✔
198
  }
199

200
  private static async createConnection(
201
    options: DataSourceOptions
202
  ): Promise<Connection> {
203
    if (options.type === 'sqlite' || options.type === 'better-sqlite3') {
455✔
204
      return await createConnection(options);
164✔
205
    }
206
    try {
291✔
207
      return await createConnection(options);
291✔
208
    } catch (e) {
209
      if (e.sqlMessage === 'Unknown database \'' + options.database + '\'') {
110✔
210
        Logger.debug(LOG_TAG, 'creating database: ' + options.database);
110✔
211
        const tmpOption = Utils.clone(options);
110✔
212
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
213
        // @ts-ignore
214
        delete tmpOption.database;
110✔
215
        const tmpConn = await createConnection(tmpOption);
110✔
216
        await tmpConn.query(
110✔
217
          'CREATE DATABASE IF NOT EXISTS ' + options.database
218
        );
219
        await tmpConn.close();
110✔
220
        return await createConnection(options);
110✔
221
      }
222
      throw e;
×
223
    }
224
  }
225

226
  private static async schemeSync(connection: Connection): Promise<void> {
227
    let version = null;
455✔
228
    try {
455✔
229
      version = (await connection.getRepository(VersionEntity).find())[0];
455✔
230
      // eslint-disable-next-line no-empty
231
    } catch (ex) {
232
    }
233
    if (version && version.version === DataStructureVersion) {
455✔
234
      return;
237✔
235
    }
236
    Logger.info(LOG_TAG, 'Updating database scheme');
218✔
237
    if (!version) {
218✔
238
      version = new VersionEntity();
217✔
239
    }
240
    version.version = DataStructureVersion;
218✔
241

242
    let users: UserEntity[] = [];
218✔
243
    try {
218✔
244
      users = await connection
218✔
245
        .getRepository(UserEntity)
246
        .createQueryBuilder('user')
247
        .getMany();
248
      // eslint-disable-next-line no-empty
249
    } catch (ex) {
250
    }
251
    await connection.dropDatabase();
218✔
252
    await connection.synchronize();
218✔
253
    await connection.getRepository(VersionEntity).save(version);
218✔
254
    try {
218✔
255
      await connection.getRepository(UserEntity).save(users);
218✔
256
    } catch (e) {
257
      await connection.dropDatabase();
×
258
      await connection.synchronize();
×
259
      await connection.getRepository(VersionEntity).save(version);
×
260
      Logger.warn(
×
261
        LOG_TAG,
262
        'Could not move users to the new db scheme, deleting them. Details:' +
263
        e.toString()
264
      );
265
    }
266
  }
267

268

269
  private static getDriver(config: ServerDataBaseConfig): Writeable<DataSourceOptions> {
270
    let driver: Writeable<DataSourceOptions>;
271
    if (config.type === DatabaseType.mysql) {
455✔
272
      driver = {
291✔
273
        type: 'mysql',
274
        host: config.mysql.host,
275
        port: config.mysql.port,
276
        username: config.mysql.username,
277
        password: config.mysql.password,
278
        database: config.mysql.database,
279
        charset: 'utf8mb4',
280
      };
281
    } else if (config.type === DatabaseType.sqlite) {
164✔
282
      driver = {
164✔
283
        type: 'better-sqlite3',
284
        database: path.join(
285
          ProjectPath.getAbsolutePath(config.dbFolder),
286
          config.sqlite.DBFileName
287
        ),
288
      };
289
    }
290
    driver.entities = this.entries;
455✔
291
    driver.synchronize = false;
455✔
292
    if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
455✔
293
      driver.logging = SQLLogLevel[Config.Server.Log.sqlLevel] as LoggerOptions;
455✔
294
    }
295
    return driver;
455✔
296
  }
297
}
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