diff --git a/ansible/roles/h5p-library-management/meta/main.yml b/ansible/roles/h5p-library-management/meta/main.yml new file mode 100644 index 00000000000..2af8ef00aea --- /dev/null +++ b/ansible/roles/h5p-library-management/meta/main.yml @@ -0,0 +1,9 @@ +galaxy_info: + role_name: h5p-library-management + author: Schul-Cloud Verbund + description: h5p library role for the management of libraries + company: Schul-Cloud Verbund + license: license (AGPLv3) + min_ansible_version: 2.8 + galaxy_tags: [] +dependencies: [] diff --git a/ansible/roles/h5p-library-management/tasks/main.yml b/ansible/roles/h5p-library-management/tasks/main.yml new file mode 100644 index 00000000000..7d25364c934 --- /dev/null +++ b/ansible/roles/h5p-library-management/tasks/main.yml @@ -0,0 +1,6 @@ + - name: H5pLibraryManagement CronJob + when: WITH_H5P_LIBRARY_MANAGEMENT is defined and WITH_H5P_LIBRARY_MANAGEMENT|bool == true + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: "{{ NAMESPACE }}" + template: api-h5p-library-management-cronjob.yml.j2 diff --git a/ansible/roles/h5p-library-management/templates/api-h5p-library-management-cronjob.yml.j2 b/ansible/roles/h5p-library-management/templates/api-h5p-library-management-cronjob.yml.j2 new file mode 100644 index 00000000000..6e5b36fa91b --- /dev/null +++ b/ansible/roles/h5p-library-management/templates/api-h5p-library-management-cronjob.yml.j2 @@ -0,0 +1,33 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + namespace: {{ NAMESPACE }} + labels: + app: api-library-management-cronjob + name: api-library-management-cronjob +spec: + schedule: "{{ SERVER_H5P_LIBRARY_MANAGEMENT_CRONJOB|default("0 3 * * 3,6", true) }}" + concurrencyPolicy: Forbid + jobTemplate: + spec: + activeDeadlineSeconds: {{ SERVER_H5P_LIBRARY_MANAGEMENT_CRONJOB_TIMEOUT|default("39600", true) }} + template: + spec: + containers: + - name: api-h5p-library-management-cronjob + image: {{ SCHULCLOUD_SERVER_IMAGE }}:{{ SCHULCLOUD_SERVER_IMAGE_TAG }} + envFrom: + - configMapRef: + name: api-configmap + - secretRef: + name: api-secret + command: ['/bin/sh', '-c'] + args: ['npm run nest:start:h5p:library-management'] + resources: + limits: + cpu: {{ API_H5P_LIBRARY_MANAGEMENT_CPU_LIMITS|default("2000m", true) }} + memory: {{ API_H5P_LIBRARY_MANAGEMENT_MEMORY_LIMITS|default("2Gi", true) }} + requests: + cpu: {{ API_H5P_LIBRARY_MANAGEMENT_CPU_REQUESTS|default("100m", true) }} + memory: {{ API_H5P_LIBRARY_MANAGEMENT_MEMORY_REQUESTS|default("150Mi", true) }} + restartPolicy: OnFailure diff --git a/apps/server/src/apps/h5p-library-management.app.ts b/apps/server/src/apps/h5p-library-management.app.ts new file mode 100644 index 00000000000..840141c3a7d --- /dev/null +++ b/apps/server/src/apps/h5p-library-management.app.ts @@ -0,0 +1,31 @@ +/* istanbul ignore file */ +/* eslint-disable no-console */ +import { NestFactory } from '@nestjs/core'; +import { install as sourceMapInstall } from 'source-map-support'; +import { LegacyLogger } from '@src/core/logger'; +import { H5PLibraryManagementModule, H5PLibraryManagementService } from '@modules/h5p-library-management'; + +async function bootstrap() { + sourceMapInstall(); + + const nestApp = await NestFactory.createApplicationContext(H5PLibraryManagementModule); + + // WinstonLogger + nestApp.useLogger(await nestApp.resolve(LegacyLogger)); + + await nestApp.init(); + + console.log('#########################################'); + console.log(`##### Start H5P Library Management ######`); + console.log('#########################################'); + + // to execute it on this place for the ORM the allowGlobalContext: true must be set, but to executed in this way is a hack + await nestApp.get(H5PLibraryManagementService).run(); + // TODO: properly close app (there is some issue with the logger) + console.log('#########################################'); + console.log(`##### Close H5P Library Management ######`); + console.log('#########################################'); + await nestApp.close(); + process.exit(0); +} +void bootstrap(); diff --git a/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts b/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts index c46c782da2d..88809092e92 100644 --- a/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts +++ b/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts @@ -21,7 +21,6 @@ import { FileFieldsInterceptor } from '@nestjs/platform-express'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; import { Request, Response } from 'express'; - import { H5PEditorUc } from '../uc/h5p.uc'; import { diff --git a/apps/server/src/modules/h5p-editor/h5p-editor.module.ts b/apps/server/src/modules/h5p-editor/h5p-editor.module.ts index c80ff8bd6c0..b614622610c 100644 --- a/apps/server/src/modules/h5p-editor/h5p-editor.module.ts +++ b/apps/server/src/modules/h5p-editor/h5p-editor.module.ts @@ -39,6 +39,7 @@ const imports = [ password: DB_PASSWORD, user: DB_USERNAME, // Needs ALL_ENTITIES for authorization + allowGlobalContext: true, entities: [...ALL_ENTITIES, H5PContent, H5pEditorTempFile, InstalledLibrary], }), ConfigModule.forRoot(createConfigModuleOptions(config)), @@ -65,5 +66,6 @@ const providers = [ imports, controllers, providers, + exports: [ContentStorage, LibraryStorage], }) export class H5PEditorModule {} diff --git a/apps/server/src/modules/h5p-editor/index.ts b/apps/server/src/modules/h5p-editor/index.ts index f8808788441..b8565a944c2 100644 --- a/apps/server/src/modules/h5p-editor/index.ts +++ b/apps/server/src/modules/h5p-editor/index.ts @@ -1 +1,3 @@ -export * from './h5p-editor.module'; +export { H5PEditorModule } from './h5p-editor.module'; +export { ContentStorage, LibraryStorage } from './service'; +export { s3ConfigContent, s3ConfigLibraries } from './h5p-editor.config'; diff --git a/apps/server/src/modules/h5p-editor/uc/h5p.uc.ts b/apps/server/src/modules/h5p-editor/uc/h5p.uc.ts index f456491509a..89d4ba4da2a 100644 --- a/apps/server/src/modules/h5p-editor/uc/h5p.uc.ts +++ b/apps/server/src/modules/h5p-editor/uc/h5p.uc.ts @@ -374,8 +374,8 @@ export class H5PEditorUc { private changeUserType(currentUser: ICurrentUser): LumiIUser { const user: LumiIUser = { canCreateRestricted: false, - canInstallRecommended: true, - canUpdateAndInstallLibraries: true, + canInstallRecommended: false, + canUpdateAndInstallLibraries: false, email: '', id: currentUser.userId, name: '', diff --git a/apps/server/src/modules/h5p-library-management/h5p-library-management.module.ts b/apps/server/src/modules/h5p-library-management/h5p-library-management.module.ts new file mode 100644 index 00000000000..e3133754b31 --- /dev/null +++ b/apps/server/src/modules/h5p-library-management/h5p-library-management.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { RabbitMQWrapperModule } from '@infra/rabbitmq'; +import { S3ClientModule } from '@infra/s3-client'; +import { createConfigModuleOptions } from '@src/config'; +import { CoreModule } from '@src/core'; +import { Logger } from '@src/core/logger'; +import { H5PEditorModule, s3ConfigContent, s3ConfigLibraries } from '@modules/h5p-editor'; +import { H5PLibraryManagementService, h5PLibraryManagementConfig } from './service'; + +const imports = [ + ConfigModule.forRoot(createConfigModuleOptions(h5PLibraryManagementConfig)), + CoreModule, + H5PEditorModule, + RabbitMQWrapperModule, + S3ClientModule.register([s3ConfigContent, s3ConfigLibraries]), +]; + +const controllers = []; + +const providers = [Logger, H5PLibraryManagementService]; + +@Module({ + imports, + controllers, + providers, + exports: [], +}) +export class H5PLibraryManagementModule {} diff --git a/apps/server/src/modules/h5p-library-management/index.ts b/apps/server/src/modules/h5p-library-management/index.ts new file mode 100644 index 00000000000..442c5dac91d --- /dev/null +++ b/apps/server/src/modules/h5p-library-management/index.ts @@ -0,0 +1,2 @@ +export { H5PLibraryManagementModule } from './h5p-library-management.module'; +export { H5PLibraryManagementService } from './service'; diff --git a/apps/server/src/modules/h5p-library-management/service/h5p-library-management.config.spec.ts b/apps/server/src/modules/h5p-library-management/service/h5p-library-management.config.spec.ts new file mode 100644 index 00000000000..7d8b68bd893 --- /dev/null +++ b/apps/server/src/modules/h5p-library-management/service/h5p-library-management.config.spec.ts @@ -0,0 +1,12 @@ +import { h5PLibraryManagementConfig } from './h5p-library-management.config'; + +describe('H5PLibraryManagementConfig', () => { + describe('h5PLibraryManagementConfig', () => { + describe('when h5PLibraryManagementConfig is called', () => { + it('should get Object s3ConfigLibraries', () => { + const config = h5PLibraryManagementConfig(); + expect(config).toBeDefined(); + }); + }); + }); +}); diff --git a/apps/server/src/modules/h5p-library-management/service/h5p-library-management.config.ts b/apps/server/src/modules/h5p-library-management/service/h5p-library-management.config.ts new file mode 100644 index 00000000000..0e0b39f3250 --- /dev/null +++ b/apps/server/src/modules/h5p-library-management/service/h5p-library-management.config.ts @@ -0,0 +1,11 @@ +import { Configuration } from '@hpi-schul-cloud/commons'; + +export interface IH5PLibraryManagementConfig { + H5P_EDITOR__LIBRARY_LIST_PATH: string; +} + +export const config: IH5PLibraryManagementConfig = { + H5P_EDITOR__LIBRARY_LIST_PATH: Configuration.get('H5P_EDITOR__LIBRARY_LIST_PATH') as string, +}; + +export const h5PLibraryManagementConfig = () => config; diff --git a/apps/server/src/modules/h5p-library-management/service/h5p-library-management.service.spec.ts b/apps/server/src/modules/h5p-library-management/service/h5p-library-management.service.spec.ts new file mode 100644 index 00000000000..9d247682c3c --- /dev/null +++ b/apps/server/src/modules/h5p-library-management/service/h5p-library-management.service.spec.ts @@ -0,0 +1,255 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { createMock } from '@golevelup/ts-jest'; +import { ContentStorage, LibraryStorage } from '@src/modules/h5p-editor/service'; +import { IHubContentType, ILibraryAdministrationOverviewItem } from '@lumieducation/h5p-server/build/src/types'; +import { ConfigService } from '@nestjs/config'; +import { InternalServerErrorException } from '@nestjs/common'; +import { H5PLibraryManagementService, castToLibrariesContentType } from './h5p-library-management.service'; +import { IH5PLibraryManagementConfig } from './h5p-library-management.config'; + +jest.mock('@lumieducation/h5p-server', () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { + ...jest.requireActual('@lumieducation/h5p-server'), + LibraryAdministration: jest.fn().mockImplementation(() => { + return { + getLibraries: jest.fn().mockResolvedValue([ + { machineName: 'a', dependentsCount: 0 }, + { machineName: 'b', dependentsCount: 1 }, + { machineName: 'c', dependentsCount: 0 }, + ]), + deleteLibraries: jest.fn().mockResolvedValue({}), + }; + }), + }; +}); + +describe('H5PLibraryManagementService', () => { + let module: TestingModule; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + H5PLibraryManagementService, + { + provide: LibraryStorage, + useValue: createMock(), + }, + { + provide: ContentStorage, + useValue: createMock(), + }, + { + provide: ConfigService, + useValue: createMock>({ + get: () => 'config/h5p-libraries.yaml', + }), + }, + ], + }).compile(); + }); + + afterAll(async () => { + await module.close(); + }); + + describe('uninstallUnwantedLibraries is called', () => { + describe('when wantedLibraries have no dependands', () => { + const setup = () => { + const libraries: ILibraryAdministrationOverviewItem[] = [ + { + canBeDeleted: true, + canBeUpdated: true, + dependentsCount: 0, + instancesAsDependencyCount: 0, + instancesCount: 0, + isAddon: false, + machineName: 'a', + majorVersion: 1, + minorVersion: 1, + patchVersion: 1, + restricted: false, + runnable: true, + title: 'a', + }, + { + canBeDeleted: true, + canBeUpdated: true, + dependentsCount: 1, + instancesAsDependencyCount: 0, + instancesCount: 0, + isAddon: false, + machineName: 'b', + majorVersion: 1, + minorVersion: 1, + patchVersion: 1, + restricted: false, + runnable: true, + title: 'b', + }, + { + canBeDeleted: true, + canBeUpdated: true, + dependentsCount: 0, + instancesAsDependencyCount: 0, + instancesCount: 0, + isAddon: false, + machineName: 'c', + majorVersion: 1, + minorVersion: 1, + patchVersion: 1, + restricted: false, + runnable: true, + title: 'c', + }, + ]; + const service = module.get(H5PLibraryManagementService); + const libraryStorageMock = module.get(LibraryStorage); + + return { service, libraryStorageMock, libraries }; + }; + it('should delete libraries not in the wanted list and with no dependents', async () => { + const { service, libraryStorageMock, libraries } = setup(); + const wantedLibraries = ['a', 'c']; + await service.uninstallUnwantedLibraries(wantedLibraries, libraries); + expect(libraryStorageMock.deleteLibrary).toHaveBeenCalledWith(libraries[0]); + expect(libraryStorageMock.deleteLibrary).not.toHaveBeenCalledWith(libraries[1]); + expect(libraryStorageMock.deleteLibrary).not.toHaveBeenCalledWith(libraries[2]); + }); + }); + + describe('when wantedLIbraries have dependands', () => { + const setup = () => { + const libraries: ILibraryAdministrationOverviewItem[] = [ + { + canBeDeleted: true, + canBeUpdated: true, + dependentsCount: 0, + instancesAsDependencyCount: 0, + instancesCount: 0, + isAddon: false, + machineName: 'a', + majorVersion: 1, + minorVersion: 1, + patchVersion: 1, + restricted: false, + runnable: true, + title: 'a', + }, + { + canBeDeleted: true, + canBeUpdated: true, + dependentsCount: 1, + instancesAsDependencyCount: 0, + instancesCount: 0, + isAddon: false, + machineName: 'b', + majorVersion: 1, + minorVersion: 1, + patchVersion: 1, + restricted: false, + runnable: true, + title: 'b', + }, + { + canBeDeleted: true, + canBeUpdated: true, + dependentsCount: 0, + instancesAsDependencyCount: 0, + instancesCount: 0, + isAddon: false, + machineName: 'c', + majorVersion: 1, + minorVersion: 1, + patchVersion: 1, + restricted: false, + runnable: true, + title: 'c', + }, + ]; + const service = module.get(H5PLibraryManagementService); + const libraryStorageMock = module.get(LibraryStorage); + + return { service, libraryStorageMock, libraries }; + }; + it('should not delete libraries with dependents', async () => { + const { service, libraryStorageMock, libraries } = setup(); + libraryStorageMock.deleteLibrary = jest.fn().mockResolvedValueOnce({}); + const wantedLibraries = ['a', 'b']; + await service.uninstallUnwantedLibraries(wantedLibraries, libraries); + expect(libraryStorageMock.deleteLibrary).toHaveBeenCalledWith(libraries[0]); + expect(libraryStorageMock.deleteLibrary).not.toHaveBeenCalledWith(libraries[1]); + expect(libraryStorageMock.deleteLibrary).toHaveBeenCalledWith(libraries[2]); + }); + }); + }); + + describe('installLibraries is called', () => { + describe('when libraries exist', () => { + const setup = () => { + const service = module.get(H5PLibraryManagementService); + + return { service }; + }; + it('should install all libraries in the list', async () => { + const { service } = setup(); + const wantedLibraries = ['a', 'b', 'c']; + const installContentTypeSpy = jest.spyOn(service.contentTypeRepo, 'installContentType').mockResolvedValue([]); + await service.installLibraries(wantedLibraries); + for (const libName of wantedLibraries) { + expect(installContentTypeSpy).toHaveBeenCalledWith(libName, expect.anything()); + } + }); + }); + + describe('when libraries does not exist', () => { + const setup = () => { + const service = module.get(H5PLibraryManagementService); + + return { service }; + }; + it('should throw an error if the library does not exist', async () => { + const { service } = setup(); + const nonExistentLibrary = 'nonExistentLibrary'; + jest + .spyOn(service.contentTypeCache, 'get') + .mockResolvedValueOnce(undefined as unknown as Promise); + await expect(service.installLibraries([nonExistentLibrary])).rejects.toThrow('this library does not exist'); + }); + }); + }); + + describe('run is called', () => { + describe('when run has been called successfully', () => { + const setup = () => { + const service = module.get(H5PLibraryManagementService); + + return { service }; + }; + it('should trigger uninstallUnwantedLibraries and installLibraries', async () => { + const { service } = setup(); + const uninstallSpy = jest.spyOn(service, 'uninstallUnwantedLibraries').mockResolvedValueOnce(undefined); + const installSpy = jest.spyOn(service, 'installLibraries').mockResolvedValueOnce(undefined); + + await service.run(); + + expect(uninstallSpy).toHaveBeenCalledTimes(1); + expect(installSpy).toHaveBeenCalledTimes(1); + + uninstallSpy.mockRestore(); + installSpy.mockRestore(); + }); + }); + }); + + describe('castToLibrariesContentType', () => { + describe('when castToLibrariesContentType has been called successfully', () => { + it('should throw InternalServerErrorException', () => { + const randomObject = { + random: 1, + }; + expect(() => castToLibrariesContentType(randomObject)).toThrow(InternalServerErrorException); + }); + }); + }); +}); diff --git a/apps/server/src/modules/h5p-library-management/service/h5p-library-management.service.ts b/apps/server/src/modules/h5p-library-management/service/h5p-library-management.service.ts new file mode 100644 index 00000000000..a6ab56ae29a --- /dev/null +++ b/apps/server/src/modules/h5p-library-management/service/h5p-library-management.service.ts @@ -0,0 +1,150 @@ +import { + H5PConfig, + cacheImplementations, + LibraryManager, + ContentTypeCache, + IUser, + LibraryAdministration, + ILibraryAdministrationOverviewItem, +} from '@lumieducation/h5p-server'; +import ContentManager from '@lumieducation/h5p-server/build/src/ContentManager'; +import ContentTypeInformationRepository from '@lumieducation/h5p-server/build/src/ContentTypeInformationRepository'; +import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; +import { ContentStorage, LibraryStorage } from '@src/modules/h5p-editor'; +import { readFileSync } from 'fs'; +import { parse } from 'yaml'; +import { ConfigService } from '@nestjs/config'; +import { IHubContentType } from '@lumieducation/h5p-server/build/src/types'; +import { IH5PLibraryManagementConfig } from './h5p-library-management.config'; + +const h5pConfig = new H5PConfig(undefined, { + baseUrl: '/api/v3/h5p-editor', + contentUserStateSaveInterval: false, + setFinishedEnabled: false, +}); + +interface LibrariesContentType { + h5p_libraries: string[]; +} + +function isLibrariesContentType(object: unknown): object is LibrariesContentType { + const isType = + typeof object === 'object' && + !Array.isArray(object) && + object !== null && + 'h5p_libraries' in object && + Array.isArray(object.h5p_libraries); + + return isType; +} + +export const castToLibrariesContentType = (object: unknown): LibrariesContentType => { + if (!isLibrariesContentType(object)) { + throw new InternalServerErrorException('Invalid input type for castToLibrariesContentType'); + } + + return object; +}; + +@Injectable() +export class H5PLibraryManagementService { + // should all this prop private? + contentTypeCache: ContentTypeCache; + + contentTypeRepo: ContentTypeInformationRepository; + + libraryManager: LibraryManager; + + libraryAdministration: LibraryAdministration; + + libraryWishList: string[]; + + constructor( + private readonly libraryStorage: LibraryStorage, + private readonly contentStorage: ContentStorage, + private readonly configService: ConfigService + ) { + const kvCache = new cacheImplementations.CachedKeyValueStorage('kvcache'); + this.contentTypeCache = new ContentTypeCache(h5pConfig, kvCache); + this.libraryManager = new LibraryManager( + this.libraryStorage, + undefined, + undefined, + undefined, + undefined, + undefined, + h5pConfig + ); + this.contentTypeRepo = new ContentTypeInformationRepository(this.contentTypeCache, this.libraryManager, h5pConfig); + const contentManager = new ContentManager(this.contentStorage); + this.libraryAdministration = new LibraryAdministration(this.libraryManager, contentManager); + const filePath = this.configService.get('H5P_EDITOR__LIBRARY_LIST_PATH'); + + const librariesYamlContent = readFileSync(filePath, { encoding: 'utf-8' }); + const librariesContentType = castToLibrariesContentType(parse(librariesYamlContent)); + this.libraryWishList = librariesContentType.h5p_libraries; + } + + public async uninstallUnwantedLibraries( + wantedLibraries: string[], + librariesToCheck: ILibraryAdministrationOverviewItem[] + ): Promise { + if (librariesToCheck.length === 0) { + return; + } + const lastPositionLibrariesToCheckArray = librariesToCheck.length - 1; + if ( + !wantedLibraries.includes(librariesToCheck[lastPositionLibrariesToCheckArray].machineName) && + librariesToCheck[lastPositionLibrariesToCheckArray].dependentsCount === 0 + ) { + // force removal, don't let content prevent it, therefore use libraryStorage directly + // also to avoid conflicts, remove one-by-one, not using for-await: + await this.libraryStorage.deleteLibrary(librariesToCheck[lastPositionLibrariesToCheckArray]); + } + await this.uninstallUnwantedLibraries( + this.libraryWishList, + librariesToCheck.slice(0, lastPositionLibrariesToCheckArray) + ); + } + + private checkContentTypeExists(contentType: IHubContentType[]): void { + if (contentType === undefined) { + throw new NotFoundException('this library does not exist'); + } + } + + private createDefaultIUser(): IUser { + const user: IUser = { + canCreateRestricted: true, + canInstallRecommended: true, + canUpdateAndInstallLibraries: true, + email: 'a@b.de', + id: 'a', + name: 'a', + type: 'local', + }; + + return user; + } + + public async installLibraries(librariesToInstall: string[]): Promise { + if (librariesToInstall.length === 0) { + return; + } + const lastPositionLibrariesToInstallArray = librariesToInstall.length - 1; + // avoid conflicts, install one-by-one: + const contentType = await this.contentTypeCache.get(librariesToInstall[lastPositionLibrariesToInstallArray]); + this.checkContentTypeExists(contentType); + + const user = this.createDefaultIUser(); + + await this.contentTypeRepo.installContentType(librariesToInstall[lastPositionLibrariesToInstallArray], user); + await this.installLibraries(librariesToInstall.slice(0, lastPositionLibrariesToInstallArray)); + } + + public async run(): Promise { + const installedLibraries = await this.libraryAdministration.getLibraries(); + await this.uninstallUnwantedLibraries(this.libraryWishList, installedLibraries); + await this.installLibraries(this.libraryWishList); + } +} diff --git a/apps/server/src/modules/h5p-library-management/service/index.ts b/apps/server/src/modules/h5p-library-management/service/index.ts new file mode 100644 index 00000000000..01ed55caf01 --- /dev/null +++ b/apps/server/src/modules/h5p-library-management/service/index.ts @@ -0,0 +1,2 @@ +export * from './h5p-library-management.service'; +export * from './h5p-library-management.config'; diff --git a/config/default.schema.json b/config/default.schema.json index 89d0a328a59..d57355c8c0f 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -309,7 +309,7 @@ }, "H5P_EDITOR": { "type": "object", - "description": "Properties of the H5P server microservice", + "description": "Properties of the H5P server microservice and library management job", "required": [ "S3_ENDPOINT", "S3_REGION", @@ -368,6 +368,45 @@ "minimum": 0, "default": 10000, "description": "Timeout for incoming requests to the h5p editor in milliseconds." + }, + "LIBRARY_LIST_PATH": { + "type": "string", + "default": "config/h5p-libraries.yaml", + "description": "Path to yaml file with list of h5p content type libraries which should be installed" + } + } + }, + "H5P_Library": { + "type": "object", + "description": "Properties of the H5P server microservice", + "required": [ + "S3_ENDPOINT", + "S3_BUCKET_LIBRARIES", + "S3_ACCESS_KEY_ID", + "S3_SECRET_ACCESS_KEY" + ], + "default": {}, + "properties": { + "S3_ENDPOINT": { + "type": "string", + "format": "uri", + "default": "http://localhost:4568", + "description": "The URI of the S3 provider" + }, + "S3_ACCESS_KEY_ID": { + "type": "string", + "default": "", + "description": "Access Key to S3_BUCKET_CONTENT with R/W permissions" + }, + "S3_SECRET_ACCESS_KEY": { + "type": "string", + "default": "", + "description": "Secret key to S3_BUCKET_CONTENT with R/W permissions" + }, + "S3_BUCKET_LIBRARIES": { + "type": "string", + "default": "", + "description": "Library Bucket name" } } }, diff --git a/config/h5p-libraries.yaml b/config/h5p-libraries.yaml new file mode 100644 index 00000000000..fb4bfd27138 --- /dev/null +++ b/config/h5p-libraries.yaml @@ -0,0 +1 @@ +h5p_libraries: ['H5P.Accordion','H5P.MultiChoice'] diff --git a/nest-cli.json b/nest-cli.json index 8ce5461bb6f..b3fd935b81f 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -98,6 +98,15 @@ "compilerOptions": { "tsConfigPath": "apps/server/tsconfig.app.json" } + }, + "h5p-library-management": { + "type": "application", + "root": "apps/server", + "entryFile": "apps/h5p-library-management.app", + "sourceRoot": "apps/server/src", + "compilerOptions": { + "tsConfigPath": "apps/server/tsconfig.app.json" + } } } } diff --git a/package.json b/package.json index 4153b7636e7..6e179f67264 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,9 @@ "nest:start:h5p": "nest start h5p-editor", "nest:start:h5p:dev": "nest start h5p-editor --debug --watch", "nest:start:h5p:prod": "node dist/apps/server/apps/h5p-editor.app", + "nest:start:h5p:library-management": "nest start h5p-library-management", + "nest:start:h5p:library-management:dev": "nest start h5p-library-management --debug --watch", + "nest:start:h5p:library-management:prod": "node dist/apps/server/apps/h5p-library-management.app", "nest:start:console": "nest start console --", "nest:start:console:dev": "nest start console --watch --", "nest:start:console:debug": "nest start console --debug --watch --", diff --git a/scripts/libraryManagement.sh b/scripts/libraryManagement.sh new file mode 100755 index 00000000000..798c1869208 --- /dev/null +++ b/scripts/libraryManagement.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +npm run nest:start:h5p:library-management