/** * BreakPilot H5P Service * Self-hosted H5P Interactive Content Server using @lumieducation/h5p-express */ import express from 'express'; import cors from 'cors'; import path from 'path'; import { fileURLToPath } from 'url'; import bodyParser from 'body-parser'; import { H5PEditor, H5PPlayer, fsImplementations, H5PConfig } from '@lumieducation/h5p-server'; import { h5pAjaxExpressRouter, libraryAdministrationExpressRouter, contentTypeCacheExpressRouter } from '@lumieducation/h5p-express'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = process.env.PORT || 8080; // Middleware app.use(cors()); app.use(bodyParser.json({ limit: '500mb' })); app.use(bodyParser.urlencoded({ extended: true, limit: '500mb' })); // H5P Configuration const config = new H5PConfig( new fsImplementations.InMemoryStorage(), { baseUrl: 'http://localhost:8003', contentFilesUrl: '/h5p/content', downloadUrl: '/h5p/download', coreUrl: '/h5p/core', librariesUrl: '/h5p/libraries', playUrl: '/h5p/play', ajaxUrl: '/h5p/ajax' } ); // Storage implementations const contentStorage = new fsImplementations.FileContentStorage( process.env.H5P_STORAGE_PATH || path.join(__dirname, 'h5p-content') ); const libraryStorage = new fsImplementations.FileLibraryStorage( path.join(__dirname, 'h5p-libraries') ); const temporaryStorage = new fsImplementations.DirectoryTemporaryFileStorage( path.join(__dirname, 'h5p-temp') ); // Initialize H5P Editor and Player let h5pEditor; let h5pPlayer; async function initH5P() { try { h5pEditor = new H5PEditor( contentStorage, config, libraryStorage, undefined, undefined, temporaryStorage ); h5pPlayer = new H5PPlayer( libraryStorage, contentStorage, config ); // Install H5P core files await h5pEditor.installLibraryFromHub('H5P.Column'); console.log('✅ H5P Editor and Player initialized'); return true; } catch (error) { console.error('⚠️ H5P initialization:', error.message); // Continue even if library install fails - editor will show library list return true; } } // User object for H5P (simplified) const getUser = (req) => ({ id: req.headers['x-user-id'] || 'anonymous', name: req.headers['x-user-name'] || 'Anonymous', email: req.headers['x-user-email'] || 'anonymous@breakpilot.app', canInstallRecommended: true, canUpdateAndInstallLibraries: true, canCreateRestricted: true, type: 'local' }); // Function to register H5P routes (called after init) function registerH5PRoutes() { // Serve H5P core static files (JS/CSS) app.use('/h5p/core', express.static(path.join(__dirname, 'h5p-core'))); app.use('/h5p/editor', express.static(path.join(__dirname, 'h5p-core'))); // Serve H5P libraries app.use('/h5p/libraries', express.static(path.join(__dirname, 'h5p-libraries'))); // Serve H5P content files app.use('/h5p/content', express.static(path.join(__dirname, 'h5p-content'))); // ============= H5P AJAX ROUTES (from h5p-express) ============= app.use( '/h5p/ajax', h5pAjaxExpressRouter( h5pEditor, path.resolve('h5p-core'), path.resolve('h5p-libraries'), (req) => getUser(req) ) ); // ============= LIBRARY ADMINISTRATION ============= app.use( '/h5p/libraries-admin', libraryAdministrationExpressRouter( h5pEditor, (req) => getUser(req) ) ); // ============= CONTENT TYPE CACHE ============= app.use( '/h5p/content-type-cache', contentTypeCacheExpressRouter( h5pEditor, (req) => getUser(req) ) ); } // ============= EDITOR & PLAYER HTML PAGES ============= // Create new H5P content (Editor UI) app.get('/h5p/editor/new', async (req, res) => { try { const editorModel = await h5pEditor.render(undefined, 'en', getUser(req)); const html = `
Error: ${error.message}
Please check that H5P libraries are installed.
`); } }); // Edit existing H5P content app.get('/h5p/editor/:contentId', async (req, res) => { try { const editorModel = await h5pEditor.render(req.params.contentId, 'en', getUser(req)); const html = `