Skip to content

refactor: extract GitHub DB ops to service layer (OPE-80)#261

Merged
DevanshuNEU merged 2 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:refactor/extract-github-service
Feb 24, 2026
Merged

refactor: extract GitHub DB ops to service layer (OPE-80)#261
DevanshuNEU merged 2 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:refactor/extract-github-service

Conversation

@DevanshuNEU

@DevanshuNEU DevanshuNEU commented Feb 24, 2026

Copy link
Copy Markdown
Collaborator

Problem

routes/github.py had 4 database helper functions (_get_github_connection, _save_github_connection, _delete_github_connection, _update_last_used) directly calling get_supabase_service().client.table(...). Routes should orchestrate, not query databases.

Fix

New services/github_connections.py (75 lines) with clean public API:

Function What
get_connection(user_id) Fetch GitHub OAuth connection
save_connection(...) Upsert OAuth token + user info
delete_connection(user_id) Remove connection
update_last_used(user_id) Update last_used_at timestamp

Functions changed from async to sync -- Supabase client is sync, the async wrappers were misleading.

Changes

File Change
services/github_connections.py NEW -- 75 lines, 4 DB operations
routes/github.py 360 -> 297 lines. Removed 4 inline DB functions, imports from service.

2 files changed. 289 tests pass.

Closes OPE-80

Summary by CodeRabbit

  • Refactor
    • Reorganized GitHub connection operations into a dedicated service layer for improved code structure and maintainability.

…vices/github_connections.py

Routes should orchestrate HTTP flow, not query databases directly.

Extracted 4 functions from routes/github.py to services/github_connections.py:
- get_connection(user_id) -- fetch GitHub OAuth connection
- save_connection(...) -- upsert OAuth token + user info
- delete_connection(user_id) -- remove connection
- update_last_used(user_id) -- update last_used_at timestamp

Functions changed from async to sync (Supabase client is sync).
Callers in routes/github.py updated: dropped await, renamed to
service function names.

routes/github.py: 360 -> 297 lines (-63)
services/github_connections.py: 75 lines (new)

289 tests pass.

Closes OPE-80
@vercel

vercel Bot commented Feb 24, 2026

Copy link
Copy Markdown

@DevanshuNEU is attempting to deploy a commit to the Dev's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This refactoring extracts GitHub OAuth database operations from route handlers into a dedicated service layer. The new GitHub Connections Service provides four helper functions for managing GitHub authentication data, while the route module is simplified to delegate persistence logic to this service.

Changes

Cohort / File(s) Summary
GitHub Connections Service
backend/services/github_connections.py
New service module providing four public functions for GitHub connection persistence: get_connection(), save_connection(), delete_connection(), and update_last_used(). Each function interacts with Supabase and includes error logging.
GitHub Routes Refactoring
backend/routes/github.py
Replaced internal helper functions (_get_github_connection, _delete_github_connection, _save_github_connection, _update_last_used) with calls to the new service layer functions. Control flow logic remains unchanged; only the persistence implementation location has shifted.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit's verse on cleaner code:

The helpers hop to their own file,
Where service layers dwell in style,
Less clutter in the routes today,
More organized, a better way!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: extract GitHub DB ops to service layer' directly and clearly describes the main change: extracting database operations from routes to a service layer. It is concise, specific, and matches the primary objective of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@DevanshuNEU

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
backend/services/github_connections.py (1)

74-75: update_last_used swallows all exceptions silently — add at least a debug-level log.

except Exception: pass discards every failure with no trace in logs or metrics. A transient network error or schema mismatch during a last_used_at update will be completely invisible, making future debugging difficult.

♻️ Proposed fix
     except Exception:
