From 450fcb4973a4f4b183a0a93eefd3bd87c756f111 Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Sun, 8 Sep 2024 01:14:08 +0930 Subject: [PATCH] feat: implement dev mode --- backend/app/git/__init__.py | 7 + backend/app/git/auth/authenticate.py | 82 +- docker-compose.yml | 2 + frontend/src/api/api.js | 516 +++--- .../src/components/settings/CommitSection.jsx | 46 +- .../src/components/settings/SettingsPage.jsx | 1590 +++++++++-------- 6 files changed, 1242 insertions(+), 1001 deletions(-) diff --git a/backend/app/git/__init__.py b/backend/app/git/__init__.py index 2d88672..45a3091 100644 --- a/backend/app/git/__init__.py +++ b/backend/app/git/__init__.py @@ -5,6 +5,7 @@ from .branches.manager import Branch_Manager from .operations.manager import GitOperations from .repo.unlink import unlink_repository from .repo.clone import clone_repository +from .auth.authenticate import check_dev_mode from ..settings_utils import save_settings import logging @@ -249,3 +250,9 @@ def generate_commit_message(user_message, files): commit_message = f"{user_message}\n\nChanges:\n" + "\n".join(file_changes) return commit_message + + +@bp.route('/dev', methods=['GET']) +def dev_mode(): + is_dev_mode = check_dev_mode() + return jsonify({'devMode': is_dev_mode}), 200 diff --git a/backend/app/git/auth/authenticate.py b/backend/app/git/auth/authenticate.py index 797b9c6..8dde86a 100644 --- a/backend/app/git/auth/authenticate.py +++ b/backend/app/git/auth/authenticate.py @@ -2,33 +2,95 @@ import subprocess import logging +import os +from ...settings_utils import load_settings logger = logging.getLogger(__name__) + +def get_github_token(): + token = os.environ.get('GITHUB_TOKEN') + if token: + logger.info("GitHub token retrieved from environment variable") + else: + logger.warning("GitHub token not found in environment variables") + return token + + def validate_git_token(repo_url, git_token): + logger.info(f"Validating git token for repo: {repo_url}") try: parts = repo_url.strip('/').split('/') if len(parts) < 2: + logger.error(f"Invalid repo URL format: {repo_url}") return False - repo_owner, repo_name = parts[-2], parts[-1].replace('.git', '') + logger.info(f"Parsed repo owner: {repo_owner}, repo name: {repo_name}") + api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}" + logger.debug(f"GitHub API URL: {api_url}") curl_command = [ - 'curl', '-s', '-o', '/dev/null', '-w', '%{http_code}', - '-H', f'Authorization: Bearer {git_token}', - '-H', 'Accept: application/vnd.github+json', - api_url + 'curl', '-s', '-o', '/dev/null', '-w', '%{http_code}', '-H', + f'Authorization: Bearer {git_token}', '-H', + 'Accept: application/vnd.github+json', api_url ] - + logger.debug(f"Executing curl command: {' '.join(curl_command)}") result = subprocess.run(curl_command, capture_output=True, text=True) http_status_code = int(result.stdout.strip()) + logger.info(f"API response status code: {http_status_code}") if http_status_code == 200: - return True - elif http_status_code == 401: - return False + logger.info( + "Token has access to the repository. Checking write permissions." + ) + permissions_url = f"{api_url}/collaborators/{repo_owner}/permission" + logger.debug(f"Permissions check URL: {permissions_url}") + permissions_command = [ + 'curl', '-s', '-H', f'Authorization: Bearer {git_token}', '-H', + 'Accept: application/vnd.github+json', permissions_url + ] + logger.debug( + f"Executing permissions check command: {' '.join(permissions_command)}" + ) + permissions_result = subprocess.run(permissions_command, + capture_output=True, + text=True) + permissions_data = permissions_result.stdout.strip() + logger.debug(f"Permissions data: {permissions_data}") + + has_write_access = any(perm in permissions_data + for perm in ['admin', 'write', 'maintain']) + logger.info(f"Write access: {has_write_access}") + return has_write_access else: + logger.warning( + f"Token validation failed with status code: {http_status_code}" + ) return False except Exception as e: - return False \ No newline at end of file + logger.exception(f"Error validating git token: {str(e)}") + return False + + +def check_dev_mode(): + logger.info("Checking dev mode") + settings = load_settings() + if not settings: + logger.warning("Settings not found") + return False + if 'gitRepo' not in settings: + logger.warning("Git repo URL not found in settings") + return False + + repo_url = settings['gitRepo'] + logger.info(f"Git repo URL from settings: {repo_url}") + + github_token = get_github_token() + if not github_token: + logger.warning("GitHub token not available") + return False + + is_dev_mode = validate_git_token(repo_url, github_token) + logger.info(f"Dev mode status: {is_dev_mode}") + return is_dev_mode diff --git a/docker-compose.yml b/docker-compose.yml index 9034625..408a654 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,5 +20,7 @@ services: - backend_data:/app/data environment: - FLASK_ENV=development + env_file: + - .env volumes: backend_data: diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index 292e30e..3f2656c 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -3,336 +3,356 @@ import axios from 'axios'; const API_BASE_URL = 'http://localhost:5000'; export const getRegexes = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/regex`); - return response.data; - } catch (error) { - console.error('Error fetching regexes:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/regex`); + return response.data; + } catch (error) { + console.error('Error fetching regexes:', error); + throw error; + } }; export const saveRegex = async regex => { - try { - const response = await axios.post(`${API_BASE_URL}/regex`, regex); - return response.data; - } catch (error) { - console.error('Error saving regex:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/regex`, regex); + return response.data; + } catch (error) { + console.error('Error saving regex:', error); + throw error; + } }; export const updateRegex = async (id, regex) => { - try { - const response = await axios.put(`${API_BASE_URL}/regex/${id}`, regex); - return response.data; - } catch (error) { - console.error('Error updating regex:', error); - throw error; - } + try { + const response = await axios.put(`${API_BASE_URL}/regex/${id}`, regex); + return response.data; + } catch (error) { + console.error('Error updating regex:', error); + throw error; + } }; export const deleteRegex = async (id, force = false) => { - try { - const response = await axios.delete( - `${API_BASE_URL}/regex/${id}${force ? '?force=true' : ''}`, - { - validateStatus: status => { - return ( - (status >= 200 && status < 300) || status === 400 || status === 409 - ); - } - } - ); - return response.data; - } catch (error) { - console.error('Error deleting regex:', error); - throw error; - } + try { + const response = await axios.delete( + `${API_BASE_URL}/regex/${id}${force ? '?force=true' : ''}`, + { + validateStatus: status => { + return ( + (status >= 200 && status < 300) || + status === 400 || + status === 409 + ); + } + } + ); + return response.data; + } catch (error) { + console.error('Error deleting regex:', error); + throw error; + } }; export const getFormats = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/format`); - return response.data; - } catch (error) { - console.error('Error fetching formats:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/format`); + return response.data; + } catch (error) { + console.error('Error fetching formats:', error); + throw error; + } }; export const saveFormat = async format => { - try { - const response = await axios.post(`${API_BASE_URL}/format`, format); - return response.data; - } catch (error) { - console.error('Error saving format:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/format`, format); + return response.data; + } catch (error) { + console.error('Error saving format:', error); + throw error; + } }; export const updateFormat = async (id, format) => { - try { - const response = await axios.put(`${API_BASE_URL}/format/${id}`, format); - return response.data; - } catch (error) { - console.error('Error updating format:', error); - throw error; - } + try { + const response = await axios.put( + `${API_BASE_URL}/format/${id}`, + format + ); + return response.data; + } catch (error) { + console.error('Error updating format:', error); + throw error; + } }; export const deleteFormat = async (id, force = false) => { - try { - const response = await axios.delete( - `${API_BASE_URL}/format/${id}${force ? '?force=true' : ''}`, - { - validateStatus: status => { - return ( - (status >= 200 && status < 300) || status === 400 || status === 409 - ); - } - } - ); - return response.data; - } catch (error) { - console.error('Error deleting format:', error); - throw error; - } + try { + const response = await axios.delete( + `${API_BASE_URL}/format/${id}${force ? '?force=true' : ''}`, + { + validateStatus: status => { + return ( + (status >= 200 && status < 300) || + status === 400 || + status === 409 + ); + } + } + ); + return response.data; + } catch (error) { + console.error('Error deleting format:', error); + throw error; + } }; export const createRegex101Link = async regexData => { - try { - const response = await axios.post( - `${API_BASE_URL}/regex/regex101`, - regexData - ); - return response.data; - } catch (error) { - console.error('Error creating regex101 link:', error); - throw error; - } + try { + const response = await axios.post( + `${API_BASE_URL}/regex/regex101`, + regexData + ); + return response.data; + } catch (error) { + console.error('Error creating regex101 link:', error); + throw error; + } }; export const getSettings = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/settings`); - return response.data; - } catch (error) { - console.error('Error fetching settings:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/settings`); + return response.data; + } catch (error) { + console.error('Error fetching settings:', error); + throw error; + } }; export const getGitStatus = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/git/status`); - return response.data; - } catch (error) { - console.error('Error fetching Git status:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/git/status`); + return response.data; + } catch (error) { + console.error('Error fetching Git status:', error); + throw error; + } }; export const getBranches = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/git/branches`); - return response.data; - } catch (error) { - console.error('Error fetching branches:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/git/branches`); + return response.data; + } catch (error) { + console.error('Error fetching branches:', error); + throw error; + } }; export const checkoutBranch = async branchName => { - try { - const response = await axios.post(`${API_BASE_URL}/git/checkout`, { - branch: branchName - }); - return response.data; - } catch (error) { - console.error('Error checking out branch:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/checkout`, { + branch: branchName + }); + return response.data; + } catch (error) { + console.error('Error checking out branch:', error); + throw error; + } }; export const createBranch = async (branchName, baseBranch) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/branch`, { - name: branchName, - base: baseBranch - }); - return response.data; - } catch (error) { - console.error('Error creating branch:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/branch`, { + name: branchName, + base: baseBranch + }); + return response.data; + } catch (error) { + console.error('Error creating branch:', error); + throw error; + } }; export const deleteBranch = async branchName => { - try { - const response = await axios.delete( - `${API_BASE_URL}/git/branch/${branchName}` - ); - return response.data; - } catch (error) { - console.error('Error deleting branch:', error); - throw error; - } + try { + const response = await axios.delete( + `${API_BASE_URL}/git/branch/${branchName}` + ); + return response.data; + } catch (error) { + console.error('Error deleting branch:', error); + throw error; + } }; export const addFiles = async files => { - try { - const response = await axios.post(`${API_BASE_URL}/git/stage`, {files}); - return response.data; - } catch (error) { - console.error('Error staging files:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/stage`, {files}); + return response.data; + } catch (error) { + console.error('Error staging files:', error); + throw error; + } }; export const pushFiles = async (files, commitMessage) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/push`, { - files, - commit_message: commitMessage - }); - return response.data; - } catch (error) { - console.error('Error pushing files:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/push`, { + files, + commit_message: commitMessage + }); + return response.data; + } catch (error) { + console.error('Error pushing files:', error); + throw error; + } }; export const revertFile = async filePath => { - try { - const response = await axios.post(`${API_BASE_URL}/git/revert`, { - file_path: filePath - }); - return response.data; - } catch (error) { - console.error('Error reverting file:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/revert`, { + file_path: filePath + }); + return response.data; + } catch (error) { + console.error('Error reverting file:', error); + throw error; + } }; export const revertAll = async () => { - try { - const response = await axios.post(`${API_BASE_URL}/git/revert-all`); - return response.data; - } catch (error) { - console.error('Error reverting all changes:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/revert-all`); + return response.data; + } catch (error) { + console.error('Error reverting all changes:', error); + throw error; + } }; export const deleteFile = async filePath => { - try { - const response = await axios.delete(`${API_BASE_URL}/git/file`, { - data: {file_path: filePath} - }); - return response.data; - } catch (error) { - console.error('Error deleting file:', error); - return {success: false, error: 'Error deleting file'}; - } + try { + const response = await axios.delete(`${API_BASE_URL}/git/file`, { + data: {file_path: filePath} + }); + return response.data; + } catch (error) { + console.error('Error deleting file:', error); + return {success: false, error: 'Error deleting file'}; + } }; export const pullBranch = async branchName => { - try { - const response = await axios.post(`${API_BASE_URL}/git/pull`, { - branch: branchName - }); - return response.data; - } catch (error) { - console.error('Error pulling branch:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/pull`, { + branch: branchName + }); + return response.data; + } catch (error) { + console.error('Error pulling branch:', error); + throw error; + } }; export const getDiff = async filePath => { - try { - const response = await axios.post(`${API_BASE_URL}/git/diff`, { - file_path: filePath - }); - return response.data; - } catch (error) { - console.error('Error fetching diff:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/diff`, { + file_path: filePath + }); + return response.data; + } catch (error) { + console.error('Error fetching diff:', error); + throw error; + } }; export const cloneRepo = async gitRepo => { - try { - const response = await axios.post(`${API_BASE_URL}/git/clone`, { - gitRepo - }); - return response.data; - } catch (error) { - console.error('Error cloning repository:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/clone`, { + gitRepo + }); + return response.data; + } catch (error) { + console.error('Error cloning repository:', error); + throw error; + } }; export const getProfiles = async () => { - try { - const response = await axios.get(`${API_BASE_URL}/profile`); - return response.data; - } catch (error) { - console.error('Error fetching profiles:', error); - throw error; - } + try { + const response = await axios.get(`${API_BASE_URL}/profile`); + return response.data; + } catch (error) { + console.error('Error fetching profiles:', error); + throw error; + } }; export const saveProfile = async profile => { - try { - const response = await axios.post(`${API_BASE_URL}/profile`, profile); - return response.data; - } catch (error) { - console.error('Error saving profile:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/profile`, profile); + return response.data; + } catch (error) { + console.error('Error saving profile:', error); + throw error; + } }; export const updateProfile = async (id, profile) => { - try { - const response = await axios.put(`${API_BASE_URL}/profile/${id}`, profile); - return response.data; - } catch (error) { - console.error('Error updating profile:', error); - throw error; - } + try { + const response = await axios.put( + `${API_BASE_URL}/profile/${id}`, + profile + ); + return response.data; + } catch (error) { + console.error('Error updating profile:', error); + throw error; + } }; export const deleteProfile = async id => { - try { - const response = await axios.delete(`${API_BASE_URL}/profile/${id}`); - return response.data; - } catch (error) { - console.error('Error deleting profile:', error); - throw error; - } + try { + const response = await axios.delete(`${API_BASE_URL}/profile/${id}`); + return response.data; + } catch (error) { + console.error('Error deleting profile:', error); + throw error; + } }; export const unlinkRepo = async (removeFiles = false) => { - try { - const response = await axios.post(`${API_BASE_URL}/git/unlink`, { - removeFiles - }); - return response.data; - } catch (error) { - console.error('Error unlinking repository:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/unlink`, { + removeFiles + }); + return response.data; + } catch (error) { + console.error('Error unlinking repository:', error); + throw error; + } }; export const pushBranchToRemote = async branchName => { - try { - const response = await axios.post(`${API_BASE_URL}/git/branch/push`, { - branch: branchName - }); - return response.data; - } catch (error) { - console.error('Error pushing branch to remote:', error); - throw error; - } + try { + const response = await axios.post(`${API_BASE_URL}/git/branch/push`, { + branch: branchName + }); + return response.data; + } catch (error) { + console.error('Error pushing branch to remote:', error); + throw error; + } +}; + +export const checkDevMode = async () => { + try { + const response = await axios.get(`${API_BASE_URL}/git/dev`); + return response.data; + } catch (error) { + console.error('Error checking dev mode:', error); + throw error; + } }; diff --git a/frontend/src/components/settings/CommitSection.jsx b/frontend/src/components/settings/CommitSection.jsx index cdb0d81..100ef1c 100644 --- a/frontend/src/components/settings/CommitSection.jsx +++ b/frontend/src/components/settings/CommitSection.jsx @@ -6,7 +6,8 @@ const CommitSection = ({ commitMessage, setCommitMessage, hasIncomingChanges, - funMessage + funMessage, + isDevMode }) => { const hasUnstagedChanges = status.outgoing_changes.some( change => !change.staged || (change.staged && change.modified) @@ -18,24 +19,43 @@ const CommitSection = ({ return (
- {hasAnyChanges || hasIncomingChanges ? ( + {isDevMode ? ( <> - {hasStagedChanges && ( + {hasAnyChanges || hasIncomingChanges ? ( <> -

- Commit Message: -

-