aaa
This commit is contained in:
parent
e9289a7d3a
commit
1efa9dc052
@ -1258,3 +1258,101 @@ async def get_conversation_messages(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error getting conversation messages: {str(e)}"
|
||||
)
|
||||
|
||||
# --- Git Monitor Webhook Event Configuration Endpoints ---
|
||||
|
||||
class GitRepositoryEventSettings(BaseModel):
|
||||
events: List[str]
|
||||
|
||||
class AvailableGitEventsResponse(BaseModel):
|
||||
platform: str
|
||||
events: List[str]
|
||||
|
||||
SUPPORTED_GITHUB_EVENTS = [
|
||||
"push", "issues", "issue_comment", "pull_request", "pull_request_review",
|
||||
"pull_request_review_comment", "release", "fork", "star", "watch",
|
||||
"commit_comment", "create", "delete", "deployment", "deployment_status",
|
||||
"gollum", "member", "milestone", "project_card", "project_column", "project",
|
||||
"public", "repository_dispatch", "status"
|
||||
# Add more as needed/supported by formatters
|
||||
]
|
||||
SUPPORTED_GITLAB_EVENTS = [
|
||||
"push", "tag_push", "issues", "note", "merge_request", "wiki_page",
|
||||
"pipeline", "job", "release"
|
||||
# Add more as needed/supported by formatters
|
||||
# GitLab uses "push_events", "issues_events" etc. in webhook config,
|
||||
# but object_kind in payload is often singular like "push", "issue".
|
||||
# We'll store and expect the singular/object_kind style.
|
||||
]
|
||||
|
||||
@router.get("/git_monitors/available_events/{platform}", response_model=AvailableGitEventsResponse)
|
||||
async def get_available_git_events(
|
||||
platform: str,
|
||||
_user: dict = Depends(get_dashboard_user) # Basic auth to access
|
||||
):
|
||||
"""Get a list of available/supported webhook event types for a given platform."""
|
||||
if platform == "github":
|
||||
return AvailableGitEventsResponse(platform="github", events=SUPPORTED_GITHUB_EVENTS)
|
||||
elif platform == "gitlab":
|
||||
return AvailableGitEventsResponse(platform="gitlab", events=SUPPORTED_GITLAB_EVENTS)
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Invalid platform specified. Use 'github' or 'gitlab'.")
|
||||
|
||||
|
||||
@router.get("/guilds/{guild_id}/git_monitors/{repo_db_id}/events", response_model=GitRepositoryEventSettings)
|
||||
async def get_git_repository_event_settings(
|
||||
guild_id: int, # Added for verify_dashboard_guild_admin
|
||||
repo_db_id: int,
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin) # Ensures user is admin of the guild
|
||||
):
|
||||
"""Get the current allowed webhook events for a specific monitored repository."""
|
||||
try:
|
||||
repo_config = await settings_manager.get_monitored_repository_by_id(repo_db_id)
|
||||
if not repo_config:
|
||||
raise HTTPException(status_code=404, detail="Monitored repository not found.")
|
||||
if repo_config['guild_id'] != guild_id: # Ensure the repo belongs to the specified guild
|
||||
raise HTTPException(status_code=403, detail="Repository does not belong to this guild.")
|
||||
|
||||
allowed_events = repo_config.get('allowed_webhook_events', ['push']) # Default to ['push']
|
||||
return GitRepositoryEventSettings(events=allowed_events)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error getting git repository event settings for repo {repo_db_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to retrieve repository event settings.")
|
||||
|
||||
@router.put("/guilds/{guild_id}/git_monitors/{repo_db_id}/events", status_code=status.HTTP_200_OK)
|
||||
async def update_git_repository_event_settings(
|
||||
guild_id: int, # Added for verify_dashboard_guild_admin
|
||||
repo_db_id: int,
|
||||
settings: GitRepositoryEventSettings,
|
||||
_user: dict = Depends(get_dashboard_user),
|
||||
_admin: bool = Depends(verify_dashboard_guild_admin) # Ensures user is admin of the guild
|
||||
):
|
||||
"""Update the allowed webhook events for a specific monitored repository."""
|
||||
try:
|
||||
repo_config = await settings_manager.get_monitored_repository_by_id(repo_db_id)
|
||||
if not repo_config:
|
||||
raise HTTPException(status_code=404, detail="Monitored repository not found.")
|
||||
if repo_config['guild_id'] != guild_id: # Ensure the repo belongs to the specified guild
|
||||
raise HTTPException(status_code=403, detail="Repository does not belong to this guild.")
|
||||
if repo_config['monitoring_method'] != 'webhook':
|
||||
raise HTTPException(status_code=400, detail="Event settings are only applicable for webhook monitoring method.")
|
||||
|
||||
# Validate events against supported list for the platform
|
||||
platform = repo_config['platform']
|
||||
supported_events = SUPPORTED_GITHUB_EVENTS if platform == "github" else SUPPORTED_GITLAB_EVENTS
|
||||
for event in settings.events:
|
||||
if event not in supported_events:
|
||||
raise HTTPException(status_code=400, detail=f"Event '{event}' is not supported for platform '{platform}'.")
|
||||
|
||||
success = await settings_manager.update_monitored_repository_events(repo_db_id, settings.events)
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to update repository event settings.")
|
||||
return {"message": "Repository event settings updated successfully."}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Error updating git repository event settings for repo {repo_db_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to update repository event settings.")
|
||||
|
188
api_service/dashboard_web/git-monitor-settings.html
Normal file
188
api_service/dashboard_web/git-monitor-settings.html
Normal file
@ -0,0 +1,188 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Git Monitor Event Settings</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
|
||||
.container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
|
||||
h1, h2 { color: #333; }
|
||||
.event-list { list-style-type: none; padding: 0; }
|
||||
.event-list li { margin-bottom: 10px; }
|
||||
.event-list label { margin-left: 8px; }
|
||||
button {
|
||||
background-color: #007bff; color: white; padding: 10px 15px;
|
||||
border: none; border-radius: 4px; cursor: pointer; font-size: 16px;
|
||||
}
|
||||
button:hover { background-color: #0056b3; }
|
||||
#statusMessage { margin-top: 15px; padding: 10px; border-radius: 4px; }
|
||||
.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
|
||||
.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
|
||||
.loading { text-align: center; font-style: italic; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Git Monitor Event Settings</h1>
|
||||
<div id="repoInfo">
|
||||
<h2 id="repoUrl">Repository: Loading...</h2>
|
||||
<p>Platform: <span id="repoPlatform">Loading...</span></p>
|
||||
</div>
|
||||
|
||||
<div id="loadingIndicator" class="loading">Loading event settings...</div>
|
||||
|
||||
<form id="eventSettingsForm" style="display:none;">
|
||||
<h3>Select events to receive notifications for:</h3>
|
||||
<ul id="eventList" class="event-list">
|
||||
<!-- Event checkboxes will be populated here -->
|
||||
</ul>
|
||||
<button type="button" onclick="saveEventSettings()">Save Settings</button>
|
||||
</form>
|
||||
<div id="statusMessage"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let guildId;
|
||||
let repoDbId;
|
||||
let currentPlatform; // To store the platform ('github' or 'gitlab')
|
||||
|
||||
// Assumes bearer token is stored in localStorage, adjust if needed
|
||||
function getAuthToken() {
|
||||
return localStorage.getItem('dashboard_token');
|
||||
}
|
||||
|
||||
async function fetchWithAuth(url, options = {}) {
|
||||
const token = getAuthToken();
|
||||
const headers = {
|
||||
...options.headers,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
const response = await fetch(url, { ...options, headers });
|
||||
if (response.status === 401) {
|
||||
// Handle unauthorized access, e.g., redirect to login
|
||||
document.getElementById('statusMessage').textContent = 'Unauthorized. Please log in.';
|
||||
document.getElementById('statusMessage').className = 'error';
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ detail: 'Unknown error occurred' }));
|
||||
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function loadRepositoryInfoAndEvents() {
|
||||
const pathParts = window.location.pathname.split('/');
|
||||
// Assuming URL like /dashboard/guilds/{guildId}/git_monitors/{repoDbId}/settings
|
||||
// Find 'guilds' and 'git_monitors' to get the IDs
|
||||
const guildsIndex = pathParts.indexOf('guilds');
|
||||
const gitMonitorsIndex = pathParts.indexOf('git_monitors');
|
||||
|
||||
if (guildsIndex !== -1 && pathParts.length > guildsIndex + 1) {
|
||||
guildId = pathParts[guildsIndex + 1];
|
||||
}
|
||||
if (gitMonitorsIndex !== -1 && pathParts.length > gitMonitorsIndex + 1) {
|
||||
repoDbId = pathParts[gitMonitorsIndex + 1];
|
||||
}
|
||||
|
||||
if (!guildId || !repoDbId) {
|
||||
document.getElementById('loadingIndicator').textContent = 'Error: Could not parse Guild ID or Repository ID from URL.';
|
||||
document.getElementById('loadingIndicator').className = 'error';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Fetch current settings (this also gives us the platform)
|
||||
const currentSettingsUrl = `/api/guilds/${guildId}/git_monitors/${repoDbId}/events`;
|
||||
const currentSettings = await fetchWithAuth(currentSettingsUrl);
|
||||
|
||||
document.getElementById('repoUrl').textContent = `Repository: ${currentSettings.repository_url}`;
|
||||
document.getElementById('repoPlatform').textContent = currentSettings.platform;
|
||||
currentPlatform = currentSettings.platform;
|
||||
const currentlyAllowedEvents = new Set(currentSettings.allowed_events);
|
||||
|
||||
// 2. Fetch available events for the platform
|
||||
const availableEventsUrl = `/api/git_monitors/available_events/${currentPlatform}`;
|
||||
const availableEventsData = await fetchWithAuth(availableEventsUrl);
|
||||
const availableEvents = availableEventsData.events;
|
||||
|
||||
// 3. Populate checkboxes
|
||||
const eventListUl = document.getElementById('eventList');
|
||||
eventListUl.innerHTML = ''; // Clear previous items
|
||||
|
||||
availableEvents.forEach(event => {
|
||||
const li = document.createElement('li');
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.id = `event-${event}`;
|
||||
checkbox.name = 'webhook_events';
|
||||
checkbox.value = event;
|
||||
if (currentlyAllowedEvents.has(event)) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = `event-${event}`;
|
||||
label.textContent = event.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); // Prettify event name
|
||||
|
||||
li.appendChild(checkbox);
|
||||
li.appendChild(label);
|
||||
eventListUl.appendChild(li);
|
||||
});
|
||||
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('eventSettingsForm').style.display = 'block';
|
||||
|
||||
} catch (error) {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
const statusMsg = document.getElementById('statusMessage');
|
||||
statusMsg.textContent = `Error loading settings: ${error.message}`;
|
||||
statusMsg.className = 'error';
|
||||
console.error('Error loading repository info and events:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveEventSettings() {
|
||||
const statusMsg = document.getElementById('statusMessage');
|
||||
statusMsg.textContent = '';
|
||||
statusMsg.className = '';
|
||||
|
||||
if (!guildId || !repoDbId || !currentPlatform) {
|
||||
statusMsg.textContent = 'Error: Missing critical information (Guild ID, Repo ID, or Platform).';
|
||||
statusMsg.className = 'error';
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedEvents = [];
|
||||
document.querySelectorAll('#eventList input[name="webhook_events"]:checked').forEach(checkbox => {
|
||||
selectedEvents.push(checkbox.value);
|
||||
});
|
||||
|
||||
const payload = {
|
||||
allowed_events: selectedEvents
|
||||
};
|
||||
|
||||
try {
|
||||
const saveUrl = `/api/guilds/${guildId}/git_monitors/${repoDbId}/events`;
|
||||
await fetchWithAuth(saveUrl, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
statusMsg.textContent = 'Settings saved successfully!';
|
||||
statusMsg.className = 'success';
|
||||
} catch (error) {
|
||||
statusMsg.textContent = `Error saving settings: ${error.message}`;
|
||||
statusMsg.className = 'error';
|
||||
console.error('Error saving event settings:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load data when the page loads
|
||||
document.addEventListener('DOMContentLoaded', loadRepositoryInfoAndEvents);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -106,6 +106,13 @@ async def get_monitored_repository_by_id_api(request: Request, repo_db_id: int)
|
||||
log.exception(f"Failed to create a new PostgreSQL connection after pool error: {e2}")
|
||||
return None
|
||||
|
||||
async def get_allowed_events_for_repo(request: Request, repo_db_id: int) -> list[str]:
|
||||
"""Helper to fetch allowed_webhook_events for a repo."""
|
||||
repo_config = await get_monitored_repository_by_id_api(request, repo_db_id)
|
||||
if repo_config and repo_config.get('allowed_webhook_events'):
|
||||
return repo_config['allowed_webhook_events']
|
||||
return ['push'] # Default to 'push' if not set or not found, for safety
|
||||
|
||||
def verify_github_signature(payload_body: bytes, secret_token: str, signature_header: str) -> bool:
|
||||
"""Verify that the payload was sent from GitHub by validating the signature."""
|
||||
if not signature_header:
|
||||
@ -135,7 +142,12 @@ def verify_gitlab_token(secret_token: str, gitlab_token_header: str) -> bool:
|
||||
return False
|
||||
return True
|
||||
|
||||
def format_github_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
# Placeholder for other GitHub event formatters
|
||||
# def format_github_issue_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed: ...
|
||||
# def format_github_pull_request_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed: ...
|
||||
# def format_github_release_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed: ...
|
||||
|
||||
def format_github_push_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a GitHub push event payload into a Discord embed."""
|
||||
try:
|
||||
repo_name = payload.get('repository', {}).get('full_name', repo_url)
|
||||
@ -190,12 +202,16 @@ def format_github_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.exception(f"Error formatting GitHub embed: {e}")
|
||||
embed = discord.Embed(title="Error Processing GitHub Webhook", description=f"Could not parse commit details. Raw payload might be available in logs.\nError: {e}", color=discord.Color.red())
|
||||
log.exception(f"Error formatting GitHub push embed: {e}")
|
||||
embed = discord.Embed(title="Error Processing GitHub Push Webhook", description=f"Could not parse commit details. Raw payload might be available in logs.\nError: {e}", color=discord.Color.red())
|
||||
return embed
|
||||
|
||||
# Placeholder for other GitLab event formatters
|
||||
# def format_gitlab_issue_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed: ...
|
||||
# def format_gitlab_merge_request_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed: ...
|
||||
# def format_gitlab_tag_push_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed: ...
|
||||
|
||||
def format_gitlab_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
def format_gitlab_push_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a GitLab push event payload into a Discord embed."""
|
||||
try:
|
||||
project_name = payload.get('project', {}).get('path_with_namespace', repo_url)
|
||||
@ -244,10 +260,361 @@ def format_gitlab_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.exception(f"Error formatting GitLab embed: {e}")
|
||||
embed = discord.Embed(title="Error Processing GitLab Webhook", description=f"Could not parse commit details. Raw payload might be available in logs.\nError: {e}", color=discord.Color.red())
|
||||
log.exception(f"Error formatting GitLab push embed: {e}")
|
||||
embed = discord.Embed(title="Error Processing GitLab Push Webhook", description=f"Could not parse commit details. Raw payload might be available in logs.\nError: {e}", color=discord.Color.red())
|
||||
return embed
|
||||
|
||||
# --- GitHub - New Event Formatters ---
|
||||
|
||||
def format_github_issues_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a Discord embed for a GitHub issues event."""
|
||||
try:
|
||||
action = payload.get('action', 'Unknown action')
|
||||
issue_data = payload.get('issue', {})
|
||||
repo_name = payload.get('repository', {}).get('full_name', repo_url)
|
||||
sender = payload.get('sender', {})
|
||||
|
||||
title = issue_data.get('title', 'Untitled Issue')
|
||||
issue_number = issue_data.get('number')
|
||||
issue_url = issue_data.get('html_url', repo_url)
|
||||
user_login = sender.get('login', 'Unknown User')
|
||||
user_url = sender.get('html_url', '#')
|
||||
user_avatar = sender.get('avatar_url')
|
||||
|
||||
color = discord.Color.green() if action == "opened" else \
|
||||
discord.Color.red() if action == "closed" else \
|
||||
discord.Color.gold() if action == "reopened" else \
|
||||
discord.Color.light_grey()
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Issue {action.capitalize()}: #{issue_number} {title}",
|
||||
url=issue_url,
|
||||
description=f"Issue in `{repo_name}` was {action}.",
|
||||
color=color
|
||||
)
|
||||
embed.set_author(name=user_login, url=user_url, icon_url=user_avatar)
|
||||
|
||||
if issue_data.get('body') and action == "opened":
|
||||
body = issue_data['body']
|
||||
embed.add_field(name="Description", value=body[:1020] + "..." if len(body) > 1024 else body, inline=False)
|
||||
|
||||
if issue_data.get('labels'):
|
||||
labels = ", ".join([f"`{label['name']}`" for label in issue_data['labels']])
|
||||
embed.add_field(name="Labels", value=labels if labels else "None", inline=True)
|
||||
|
||||
if issue_data.get('assignee'):
|
||||
assignee = issue_data['assignee']['login']
|
||||
embed.add_field(name="Assignee", value=f"[{assignee}]({issue_data['assignee']['html_url']})", inline=True)
|
||||
elif issue_data.get('assignees'):
|
||||
assignees = ", ".join([f"[{a['login']}]({a['html_url']})" for a in issue_data['assignees']])
|
||||
embed.add_field(name="Assignees", value=assignees if assignees else "None", inline=True)
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.error(f"Error formatting GitHub issues embed: {e}\nPayload: {payload}")
|
||||
return discord.Embed(title="Error Processing GitHub Issue Event", description=str(e), color=discord.Color.red())
|
||||
|
||||
def format_github_pull_request_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a Discord embed for a GitHub pull_request event."""
|
||||
try:
|
||||
action = payload.get('action', 'Unknown action')
|
||||
pr_data = payload.get('pull_request', {})
|
||||
repo_name = payload.get('repository', {}).get('full_name', repo_url)
|
||||
sender = payload.get('sender', {})
|
||||
|
||||
title = pr_data.get('title', 'Untitled Pull Request')
|
||||
pr_number = payload.get('number', pr_data.get('number')) # 'number' is top-level for some PR actions
|
||||
pr_url = pr_data.get('html_url', repo_url)
|
||||
user_login = sender.get('login', 'Unknown User')
|
||||
user_url = sender.get('html_url', '#')
|
||||
user_avatar = sender.get('avatar_url')
|
||||
|
||||
color = discord.Color.green() if action == "opened" else \
|
||||
discord.Color.red() if action == "closed" and pr_data.get('merged') is False else \
|
||||
discord.Color.purple() if action == "closed" and pr_data.get('merged') is True else \
|
||||
discord.Color.gold() if action == "reopened" else \
|
||||
discord.Color.blue() if action in ["synchronize", "ready_for_review"] else \
|
||||
discord.Color.light_grey()
|
||||
|
||||
description = f"Pull Request #{pr_number} in `{repo_name}` was {action}."
|
||||
if action == "closed" and pr_data.get('merged'):
|
||||
description = f"Pull Request #{pr_number} in `{repo_name}` was merged."
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"PR {action.capitalize()}: #{pr_number} {title}",
|
||||
url=pr_url,
|
||||
description=description,
|
||||
color=color
|
||||
)
|
||||
embed.set_author(name=user_login, url=user_url, icon_url=user_avatar)
|
||||
|
||||
if pr_data.get('body') and action == "opened":
|
||||
body = pr_data['body']
|
||||
embed.add_field(name="Description", value=body[:1020] + "..." if len(body) > 1024 else body, inline=False)
|
||||
|
||||
embed.add_field(name="Base Branch", value=f"`{pr_data.get('base', {}).get('ref', 'N/A')}`", inline=True)
|
||||
embed.add_field(name="Head Branch", value=f"`{pr_data.get('head', {}).get('ref', 'N/A')}`", inline=True)
|
||||
|
||||
if action == "closed":
|
||||
merged_by = pr_data.get('merged_by')
|
||||
if merged_by:
|
||||
embed.add_field(name="Merged By", value=f"[{merged_by['login']}]({merged_by['html_url']})", inline=True)
|
||||
else:
|
||||
embed.add_field(name="Status", value="Closed without merging", inline=True)
|
||||
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.error(f"Error formatting GitHub PR embed: {e}\nPayload: {payload}")
|
||||
return discord.Embed(title="Error Processing GitHub PR Event", description=str(e), color=discord.Color.red())
|
||||
|
||||
def format_github_release_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a Discord embed for a GitHub release event."""
|
||||
try:
|
||||
action = payload.get('action', 'Unknown action') # e.g., published, created, edited
|
||||
release_data = payload.get('release', {})
|
||||
repo_name = payload.get('repository', {}).get('full_name', repo_url)
|
||||
sender = payload.get('sender', {})
|
||||
|
||||
tag_name = release_data.get('tag_name', 'N/A')
|
||||
release_name = release_data.get('name', tag_name)
|
||||
release_url = release_data.get('html_url', repo_url)
|
||||
user_login = sender.get('login', 'Unknown User')
|
||||
user_url = sender.get('html_url', '#')
|
||||
user_avatar = sender.get('avatar_url')
|
||||
|
||||
color = discord.Color.teal() if action == "published" else discord.Color.blurple()
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Release {action.capitalize()}: {release_name}",
|
||||
url=release_url,
|
||||
description=f"A new release `{tag_name}` was {action} in `{repo_name}`.",
|
||||
color=color
|
||||
)
|
||||
embed.set_author(name=user_login, url=user_url, icon_url=user_avatar)
|
||||
|
||||
if release_data.get('body'):
|
||||
body = release_data['body']
|
||||
embed.add_field(name="Release Notes", value=body[:1020] + "..." if len(body) > 1024 else body, inline=False)
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.error(f"Error formatting GitHub release embed: {e}\nPayload: {payload}")
|
||||
return discord.Embed(title="Error Processing GitHub Release Event", description=str(e), color=discord.Color.red())
|
||||
|
||||
def format_github_issue_comment_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a Discord embed for a GitHub issue_comment event."""
|
||||
try:
|
||||
action = payload.get('action', 'Unknown action') # created, edited, deleted
|
||||
comment_data = payload.get('comment', {})
|
||||
issue_data = payload.get('issue', {})
|
||||
repo_name = payload.get('repository', {}).get('full_name', repo_url)
|
||||
sender = payload.get('sender', {})
|
||||
|
||||
comment_url = comment_data.get('html_url', repo_url)
|
||||
user_login = sender.get('login', 'Unknown User')
|
||||
user_url = sender.get('html_url', '#')
|
||||
user_avatar = sender.get('avatar_url')
|
||||
|
||||
issue_title = issue_data.get('title', 'Untitled Issue')
|
||||
issue_number = issue_data.get('number')
|
||||
|
||||
color = discord.Color.greyple()
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Comment {action} on Issue #{issue_number}: {issue_title}",
|
||||
url=comment_url,
|
||||
color=color
|
||||
)
|
||||
embed.set_author(name=user_login, url=user_url, icon_url=user_avatar)
|
||||
|
||||
if comment_data.get('body'):
|
||||
body = comment_data['body']
|
||||
embed.description = body[:2040] + "..." if len(body) > 2048 else body
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.error(f"Error formatting GitHub issue_comment embed: {e}\nPayload: {payload}")
|
||||
return discord.Embed(title="Error Processing GitHub Issue Comment Event", description=str(e), color=discord.Color.red())
|
||||
|
||||
# --- GitLab - New Event Formatters ---
|
||||
|
||||
def format_gitlab_issue_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a Discord embed for a GitLab issue event (object_kind: 'issue')."""
|
||||
try:
|
||||
attributes = payload.get('object_attributes', {})
|
||||
user = payload.get('user', {})
|
||||
project_data = payload.get('project', {})
|
||||
repo_name = project_data.get('path_with_namespace', repo_url)
|
||||
|
||||
action = attributes.get('action', 'unknown') # open, close, reopen, update
|
||||
title = attributes.get('title', 'Untitled Issue')
|
||||
issue_iid = attributes.get('iid') # Internal ID for display
|
||||
issue_url = attributes.get('url', repo_url)
|
||||
user_name = user.get('name', 'Unknown User')
|
||||
user_avatar = user.get('avatar_url')
|
||||
|
||||
color = discord.Color.green() if action == "open" else \
|
||||
discord.Color.red() if action == "close" else \
|
||||
discord.Color.gold() if action == "reopen" else \
|
||||
discord.Color.light_grey()
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Issue {action.capitalize()}: #{issue_iid} {title}",
|
||||
url=issue_url,
|
||||
description=f"Issue in `{repo_name}` was {action}.",
|
||||
color=color
|
||||
)
|
||||
embed.set_author(name=user_name, icon_url=user_avatar)
|
||||
|
||||
if attributes.get('description') and action == "open":
|
||||
desc = attributes['description']
|
||||
embed.add_field(name="Description", value=desc[:1020] + "..." if len(desc) > 1024 else desc, inline=False)
|
||||
|
||||
if attributes.get('labels'):
|
||||
labels = ", ".join([f"`{label['title']}`" for label in attributes['labels']])
|
||||
embed.add_field(name="Labels", value=labels if labels else "None", inline=True)
|
||||
|
||||
assignees_data = payload.get('assignees', [])
|
||||
if assignees_data:
|
||||
assignees = ", ".join([f"{a['name']}" for a in assignees_data])
|
||||
embed.add_field(name="Assignees", value=assignees, inline=True)
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.error(f"Error formatting GitLab issue embed: {e}\nPayload: {payload}")
|
||||
return discord.Embed(title="Error Processing GitLab Issue Event", description=str(e), color=discord.Color.red())
|
||||
|
||||
def format_gitlab_merge_request_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a Discord embed for a GitLab merge_request event."""
|
||||
try:
|
||||
attributes = payload.get('object_attributes', {})
|
||||
user = payload.get('user', {})
|
||||
project_data = payload.get('project', {})
|
||||
repo_name = project_data.get('path_with_namespace', repo_url)
|
||||
|
||||
action = attributes.get('action', 'unknown') # open, close, reopen, update, merge
|
||||
title = attributes.get('title', 'Untitled Merge Request')
|
||||
mr_iid = attributes.get('iid')
|
||||
mr_url = attributes.get('url', repo_url)
|
||||
user_name = user.get('name', 'Unknown User')
|
||||
user_avatar = user.get('avatar_url')
|
||||
|
||||
color = discord.Color.green() if action == "open" else \
|
||||
discord.Color.red() if action == "close" else \
|
||||
discord.Color.purple() if action == "merge" else \
|
||||
discord.Color.gold() if action == "reopen" else \
|
||||
discord.Color.blue() if action == "update" else \
|
||||
discord.Color.light_grey()
|
||||
|
||||
description = f"Merge Request !{mr_iid} in `{repo_name}` was {action}."
|
||||
if action == "merge":
|
||||
description = f"Merge Request !{mr_iid} in `{repo_name}` was merged."
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"MR {action.capitalize()}: !{mr_iid} {title}",
|
||||
url=mr_url,
|
||||
description=description,
|
||||
color=color
|
||||
)
|
||||
embed.set_author(name=user_name, icon_url=user_avatar)
|
||||
|
||||
if attributes.get('description') and action == "open":
|
||||
desc = attributes['description']
|
||||
embed.add_field(name="Description", value=desc[:1020] + "..." if len(desc) > 1024 else desc, inline=False)
|
||||
|
||||
embed.add_field(name="Source Branch", value=f"`{attributes.get('source_branch', 'N/A')}`", inline=True)
|
||||
embed.add_field(name="Target Branch", value=f"`{attributes.get('target_branch', 'N/A')}`", inline=True)
|
||||
|
||||
if action == "merge" and attributes.get('merge_commit_sha'):
|
||||
embed.add_field(name="Merge Commit", value=f"`{attributes['merge_commit_sha'][:8]}`", inline=True)
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.error(f"Error formatting GitLab MR embed: {e}\nPayload: {payload}")
|
||||
return discord.Embed(title="Error Processing GitLab MR Event", description=str(e), color=discord.Color.red())
|
||||
|
||||
def format_gitlab_release_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a Discord embed for a GitLab release event."""
|
||||
try:
|
||||
# GitLab release webhook payload structure is simpler
|
||||
action = payload.get('action', 'created') # create, update
|
||||
tag_name = payload.get('tag', 'N/A')
|
||||
release_name = payload.get('name', tag_name)
|
||||
release_url = payload.get('url', repo_url)
|
||||
project_data = payload.get('project', {})
|
||||
repo_name = project_data.get('path_with_namespace', repo_url)
|
||||
# GitLab release hooks don't typically include a 'user' who performed the action directly in the root.
|
||||
# It might be inferred or logged differently by GitLab. For now, we'll omit a specific author.
|
||||
|
||||
color = discord.Color.teal() if action == "create" else discord.Color.blurple()
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Release {action.capitalize()}: {release_name}",
|
||||
url=release_url,
|
||||
description=f"A release `{tag_name}` was {action} in `{repo_name}`.",
|
||||
color=color
|
||||
)
|
||||
# embed.set_author(name=project_data.get('namespace', 'GitLab')) # Or project name
|
||||
|
||||
if payload.get('description'):
|
||||
desc = payload['description']
|
||||
embed.add_field(name="Release Notes", value=desc[:1020] + "..." if len(desc) > 1024 else desc, inline=False)
|
||||
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.error(f"Error formatting GitLab release embed: {e}\nPayload: {payload}")
|
||||
return discord.Embed(title="Error Processing GitLab Release Event", description=str(e), color=discord.Color.red())
|
||||
|
||||
def format_gitlab_note_embed(payload: Dict[str, Any], repo_url: str) -> discord.Embed:
|
||||
"""Formats a Discord embed for a GitLab note event (comments)."""
|
||||
try:
|
||||
attributes = payload.get('object_attributes', {})
|
||||
user = payload.get('user', {})
|
||||
project_data = payload.get('project', {})
|
||||
repo_name = project_data.get('path_with_namespace', repo_url)
|
||||
|
||||
note_type = attributes.get('noteable_type', 'Comment') # Issue, MergeRequest, Commit, Snippet
|
||||
note_url = attributes.get('url', repo_url)
|
||||
user_name = user.get('name', 'Unknown User')
|
||||
user_avatar = user.get('avatar_url')
|
||||
|
||||
title_prefix = "New Comment"
|
||||
target_info = ""
|
||||
|
||||
if note_type == 'Commit':
|
||||
commit_data = payload.get('commit', {})
|
||||
title_prefix = f"Comment on Commit `{commit_data.get('id', 'N/A')[:7]}`"
|
||||
elif note_type == 'Issue':
|
||||
issue_data = payload.get('issue', {})
|
||||
title_prefix = f"Comment on Issue #{issue_data.get('iid', 'N/A')}"
|
||||
target_info = issue_data.get('title', '')
|
||||
elif note_type == 'MergeRequest':
|
||||
mr_data = payload.get('merge_request', {})
|
||||
title_prefix = f"Comment on MR !{mr_data.get('iid', 'N/A')}"
|
||||
target_info = mr_data.get('title', '')
|
||||
elif note_type == 'Snippet':
|
||||
snippet_data = payload.get('snippet', {})
|
||||
title_prefix = f"Comment on Snippet #{snippet_data.get('id', 'N/A')}"
|
||||
target_info = snippet_data.get('title', '')
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"{title_prefix}: {target_info}".strip(),
|
||||
url=note_url,
|
||||
color=discord.Color.greyple()
|
||||
)
|
||||
embed.set_author(name=user_name, icon_url=user_avatar)
|
||||
|
||||
if attributes.get('note'):
|
||||
note_body = attributes['note']
|
||||
embed.description = note_body[:2040] + "..." if len(note_body) > 2048 else note_body
|
||||
|
||||
embed.set_footer(text=f"Comment in {repo_name}")
|
||||
return embed
|
||||
except Exception as e:
|
||||
log.error(f"Error formatting GitLab note embed: {e}\nPayload: {payload}")
|
||||
return discord.Embed(title="Error Processing GitLab Note Event", description=str(e), color=discord.Color.red())
|
||||
|
||||
|
||||
@router.post("/github/{repo_db_id}")
|
||||
async def webhook_github(
|
||||
@ -280,18 +647,51 @@ async def webhook_github(
|
||||
|
||||
log.debug(f"GitHub webhook payload for {repo_db_id}: {payload}")
|
||||
|
||||
# We only care about 'push' events for commits
|
||||
event_type = request.headers.get("X-GitHub-Event")
|
||||
if event_type != "push":
|
||||
log.info(f"Ignoring GitHub event type '{event_type}' for repo_db_id: {repo_db_id}")
|
||||
return {"status": "success", "message": f"Event type '{event_type}' ignored."}
|
||||
allowed_events = repo_config.get('allowed_webhook_events', ['push']) # Default to 'push'
|
||||
|
||||
if not payload.get('commits'):
|
||||
log.info(f"GitHub push event for {repo_db_id} has no commits (e.g. branch creation/deletion). Ignoring.")
|
||||
return {"status": "success", "message": "Push event with no commits ignored."}
|
||||
if event_type not in allowed_events:
|
||||
log.info(f"Ignoring GitHub event type '{event_type}' for repo_db_id: {repo_db_id} as it's not in allowed events: {allowed_events}")
|
||||
return {"status": "success", "message": f"Event type '{event_type}' ignored per configuration."}
|
||||
|
||||
discord_embed = None
|
||||
if event_type == "push":
|
||||
if not payload.get('commits') and not payload.get('deleted', False): # Also consider branch deletion as a push event
|
||||
log.info(f"GitHub push event for {repo_db_id} has no commits and is not a delete event. Ignoring.")
|
||||
return {"status": "success", "message": "Push event with no commits ignored."}
|
||||
discord_embed = format_github_push_embed(payload, repo_config['repository_url'])
|
||||
elif event_type == "issues":
|
||||
discord_embed = format_github_issues_embed(payload, repo_config['repository_url'])
|
||||
elif event_type == "pull_request":
|
||||
discord_embed = format_github_pull_request_embed(payload, repo_config['repository_url'])
|
||||
elif event_type == "release":
|
||||
discord_embed = format_github_release_embed(payload, repo_config['repository_url'])
|
||||
elif event_type == "issue_comment":
|
||||
discord_embed = format_github_issue_comment_embed(payload, repo_config['repository_url'])
|
||||
# Add other specific event types above this else block
|
||||
else:
|
||||
log.info(f"GitHub event type '{event_type}' is allowed but not yet handled by a specific formatter for repo_db_id: {repo_db_id}. Sending generic message.")
|
||||
# For unhandled but allowed events, send a generic notification or log.
|
||||
# For now, we'll just acknowledge. If you want to notify for all allowed events, create generic formatter.
|
||||
# return {"status": "success", "message": f"Event type '{event_type}' received but no specific formatter yet."}
|
||||
# Or, create a generic embed:
|
||||
embed_title = f"GitHub Event: {event_type.replace('_', ' ').title()} in {repo_config.get('repository_url')}"
|
||||
embed_description = f"Received a '{event_type}' event."
|
||||
# Try to get a relevant URL
|
||||
action_url = payload.get('repository', {}).get('html_url', '#')
|
||||
if event_type == 'issues' and 'issue' in payload and 'html_url' in payload['issue']:
|
||||
action_url = payload['issue']['html_url']
|
||||
elif event_type == 'pull_request' and 'pull_request' in payload and 'html_url' in payload['pull_request']:
|
||||
action_url = payload['pull_request']['html_url']
|
||||
|
||||
discord_embed = discord.Embed(title=embed_title, description=embed_description, url=action_url, color=discord.Color.light_grey())
|
||||
|
||||
|
||||
if not discord_embed:
|
||||
log.warning(f"No embed generated for allowed GitHub event '{event_type}' for repo {repo_db_id}. This shouldn't happen if event is handled.")
|
||||
return {"status": "error", "message": "Embed generation failed for an allowed event."}
|
||||
|
||||
notification_channel_id = repo_config['notification_channel_id']
|
||||
discord_embed = format_github_embed(payload, repo_config['repository_url'])
|
||||
|
||||
# Convert embed to dict for sending via API
|
||||
message_content = {"embeds": [discord_embed.to_dict()]}
|
||||
@ -384,18 +784,69 @@ async def webhook_gitlab(
|
||||
|
||||
log.debug(f"GitLab webhook payload for {repo_db_id}: {payload}")
|
||||
|
||||
# GitLab uses 'object_kind' for event type
|
||||
event_type = payload.get("object_kind")
|
||||
if event_type != "push": # GitLab calls it 'push' for push hooks
|
||||
log.info(f"Ignoring GitLab event type '{event_type}' for repo_db_id: {repo_db_id}")
|
||||
return {"status": "success", "message": f"Event type '{event_type}' ignored."}
|
||||
# GitLab uses 'object_kind' for event type, or 'event_name' for system hooks
|
||||
event_type = payload.get("object_kind", payload.get("event_name"))
|
||||
allowed_events = repo_config.get('allowed_webhook_events', ['push']) # Default to 'push' (GitLab calls push hooks 'push events' or 'tag_push events')
|
||||
|
||||
if not payload.get('commits'):
|
||||
log.info(f"GitLab push event for {repo_db_id} has no commits. Ignoring.")
|
||||
return {"status": "success", "message": "Push event with no commits ignored."}
|
||||
# Normalize GitLab event types if needed, e.g. 'push' for 'push_hook' or 'tag_push_hook'
|
||||
# For now, assume direct match or that 'push' covers both.
|
||||
# GitLab event names for webhooks: push_events, tag_push_events, issues_events, merge_requests_events, etc.
|
||||
# The payload's object_kind is often more specific: 'push', 'tag_push', 'issue', 'merge_request'.
|
||||
# We should aim to match against object_kind primarily.
|
||||
# Let's simplify: if 'push' is in allowed_events, we'll accept 'push' and 'tag_push' object_kinds.
|
||||
|
||||
effective_event_type = event_type
|
||||
if event_type == "tag_push" and "push" in allowed_events and "tag_push" not in allowed_events:
|
||||
# If only "push" is allowed, but we receive "tag_push", treat it as a push for now.
|
||||
# This logic might need refinement based on how granular the user wants control.
|
||||
pass # It will be caught by the 'push' check if 'push' is allowed.
|
||||
|
||||
is_event_allowed = False
|
||||
if event_type in allowed_events:
|
||||
is_event_allowed = True
|
||||
elif event_type == "tag_push" and "push" in allowed_events: # Special handling if 'push' implies 'tag_push'
|
||||
is_event_allowed = True
|
||||
effective_event_type = "push" # Treat as push for formatter if only push is configured
|
||||
|
||||
if not is_event_allowed:
|
||||
log.info(f"Ignoring GitLab event type '{event_type}' (object_kind/event_name) for repo_db_id: {repo_db_id} as it's not in allowed events: {allowed_events}")
|
||||
return {"status": "success", "message": f"Event type '{event_type}' ignored per configuration."}
|
||||
|
||||
discord_embed = None
|
||||
# Use effective_event_type for choosing formatter
|
||||
if effective_event_type == "push": # This will catch 'push' and 'tag_push' if 'push' is allowed
|
||||
if not payload.get('commits') and payload.get('total_commits_count', 0) == 0:
|
||||
log.info(f"GitLab push event for {repo_db_id} has no commits. Ignoring.")
|
||||
return {"status": "success", "message": "Push event with no commits ignored."}
|
||||
discord_embed = format_gitlab_push_embed(payload, repo_config['repository_url'])
|
||||
elif effective_event_type == "issue": # Matches object_kind 'issue'
|
||||
discord_embed = format_gitlab_issue_embed(payload, repo_config['repository_url'])
|
||||
elif effective_event_type == "merge_request":
|
||||
discord_embed = format_gitlab_merge_request_embed(payload, repo_config['repository_url'])
|
||||
elif effective_event_type == "release":
|
||||
discord_embed = format_gitlab_release_embed(payload, repo_config['repository_url'])
|
||||
elif effective_event_type == "note": # For comments
|
||||
discord_embed = format_gitlab_note_embed(payload, repo_config['repository_url'])
|
||||
# Add other specific event types above this else block
|
||||
else:
|
||||
log.info(f"GitLab event type '{event_type}' (effective: {effective_event_type}) is allowed but not yet handled by a specific formatter for repo_db_id: {repo_db_id}. Sending generic message.")
|
||||
embed_title = f"GitLab Event: {event_type.replace('_', ' ').title()} in {repo_config.get('repository_url')}"
|
||||
embed_description = f"Received a '{event_type}' event."
|
||||
action_url = payload.get('project', {}).get('web_url', '#')
|
||||
# Try to get more specific URLs for common GitLab events
|
||||
if 'object_attributes' in payload and 'url' in payload['object_attributes']:
|
||||
action_url = payload['object_attributes']['url']
|
||||
elif 'project' in payload and 'web_url' in payload['project']:
|
||||
action_url = payload['project']['web_url']
|
||||
|
||||
discord_embed = discord.Embed(title=embed_title, description=embed_description, url=action_url, color=discord.Color.dark_orange())
|
||||
|
||||
|
||||
if not discord_embed:
|
||||
log.warning(f"No embed generated for allowed GitLab event '{event_type}' for repo {repo_db_id}.")
|
||||
return {"status": "error", "message": "Embed generation failed for an allowed event."}
|
||||
|
||||
notification_channel_id = repo_config['notification_channel_id']
|
||||
discord_embed = format_gitlab_embed(payload, repo_config['repository_url'])
|
||||
|
||||
# Similar to GitHub, sending embed needs careful handling with send_discord_message_via_api
|
||||
if not api_settings.DISCORD_BOT_TOKEN:
|
||||
|
@ -243,6 +243,7 @@ async def initialize_database():
|
||||
is_public_repo BOOLEAN DEFAULT TRUE, -- Relevant for polling
|
||||
added_by_user_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
allowed_webhook_events TEXT[] DEFAULT ARRAY['push']::TEXT[], -- Stores which webhook events to notify for
|
||||
CONSTRAINT uq_guild_repo_channel UNIQUE (guild_id, repository_url, notification_channel_id),
|
||||
FOREIGN KEY (guild_id) REFERENCES guilds(guild_id) ON DELETE CASCADE
|
||||
);
|
||||
@ -252,6 +253,30 @@ async def initialize_database():
|
||||
await conn.execute("CREATE INDEX IF NOT EXISTS idx_git_monitored_repo_method ON git_monitored_repositories (monitoring_method);")
|
||||
await conn.execute("CREATE INDEX IF NOT EXISTS idx_git_monitored_repo_url ON git_monitored_repositories (repository_url);")
|
||||
|
||||
# Migration: Add allowed_webhook_events column if it doesn't exist and set default for old rows
|
||||
column_exists_git_events = await conn.fetchval("""
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'git_monitored_repositories'
|
||||
AND column_name = 'allowed_webhook_events'
|
||||
);
|
||||
""")
|
||||
if not column_exists_git_events:
|
||||
log.info("Adding allowed_webhook_events column to git_monitored_repositories table...")
|
||||
await conn.execute("""
|
||||
ALTER TABLE git_monitored_repositories
|
||||
ADD COLUMN allowed_webhook_events TEXT[] DEFAULT ARRAY['push']::TEXT[];
|
||||
""")
|
||||
# Update existing rows to have a default value if they are NULL
|
||||
await conn.execute("""
|
||||
UPDATE git_monitored_repositories
|
||||
SET allowed_webhook_events = ARRAY['push']::TEXT[]
|
||||
WHERE allowed_webhook_events IS NULL;
|
||||
""")
|
||||
log.info("Added allowed_webhook_events column and set default for existing rows.")
|
||||
else:
|
||||
log.debug("allowed_webhook_events column already exists in git_monitored_repositories table.")
|
||||
|
||||
# Logging Event Toggles table - Stores enabled/disabled state per event type
|
||||
await conn.execute("""
|
||||
@ -2194,7 +2219,8 @@ async def add_monitored_repository(
|
||||
target_branch: str | None = None, # For polling
|
||||
polling_interval_minutes: int = 15,
|
||||
is_public_repo: bool = True,
|
||||
last_polled_commit_sha: str | None = None # For initial poll setup
|
||||
last_polled_commit_sha: str | None = None, # For initial poll setup
|
||||
allowed_webhook_events: list[str] | None = None # List of event names like ['push', 'issues']
|
||||
) -> int | None:
|
||||
"""Adds a new repository to monitor. Returns the ID of the new row, or None on failure."""
|
||||
bot = get_bot_instance()
|
||||
@ -2208,23 +2234,28 @@ async def add_monitored_repository(
|
||||
await conn.execute("INSERT INTO guilds (guild_id) VALUES ($1) ON CONFLICT (guild_id) DO NOTHING;", guild_id)
|
||||
|
||||
# Insert the new repository monitoring entry
|
||||
# Default allowed_webhook_events if not provided or empty
|
||||
final_allowed_events = allowed_webhook_events if allowed_webhook_events else ['push']
|
||||
|
||||
repo_id = await conn.fetchval(
|
||||
"""
|
||||
INSERT INTO git_monitored_repositories (
|
||||
guild_id, repository_url, platform, monitoring_method,
|
||||
notification_channel_id, added_by_user_id, webhook_secret, target_branch,
|
||||
polling_interval_minutes, is_public_repo, last_polled_commit_sha
|
||||
polling_interval_minutes, is_public_repo, last_polled_commit_sha,
|
||||
allowed_webhook_events
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
ON CONFLICT (guild_id, repository_url, notification_channel_id) DO NOTHING
|
||||
RETURNING id;
|
||||
""",
|
||||
guild_id, repository_url, platform, monitoring_method,
|
||||
notification_channel_id, added_by_user_id, webhook_secret, target_branch,
|
||||
polling_interval_minutes, is_public_repo, last_polled_commit_sha
|
||||
polling_interval_minutes, is_public_repo, last_polled_commit_sha,
|
||||
final_allowed_events
|
||||
)
|
||||
if repo_id:
|
||||
log.info(f"Added repository '{repository_url}' (Branch: {target_branch or 'default'}) for monitoring in guild {guild_id}, channel {notification_channel_id}. ID: {repo_id}")
|
||||
log.info(f"Added repository '{repository_url}' (Branch: {target_branch or 'default'}, Events: {final_allowed_events}) for monitoring in guild {guild_id}, channel {notification_channel_id}. ID: {repo_id}")
|
||||
else:
|
||||
# This means ON CONFLICT DO NOTHING was triggered, fetch existing ID
|
||||
existing_id = await conn.fetchval(
|
||||
@ -2251,10 +2282,10 @@ async def get_monitored_repository_by_id(repo_db_id: int) -> Dict | None:
|
||||
try:
|
||||
async with bot.pg_pool.acquire() as conn:
|
||||
record = await conn.fetchrow(
|
||||
"SELECT * FROM git_monitored_repositories WHERE id = $1",
|
||||
"SELECT *, allowed_webhook_events FROM git_monitored_repositories WHERE id = $1", # Ensure new column is fetched
|
||||
repo_db_id
|
||||
)
|
||||
print(f"Grep this line: {dict(record)}")
|
||||
# log.info(f"Grep this line: {dict(record) if record else 'No record found'}") # Keep for debugging if needed
|
||||
return dict(record) if record else None
|
||||
except Exception as e:
|
||||
log.exception(f"Database error getting monitored repository by ID {repo_db_id}: {e}")
|
||||
@ -2270,7 +2301,7 @@ async def get_monitored_repository_by_url(guild_id: int, repository_url: str, no
|
||||
async with bot.pg_pool.acquire() as conn:
|
||||
record = await conn.fetchrow(
|
||||
"""
|
||||
SELECT * FROM git_monitored_repositories
|
||||
SELECT *, allowed_webhook_events FROM git_monitored_repositories
|
||||
WHERE guild_id = $1 AND repository_url = $2 AND notification_channel_id = $3
|
||||
""",
|
||||
guild_id, repository_url, notification_channel_id
|
||||
@ -2280,6 +2311,28 @@ async def get_monitored_repository_by_url(guild_id: int, repository_url: str, no
|
||||
log.exception(f"Database error getting monitored repository by URL '{repository_url}' for guild {guild_id}: {e}")
|
||||
return None
|
||||
|
||||
async def update_monitored_repository_events(repo_db_id: int, allowed_events: list[str]) -> bool:
|
||||
"""Updates the allowed webhook events for a specific monitored repository."""
|
||||
bot = get_bot_instance()
|
||||
if not bot or not bot.pg_pool:
|
||||
log.error(f"Bot instance or PostgreSQL pool not available for update_monitored_repository_events (ID {repo_db_id}).")
|
||||
return False
|
||||
try:
|
||||
async with bot.pg_pool.acquire() as conn:
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE git_monitored_repositories
|
||||
SET allowed_webhook_events = $2
|
||||
WHERE id = $1;
|
||||
""",
|
||||
repo_db_id, allowed_events
|
||||
)
|
||||
log.info(f"Updated allowed webhook events for repository ID {repo_db_id} to {allowed_events}.")
|
||||
# Consider cache invalidation here if caching these lists directly per repo_id
|
||||
return True
|
||||
except Exception as e:
|
||||
log.exception(f"Database error updating allowed webhook events for repository ID {repo_db_id}: {e}")
|
||||
return False
|
||||
|
||||
async def update_repository_polling_status(repo_db_id: int, last_polled_commit_sha: str, last_polled_at: asyncio.Future | None = None) -> bool:
|
||||
"""Updates the last polled commit SHA and timestamp for a repository."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user