UNPKG

@silexlabs/silex

Version:

Free and easy website builder for everyone.

160 lines (137 loc) 6.22 kB
/* * Silex website builder, free/libre no-code tool for makers. * Copyright (c) 2023 lexoyo and Silex Labs foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ import { Router } from 'express' import { API_PUBLICATION_PUBLISH, API_PUBLICATION_STATUS, API_WEBSITE_ASSET_READ } from '../../constants' import { ApiPublicationPublishBody, ApiPublicationPublishQuery, ApiPublicationPublishResponse, ApiPublicationStatusQuery, ApiPublicationStatusResponse, JobId, PublicationData, ConnectorType, ConnectorId, WebsiteId, ClientSideFile, ClientSideFileWithContent, ClientSideFileWithSrc} from '../../types' import { ConnectorFile, HostingConnector, StorageConnector, getConnector } from '../connectors/connectors' import { ServerConfig } from '../config' import { requiredParam } from '../utils/validation' import { PublishEndEventType, PublishStartEventType, ServerEvent } from '../events' import { getJob, jobManager } from '../jobs' /** * @fileoverview Publication plugin for Silex * Adds a publication API to Silex server */ /** * Error thrown by the publication API * @param message error message * @param code http status code */ export class PublicationError extends Error { constructor(message: string, public code: number) { super(message) } } const PROJECT_ROOT = require.main ? require.main.path : process.cwd() /** * Get the desired connector * Can be the default connector or a specific one */ export async function getHostingConnector(session: any, config: ServerConfig, connectorId?: string): Promise<HostingConnector> { const hostingConnector = await getConnector(config, session, ConnectorType.HOSTING, connectorId) // ?? config.getHostingConnectors()[0] if (!hostingConnector) { throw new PublicationError('No hosting connector found', 500) } if (!await hostingConnector.isLoggedIn(session)) { throw new PublicationError('Not logged in', 401) } return hostingConnector as HostingConnector } export async function getStorageConnector(session: any, config: ServerConfig, connectorId?: string): Promise<StorageConnector> { const storageConnector = await getConnector(config, session, ConnectorType.STORAGE, connectorId) // ?? config.getStorageConnectors()[0] if (!storageConnector) { throw new PublicationError('No storage connector found', 500) } if (!await storageConnector.isLoggedIn(session)) { throw new PublicationError('Not logged in to the storage connector', 401) } return storageConnector as StorageConnector } export default function (config: ServerConfig): Router { // Create a new router const router = Router() // Get publication status router.get(API_PUBLICATION_STATUS, async function (req, res) { const query: ApiPublicationStatusQuery = req.query as any const jobId: JobId = query.jobId as string const job = jobId && getJob(jobId) if (!job) { console.error(`Error: job not found with id ${jobId}`) res.status(404).json({ message: 'Error: job not found.', }) return } res.json({ ...job, _timeout: undefined, } as ApiPublicationStatusResponse) }) // Publish website router.post(API_PUBLICATION_PUBLISH, async function (req, res) { try { const query: ApiPublicationPublishQuery = req.query as any const body: ApiPublicationPublishBody = req.body const websiteId = requiredParam<WebsiteId>(query.websiteId as string, 'id in query') const storageId = requiredParam<ConnectorId>(query.storageId as string, 'storageId in query') const hostingId = requiredParam<ConnectorId>(query.hostingId as string, 'hostingId in query') const session = requiredParam(req['session'], 'session on express request') // Check params const data = body as PublicationData const { files } = data if(!files) { throw new PublicationError('Missing files in body', 400) } // Hook for plugins config.emit(ServerEvent.PUBLISH_START, data as PublishStartEventType) // Get hosting connector and make sure the user is logged in const hostingConnector = await getHostingConnector(session, config, hostingId) // Get storage connector which holds the assets const storage = await getStorageConnector(session, config, storageId) // Load the content if necessary const filesList: ConnectorFile[] = await Promise.all(files.map(async (file: ClientSideFile) => { const fileWithContent = file as ClientSideFileWithContent const fileWithSrc = file as ClientSideFileWithSrc if(!fileWithContent.content && !fileWithSrc.src) throw new PublicationError('Missing content or src in file', 400) const content = fileWithContent.content ?? await storage.readAsset(session, websiteId, fileWithSrc.src) return { // Destination path: file.permalink ?? file.path, // Content content, } })) res.json({ url: await hostingConnector.getUrl(session, websiteId), job: await hostingConnector.publish(session, websiteId, filesList, jobManager), } as ApiPublicationPublishResponse) } catch (err) { console.error('Error publishing the website', err) res .status(typeof err.code === 'number' ? err.code : 500) .json({ message: `Error publishing the website. ${err.message}` }) // Hook for plugins config.emit(ServerEvent.PUBLISH_END, err as PublishEndEventType) return } // Hook for plugins config.emit(ServerEvent.PUBLISH_END) }) return router }