Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 89 additions & 157 deletions backend/src/config/common-path.ts
Original file line number Diff line number Diff line change
@@ -1,161 +1,93 @@
import path from 'path';
import os from 'os';
import { name } from '../../package.json';
import { existsSync, mkdirSync, promises } from 'fs-extra';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct the import and usage of promises from 'fs-extra'

The fs-extra module does not export a promises object. Instead, its methods return promises directly. Importing promises from 'fs-extra' and using methods like promises.writeFile will not work as expected.

To fix this, import the necessary methods directly from 'fs-extra' and use them without the promises namespace.

Apply this diff to adjust the imports and usage:

-import { existsSync, mkdirSync, promises } from 'fs-extra';
+import { existsSync, mkdirSync, writeFile, readFile, unlink, readdir } from 'fs-extra';

...

 export const writeProjectContent = async (
   userId: string,
   projectId: string,
   fileName: string,
   content: string,
 ): Promise<void> => {
   const contentPath = getProjectContentPath(userId, projectId, fileName);
-  await promises.writeFile(contentPath, content, 'utf8');
+  await writeFile(contentPath, content, 'utf8');
 };

...

 export const readProjectContent = async (
   userId: string,
   projectId: string,
   fileName: string,
 ): Promise<string> => {
   const contentPath = getProjectContentPath(userId, projectId, fileName);
-  return promises.readFile(contentPath, 'utf8');
+  return readFile(contentPath, 'utf8');
 };

...

 export const deleteProjectContent = async (
   userId: string,
   projectId: string,
   fileName: string,
 ): Promise<void> => {
   const contentPath = getProjectContentPath(userId, projectId, fileName);
   if (existsSync(contentPath)) {
-    await promises.unlink(contentPath);
+    await unlink(contentPath);
   }
 };

...

 export const cleanTempDir = async (): Promise<void> => {
   const tempDir = getTempDir();
-  const files = await promises.readdir(tempDir);
+  const files = await readdir(tempDir);
   await Promise.all(
-    files.map((file) => promises.unlink(path.join(tempDir, file))),
+    files.map((file) => unlink(path.join(tempDir, file))),
   );
 };

Also applies to: 93-95, 103-105, 113-115, 126-131


export class CodeFoxPaths {
private static readonly APP_NAME = name;
private static readonly ROOT_DIR = path.join(
os.homedir(),
`.${CodeFoxPaths.APP_NAME}`,
import { createHash } from 'crypto';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused import.

The createHash import from 'crypto' is not used anywhere in the file and should be removed.

-import { createHash } from 'crypto';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { createHash } from 'crypto';


// Constants for base directories
const APP_NAME = 'codefox';
const ROOT_DIR = path.join(os.homedir(), `.${APP_NAME}`);

// Utility function to ensure a directory exists
const ensureDir = (dirPath: string): string => {
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
}
return dirPath;
};

// ----------- We need path traverse Protection after we decide how we read and store the file !!!!!!!!!!!!! ------------
// -------------------------------------------------------------------------------------------------------------
Comment on lines +18 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Implement path traversal protection.

The comment indicates a known security vulnerability. Path traversal attacks could allow unauthorized access to files outside the intended directory structure.

Consider implementing:

