UNPKG

@azure-utils/storybooks

Version:

Utils to upload and manage Storybooks via Azure Functions and storage.

210 lines (184 loc) 6.12 kB
/** * Module exports a single function to register * the Storybooks management endpoints. * * @module */ import { app, type HttpFunctionOptions } from "@azure/functions"; import z from "zod"; import { timerPurgeHandler } from "./handlers/timer-purge-handler"; import { registerProjectsRouter } from "./routers/projects-router"; import { registerBuildsRouter } from "./routers/builds-router"; import { registerLabelsRouter } from "./routers/labels-router"; import { registerWebUIRouter } from "./routers/web-ui-router"; import { registerStorybookRouter } from "./routers/storybook-router"; import { DEFAULT_CHECK_PERMISSIONS_CALLBACK, DEFAULT_PURGE_SCHEDULE_CRON, DEFAULT_STORAGE_CONN_STR_ENV_VAR, DEFAULT_SERVICE_NAME, } from "./utils/constants"; import type { CheckPermissionsCallback, OpenAPIOptions } from "./utils/types"; import { joinUrl } from "./utils/url-utils"; import { wrapHttpHandlerWithStore } from "./utils/store"; import { EmptyObjectSchema, ProjectIdSchema } from "./models/shared"; export type { CheckPermissionsCallback, OpenAPIOptions }; /** * Options to register the storybooks router */ export type RegisterStorybooksRouterOptions = { /** * Name of the service. @default "storybooks" */ serviceName?: string; /** * Define the route on which all router is placed. * Can be a sub-path of the main API route. * * @default '' */ baseRoute?: string; /** * Set the Azure Functions authentication level for all routes. * * This is a good option to set if the service is used in * Headless mode and requires single token authentication * for all the requests. * * This setting does not affect health-check route. */ authLevel?: "admin"; /** * Name of the Environment variable which stores * the connection string to the Azure Storage resource. * @default 'AzureWebJobsStorage' */ storageConnectionStringEnvVar?: string; /** * Modify the cron-schedule of timer function * which purge outdated storybooks. * * Pass `null` to disable auto-purge functionality. * * @default "0 0 0 * * *" // Every midnight */ purgeScheduleCron?: string | null; /** * Options to configure OpenAPI schema */ openapi?: OpenAPIOptions; /** * Directories to serve static files from relative to project root (package.json) * @default './public' */ staticDirs?: string[]; /** * Callback function to check permissions. The function receives following params * @param permission - object containing resource and action to permit * @param context - Invocation context of Azure Function * @param request - the HTTP request object * * @return `true` to allow access, or following to deny: * - `false` - returns 403 response * - `HttpResponse` - returns the specified HTTP response */ checkPermissions?: CheckPermissionsCallback; }; /** * Function to register all routes required to manage the Storybooks including * GET, POST and DELETE methods. * * @returns a function to register additional HTTP handlers for the service. */ export function registerStorybooksRouter( options: RegisterStorybooksRouterOptions = {} ): (name: string, options: HttpFunctionOptions) => void { const { serviceName = DEFAULT_SERVICE_NAME, baseRoute = "", authLevel, storageConnectionStringEnvVar = DEFAULT_STORAGE_CONN_STR_ENV_VAR, purgeScheduleCron, openapi, checkPermissions = DEFAULT_CHECK_PERMISSIONS_CALLBACK, } = options; const storageConnectionString = process.env[storageConnectionStringEnvVar]; if (!storageConnectionString) { throw new Error( "Missing env-var '${storageConnectionStringEnvVar}' value.\n" + "It is required to connect with Azure Storage resource." ); } console.log("Registering Storybooks Router"); const openAPIEnabled = !openapi?.disabled; app.setup({ enableHttpStream: true }); const handlerWrapper = wrapHttpHandlerWithStore.bind(null, { serviceName, baseRoute, authLevel, connectionString: storageConnectionString, openapi, staticDirs: options.staticDirs || ["./public"], checkPermissions, }); const normalisedServiceName = serviceName.toLowerCase().replace(/\s+/g, "_"); registerProjectsRouter({ serviceName: normalisedServiceName, baseRoute: joinUrl(baseRoute, "projects"), basePathParamsSchema: EmptyObjectSchema, openAPIEnabled, handlerWrapper, }); registerBuildsRouter({ serviceName: normalisedServiceName, baseRoute: joinUrl(baseRoute, "projects", "{projectId}", "builds"), basePathParamsSchema: z.object({ projectId: ProjectIdSchema }), openAPIEnabled, handlerWrapper, }); registerLabelsRouter({ serviceName: normalisedServiceName, baseRoute: joinUrl(baseRoute, "projects", "{projectId}", "labels"), basePathParamsSchema: z.object({ projectId: ProjectIdSchema }), openAPIEnabled, handlerWrapper, }); registerStorybookRouter({ serviceName: normalisedServiceName, baseRoute: joinUrl(baseRoute, "_"), basePathParamsSchema: EmptyObjectSchema, openAPIEnabled, handlerWrapper, }); registerWebUIRouter({ serviceName: normalisedServiceName, baseRoute, basePathParamsSchema: EmptyObjectSchema, openAPIEnabled, handlerWrapper, }); if (purgeScheduleCron !== null) { app.timer(`${normalisedServiceName}-timer_purge`, { schedule: purgeScheduleCron || DEFAULT_PURGE_SCHEDULE_CRON, handler: timerPurgeHandler(storageConnectionString), }); } /** * Register an HTTP function. * * The baseRoute and authLevel is inherited. * * @param name unique name for the HTTP function * @param options Options for Azure HTTP function */ function registerRoute(name: string, options: HttpFunctionOptions) { app.http(`${normalisedServiceName}-${name}`, { authLevel, ...options, route: joinUrl(baseRoute, options.route || name), handler: handlerWrapper(options.handler, []), methods: options.methods || ["GET"], }); } return registerRoute; }