-        pass
+        logger.error("Failed to update last_used_at", user_id=user_id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/services/github_connections.py` around lines 74 - 75, The except
Exception: pass in update_last_used silently swallows all errors; modify that
except block in the update_last_used function to catch Exception as e and log
the error (include e and stack trace) at debug or error level instead of passing
— e.g., use the module's logger (or processLogger) to record a clear message
like "failed to update last_used_at" with the exception details so failures are
visible for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/services/github_connections.py`:
- Around line 1-75: Wrap the module-level functions (get_connection,
save_connection, delete_connection, update_last_used) into a singleton service
class (e.g., class GitHubConnectionsService) that provides those methods, move
the existing logic into instance methods, and create a module-level singleton
instance (e.g., github_connections_service = GitHubConnectionsService()) for
callers to import; keep internal calls to get_supabase_service and logger
unchanged and preserve method signatures but as instance methods so code follows
the same singleton pattern used by dependency_analyzer.py.
- Around line 13-75: The four functions get_connection, save_connection,
delete_connection, and update_last_used perform blocking Supabase network I/O
and must be converted to async: change each signature to async and wrap the
blocking DB calls (the calls to get_supabase_service().client and the subsequent
table(...).select()/upsert()/delete()/update().execute()) in asyncio.to_thread
(or asyncio.get_event_loop().run_in_executor(None, ...)) so the DB round-trips
run off the event loop; return/raise the same values as before and then update
all call sites (e.g., routes/github.py) to await these async functions. Ensure
exception logging behavior is preserved when wrapping the blocking calls.
- Line 16: The file currently performs repeated inline imports of
get_supabase_service inside functions (seen in save_connection,
delete_connection, update_last_used and another spot); hoist a single import
statement "from services.supabase_service import get_supabase_service" to the
top-level module imports and remove the deferred inline imports from those
functions so they use the module-level symbol instead, keeping behavior
identical but improving readability and following standard practice.

---

Nitpick comments:
In `@backend/services/github_connections.py`:
- Around line 74-75: The except Exception: pass in update_last_used silently
swallows all errors; modify that except block in the update_last_used function
to catch Exception as e and log the error (include e and stack trace) at debug
or error level instead of passing — e.g., use the module's logger (or
processLogger) to record a clear message like "failed to update last_used_at"
with the exception details so failures are visible for debugging.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdcc19d and 47ce43a.

📒 Files selected for processing (2)
  • backend/routes/github.py
  • backend/services/github_connections.py

Comment thread backend/services/github_connections.py Outdated
Comment thread backend/services/github_connections.py Outdated
Comment thread backend/services/github_connections.py Outdated
1. Hoisted 4 identical inline 'from services.supabase_service import
   get_supabase_service' to module-level. No circular import issue.
2. update_last_used: silent 'except: pass' now logs at debug level
   so timestamp update failures are visible.

Skipped: singleton class (stateless module, zero instance state),
async conversion (5th+ time, sync Supabase client).

289 tests pass.
@DevanshuNEU

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
backend/services/github_connections.py (2)

14-72: 🛠️ Refactor suggestion | 🟠 Major

Unresolved service pattern mismatch: wrap these functions in the project’s singleton service shape.

This new service still exposes bare module-level functions instead of the singleton class pattern used by other services (e.g., dependency_analyzer.py).

♻️ Suggested structure
+class GitHubConnectionsService:
+    def get_connection(self, user_id: str) -> Optional[dict]:
+        ...
+    def save_connection(self, ...) -> bool:
+        ...
+    def delete_connection(self, user_id: str) -> bool:
+        ...
+    def update_last_used(self, user_id: str) -> None:
+        ...
+
+_github_connections_service: Optional[GitHubConnectionsService] = None
+
+def get_github_connections_service() -> GitHubConnectionsService:
+    global _github_connections_service
+    if _github_connections_service is None:
+        _github_connections_service = GitHubConnectionsService()
+    return _github_connections_service

