diff options
author | Chocobozzz <me@florianbigard.com> | 2019-07-26 13:13:39 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-07-26 15:18:30 +0200 |
commit | ee286591a5b740702bad66c55cc900740f749e9a (patch) | |
tree | 16503d1299a107c5972ba16f95228b1ebce20f79 | |
parent | 16d54696294d15f8ab6ba3a6bcfac21528fec2f2 (diff) | |
download | PeerTube-ee286591a5b740702bad66c55cc900740f749e9a.tar.gz PeerTube-ee286591a5b740702bad66c55cc900740f749e9a.tar.zst PeerTube-ee286591a5b740702bad66c55cc900740f749e9a.zip |
Plugins can update video constants
Categories, licences and languages
9 files changed, 342 insertions, 1 deletions
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 78e8d758f..81554a09e 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -5,7 +5,7 @@ import { CONFIG } from '../../initializers/config' | |||
5 | import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins' | 5 | import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins' |
6 | import { ClientScript, PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model' | 6 | import { ClientScript, PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model' |
7 | import { createReadStream, createWriteStream } from 'fs' | 7 | import { createReadStream, createWriteStream } from 'fs' |
8 | import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants' | 8 | import { PLUGIN_GLOBAL_CSS_PATH, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../../initializers/constants' |
9 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 9 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
10 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' | 10 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' |
11 | import { outputFile, readJSON } from 'fs-extra' | 11 | import { outputFile, readJSON } from 'fs-extra' |
@@ -18,6 +18,9 @@ import { PluginLibrary } from '../../typings/plugins' | |||
18 | import { ClientHtml } from '../client-html' | 18 | import { ClientHtml } from '../client-html' |
19 | import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' | 19 | import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' |
20 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | 20 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' |
21 | import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model' | ||
22 | import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model' | ||
23 | import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model' | ||
21 | 24 | ||
22 | export interface RegisteredPlugin { | 25 | export interface RegisteredPlugin { |
23 | npmName: string | 26 | npmName: string |
@@ -46,6 +49,17 @@ export interface HookInformationValue { | |||
46 | priority: number | 49 | priority: number |
47 | } | 50 | } |
48 | 51 | ||
52 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | ||
53 | type VideoConstant = { [ key in number | string ]: string } | ||
54 | type UpdatedVideoConstant = { | ||
55 | [ name in AlterableVideoConstant ]: { | ||
56 | [ npmName: string ]: { | ||
57 | added: { key: number | string, label: string }[], | ||
58 | deleted: { key: number | string, label: string }[] | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | |||
49 | export class PluginManager implements ServerHook { | 63 | export class PluginManager implements ServerHook { |
50 | 64 | ||
51 | private static instance: PluginManager | 65 | private static instance: PluginManager |
@@ -54,6 +68,12 @@ export class PluginManager implements ServerHook { | |||
54 | private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {} | 68 | private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {} |
55 | private hooks: { [ name: string ]: HookInformationValue[] } = {} | 69 | private hooks: { [ name: string ]: HookInformationValue[] } = {} |
56 | 70 | ||
71 | private updatedVideoConstants: UpdatedVideoConstant = { | ||
72 | language: {}, | ||
73 | licence: {}, | ||
74 | category: {} | ||
75 | } | ||
76 | |||
57 | private constructor () { | 77 | private constructor () { |
58 | } | 78 | } |
59 | 79 | ||
@@ -161,6 +181,8 @@ export class PluginManager implements ServerHook { | |||
161 | this.hooks[key] = this.hooks[key].filter(h => h.pluginName !== npmName) | 181 | this.hooks[key] = this.hooks[key].filter(h => h.pluginName !== npmName) |
162 | } | 182 | } |
163 | 183 | ||
184 | this.reinitVideoConstants(plugin.npmName) | ||
185 | |||
164 | logger.info('Regenerating registered plugin CSS to global file.') | 186 | logger.info('Regenerating registered plugin CSS to global file.') |
165 | await this.regeneratePluginGlobalCSS() | 187 | await this.regeneratePluginGlobalCSS() |
166 | } | 188 | } |
@@ -427,6 +449,24 @@ export class PluginManager implements ServerHook { | |||
427 | storeData: (key: string, data: any) => PluginModel.storeData(plugin.name, plugin.type, key, data) | 449 | storeData: (key: string, data: any) => PluginModel.storeData(plugin.name, plugin.type, key, data) |
428 | } | 450 | } |
429 | 451 | ||
452 | const videoLanguageManager: PluginVideoLanguageManager = { | ||
453 | addLanguage: (key: string, label: string) => this.addConstant({ npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label }), | ||
454 | |||
455 | deleteLanguage: (key: string) => this.deleteConstant({ npmName, type: 'language', obj: VIDEO_LANGUAGES, key }) | ||
456 | } | ||
457 | |||
458 | const videoCategoryManager: PluginVideoCategoryManager= { | ||
459 | addCategory: (key: number, label: string) => this.addConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }), | ||
460 | |||
461 | deleteCategory: (key: number) => this.deleteConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key }) | ||
462 | } | ||
463 | |||
464 | const videoLicenceManager: PluginVideoLicenceManager = { | ||
465 | addLicence: (key: number, label: string) => this.addConstant({ npmName, type: 'licence', obj: VIDEO_LICENCES, key, label }), | ||
466 | |||
467 | deleteLicence: (key: number) => this.deleteConstant({ npmName, type: 'licence', obj: VIDEO_LICENCES, key }) | ||
468 | } | ||
469 | |||
430 | const peertubeHelpers = { | 470 | const peertubeHelpers = { |
431 | logger | 471 | logger |
432 | } | 472 | } |
@@ -436,10 +476,90 @@ export class PluginManager implements ServerHook { | |||
436 | registerSetting, | 476 | registerSetting, |
437 | settingsManager, | 477 | settingsManager, |
438 | storageManager, | 478 | storageManager, |
479 | videoLanguageManager, | ||
480 | videoCategoryManager, | ||
481 | videoLicenceManager, | ||
439 | peertubeHelpers | 482 | peertubeHelpers |
440 | } | 483 | } |
441 | } | 484 | } |
442 | 485 | ||
486 | private addConstant <T extends string | number> (parameters: { | ||
487 | npmName: string, | ||
488 | type: AlterableVideoConstant, | ||
489 | obj: VideoConstant, | ||
490 | key: T, | ||
491 | label: string | ||
492 | }) { | ||
493 | const { npmName, type, obj, key, label } = parameters | ||
494 | |||
495 | if (obj[key]) { | ||
496 | logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key) | ||
497 | return false | ||
498 | } | ||
499 | |||
500 | if (!this.updatedVideoConstants[type][npmName]) { | ||
501 | this.updatedVideoConstants[type][npmName] = { | ||
502 | added: [], | ||
503 | deleted: [] | ||
504 | } | ||
505 | } | ||
506 | |||
507 | this.updatedVideoConstants[type][npmName].added.push({ key, label }) | ||
508 | obj[key] = label | ||
509 | |||
510 | return true | ||
511 | } | ||
512 | |||
513 | private deleteConstant <T extends string | number> (parameters: { | ||
514 | npmName: string, | ||
515 | type: AlterableVideoConstant, | ||
516 | obj: VideoConstant, | ||
517 | key: T | ||
518 | }) { | ||
519 | const { npmName, type, obj, key } = parameters | ||
520 | |||
521 | if (!obj[key]) { | ||
522 | logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key) | ||
523 | return false | ||
524 | } | ||
525 | |||
526 | if (!this.updatedVideoConstants[type][npmName]) { | ||
527 | this.updatedVideoConstants[type][npmName] = { | ||
528 | added: [], | ||
529 | deleted: [] | ||
530 | } | ||
531 | } | ||
532 | |||
533 | this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] }) | ||
534 | delete obj[key] | ||
535 | |||
536 | return true | ||
537 | } | ||
538 | |||
539 | private reinitVideoConstants (npmName: string) { | ||
540 | const hash = { | ||
541 | language: VIDEO_LANGUAGES, | ||
542 | licence: VIDEO_LICENCES, | ||
543 | category: VIDEO_CATEGORIES | ||
544 | } | ||
545 | const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category' ] | ||
546 | |||
547 | for (const type of types) { | ||
548 | const updatedConstants = this.updatedVideoConstants[type][npmName] | ||
549 | if (!updatedConstants) continue | ||
550 | |||
551 | for (const added of updatedConstants.added) { | ||
552 | delete hash[type][added.key] | ||
553 | } | ||
554 | |||
555 | for (const deleted of updatedConstants.deleted) { | ||
556 | hash[type][deleted.key] = deleted.label | ||
557 | } | ||
558 | |||
559 | delete this.updatedVideoConstants[type][npmName] | ||
560 | } | ||
561 | } | ||
562 | |||
443 | static get Instance () { | 563 | static get Instance () { |
444 | return this.instance || (this.instance = new this()) | 564 | return this.instance || (this.instance = new this()) |
445 | } | 565 | } |
diff --git a/server/tests/fixtures/peertube-plugin-test-three/main.js b/server/tests/fixtures/peertube-plugin-test-three/main.js new file mode 100644 index 000000000..4945feb55 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-three/main.js | |||
@@ -0,0 +1,39 @@ | |||
1 | async function register ({ | ||
2 | registerHook, | ||
3 | registerSetting, | ||
4 | settingsManager, | ||
5 | storageManager, | ||
6 | videoCategoryManager, | ||
7 | videoLicenceManager, | ||
8 | videoLanguageManager | ||
9 | }) { | ||
10 | videoLanguageManager.addLanguage('al_bhed', 'Al Bhed') | ||
11 | videoLanguageManager.addLanguage('al_bhed2', 'Al Bhed 2') | ||
12 | videoLanguageManager.deleteLanguage('en') | ||
13 | videoLanguageManager.deleteLanguage('fr') | ||
14 | |||
15 | videoCategoryManager.addCategory(42, 'Best category') | ||
16 | videoCategoryManager.addCategory(43, 'High best category') | ||
17 | videoCategoryManager.deleteCategory(1) // Music | ||
18 | videoCategoryManager.deleteCategory(2) // Films | ||
19 | |||
20 | videoLicenceManager.addLicence(42, 'Best licence') | ||
21 | videoLicenceManager.addLicence(43, 'High best licence') | ||
22 | videoLicenceManager.deleteLicence(1) // Attribution | ||
23 | videoLicenceManager.deleteLicence(7) // Public domain | ||
24 | } | ||
25 | |||
26 | async function unregister () { | ||
27 | return | ||
28 | } | ||
29 | |||
30 | module.exports = { | ||
31 | register, | ||
32 | unregister | ||
33 | } | ||
34 | |||
35 | // ############################################################################ | ||
36 | |||
37 | function addToCount (obj) { | ||
38 | return Object.assign({}, obj, { count: obj.count + 1 }) | ||
39 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-three/package.json b/server/tests/fixtures/peertube-plugin-test-three/package.json new file mode 100644 index 000000000..3f7819db3 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-three/package.json | |||
@@ -0,0 +1,19 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-three", | ||
3 | "version": "0.0.1", | ||
4 | "description": "Plugin test 3", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [] | ||
19 | } | ||
diff --git a/server/tests/plugins/index.ts b/server/tests/plugins/index.ts index d97ca1515..95e358732 100644 --- a/server/tests/plugins/index.ts +++ b/server/tests/plugins/index.ts | |||
@@ -1,2 +1,3 @@ | |||
1 | import './action-hooks' | 1 | import './action-hooks' |
2 | import './filter-hooks' | 2 | import './filter-hooks' |
3 | import './video-constants' | ||
diff --git a/server/tests/plugins/video-constants.ts b/server/tests/plugins/video-constants.ts new file mode 100644 index 000000000..6562e2b45 --- /dev/null +++ b/server/tests/plugins/video-constants.ts | |||
@@ -0,0 +1,140 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | flushAndRunMultipleServers, | ||
8 | flushAndRunServer, killallServers, reRunServer, | ||
9 | ServerInfo, | ||
10 | waitUntilLog | ||
11 | } from '../../../shared/extra-utils/server/servers' | ||
12 | import { | ||
13 | addVideoCommentReply, | ||
14 | addVideoCommentThread, | ||
15 | deleteVideoComment, | ||
16 | getPluginTestPath, | ||
17 | getVideosList, | ||
18 | installPlugin, | ||
19 | removeVideo, | ||
20 | setAccessTokensToServers, | ||
21 | updateVideo, | ||
22 | uploadVideo, | ||
23 | viewVideo, | ||
24 | getVideosListPagination, | ||
25 | getVideo, | ||
26 | getVideoCommentThreads, | ||
27 | getVideoThreadComments, | ||
28 | getVideoWithToken, | ||
29 | setDefaultVideoChannel, | ||
30 | waitJobs, | ||
31 | doubleFollow, getVideoLanguages, getVideoLicences, getVideoCategories, uninstallPlugin | ||
32 | } from '../../../shared/extra-utils' | ||
33 | import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' | ||
34 | import { VideoDetails } from '../../../shared/models/videos' | ||
35 | import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports' | ||
36 | |||
37 | const expect = chai.expect | ||
38 | |||
39 | describe('Test plugin altering video constants', function () { | ||
40 | let server: ServerInfo | ||
41 | |||
42 | before(async function () { | ||
43 | this.timeout(30000) | ||
44 | |||
45 | server = await flushAndRunServer(1) | ||
46 | await setAccessTokensToServers([ server ]) | ||
47 | |||
48 | await installPlugin({ | ||
49 | url: server.url, | ||
50 | accessToken: server.accessToken, | ||
51 | path: getPluginTestPath('-three') | ||
52 | }) | ||
53 | }) | ||
54 | |||
55 | it('Should have updated languages', async function () { | ||
56 | const res = await getVideoLanguages(server.url) | ||
57 | const languages = res.body | ||
58 | |||
59 | expect(languages['en']).to.not.exist | ||
60 | expect(languages['fr']).to.not.exist | ||
61 | |||
62 | expect(languages['al_bhed']).to.equal('Al Bhed') | ||
63 | expect(languages['al_bhed2']).to.equal('Al Bhed 2') | ||
64 | }) | ||
65 | |||
66 | it('Should have updated categories', async function () { | ||
67 | const res = await getVideoCategories(server.url) | ||
68 | const categories = res.body | ||
69 | |||
70 | expect(categories[1]).to.not.exist | ||
71 | expect(categories[2]).to.not.exist | ||
72 | |||
73 | expect(categories[42]).to.equal('Best category') | ||
74 | expect(categories[43]).to.equal('High best category') | ||
75 | }) | ||
76 | |||
77 | it('Should have updated licences', async function () { | ||
78 | const res = await getVideoLicences(server.url) | ||
79 | const licences = res.body | ||
80 | |||
81 | expect(licences[1]).to.not.exist | ||
82 | expect(licences[7]).to.not.exist | ||
83 | |||
84 | expect(licences[42]).to.equal('Best licence') | ||
85 | expect(licences[43]).to.equal('High best licence') | ||
86 | }) | ||
87 | |||
88 | it('Should be able to upload a video with these values', async function () { | ||
89 | const attrs = { name: 'video', category: 42, licence: 42, language: 'al_bhed2' } | ||
90 | const resUpload = await uploadVideo(server.url, server.accessToken, attrs) | ||
91 | |||
92 | const res = await getVideo(server.url, resUpload.body.video.uuid) | ||
93 | |||
94 | const video: VideoDetails = res.body | ||
95 | expect(video.language.label).to.equal('Al Bhed 2') | ||
96 | expect(video.licence.label).to.equal('Best licence') | ||
97 | expect(video.category.label).to.equal('Best category') | ||
98 | }) | ||
99 | |||
100 | it('Should uninstall the plugin and reset languages, categories and licences', async function () { | ||
101 | await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-test-three' }) | ||
102 | |||
103 | { | ||
104 | const res = await getVideoLanguages(server.url) | ||
105 | const languages = res.body | ||
106 | |||
107 | expect(languages[ 'en' ]).to.equal('English') | ||
108 | expect(languages[ 'fr' ]).to.equal('French') | ||
109 | |||
110 | expect(languages[ 'al_bhed' ]).to.not.exist | ||
111 | expect(languages[ 'al_bhed2' ]).to.not.exist | ||
112 | } | ||
113 | |||
114 | { | ||
115 | const res = await getVideoCategories(server.url) | ||
116 | const categories = res.body | ||
117 | |||
118 | expect(categories[ 1 ]).to.equal('Music') | ||
119 | expect(categories[ 2 ]).to.equal('Films') | ||
120 | |||
121 | expect(categories[ 42 ]).to.not.exist | ||
122 | expect(categories[ 43 ]).to.not.exist | ||
123 | } | ||
124 | |||
125 | { | ||
126 | const res = await getVideoLicences(server.url) | ||
127 | const licences = res.body | ||
128 | |||
129 | expect(licences[ 1 ]).to.equal('Attribution') | ||
130 | expect(licences[ 7 ]).to.equal('Public Domain Dedication') | ||
131 | |||
132 | expect(licences[ 42 ]).to.not.exist | ||
133 | expect(licences[ 43 ]).to.not.exist | ||
134 | } | ||
135 | }) | ||
136 | |||
137 | after(async function () { | ||
138 | await cleanupTests([ server ]) | ||
139 | }) | ||
140 | }) | ||
diff --git a/server/typings/plugins/register-server-option.model.ts b/server/typings/plugins/register-server-option.model.ts index 91a06a7c5..54753cc01 100644 --- a/server/typings/plugins/register-server-option.model.ts +++ b/server/typings/plugins/register-server-option.model.ts | |||
@@ -3,6 +3,9 @@ import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-set | |||
3 | import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' | 3 | import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' |
4 | import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' | 4 | import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' |
5 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | 5 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' |
6 | import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model' | ||
7 | import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model' | ||
8 | import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model' | ||
6 | 9 | ||
7 | export type RegisterServerOptions = { | 10 | export type RegisterServerOptions = { |
8 | registerHook: (options: RegisterServerHookOptions) => void | 11 | registerHook: (options: RegisterServerHookOptions) => void |
@@ -13,6 +16,10 @@ export type RegisterServerOptions = { | |||
13 | 16 | ||
14 | storageManager: PluginStorageManager | 17 | storageManager: PluginStorageManager |
15 | 18 | ||
19 | videoCategoryManager: PluginVideoCategoryManager | ||
20 | videoLanguageManager: PluginVideoLanguageManager | ||
21 | videoLicenceManager: PluginVideoLicenceManager | ||
22 | |||
16 | peertubeHelpers: { | 23 | peertubeHelpers: { |
17 | logger: typeof logger | 24 | logger: typeof logger |
18 | } | 25 | } |
diff --git a/shared/models/plugins/plugin-video-category-manager.model.ts b/shared/models/plugins/plugin-video-category-manager.model.ts new file mode 100644 index 000000000..201bfa979 --- /dev/null +++ b/shared/models/plugins/plugin-video-category-manager.model.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export interface PluginVideoCategoryManager { | ||
2 | addCategory: (categoryKey: number, categoryLabel: string) => boolean | ||
3 | |||
4 | deleteCategory: (categoryKey: number) => boolean | ||
5 | } | ||
diff --git a/shared/models/plugins/plugin-video-language-manager.model.ts b/shared/models/plugins/plugin-video-language-manager.model.ts new file mode 100644 index 000000000..3fd577a79 --- /dev/null +++ b/shared/models/plugins/plugin-video-language-manager.model.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export interface PluginVideoLanguageManager { | ||
2 | addLanguage: (languageKey: string, languageLabel: string) => boolean | ||
3 | |||
4 | deleteLanguage: (languageKey: string) => boolean | ||
5 | } | ||
diff --git a/shared/models/plugins/plugin-video-licence-manager.model.ts b/shared/models/plugins/plugin-video-licence-manager.model.ts new file mode 100644 index 000000000..82a634d3a --- /dev/null +++ b/shared/models/plugins/plugin-video-licence-manager.model.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export interface PluginVideoLicenceManager { | ||
2 | addLicence: (licenceKey: number, licenceLabel: string) => boolean | ||
3 | |||
4 | deleteLicence: (licenceKey: number) => boolean | ||
5 | } | ||