  1. Path normalization
  2. Path validation against allowed directories
  3. Sanitization of user inputs

Would you like me to help implement a secure path validation utility that can be used across all path-related functions?


// Root Directory Accessor
export const getRootDir = (): string => ensureDir(ROOT_DIR);

// Configuration Paths
export const getConfigDir = (): string =>
ensureDir(path.join(getRootDir(), 'config'));
export const getConfigPath = (configName: string): string =>
path.join(getConfigDir(), `${configName}.json`);

// Models Directory
export const getModelsDir = (): string =>
ensureDir(path.join(getRootDir(), 'models'));
export const getModelPath = (modelName: string): string =>
path.join(getModelsDir(), modelName);

// Project-Specific Paths
export const getProjectsDir = (): string =>
ensureDir(path.join(getRootDir(), 'projects'));
export const getProjectPath = (projectId: string): string =>
ensureDir(path.join(getProjectsDir(), projectId));
export const getProjectSourceDir = (projectId: string): string =>
ensureDir(path.join(getProjectPath(projectId), 'src'));
export const getProjectGeneratedDir = (projectId: string): string =>
ensureDir(path.join(getProjectPath(projectId), 'generated'));
export const getProjectTestsDir = (projectId: string): string =>
ensureDir(path.join(getProjectPath(projectId), 'tests'));

Comment on lines +22 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor directory accessor functions to reduce code duplication

The functions for accessing directories (e.g., getConfigDir, getModelsDir, getProjectsDir, etc.) follow a repetitive pattern of constructing a path and ensuring the directory exists. Refactoring these functions can reduce code duplication and enhance maintainability.

Consider creating a higher-order function or a factory function to generate these accessors:

// Helper function to create directory accessors
const createDirAccessor = (baseDirGetter: () => string, dirName: string): (() => string) => {
  return () => ensureDir(path.join(baseDirGetter(), dirName));
};

// Base directory accessor
export const getRootDir = (): string => ensureDir(ROOT_DIR);

// Using the helper function to create accessors
export const getConfigDir = createDirAccessor(getRootDir, 'config');
export const getModelsDir = createDirAccessor(getRootDir, 'models');
export const getProjectsDir = createDirAccessor(getRootDir, 'projects');

// Project-specific subdirectories
const createProjectDirAccessor = (subDirName: string): ((projectId: string) => string) => {
  return (projectId: string) => ensureDir(path.join(getProjectPath(projectId), subDirName));
};

export const getProjectSourceDir = createProjectDirAccessor('src');
export const getProjectGeneratedDir = createProjectDirAccessor('generated');
export const getProjectTestsDir = createProjectDirAccessor('tests');

This approach centralizes the directory creation logic and simplifies the addition of new directory accessors in the future.

// Database Paths
export const getDatabaseDir = (): string =>
ensureDir(path.join(getRootDir(), 'data'));
export const getDatabasePath = (): string =>
path.join(getDatabaseDir(), 'codefox.db');
Comment on lines +49 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Make database path configurable.

The database path is hardcoded to 'codefox.db'. Consider making it configurable through environment variables to support different environments and deployment scenarios.

+const DB_NAME = process.env.CODEFOX_DB_NAME || 'codefox.db';
 export const getDatabasePath = (): string =>
-  path.join(getDatabaseDir(), 'codefox.db');
+  path.join(getDatabaseDir(), DB_NAME);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getDatabaseDir = (): string =>
ensureDir(path.join(getRootDir(), 'data'));
export const getDatabasePath = (): string =>
path.join(getDatabaseDir(), 'codefox.db');
const DB_NAME = process.env.CODEFOX_DB_NAME || 'codefox.db';
export const getDatabaseDir = (): string =>
ensureDir(path.join(getRootDir(), 'data'));
export const getDatabasePath = (): string =>
path.join(getDatabaseDir(), DB_NAME);


// Vector Database (INDEX) Path
export const getIndexDir = (): string =>
ensureDir(path.join(getRootDir(), 'INDEX'));
export const getIndexFilePath = (indexFileName: string): string =>
path.join(getIndexDir(), indexFileName);
Comment on lines +54 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Vector database path needs to be configurable for container deployments

The verification confirms that the INDEX directory is hardcoded to be created under the user's home directory (~/.APP_NAME/INDEX). This is problematic for Docker deployments because:

  • Container's home directory is ephemeral and data will be lost when container restarts
  • No environment variable override is available for the path configuration
  • No Docker volume configurations were found in the repository

Recommendations:

  • Make the ROOT_DIR configurable via environment variable with home directory as fallback
  • Add documentation for required volume mounts in Docker deployments
  • Consider moving to a more container-friendly default location like /data/APP_NAME
🔗 Analysis chain

Verify vector database compatibility with Docker deployments.

The INDEX directory path is set relative to the user's home directory. This might cause issues in containerized environments where the home directory might not be persistent or accessible.


Let me gather more information about how the INDEX directory is being used and if there are any Docker-related configurations.


Let me check one more thing - the definition of ROOT_DIR constant which is crucial for understanding the base path configuration.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for Docker-related files and INDEX path usage
rg -g 'Dockerfile*' -g 'docker-compose*' "INDEX|index"
rg -g '!*.{lock,md}' "getIndexDir|getIndexFilePath"

Length of output: 512


Script:

#!/bin/bash
# Check for Docker-related files
fd -e dockerfile -e yml -e yaml

# Check the implementation of getRootDir which is used in getIndexDir
rg -A 5 "getRootDir" backend/src/config/common-path.ts

# Check for any volume mounts or persistent storage configurations
fd -e dockerfile -e yml -e yaml -x grep -l "volume\|VOLUME\|persist"

Length of output: 1636


Script:

#!/bin/bash
# Check the ROOT_DIR definition
rg "ROOT_DIR.*=" backend/src/config/common-path.ts

# Check if there are any environment variables being used for path configuration
rg -g '!*.{lock,md}' "process.env.*(?:DIR|PATH|ROOT)"

Length of output: 166


// Temporary files
export const getTempDir = (): string => {
const tempDir = path.join(ROOT_DIR, 'temp');
if (!existsSync(tempDir)) {
mkdirSync(tempDir, { recursive: true });
}
return tempDir;
};
Comment on lines +61 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Maintain consistency with directory creation pattern.

The getTempDir function implements its own directory check and creation logic, deviating from the established pattern of using ensureDir. Consider refactoring to maintain consistency:

-export const getTempDir = (): string => {
-  const tempDir = path.join(ROOT_DIR, 'temp');
-  if (!existsSync(tempDir)) {
-    mkdirSync(tempDir, { recursive: true });
-  }
-  return tempDir;
-};
+export const getTempDir = (): string =>
+  ensureDir(path.join(getRootDir(), 'temp'));

Committable suggestion skipped: line range outside the PR's diff.


// Utility Functions
export const exists = (filePath: string): boolean => existsSync(filePath);

export const cleanTempDir = async (): Promise<void> => {
const tempDir = getTempDir();
const files = await promises.readdir(tempDir);
await Promise.all(
files.map((file) => promises.unlink(path.join(tempDir, file))),
);

/**
* Internal helper to ensure a directory exists before returning its path
* @param dirPath The directory path to check/create
* @returns The same directory path
*/
private static ensureDir(dirPath: string): string {
if (!existsSync(path.dirname(dirPath))) {
this.ensureDir(path.dirname(dirPath));
}
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
}
return dirPath;
}

/**
* Root Directory
*/
public static getRootDir(): string {
return this.ensureDir(CodeFoxPaths.ROOT_DIR);
}

/**
* Models Directory
*/
public static getModelsDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'models'));
}

public static getModelPath(modelName: string): string {
return path.join(this.getModelsDir(), modelName);
}

/**
* Projects Directory
*/
public static getProjectsDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'projects'));
}

public static getProjectPath(projectId: string): string {
return this.ensureDir(path.join(this.getProjectsDir(), projectId));
}

public static getProjectSourceDir(projectId: string): string {
return this.ensureDir(path.join(this.getProjectPath(projectId), 'src'));
}

public static getProjectGeneratedDir(projectId: string): string {
return this.ensureDir(
path.join(this.getProjectPath(projectId), 'generated'),
);
}

public static getProjectTestsDir(projectId: string): string {
return this.ensureDir(path.join(this.getProjectPath(projectId), 'tests'));
}

/**
* Database
*/
public static getDatabaseDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'data'));
}

public static getDatabasePath(): string {
this.getDatabaseDir(); // Ensure database directory exists
return path.join(this.getDatabaseDir(), 'codefox.db');
}

/**
* Configuration
*/
public static getConfigDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'config'));
}

public static getConfigPath(configName: string): string {
this.getConfigDir(); // Ensure config directory exists
return path.join(this.getConfigDir(), `${configName}.json`);
}

/**
* Cache
*/
public static getCacheDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'cache'));
}

/**
* Logs
*/
public static getLogsDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'logs'));
}

/**
* Temporary files
*/
public static getTempDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'temp'));
}

/**
* Templates
*/
public static getTemplatesDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'templates'));
}

public static getPromptTemplatePath(templateName: string): string {
this.getTemplatesDir(); // Ensure templates directory exists
return path.join(this.getTemplatesDir(), `${templateName}.txt`);
}

/**
* Utility Methods
*/
public static resolvePath(...pathSegments: string[]): string {
const resolvedPath = path.join(this.getRootDir(), ...pathSegments);
return this.ensureDir(path.dirname(resolvedPath));
}

public static exists(filePath: string): boolean {
return existsSync(filePath);
}

