Files
2020-09-20 20:30:46 +03:00

159 lines
5.2 KiB
TypeScript

import winston from 'winston';
import express from 'express';
import path from 'path';
import { defaultSchema, joi } from '../joi';
import { pathTraversal } from '../joi/customValidators';
import {
EntriesByFolderParams,
EntriesByFilesParams,
GetEntryParams,
PersistEntryParams,
GetMediaParams,
GetMediaFileParams,
PersistMediaParams,
DeleteFileParams,
DeleteFilesParams,
DataFile,
} from '../types';
import { listRepoFiles, deleteFile, writeFile, move } from '../utils/fs';
import { entriesFromFiles, readMediaFile } from '../utils/entries';
type FsOptions = {
repoPath: string;
logger: winston.Logger;
};
export const localFsMiddleware = ({ repoPath, logger }: FsOptions) => {
return async function(req: express.Request, res: express.Response) {
try {
const { body } = req;
switch (body.action) {
case 'info': {
res.json({
repo: path.basename(repoPath),
// eslint-disable-next-line @typescript-eslint/camelcase
publish_modes: ['simple'],
type: 'local_fs',
});
break;
}
case 'entriesByFolder': {
const payload = body.params as EntriesByFolderParams;
const { folder, extension, depth } = payload;
const entries = await listRepoFiles(repoPath, folder, extension, depth).then(files =>
entriesFromFiles(
repoPath,
files.map(file => ({ path: file })),
),
);
res.json(entries);
break;
}
case 'entriesByFiles': {
const payload = body.params as EntriesByFilesParams;
const entries = await entriesFromFiles(repoPath, payload.files);
res.json(entries);
break;
}
case 'getEntry': {
const payload = body.params as GetEntryParams;
const [entry] = await entriesFromFiles(repoPath, [{ path: payload.path }]);
res.json(entry);
break;
}
case 'persistEntry': {
const {
entry,
dataFiles = [entry as DataFile],
assets,
} = body.params as PersistEntryParams;
await Promise.all(
dataFiles.map(dataFile => writeFile(path.join(repoPath, dataFile.path), dataFile.raw)),
);
// save assets
await Promise.all(
assets.map(a =>
writeFile(path.join(repoPath, a.path), Buffer.from(a.content, a.encoding)),
),
);
if (dataFiles.every(dataFile => dataFile.newPath)) {
dataFiles.forEach(async dataFile => {
await move(
path.join(repoPath, dataFile.path),
path.join(repoPath, dataFile.newPath!),
);
});
}
res.json({ message: 'entry persisted' });
break;
}
case 'getMedia': {
const { mediaFolder } = body.params as GetMediaParams;
const files = await listRepoFiles(repoPath, mediaFolder, '', 1);
const mediaFiles = await Promise.all(files.map(file => readMediaFile(repoPath, file)));
res.json(mediaFiles);
break;
}
case 'getMediaFile': {
const { path } = body.params as GetMediaFileParams;
const mediaFile = await readMediaFile(repoPath, path);
res.json(mediaFile);
break;
}
case 'persistMedia': {
const { asset } = body.params as PersistMediaParams;
await writeFile(
path.join(repoPath, asset.path),
Buffer.from(asset.content, asset.encoding),
);
const file = await readMediaFile(repoPath, asset.path);
res.json(file);
break;
}
case 'deleteFile': {
const { path: filePath } = body.params as DeleteFileParams;
await deleteFile(repoPath, filePath);
res.json({ message: `deleted file ${filePath}` });
break;
}
case 'deleteFiles': {
const { paths } = body.params as DeleteFilesParams;
await Promise.all(paths.map(filePath => deleteFile(repoPath, filePath)));
res.json({ message: `deleted files ${paths.join(', ')}` });
break;
}
case 'getDeployPreview': {
res.json(null);
break;
}
default: {
const message = `Unknown action ${body.action}`;
res.status(422).json({ error: message });
break;
}
}
} catch (e) {
logger.error(`Error handling ${JSON.stringify(req.body)}: ${e.message}`);
res.status(500).json({ error: 'Unknown error' });
}
};
};
export const getSchema = ({ repoPath }: { repoPath: string }) => {
const schema = defaultSchema({ path: pathTraversal(repoPath) });
return schema;
};
type Options = {
logger: winston.Logger;
};
export const registerMiddleware = async (app: express.Express, options: Options) => {
const { logger } = options;
const repoPath = path.resolve(process.env.GIT_REPO_DIRECTORY || process.cwd());
app.post('/api/v1', joi(getSchema({ repoPath })));
app.post('/api/v1', localFsMiddleware({ repoPath, logger }));
logger.info(`Netlify CMS File System Proxy Server configured with ${repoPath}`);
};