From 0fe3754cf5853be4c29d76a22938d10a94f28925 Mon Sep 17 00:00:00 2001 From: Darrien Rushing Date: Sun, 15 Mar 2026 18:09:28 -0500 Subject: [PATCH] pushing back up to get started again --- .gitignore | 3 ++ README.md | 5 +++ docker-compose.yml | 31 +++++++++++++++++++ requirements.txt | 2 ++ src/github_cli/actions/refresh.py | 41 ++++++++++++++++++++++-- src/github_cli/actions/repos.py | 17 +++++++++- src/github_cli/main.py | 22 +------------ src/github_cli/storage/db.py | 28 +++++++++++++++++ src/github_cli/utilities/file_utils.py | 43 ++++++++++++++++++++++++++ 9 files changed, 168 insertions(+), 24 deletions(-) create mode 100644 docker-compose.yml create mode 100644 src/github_cli/storage/db.py create mode 100644 src/github_cli/utilities/file_utils.py diff --git a/.gitignore b/.gitignore index 7395f3a..b689e79 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +/postgres-data +/pgadmin-data + # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. __pycache__/ diff --git a/README.md b/README.md index a94c749..6400c9f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ A better way to manage your GitHub. +## Tech Stack + +- Python CLI +- Postgres + ## Useful Tools for this repo - Draw.io Integration (VSCode extension) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c072421 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.9" + +services: + postgres: + image: postgres:16-alpine + container_name: github-inventory-postgres + ports: + - "5432:5432" + environment: + POSTGRES_USER: meddlin + POSTGRES_PASSWORD: jailbreak + POSTGRES_DB: github_inventory + volumes: + - ./postgres-data:/var/lib/postgresql/data # persistent data stored locally + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-meddlin} -d ${POSTGRES_DB:-github_inventory}"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + pgadmin: + image: dpage/pgadmin4 + container_name: github-inventory-pgadmin + restart: always + ports: + - "8080:80" + environment: + PGADMIN_DEFAULT_EMAIL: drushing.dev@gmail.com + PGADMIN_DEFAULT_PASSWORD: jailbreak + volumes: + - ./pgadmin-data:/var/lib/pgadmin \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1706c66..789d897 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,7 @@ Pygments==2.19.2 python-dotenv==1.1.1 requests==2.32.4 rich==14.0.0 +termcolor==3.2.0 typing_extensions==4.14.1 urllib3==2.5.0 +yaspin==3.3.0 diff --git a/src/github_cli/actions/refresh.py b/src/github_cli/actions/refresh.py index 081b285..ab65b95 100644 --- a/src/github_cli/actions/refresh.py +++ b/src/github_cli/actions/refresh.py @@ -1,5 +1,42 @@ +from github_cli.actions.repos import handle_repo_import +from yaspin import yaspin +from yaspin.spinners import Spinners -def handle_refresh(): +from github_cli.utilities import file_utils + +def handle_refresh(is_tty: bool = True): """Handle refresh database""" - print("to be implemented - refresh actions go here") + print('Refreshing...') + + # if nuclear option + # issue: docker-compose down + # remove ./postgres-data directory + # remove ./pgadmin-data directory + # restart docker-compose up -d + + # Basic refresh + # drop database + # + + __tmp_dir__ = "./tmp" + __db_path__ = "./inventory.db" + + # Configure spinner based on TTY detection + if is_tty: + spinner_config = {"color": "green"} + else: + # No color configuration to avoid warnings + spinner_config = {} + + with yaspin(**spinner_config) as sp: + sp.spinner = Spinners.arc + + # print("Deleting cve.db...") + # file_utils.remove_file(__db_path__) + + # print("Deleting temp dir: /tmp ...") + # file_utils.clean_directory(dir_to_be_deleted=__tmp_dir__) + + print("Ingesting GitHub data...") + handle_repo_import() diff --git a/src/github_cli/actions/repos.py b/src/github_cli/actions/repos.py index 1914cac..c4e8d34 100644 --- a/src/github_cli/actions/repos.py +++ b/src/github_cli/actions/repos.py @@ -91,7 +91,7 @@ def user_repos_report(username: str): # Render table console.print(table) -def handle_repos(username: str): +def handle_list_repos(username: str): repo_data = __request_repos_for_user(username=username) for repo in repo_data: gh_repo = GitHubRepository( @@ -102,3 +102,18 @@ def handle_repos(username: str): ) print(gh_repo.url) print('all repos processed...') + +def handle_repo_import(username: str = "meddlin"): + """Handle repo import""" + + repo_data = __request_repos_for_user(username=username) + for repo in repo_data: + gh_repo = GitHubRepository( + id=repo['id'], + name=repo['name'], + node_id=repo['node_id'], + url=repo['url'], + language=repo['language'] + ) + print(gh_repo.name) + # insert_repo(gh_repo) diff --git a/src/github_cli/main.py b/src/github_cli/main.py index abb7634..50c7c75 100644 --- a/src/github_cli/main.py +++ b/src/github_cli/main.py @@ -64,27 +64,7 @@ def main(): refresh_actions.handle_refresh() elif args.command == "list": if args.repos: - repo_actions.handle_repos(username="meddlin") - # repo_data = repos.__request_repos_for_user(username="meddlin") - # print(repo_data) - - # parser.add_argument('--repl', action = argparse.BooleanOptionalAction, help = "Start REPL-mode") - - # subparsers = parser.add_subparsers(dest = 'service', required = True) - # gh_parser = subparsers.add_parser('github', help = 'GitHub related commands') - # gh_subparser = gh_parser.add_subparsers(dest = 'command') - - # repo_parser = gh_subparser.add_parser('repo', help = 'GitHub repo commands') - # repo_parser.add_argument('--name', type = str, help = 'Name of repository') - # repo_parser.add_argument('--owner', type = str, help = 'Owner of repository') - # repo_parser.add_argument('--report', type = str, help = 'Type of report to execute') - # repo_parser.add_argument('--user', type = str, help = 'GitHub username') - # repo_parser.add_argument('--csv', action = argparse.BooleanOptionalAction) - # repo_parser.set_defaults(func = repos.handle_args) - - # args = parser.parse_args() - # if hasattr(args, 'func'): - # args.func(args) + repo_actions.handle_list_repos(username="meddlin") if __name__ == "__main__": main() diff --git a/src/github_cli/storage/db.py b/src/github_cli/storage/db.py new file mode 100644 index 0000000..056455b --- /dev/null +++ b/src/github_cli/storage/db.py @@ -0,0 +1,28 @@ + + +DB_DSN = "postgresql://meddlin:jailbreak@localhost:5432/github_inventory" # update me + +SCHEMA_STATEMENTS = [ + """ + -- Enable one extension for UUID generation (pick one): + CREATE EXTENSION IF NOT EXISTS pgcrypto; -- then use gen_random_uuid() + -- or + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- then use uuid_generate_v4() + + CREATE TABLE IF NOT EXISTS repositories ( + _id UUID PRIMARY KEY DEFAULT uuid_generate_v4() + _created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + + id integer NULL, + name TEXT NOT NULL, + full_name TEXT, + name TEXT NULL, + private boolean NULL, + owner TEXT NULL, + html_url TEXT NULL, + description TEXT NULL, + fork boolean NULL, + url text, + ); + """, +] \ No newline at end of file diff --git a/src/github_cli/utilities/file_utils.py b/src/github_cli/utilities/file_utils.py new file mode 100644 index 0000000..8d4f423 --- /dev/null +++ b/src/github_cli/utilities/file_utils.py @@ -0,0 +1,43 @@ +import os +import shutil + +def remove_file(file_name_for_delete: str) -> None: + """Remove the specified file. + + Args: + file_name_for_delete (str): Path to file to be deleted. + + Raises: + ValueError: If an empty string is passed in. + """ + + # Check if the file name is a string and not empty + if not isinstance(file_name_for_delete, str) or not file_name_for_delete: + raise ValueError("File name must be a non-empty string") + + try: + # Check if the path exists and is a regular file + if os.path.exists(file_name_for_delete) and os.path.isfile(file_name_for_delete): + os.remove(file_name_for_delete) + else: + print(f"The specified path {file_name_for_delete} does not exist or is not a file.") + except PermissionError: + print(f"You do not have permission to delete the file {file_name_for_delete}.") + except OSError as e: + print(f"Error deleting file {file_name_for_delete}: {e}") + +def clean_directory(dir_to_be_deleted: str) -> None: + """Delete the directory and all of its contents. + + Args: + dir_to_be_deleted (str): Points to path to be deleted. + """ + # Check if dir_to_be_deleted is None or not a string. + if isinstance(dir_to_be_deleted, str) and dir_to_be_deleted: + + try: + # Check if the directory exists before attempting to delete it + if os.path.exists(dir_to_be_deleted) and os.path.isdir(dir_to_be_deleted): + shutil.rmtree(dir_to_be_deleted) + except OSError as e: + print(f"Error deleting directory {dir_to_be_deleted}: {e}")