You've already forked pigallery2
							
							
				mirror of
				https://github.com/bpatrik/pigallery2.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	adding sqlite support, removing mysql required dependency
This commit is contained in:
		| @@ -2,7 +2,7 @@ import {NextFunction, Request, Response} from "express"; | ||||
| import {ErrorCodes, ErrorDTO} from "../../common/entities/Error"; | ||||
| import {ObjectManagerRepository} from "../model/ObjectManagerRepository"; | ||||
| import {Logger} from "../Logger"; | ||||
| import {MySQLConnection} from "../model/mysql/MySQLConnection"; | ||||
| import {SQLConnection} from "../model/sql/SQLConnection"; | ||||
| import {DataBaseConfig, DatabaseType, ThumbnailConfig} from "../../common/config/private/IPrivateConfig"; | ||||
| import {Config} from "../../common/config/private/Config"; | ||||
| import {ConfigDiagnostics} from "../model/ConfigDiagnostics"; | ||||
| @@ -25,8 +25,8 @@ export class AdminMWs { | ||||
|     const databaseSettings = <DataBaseConfig>req.body.settings; | ||||
|  | ||||
|     try { | ||||
|       if (databaseSettings.type == DatabaseType.mysql) { | ||||
|         await MySQLConnection.tryConnection(databaseSettings); | ||||
|       if (databaseSettings.type != DatabaseType.memory) { | ||||
|         await SQLConnection.tryConnection(databaseSettings); | ||||
|       } | ||||
|       Config.Server.database = databaseSettings; | ||||
|       //only updating explicitly set config (not saving config set by the diagnostics) | ||||
| @@ -42,8 +42,8 @@ export class AdminMWs { | ||||
|       Logger.info(LOG_TAG, JSON.stringify(Config, null, '\t')); | ||||
|  | ||||
|       ObjectManagerRepository.reset(); | ||||
|       if (Config.Server.database.type == DatabaseType.mysql) { | ||||
|         await ObjectManagerRepository.InitMySQLManagers(); | ||||
|       if (Config.Server.database.type != DatabaseType.memory) { | ||||
|         await ObjectManagerRepository.InitSQLManagers(); | ||||
|       } else { | ||||
|         await ObjectManagerRepository.InitMemoryManagers(); | ||||
|       } | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import { | ||||
| import {Logger} from "../Logger"; | ||||
| import {NotificationManager} from "./NotifocationManager"; | ||||
| import {ProjectPath} from "../ProjectPath"; | ||||
| import {MySQLConnection} from "./mysql/MySQLConnection"; | ||||
| import {SQLConnection} from "./sql/SQLConnection"; | ||||
| import * as fs from "fs"; | ||||
| import {ClientConfig} from "../../common/config/public/ConfigClass"; | ||||
|  | ||||
| @@ -17,8 +17,8 @@ const LOG_TAG = "[ConfigDiagnostics]"; | ||||
| export class ConfigDiagnostics { | ||||
|  | ||||
|   static async testDatabase(databaseConfig: DataBaseConfig) { | ||||
|     if (databaseConfig.type == DatabaseType.mysql) { | ||||
|       await MySQLConnection.tryConnection(databaseConfig); | ||||
|     if (databaseConfig.type != DatabaseType.memory) { | ||||
|       await SQLConnection.tryConnection(databaseConfig); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -112,13 +112,13 @@ export class ConfigDiagnostics { | ||||
|  | ||||
|   static async runDiagnostics() { | ||||
|  | ||||
|     if (Config.Server.database.type == DatabaseType.mysql) { | ||||
|     if (Config.Server.database.type != DatabaseType.memory) { | ||||
|       try { | ||||
|         await ConfigDiagnostics.testDatabase(Config.Server.database); | ||||
|       } catch (err) { | ||||
|         Logger.warn(LOG_TAG, "[MYSQL error]", err); | ||||
|         Logger.warn(LOG_TAG, "Error during initializing mysql falling back temporally to memory DB"); | ||||
|         NotificationManager.warning("Error during initializing mysql falling back temporally to memory DB", err); | ||||
|         Logger.warn(LOG_TAG, "[SQL error]", err); | ||||
|         Logger.warn(LOG_TAG, "Error during initializing SQL falling back temporally to memory DB"); | ||||
|         NotificationManager.warning("Error during initializing SQL falling back temporally to memory DB", err); | ||||
|         Config.setDatabaseType(DatabaseType.memory); | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import {IUserManager} from "./interfaces/IUserManager"; | ||||
| import {IGalleryManager} from "./interfaces/IGalleryManager"; | ||||
| import {ISearchManager} from "./interfaces/ISearchManager"; | ||||
| import {MySQLConnection} from "./mysql/MySQLConnection"; | ||||
| import {SQLConnection} from "./sql/SQLConnection"; | ||||
| import {ISharingManager} from "./interfaces/ISharingManager"; | ||||
| import {Logger} from "../Logger"; | ||||
|  | ||||
| @@ -26,18 +26,18 @@ export class ObjectManagerRepository { | ||||
|     ObjectManagerRepository.getInstance().SharingManager = new SharingManager(); | ||||
|   } | ||||
|  | ||||
|   public static async InitMySQLManagers() { | ||||
|   public static async InitSQLManagers() { | ||||
|     await ObjectManagerRepository.reset(); | ||||
|     await MySQLConnection.init(); | ||||
|     const GalleryManager = require("./mysql/GalleryManager").GalleryManager; | ||||
|     const UserManager = require("./mysql/UserManager").UserManager; | ||||
|     const SearchManager = require("./mysql/SearchManager").SearchManager; | ||||
|     const SharingManager = require("./mysql/SharingManager").SharingManager; | ||||
|     await SQLConnection.init(); | ||||
|     const GalleryManager = require("./sql/GalleryManager").GalleryManager; | ||||
|     const UserManager = require("./sql/UserManager").UserManager; | ||||
|     const SearchManager = require("./sql/SearchManager").SearchManager; | ||||
|     const SharingManager = require("./sql/SharingManager").SharingManager; | ||||
|     ObjectManagerRepository.getInstance().GalleryManager = new GalleryManager(); | ||||
|     ObjectManagerRepository.getInstance().UserManager = new UserManager(); | ||||
|     ObjectManagerRepository.getInstance().SearchManager = new SearchManager(); | ||||
|     ObjectManagerRepository.getInstance().SharingManager = new SharingManager(); | ||||
|     Logger.debug("MySQL DB inited"); | ||||
|     Logger.debug("SQL DB inited"); | ||||
|   } | ||||
|  | ||||
|   public static getInstance() { | ||||
| @@ -48,7 +48,7 @@ export class ObjectManagerRepository { | ||||
|   } | ||||
|  | ||||
|   public static async reset() { | ||||
|     await MySQLConnection.close(); | ||||
|     await SQLConnection.close(); | ||||
|     this._instance = null; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import {DirectoryDTO} from "../../../common/entities/DirectoryDTO"; | ||||
| import * as path from "path"; | ||||
| import * as fs from "fs"; | ||||
| import {DirectoryEntity} from "./enitites/DirectoryEntity"; | ||||
| import {MySQLConnection} from "./MySQLConnection"; | ||||
| import {SQLConnection} from "./SQLConnection"; | ||||
| import {DiskManager} from "../DiskManger"; | ||||
| import {PhotoEntity, PhotoMetadataEntity} from "./enitites/PhotoEntity"; | ||||
| import {Utils} from "../../../common/Utils"; | ||||
| @@ -19,7 +19,7 @@ export class GalleryManager implements IGalleryManager { | ||||
|     relativeDirectoryName = path.normalize(path.join("." + path.sep, relativeDirectoryName)); | ||||
|     const directoryName = path.basename(relativeDirectoryName); | ||||
|     const directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep); | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName)); | ||||
|     const lastModified = Math.max(stat.ctime.getTime(), stat.mtime.getTime()); | ||||
|     let dir = await connection | ||||
| @@ -95,7 +95,7 @@ export class GalleryManager implements IGalleryManager { | ||||
|     return new Promise(async (resolve, reject) => { | ||||
|       try { | ||||
|         const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName); | ||||
|         const connection = await MySQLConnection.getConnection(); | ||||
|         const connection = await SQLConnection.getConnection(); | ||||
| 
 | ||||
|         //returning with the result
 | ||||
|         scannedDirectory.photos.forEach(p => p.readyThumbnails = []); | ||||
| @@ -1,16 +1,17 @@ | ||||
| import "reflect-metadata"; | ||||
| import {Connection, createConnection, getConnection} from "typeorm"; | ||||
| import {Connection, createConnection, DriverOptions, getConnection} from "typeorm"; | ||||
| import {UserEntity} from "./enitites/UserEntity"; | ||||
| import {UserRoles} from "../../../common/entities/UserDTO"; | ||||
| import {PhotoEntity, PhotoMetadataEntity} from "./enitites/PhotoEntity"; | ||||
| import {DirectoryEntity} from "./enitites/DirectoryEntity"; | ||||
| import {Config} from "../../../common/config/private/Config"; | ||||
| import {SharingEntity} from "./enitites/SharingEntity"; | ||||
| import {DataBaseConfig} from "../../../common/config/private/IPrivateConfig"; | ||||
| import {DataBaseConfig, DatabaseType} from "../../../common/config/private/IPrivateConfig"; | ||||
| import {PasswordHelper} from "../PasswordHelper"; | ||||
| import {ProjectPath} from "../../ProjectPath"; | ||||
| 
 | ||||
| 
 | ||||
| export class MySQLConnection { | ||||
| export class SQLConnection { | ||||
| 
 | ||||
|   constructor() { | ||||
| 
 | ||||
| @@ -22,16 +23,10 @@ export class MySQLConnection { | ||||
|   public static async getConnection(): Promise<Connection> { | ||||
| 
 | ||||
|     if (this.connection == null) { | ||||
| 
 | ||||
|       this.connection = await createConnection({ | ||||
|         name: "main", | ||||
|         driver: { | ||||
|           type: "mysql", | ||||
|           host: Config.Server.database.mysql.host, | ||||
|           port: 3306, | ||||
|           username: Config.Server.database.mysql.username, | ||||
|           password: Config.Server.database.mysql.password, | ||||
|           database: Config.Server.database.mysql.database | ||||
|         }, | ||||
|         driver: this.getDriver(Config.Server.database), | ||||
|         entities: [ | ||||
|           UserEntity, | ||||
|           DirectoryEntity, | ||||
| @@ -59,17 +54,30 @@ export class MySQLConnection { | ||||
|     } | ||||
|     const conn = await createConnection({ | ||||
|       name: "test", | ||||
|       driver: { | ||||
|       driver: this.getDriver(config) | ||||
|     }); | ||||
|     await conn.close(); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   private static getDriver(config: DataBaseConfig): DriverOptions { | ||||
|     let driver: DriverOptions = null; | ||||
|     if (config.type == DatabaseType.mysql) { | ||||
|       driver = { | ||||
|         type: "mysql", | ||||
|         host: config.mysql.host, | ||||
|         port: 3306, | ||||
|         username: config.mysql.username, | ||||
|         password: config.mysql.password, | ||||
|         database: config.mysql.database | ||||
|       } | ||||
|     }); | ||||
|     await conn.close(); | ||||
|     return true; | ||||
|       }; | ||||
|     } else if (config.type == DatabaseType.sqlite) { | ||||
|       driver = { | ||||
|         type: "sqlite", | ||||
|         storage: ProjectPath.getAbsolutePath(config.sqlite.storage) | ||||
|       }; | ||||
|     } | ||||
|     return driver; | ||||
|   } | ||||
| 
 | ||||
|   public static async init(): Promise<void> { | ||||
| @@ -1,7 +1,7 @@ | ||||
| import {AutoCompleteItem, SearchTypes} from "../../../common/entities/AutoCompleteItem"; | ||||
| import {ISearchManager} from "../interfaces/ISearchManager"; | ||||
| import {SearchResultDTO} from "../../../common/entities/SearchResultDTO"; | ||||
| import {MySQLConnection} from "./MySQLConnection"; | ||||
| import {SQLConnection} from "./SQLConnection"; | ||||
| import {PhotoEntity} from "./enitites/PhotoEntity"; | ||||
| import {DirectoryEntity} from "./enitites/DirectoryEntity"; | ||||
| import {PositionMetaData} from "../../../common/entities/PhotoDTO"; | ||||
| @@ -10,7 +10,7 @@ export class SearchManager implements ISearchManager { | ||||
| 
 | ||||
|   async autocomplete(text: string) { | ||||
| 
 | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
| 
 | ||||
|     let result: Array<AutoCompleteItem> = []; | ||||
|     let photoRepository = connection.getRepository(PhotoEntity); | ||||
| @@ -64,7 +64,7 @@ export class SearchManager implements ISearchManager { | ||||
|   } | ||||
| 
 | ||||
|   async  search(text: string, searchType: SearchTypes) { | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
| 
 | ||||
|     let result: SearchResultDTO = <SearchResultDTO>{ | ||||
|       searchText: text, | ||||
| @@ -119,7 +119,7 @@ export class SearchManager implements ISearchManager { | ||||
|   } | ||||
| 
 | ||||
|   async  instantSearch(text: string) { | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
| 
 | ||||
|     let result: SearchResultDTO = <SearchResultDTO>{ | ||||
|       searchText: text, | ||||
| @@ -1,31 +1,21 @@ | ||||
| import {ISharingManager} from "../interfaces/ISharingManager"; | ||||
| import {SharingDTO} from "../../../common/entities/SharingDTO"; | ||||
| import {MySQLConnection} from "./MySQLConnection"; | ||||
| import {SQLConnection} from "./SQLConnection"; | ||||
| import {SharingEntity} from "./enitites/SharingEntity"; | ||||
| import {Config} from "../../../common/config/private/Config"; | ||||
| import {PasswordHelper} from "../PasswordHelper"; | ||||
| 
 | ||||
| export class SharingManager implements ISharingManager { | ||||
| 
 | ||||
|   private async removeExpiredLink() { | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     return connection | ||||
|       .getRepository(SharingEntity) | ||||
|       .createQueryBuilder("share") | ||||
|       .where("expires < :now", {now: Date.now()}) | ||||
|       .delete() | ||||
|       .execute(); | ||||
|   } | ||||
| 
 | ||||
|   async findOne(filter: any): Promise<SharingDTO> { | ||||
|     await this.removeExpiredLink(); | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     return await connection.getRepository(SharingEntity).findOne(filter); | ||||
|   } | ||||
| 
 | ||||
|   async createSharing(sharing: SharingDTO): Promise<SharingDTO> { | ||||
|     await this.removeExpiredLink(); | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     if (sharing.password) { | ||||
|       sharing.password = PasswordHelper.cryptPassword(sharing.password); | ||||
|     } | ||||
| @@ -35,7 +25,7 @@ export class SharingManager implements ISharingManager { | ||||
|   } | ||||
| 
 | ||||
|   async updateSharing(inSharing: SharingDTO): Promise<SharingDTO> { | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
| 
 | ||||
|     let sharing = await connection.getRepository(SharingEntity).findOne({ | ||||
|       id: inSharing.id, | ||||
| @@ -54,5 +44,15 @@ export class SharingManager implements ISharingManager { | ||||
|     return await connection.getRepository(SharingEntity).persist(sharing); | ||||
|   } | ||||
| 
 | ||||
|   private async removeExpiredLink() { | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     return connection | ||||
|       .getRepository(SharingEntity) | ||||
|       .createQueryBuilder("share") | ||||
|       .where("expires < :now", {now: Date.now()}) | ||||
|       .delete() | ||||
|       .execute(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| import {UserDTO, UserRoles} from "../../../common/entities/UserDTO"; | ||||
| import {IUserManager} from "../interfaces/IUserManager"; | ||||
| import {UserEntity} from "./enitites/UserEntity"; | ||||
| import {MySQLConnection} from "./MySQLConnection"; | ||||
| import {SQLConnection} from "./SQLConnection"; | ||||
| import {PasswordHelper} from "../PasswordHelper"; | ||||
| 
 | ||||
| 
 | ||||
| @@ -12,7 +12,7 @@ export class UserManager implements IUserManager { | ||||
| 
 | ||||
| 
 | ||||
|   public async findOne(filter: any) { | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     let pass = filter.password; | ||||
|     delete filter.password; | ||||
|     const user = (await connection.getRepository(UserEntity).findOne(filter)); | ||||
| @@ -29,7 +29,7 @@ export class UserManager implements IUserManager { | ||||
|   }; | ||||
| 
 | ||||
|   public async find(filter: any) { | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     return (await connection.getRepository(UserEntity).find(filter)).map(user => { | ||||
|       if (user.permissions && user.permissions != null) { | ||||
|         user.permissions = <any>JSON.parse(<any>user.permissions); | ||||
| @@ -39,7 +39,7 @@ export class UserManager implements IUserManager { | ||||
|   } | ||||
| 
 | ||||
|   public async createUser(user: UserDTO) { | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     if (user.permissions && user.permissions != null) { | ||||
|       user.permissions = <any>JSON.stringify(<any>user.permissions); | ||||
|     } | ||||
| @@ -48,14 +48,14 @@ export class UserManager implements IUserManager { | ||||
|   } | ||||
| 
 | ||||
|   public async deleteUser(id: number) { | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     const user = await connection.getRepository(UserEntity).findOne({id: id}); | ||||
|     return await connection.getRepository(UserEntity).remove(user); | ||||
|   } | ||||
| 
 | ||||
|   public async changeRole(id: number, newRole: UserRoles) { | ||||
| 
 | ||||
|     const connection = await MySQLConnection.getConnection(); | ||||
|     const connection = await SQLConnection.getConnection(); | ||||
|     let userRepository = connection.getRepository(UserEntity); | ||||
|     const user = await userRepository.findOne({id: id}); | ||||
|     user.role = newRole; | ||||
| @@ -67,8 +67,8 @@ export class Server { | ||||
|  | ||||
|     DiskManager.init(); | ||||
|     ThumbnailGeneratorMWs.init(); | ||||
|     if (Config.Server.database.type == DatabaseType.mysql) { | ||||
|       await  ObjectManagerRepository.InitMySQLManagers(); | ||||
|     if (Config.Server.database.type != DatabaseType.memory) { | ||||
|       await  ObjectManagerRepository.InitSQLManagers(); | ||||
|     } else { | ||||
|       await  ObjectManagerRepository.InitMemoryManagers(); | ||||
|     } | ||||
|   | ||||
| @@ -1,13 +1,14 @@ | ||||
| import {ClientConfig} from "../public/ConfigClass"; | ||||
|  | ||||
| export enum DatabaseType{ | ||||
|   memory = 0, mysql = 1 | ||||
| export enum DatabaseType { | ||||
|   memory = 0, mysql = 1, sqlite = 2 | ||||
| } | ||||
|  | ||||
| export enum LogLevel { | ||||
|   error, warn, info, debug, verbose | ||||
| } | ||||
|  | ||||
| export enum ThumbnailProcessingLib{ | ||||
| export enum ThumbnailProcessingLib { | ||||
|   Jimp = 0, | ||||
|   gm = 1, | ||||
|   sharp = 2 | ||||
| @@ -19,18 +20,27 @@ export interface MySQLConfig { | ||||
|   username: string; | ||||
|   password: string; | ||||
| } | ||||
|  | ||||
| export interface SQLiteConfig { | ||||
|   storage: string; | ||||
| } | ||||
|  | ||||
| export interface DataBaseConfig { | ||||
|   type: DatabaseType; | ||||
|   mysql?: MySQLConfig; | ||||
|   sqlite?: SQLiteConfig; | ||||
| } | ||||
|  | ||||
| export interface ThumbnailConfig { | ||||
|   folder: string; | ||||
|   processingLibrary: ThumbnailProcessingLib; | ||||
|   qualityPriority: boolean; | ||||
| } | ||||
|  | ||||
| export interface SharingConfig { | ||||
|   updateTimeout: number; | ||||
| } | ||||
|  | ||||
| export interface ServerConfig { | ||||
|   port: number; | ||||
|   imagesFolder: string; | ||||
| @@ -42,6 +52,7 @@ export interface ServerConfig { | ||||
|   folderPreviewSize: number; | ||||
|   cachedFolderTimeout: number;//Do not rescans the folder if seems ok | ||||
| } | ||||
|  | ||||
| export interface IPrivateConfig { | ||||
|   Server: ServerConfig; | ||||
|   Client: ClientConfig.Config; | ||||
|   | ||||
| @@ -18,13 +18,16 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon | ||||
|     }, | ||||
|     sessionTimeout: 1000 * 60 * 60 * 24 * 7, | ||||
|     database: { | ||||
|       type: DatabaseType.mysql, | ||||
|       type: DatabaseType.sqlite, | ||||
|       mysql: { | ||||
|         host: "", | ||||
|         username: "", | ||||
|         password: "", | ||||
|         database: "" | ||||
|  | ||||
|       }, | ||||
|       sqlite: { | ||||
|         storage: "sqlite.db" | ||||
|       } | ||||
|     }, | ||||
|     sharing: { | ||||
|   | ||||
| @@ -10,6 +10,9 @@ | ||||
|         <option *ngFor="let type of types" [ngValue]="type.key">{{type.value}} | ||||
|         </option> | ||||
|       </select> | ||||
|       <span *ngIf="settings.type == DatabaseType.mysql" | ||||
|             class="help-block">Install manually mysql node module to use mysql (npm install mysql)</span> | ||||
|  | ||||
|       <ng-container *ngIf="settings.type == DatabaseType.mysql"> | ||||
|         <p class="title">MySQL settings:</p> | ||||
|         <input type="text" class="form-control" placeholder="Host" autofocus | ||||
| @@ -21,6 +24,11 @@ | ||||
|         <input type="password" class="form-control" placeholder="Password" | ||||
|                [(ngModel)]="settings.mysql.password" name="password"> | ||||
|       </ng-container> | ||||
|       <ng-container *ngIf="settings.type == DatabaseType.sqlite"> | ||||
|         <p class="title">SQLie settings:</p> | ||||
|         <input type="text" class="form-control" placeholder="storage" autofocus | ||||
|                [(ngModel)]="settings.sqlite.storage" name="host" required> | ||||
|       </ng-container> | ||||
|  | ||||
|     </form> | ||||
|     <button class="btn btn-success pull-right" | ||||
|   | ||||
| @@ -32,8 +32,8 @@ | ||||
|     "express": "4.15.3", | ||||
|     "flat-file-db": "1.0.0", | ||||
|     "jimp": "0.2.28", | ||||
|     "mysql": "2.13.0", | ||||
|     "reflect-metadata": "0.1.10", | ||||
|     "sqlite3": "^3.1.8", | ||||
|     "ts-exif-parser": "^0.1.23", | ||||
|     "ts-node-iptc": "^1.0.9", | ||||
|     "typeconfig": "1.0.4", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user