return res.type('json').status(204).end()
}
-async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function listBlacklist (req: express.Request, res: express.Response) {
const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.type)
- return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total))
+ return res.json(getFormattedObjects(resultList.data, resultList.total))
}
-async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) {
const videoBlacklist = res.locals.videoBlacklist
const video = res.locals.video
import { servePluginStaticDirectoryValidator } from '../middlewares/validators/plugins'
import { serveThemeCSSValidator } from '../middlewares/validators/themes'
import { PluginType } from '../../shared/models/plugins/plugin.type'
+import { isTestInstance } from '../helpers/core-utils'
+
+const sendFileOptions = {
+ maxAge: '30 days',
+ immutable: !isTestInstance()
+}
const pluginsRouter = express.Router()
// ---------------------------------------------------------------------------
function servePluginGlobalCSS (req: express.Request, res: express.Response) {
- return res.sendFile(PLUGIN_GLOBAL_CSS_PATH)
+ // Only cache requests that have a ?hash=... query param
+ const globalCSSOptions = req.query.hash
+ ? sendFileOptions
+ : {}
+
+ return res.sendFile(PLUGIN_GLOBAL_CSS_PATH, globalCSSOptions)
}
function servePluginStaticDirectory (req: express.Request, res: express.Response) {
}
const filepath = file.join('/')
- return res.sendFile(join(plugin.path, staticPath, filepath))
+ return res.sendFile(join(plugin.path, staticPath, filepath), sendFileOptions)
}
function servePluginClientScripts (req: express.Request, res: express.Response) {
return res.sendStatus(404)
}
- return res.sendFile(join(plugin.path, staticEndpoint))
+ return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
}
function serveThemeCSSDirectory (req: express.Request, res: express.Response) {
return res.sendStatus(404)
}
- return res.sendFile(join(plugin.path, staticEndpoint))
+ return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
}
return raw.toString('hex')
}
-interface FormattableToJSON { toFormattedJSON (args?: any) }
-function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], objectsTotal: number, formattedArg?: any) {
- const formattedObjects: U[] = []
-
- objects.forEach(object => {
- formattedObjects.push(object.toFormattedJSON(formattedArg))
- })
+interface FormattableToJSON<U, V> { toFormattedJSON (args?: U): V }
+function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) {
+ const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg))
return {
total: objectsTotal,
data: formattedObjects
- } as ResultList<U>
+ } as ResultList<V>
}
const getServerActor = memoizee(async function () {
import * as express from 'express'
import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n'
-import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, WEBSERVER } from '../initializers/constants'
+import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER } from '../initializers/constants'
import { join } from 'path'
-import { escapeHTML } from '../helpers/core-utils'
+import { escapeHTML, sha256 } from '../helpers/core-utils'
import { VideoModel } from '../models/video/video'
import * as validator from 'validator'
import { VideoPrivacy } from '../../shared/models/videos'
let html = buffer.toString()
html = ClientHtml.addCustomCSS(html)
- html = ClientHtml.addPluginCSS(html)
+ html = await ClientHtml.addAsyncPluginCSS(html)
ClientHtml.htmlCache[ path ] = html
return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag)
}
- private static addPluginCSS (htmlStringPage: string) {
- const linkTag = `<link rel="stylesheet" href="/plugins/global.css" />`
+ private static async addAsyncPluginCSS (htmlStringPage: string) {
+ const globalCSSContent = await readFile(PLUGIN_GLOBAL_CSS_PATH)
+ if (!globalCSSContent) return htmlStringPage
+
+ const fileHash = sha256(globalCSSContent)
+ const linkTag = `<link rel="stylesheet" href="/plugins/global.css?hash=${fileHash}" />`
return htmlStringPage.replace('</head>', linkTag + '</head>')
}
import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks'
import { RegisterOptions } from '../../typings/plugins/register-options.model'
import { PluginLibrary } from '../../typings/plugins'
+import { ClientHtml } from '../client-html'
export interface RegisteredPlugin {
npmName: string
for (const cssPath of cssRelativePaths) {
await this.concatFiles(join(pluginPath, cssPath), PLUGIN_GLOBAL_CSS_PATH)
}
+
+ ClientHtml.invalidCache()
}
private concatFiles (input: string, output: string) {
+// {hookType}:{api?}.{location}.{subLocation?}.{actionType}.{target}
+
export const serverFilterHookObject = {
'filter:api.videos.list.params': true,
'filter:api.videos.list.result': true,