As per coding guidelines: "All new services should follow the singleton pattern used in dependency_analyzer.py" (backend/services/**/*.py).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/services/github_connections.py` around lines 14 - 72, Convert these
module-level functions (get_connection, save_connection, delete_connection,
update_last_used) into methods on a singleton service class (e.g.,
GitHubConnectionsService) following the pattern used in dependency_analyzer.py:
move DB access (get_supabase_service().client) and the try/except logic into
instance methods, provide a classmethod like get_instance() or module-level
INSTANCE to enforce a single instance, and export that instance for callers to
use instead of the bare functions; preserve signatures and return values but
call the methods via the singleton (e.g.,
GitHubConnectionsService.get_instance().get_connection(...)) so the service
conforms to the project's singleton service shape.

14-72: ⚠️ Potential issue | 🟠 Major

Blocking DB I/O in sync helpers is executed on async request paths.

Line 18, Line 46, Line 57, and Line 70 perform synchronous Supabase .execute() calls. These functions are then invoked inside async FastAPI handlers, so DB round-trips can block the event loop under load.

#!/bin/bash
set -euo pipefail

echo "1) Service signatures (expect async defs for I/O operations):"
rg -nP '^\s*def\s+(get_connection|save_connection|delete_connection|update_last_used)\s*\(' backend/services/github_connections.py
rg -nP '^\s*async\s+def\s+(get_connection|save_connection|delete_connection|update_last_used)\s*\(' backend/services/github_connections.py || true

echo
echo "2) Async route handlers calling these helpers (awaited should be True if helpers are async):"
python - <<'PY'
import ast, pathlib
targets = {"get_connection","save_connection","delete_connection","update_last_used"}
p = pathlib.Path("backend/routes/github.py")
tree = ast.parse(p.read_text())
parent = {}
for n in ast.walk(tree):
    for c in ast.iter_child_nodes(n):
        parent[c] = n

for fn in [n for n in tree.body if isinstance(n, ast.AsyncFunctionDef)]:
    for n in ast.walk(fn):
        if isinstance(n, ast.Call) and isinstance(n.func, ast.Name) and n.func.id in targets:
            awaited = isinstance(parent.get(n), ast.Await)
            print(f"{fn.name}: Line {n.lineno} -> {n.func.id} (awaited={awaited})")
PY

As per coding guidelines: "Use async/await for I/O operations in Python backend" (backend/**/*.py).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/services/github_connections.py` around lines 14 - 72, The four
helpers (get_connection, save_connection, delete_connection, update_last_used)
perform blocking Supabase .execute() calls on async request paths; update each
to be non-blocking by making the functions async (async def ...) and moving DB
round-trips off the event loop—either by switching to an async Supabase client
and awaiting the .execute() calls or by wrapping the current sync calls with a
thread executor (e.g., asyncio.to_thread or FastAPI's run_in_threadpool) so the
.table(...).select/...upsert/...delete/...update(...).execute() calls do not
block; keep the same return values and exception handling but await the
threaded/async call in the updated async functions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@backend/services/github_connections.py`:
- Around line 14-72: Convert these module-level functions (get_connection,
save_connection, delete_connection, update_last_used) into methods on a
singleton service class (e.g., GitHubConnectionsService) following the pattern
used in dependency_analyzer.py: move DB access (get_supabase_service().client)
and the try/except logic into instance methods, provide a classmethod like
get_instance() or module-level INSTANCE to enforce a single instance, and export
that instance for callers to use instead of the bare functions; preserve
signatures and return values but call the methods via the singleton (e.g.,
GitHubConnectionsService.get_instance().get_connection(...)) so the service
conforms to the project's singleton service shape.
- Around line 14-72: The four helpers (get_connection, save_connection,
delete_connection, update_last_used) perform blocking Supabase .execute() calls
on async request paths; update each to be non-blocking by making the functions
async (async def ...) and moving DB round-trips off the event loop—either by
switching to an async Supabase client and awaiting the .execute() calls or by
wrapping the current sync calls with a thread executor (e.g., asyncio.to_thread
or FastAPI's run_in_threadpool) so the
.table(...).select/...upsert/...delete/...update(...).execute() calls do not
block; keep the same return values and exception handling but await the
threaded/async call in the updated async functions.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdcc19d and 6c042bb.

📒 Files selected for processing (2)
  • backend/routes/github.py
  • backend/services/github_connections.py

@vercel

vercel Bot commented Feb 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
opencodeintel Ignored Ignored Preview Feb 24, 2026 11:19pm

@DevanshuNEU DevanshuNEU merged commit 27643fb into OpenCodeIntel:main Feb 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant