feat(fs): add stat API for markdown file validation (#774)
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
0e70422835
commit
14fc741cc9
@@ -283,6 +283,54 @@ export const registerFsRoutes = (app, dependencies) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/fs/stat', async (req, res) => {
|
||||
const filePath = typeof req.query.path === 'string' ? req.query.path.trim() : '';
|
||||
if (!filePath) {
|
||||
return res.status(400).json({ error: 'Path is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const resolved = await resolveWorkspacePathFromContext({
|
||||
req,
|
||||
targetPath: filePath,
|
||||
resolveProjectDirectory,
|
||||
path,
|
||||
os,
|
||||
normalizeDirectoryPath,
|
||||
openchamberUserConfigRoot,
|
||||
});
|
||||
if (!resolved.ok) {
|
||||
return res.status(400).json({ error: resolved.error });
|
||||
}
|
||||
|
||||
const [canonicalPath, canonicalBase] = await Promise.all([
|
||||
fsPromises.realpath(resolved.resolved),
|
||||
fsPromises.realpath(resolved.base).catch(() => path.resolve(resolved.base)),
|
||||
]);
|
||||
|
||||
if (!isPathWithinRoot(canonicalPath, canonicalBase, path, os)) {
|
||||
return res.status(403).json({ error: 'Access to file denied' });
|
||||
}
|
||||
|
||||
const stats = await fsPromises.stat(canonicalPath);
|
||||
if (!stats.isFile()) {
|
||||
return res.status(400).json({ error: 'Specified path is not a file' });
|
||||
}
|
||||
|
||||
return res.json({ path: canonicalPath, isFile: true, size: stats.size });
|
||||
} catch (error) {
|
||||
const err = error;
|
||||
if (err && typeof err === 'object' && err.code === 'ENOENT') {
|
||||
return res.status(404).json({ error: 'File not found' });
|
||||
}
|
||||
if (err && typeof err === 'object' && err.code === 'EACCES') {
|
||||
return res.status(403).json({ error: 'Access to file denied' });
|
||||
}
|
||||
console.error('Failed to stat file:', error);
|
||||
return res.status(500).json({ error: (error && error.message) || 'Failed to stat file' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/fs/read', async (req, res) => {
|
||||
const filePath = typeof req.query.path === 'string' ? req.query.path.trim() : '';
|
||||
if (!filePath) {
|
||||
|
||||
@@ -110,6 +110,23 @@ export const createWebFilesAPI = (): FilesAPI => ({
|
||||
};
|
||||
},
|
||||
|
||||
async statFile(path: string): Promise<{ path: string; isFile: boolean; size: number }> {
|
||||
const target = normalizePath(path);
|
||||
const response = await fetch(`/api/fs/stat?path=${encodeURIComponent(target)}`);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: response.statusText }));
|
||||
throw new Error((error as { error?: string }).error || 'Failed to stat file');
|
||||
}
|
||||
|
||||
const result = await response.json().catch(() => ({}));
|
||||
return {
|
||||
path: typeof (result as { path?: string }).path === 'string' ? normalizePath((result as { path: string }).path) : target,
|
||||
isFile: Boolean((result as { isFile?: boolean }).isFile),
|
||||
size: typeof (result as { size?: number }).size === 'number' ? (result as { size: number }).size : 0,
|
||||
};
|
||||
},
|
||||
|
||||
async readFile(path: string): Promise<{ content: string; path: string }> {
|
||||
const target = normalizePath(path);
|
||||
const response = await fetch(`/api/fs/read?path=${encodeURIComponent(target)}`);
|
||||
|
||||
Reference in New Issue
Block a user