public static async cleanTempDir(): Promise<void> {
const tempDir = this.getTempDir();
const files = await promises.readdir(tempDir);
await Promise.all(
files.map((file) => promises.unlink(path.join(tempDir, file))),
);
}

public static getProjectStructure(projectId: string): {
root: string;
src: string;
generated: string;
tests: string;
} {
return {
root: this.getProjectPath(projectId),
src: this.getProjectSourceDir(projectId),
generated: this.getProjectGeneratedDir(projectId),
tests: this.getProjectTestsDir(projectId),
};
}
}
};
Comment on lines +72 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling to cleanTempDir function.

The function should handle potential errors during directory cleanup:

  1. Check if tempDir exists before reading
  2. Handle errors during file deletion
  3. Consider adding logging for debugging purposes
 export const cleanTempDir = async (): Promise<void> => {
   const tempDir = getTempDir();
+  if (!existsSync(tempDir)) {
+    return;
+  }
   const files = await promises.readdir(tempDir);
-  await Promise.all(
-    files.map((file) => promises.unlink(path.join(tempDir, file))),
-  );
+  try {
+    await Promise.all(
+      files.map(async (file) => {
+        const filePath = path.join(tempDir, file);
+        try {
+          await promises.unlink(filePath);
+        } catch (error) {
+          console.error(`Failed to delete ${filePath}:`, error);
+        }
+      }),
+    );
+  } catch (error) {
+    console.error('Failed to clean temp directory:', error);
+    throw error;
+  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const cleanTempDir = async (): Promise<void> => {
const tempDir = getTempDir();
const files = await promises.readdir(tempDir);
await Promise.all(
files.map((file) => promises.unlink(path.join(tempDir, file))),
);
/**
* Internal helper to ensure a directory exists before returning its path
* @param dirPath The directory path to check/create
* @returns The same directory path
*/
private static ensureDir(dirPath: string): string {
if (!existsSync(path.dirname(dirPath))) {
this.ensureDir(path.dirname(dirPath));
}
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
}
return dirPath;
}
/**
* Root Directory
*/
public static getRootDir(): string {
return this.ensureDir(CodeFoxPaths.ROOT_DIR);
}
/**
* Models Directory
*/
public static getModelsDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'models'));
}
public static getModelPath(modelName: string): string {
return path.join(this.getModelsDir(), modelName);
}
/**
* Projects Directory
*/
public static getProjectsDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'projects'));
}
public static getProjectPath(projectId: string): string {
return this.ensureDir(path.join(this.getProjectsDir(), projectId));
}
public static getProjectSourceDir(projectId: string): string {
return this.ensureDir(path.join(this.getProjectPath(projectId), 'src'));
}
public static getProjectGeneratedDir(projectId: string): string {
return this.ensureDir(
path.join(this.getProjectPath(projectId), 'generated'),
);
}
public static getProjectTestsDir(projectId: string): string {
return this.ensureDir(path.join(this.getProjectPath(projectId), 'tests'));
}
/**
* Database
*/
public static getDatabaseDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'data'));
}
public static getDatabasePath(): string {
this.getDatabaseDir(); // Ensure database directory exists
return path.join(this.getDatabaseDir(), 'codefox.db');
}
/**
* Configuration
*/
public static getConfigDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'config'));
}
public static getConfigPath(configName: string): string {
this.getConfigDir(); // Ensure config directory exists
return path.join(this.getConfigDir(), `${configName}.json`);
}
/**
* Cache
*/
public static getCacheDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'cache'));
}
/**
* Logs
*/
public static getLogsDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'logs'));
}
/**
* Temporary files
*/
public static getTempDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'temp'));
}
/**
* Templates
*/
public static getTemplatesDir(): string {
return this.ensureDir(path.join(this.getRootDir(), 'templates'));
}
public static getPromptTemplatePath(templateName: string): string {
this.getTemplatesDir(); // Ensure templates directory exists
return path.join(this.getTemplatesDir(), `${templateName}.txt`);
}
/**
* Utility Methods
*/
public static resolvePath(...pathSegments: string[]): string {
const resolvedPath = path.join(this.getRootDir(), ...pathSegments);
return this.ensureDir(path.dirname(resolvedPath));
}
public static exists(filePath: string): boolean {
return existsSync(filePath);
}
public static async cleanTempDir(): Promise<void> {
const tempDir = this.getTempDir();
const files = await promises.readdir(tempDir);
await Promise.all(
files.map((file) => promises.unlink(path.join(tempDir, file))),
);
}
public static getProjectStructure(projectId: string): {
root: string;
src: string;
generated: string;
tests: string;
} {
return {
root: this.getProjectPath(projectId),
src: this.getProjectSourceDir(projectId),
generated: this.getProjectGeneratedDir(projectId),
tests: this.getProjectTestsDir(projectId),
};
}
}
};
export const cleanTempDir = async (): Promise<void> => {
const tempDir = getTempDir();
if (!existsSync(tempDir)) {
return;
}
const files = await promises.readdir(tempDir);
try {
await Promise.all(
files.map(async (file) => {
const filePath = path.join(tempDir, file);
try {
await promises.unlink(filePath);
} catch (error) {
console.error(`Failed to delete ${filePath}:`, error);
}
}),
);
} catch (error) {
console.error('Failed to clean temp directory:', error);
throw error;
}
};


