import * as express from 'express'; import { NextFunction, Request, Response } from 'express'; import { PrivateConfigClass } from '../../../common/config/private/PrivateConfigClass'; import { ObjectManagers } from '../ObjectManagers'; import { ProjectPathClass } from '../../ProjectPath'; import { ILogger } from '../../Logger'; import { UserDTO, UserRoles } from '../../../common/entities/UserDTO'; import { ParamsDictionary } from 'express-serve-static-core'; import { Connection, Repository } from 'typeorm'; import { DynamicConfig } from '../../../common/entities/DynamicConfig'; import { MediaDTOWithThPath } from '../messenger/Messenger'; import { PhotoMetadata } from '../../../common/entities/PhotoDTO'; import { VideoMetadata } from '../../../common/entities/VideoDTO'; import { MediaRendererInput, SvgRendererInput } from '../fileaccess/PhotoWorker'; import { SearchQueryDTO } from '../../../common/entities/SearchQueryDTO'; import { CoverPhotoDTOWithID } from '../database/CoverManager'; import { ParentDirectoryDTO } from '../../../common/entities/DirectoryDTO'; import { DirectoryScanSettings } from '../fileaccess/DiskManager'; import { SessionContext } from '../SessionContext'; import { IClientMediaButtonConfig, IMediaRequestBodyData } from '../../../common/entities/extension/IClientUIConfig'; import { MediaEntity } from '../database/enitites/MediaEntity'; import { VideoConverterInput } from '../fileaccess/VideoConverterWorker'; import { ProjectedDirectoryCacheEntity } from '../database/enitites/ProjectedDirectoryCacheEntity'; export type IExtensionBeforeEventHandler = (input: I, event: { stopPropagation: boolean; }) => Promise; /** * input: is the original input: this is output of all before handler. This value was also piped to app's function * output: is the output of the app's function or the previous after handler */ export type IExtensionAfterEventHandler = (data: { input: I; output: O; }) => Promise; export interface IExtensionEvent { before: (handler: IExtensionBeforeEventHandler) => void; after: (handler: IExtensionAfterEventHandler) => void; } /** * All main event callbacks in the app */ export interface IExtensionEvents { gallery: { /** * Events for Directory and Album covers */ CoverManager: { getCoverForAlbum: IExtensionEvent<[ session: SessionContext, { searchQuery: SearchQueryDTO; } ], CoverPhotoDTOWithID>; getCoverForDirectory: IExtensionEvent<[ session: SessionContext, { id: number; name: string; path: string; } ], CoverPhotoDTOWithID>; }; ProjectedCacheManager: { /** * Invalidates directory covers and caches for a given directory and every parent */ invalidateDirectoryCache: IExtensionEvent<[ParentDirectoryDTO], void>; /** * Returns the projected directory cache for a given directory * ProjectedDirectoryCacheEntity contains information, like the number of photos, videos, etc.. */ getCacheForDirectory: IExtensionEvent<[ connection: Connection, session: SessionContext, dir: { id: number; name: string; path: string; } ], ProjectedDirectoryCacheEntity>; }; VideoConverter: { /** * Converts videos with ffmpeg */ convert: IExtensionEvent<[VideoConverterInput], void>; }; ImageRenderer: { /** * Renders a thumbnail or photo */ render: IExtensionEvent<[MediaRendererInput | SvgRendererInput], void>; }; /** * Reads exif, iptc, etc.. metadata for photos/videos */ MetadataLoader: { loadVideoMetadata: IExtensionEvent<[string], VideoMetadata>; loadPhotoMetadata: IExtensionEvent<[string], PhotoMetadata>; }; /** * Scans the storage for a given directory and returns the list of child directories, * photos, videos and metafiles */ DiskManager: { excludeDir: IExtensionEvent<[ { name: string; parentDirRelativeName: string; parentDirAbsoluteName: string; } ], boolean>; scanDirectory: IExtensionEvent<[ string, DirectoryScanSettings ], ParentDirectoryDTO>; }; }; } export interface IExtensionApp { expressApp: express.Express; objectManagers: ObjectManagers; config: PrivateConfigClass; } export interface IMediaRequestBody { data: IMediaRequestBodyData; media: string; } export interface IExtensionRESTRoute { /** * Looks for req.body.media for the media path and calls the callback with that media entry. * Sends a pigallery2 standard JSON object with payload or error message back to the client. * @param paths RESTapi path, relative to the extension base endpoint * @param invalidateDirectory set to false to prevent invalidating the directory. * Invalidation is resource-intensive and should be avoided if media or its directory is not changed. * @param minRole set to null to omit auer check (ie make the endpoint public) * @param cb function callback * @return newly added REST api path */ mediaJsonResponse(paths: string[], minRole: UserRoles, invalidateDirectory: boolean, cb: (params: ParamsDictionary, body: IMediaRequestBody, user: UserDTO, media: MediaEntity, repository: Repository) => Promise | unknown): string; /** * Sends a pigallery2 standard JSON object with payload or error message back to the client. * @param paths RESTapi path, relative to the extension base endpoint * @param minRole set to null to omit auer check (ie make the endpoint public) * @param cb function callback * @return newly added REST api path */ jsonResponse(paths: string[], minRole: UserRoles, cb: (params?: ParamsDictionary, body?: any, user?: UserDTO) => Promise | unknown): string; /** * Exposes a standard expressjs middleware * @param paths RESTapi path, relative to the extension base endpoint * @param minRole set to null to omit auer check (ie make the endpoint public) * @param mw expressjs middleware * @return newly added REST api path */ rawMiddleware(paths: string[], minRole: UserRoles, mw: (req: Request, res: Response, next: NextFunction) => void | Promise): string; } export interface IExtensionRESTApi { use: IExtensionRESTRoute; get: IExtensionRESTRoute; post: IExtensionRESTRoute; put: IExtensionRESTRoute; delete: IExtensionRESTRoute; } export interface IExtensionDB { /** * Returns with a typeorm SQL connection */ getSQLConnection(): Promise; /** * Adds SQL tables to typeorm * @param tables */ setExtensionTables(tables: Function[]): Promise; /** * Exposes all tables. You can use this if you van to have a foreign key to a built-in table. * Use with caution. This exposes the app's internal working. */ _getAllTables(): Function[]; } export interface IExtensionConfig { getConfig(): C; } export interface IExtensionMessengers { /** * Adds a new messenger that the user can select e.g.: for sending top pick photos * @param name Name of the messenger (also used as id) * @param config config metadata for this messenger * @param callbacks messenger logic */ addMessenger = Record>(name: string, config: DynamicConfig[], callbacks: { sendMedia: (config: C, media: MediaDTOWithThPath[]) => Promise; }): void; } export interface IExtensionObject { /** * ID of the extension that is internally used. By default, the name and ID matches if there is no collision. */ extensionId: string; /** * Name of the extension */ extensionName: string; /** * Inner functionality of the app. Use this with caution. * If you want to go deeper than the standard exposed APIs, you can try doing so here. */ _app: IExtensionApp; /** * Create extension related configuration */ config: IExtensionConfig; /** * Create new SQL tables and access SQL connection */ db: IExtensionDB; /** * Paths to the main components of the app. */ paths: ProjectPathClass; /** * Logger of the app */ Logger: ILogger; /** * Main app events. Use this change indexing, cover or serving gallery */ events: IExtensionEvents; /** * Use this to define REST calls related to the extension */ RESTApi: IExtensionRESTApi; /** * Object to manipulate messengers. * Messengers are used to send messages (like emails) from the app. * One type of message is a list of selected photos. */ messengers: IExtensionMessengers; /** * Use this to add UI changes to the app. * This is the place to add new buttons, change the UI, etc.. */ ui: IUIExtension; } export interface IUIExtension { /** * Adds a new button on to UI to all media (photo, video). * Implement the server-side click action in the serverSB function. * @param buttonConfig * @param serverSB If not set the button will be a fake button (i.e.: only show up not clickable) */ addMediaButton(buttonConfig: IClientMediaButtonConfig, serverSB?: (params: ParamsDictionary, body: IMediaRequestBody, user: UserDTO, media: MediaEntity, repository: Repository) => Promise): void; } export interface IExtensionConfigInit { /** * Sets the config tempalte class * @param template */ setConfigTemplate(template: new () => C): void; } /** * Extension interface. All extension is expected to implement and export these methods. * This is the content of the server.js file */ export interface IServerExtension { cleanUp?: (extension: IExtensionObject) => Promise; /** * Extension init function. Extension should at minimum expose this function. * @param extension */ init(extension: IExtensionObject): Promise; } /** * Extension config interface. All extensions can implement and export these methods. * This is the content of the config.js file. */ export interface IServerExtensionConfig { /** * This function can be called any time. It should only set the config template class * @param extension */ initConfig(extension: IExtensionConfigInit): void; }