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

bpatrik / pigallery2 / 17474164690

04 Sep 2025 07:08PM UTC coverage: 64.95% (-0.07%) from 65.017%
17474164690

push

github

bpatrik
Fix mysql error

1237 of 2172 branches covered (56.95%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

428 existing lines in 14 files now uncovered.

4626 of 6855 relevant lines covered (67.48%)

4444.94 hits per line

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

84.24
/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

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

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

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

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

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

67
  private static connection: Connection = null;
1✔
68

69

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

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

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

102
  public static async init(): Promise<void> {
103
    const connection = await this.getConnection();
197✔
104

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

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

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

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

166
  private static FIXED_SQL_TABLE = [
1✔
167
    'sqlite_sequence'
168
  ];
169

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

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

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

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

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

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

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

266

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