// Access Project Structure
export const getProjectStructure = (
projectId: string,
): {
root: string;
src: string;
generated: string;
tests: string;
} => ({
root: getProjectPath(projectId),
src: getProjectSourceDir(projectId),
generated: getProjectGeneratedDir(projectId),
tests: getProjectTestsDir(projectId),
});
91 changes: 91 additions & 0 deletions frontend/src/config/common-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import path from 'path';
import { existsSync, mkdirSync, promises as fsPromises } from 'fs-extra';
import { createHash } from 'crypto';

// Constants for the frontend root directory
const FRONTEND_ROOT_DIR = path.resolve(__dirname, '../.codefox-client');
const CLIENT_CACHE_DIR = path.join(FRONTEND_ROOT_DIR, '.cache');

// Utility function to ensure a directory exists
const ensureDir = (dirPath: string): string => {
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
}
return dirPath;
};

// ----------- We need path traverse Protection after we decide how we read and store the file !!!!!!!!!!!!! ------------
// -------------------------------------------------------------------------------------------------------------
Comment on lines +17 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

Based on the analysis of the code, I can now provide a final response:

Critical: Path traversal protection needed for file operations

The codebase shows active file operations in common-path.ts without path traversal protection:

  • writeProjectContent, readProjectContent, and deleteProjectContent functions operate on files using user-provided fileName
  • No path sanitization is implemented despite the warning comment

Implement path traversal protection immediately before the file operations:

function sanitizePath(fileName: string): string {
    // Normalize and verify the path is just a filename
    const normalized = path.normalize(fileName);
    if (normalized.includes(path.sep) || normalized.includes('..')) {
        throw new Error('Invalid filename: must not contain path separators or traversal sequences');
    }
    return normalized;
}

Add this to all file operation functions:

const contentPath = getProjectContentPath(userId, projectId, sanitizePath(fileName));
🔗 Analysis chain

Critical: Implement path traversal protection

The comment indicates a known security vulnerability. Path traversal attacks could allow access to files outside the intended directory structure.

Let's verify current path handling:

Consider implementing this protection immediately:

function sanitizePath(filePath: string): string {
    const normalized = path.normalize(filePath);
    if (normalized.includes('..')) {
        throw new Error('Path traversal detected');
    }
    return normalized;
}

The first script will help us understand the current implementation in common-path.ts. The second will find any existing path sanitization functions, and the third will show file operations that might need path protection.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential path traversal vulnerabilities
rg -l "\.\./" .
rg -l "\.\.\\\" .

Length of output: 1138


Script:

#!/bin/bash
# Let's check the actual path handling implementation in common-path.ts
cat frontend/src/config/common-path.ts

# Also check for any path handling/sanitization functions
rg -n "(?i)(sanitize|normalize|validate).*path" .

# Check file system operations that might be vulnerable
rg -n "(readFile|writeFile|readdir|access|stat)" .

Length of output: 25024


// Step 1: Frontend Root Directory
export const getFrontendRootDir = (): string => ensureDir(FRONTEND_ROOT_DIR);

// Step 2: Cache Directory
export const getCacheDir = (): string => ensureDir(CLIENT_CACHE_DIR);

// Step 3: User Cache Directory
export const getUserCacheDir = (userId: string): string => {
const hashedUserId = hashUserId(userId);
return ensureDir(path.join(getCacheDir(), hashedUserId));
};

// Step 4: Project Cache Directory within a User's Directory
export const getProjectCacheDir = (
userId: string,
projectId: string
): string => {
return ensureDir(path.join(getUserCacheDir(userId), projectId));
};

// Step 5: Content Directory within a Project's Cache Directory
export const getProjectContentDir = (
userId: string,
projectId: string
): string => {
return ensureDir(path.join(getProjectCacheDir(userId, projectId), 'content'));
};

// Updated function to get the full path to a specific file within the 'content' directory
export const getProjectContentPath = (
userId: string,
projectId: string,
fileName: string
): string => {
return path.join(getProjectContentDir(userId, projectId), fileName);
};

// Helper function to hash user IDs for unique cache directories
const hashUserId = (userId: string): string => {
return createHash('md5').update(userId).digest('hex');
};

// Utility Functions for File Operations
export const writeProjectContent = async (
userId: string,
projectId: string,
fileName: string,
content: string
): Promise<void> => {
const contentPath = getProjectContentPath(userId, projectId, fileName);
await fsPromises.writeFile(contentPath, content, 'utf8');
};

export const readProjectContent = async (
userId: string,
projectId: string,
fileName: string
): Promise<string> => {
const contentPath = getProjectContentPath(userId, projectId, fileName);
return fsPromises.readFile(contentPath, 'utf8');
};

export const deleteProjectContent = async (
userId: string,
projectId: string,
fileName: string
): Promise<void> => {
const contentPath = getProjectContentPath(userId, projectId, fileName);
if (existsSync(contentPath)) {
await fsPromises.unlink(contentPath);
}
};
Comment on lines +62 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling and add content validation

The file operations are well-implemented but could benefit from additional safeguards.

Consider adding:

  1. Content size limits
  2. Better error handling
  3. File type validation
 export const writeProjectContent = async (
   userId: string,
   projectId: string,
   fileName: string,
   content: string
 ): Promise<void> => {
+  if (content.length > 10_000_000) { // 10MB limit
+    throw new Error('Content exceeds size limit');
+  }
   const contentPath = getProjectContentPath(userId, projectId, fileName);
-  await fsPromises.writeFile(contentPath, content, 'utf8');
+  try {
+    await fsPromises.writeFile(contentPath, content, 'utf8');
+  } catch (error) {
+    throw new Error(`Failed to write content: ${error.message}`);
+  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Utility Functions for File Operations
export const writeProjectContent = async (
userId: string,
projectId: string,
fileName: string,
content: string
): Promise<void> => {
const contentPath = getProjectContentPath(userId, projectId, fileName);
await fsPromises.writeFile(contentPath, content, 'utf8');
};
export const readProjectContent = async (
userId: string,
projectId: string,
fileName: string
): Promise<string> => {
const contentPath = getProjectContentPath(userId, projectId, fileName);
return fsPromises.readFile(contentPath, 'utf8');
};
export const deleteProjectContent = async (
userId: string,
projectId: string,
fileName: string
): Promise<void> => {
const contentPath = getProjectContentPath(userId, projectId, fileName);
if (existsSync(contentPath)) {
await fsPromises.unlink(contentPath);
}
};
// Utility Functions for File Operations
export const writeProjectContent = async (
userId: string,
projectId: string,
fileName: string,
content: string
): Promise<void> => {
if (content.length > 10_000_000) { // 10MB limit
throw new Error('Content exceeds size limit');
}
const contentPath = getProjectContentPath(userId, projectId, fileName);
try {
await fsPromises.writeFile(contentPath, content, 'utf8');
} catch (error) {
throw new Error(`Failed to write content: ${error.message}`);
}
};
export const readProjectContent = async (
userId: string,
projectId: string,
fileName: string
): Promise<string> => {
const contentPath = getProjectContentPath(userId, projectId, fileName);
return fsPromises.readFile(contentPath, 'utf8');
};
export const deleteProjectContent = async (
userId: string,
projectId: string,
fileName: string
): Promise<void> => {
const contentPath = getProjectContentPath(userId, projectId, fileName);
if (existsSync(contentPath)) {
await fsPromises.unlink(contentPath);
}
};

Loading