-
-
Notifications
You must be signed in to change notification settings - Fork 253
Added nest app and slack command for sponsorship #947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
cd28fee
e7f54b1
7e81614
faf9968
c448539
00da50e
c6c3ce7
b17a378
a5ac816
482ec15
d8cc69e
f062c55
2949848
763cf8b
956c164
ebcf410
78359aa
c272978
b09daae
19644b7
d8e444f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,11 @@ | ||
| """GitHub app common module.""" | ||
|
|
||
| import logging | ||
| import os | ||
| from urllib.parse import urlparse | ||
|
|
||
| from django.core.exceptions import ValidationError | ||
| from github import Github | ||
| from github.GithubException import UnknownObjectException | ||
|
|
||
| from apps.github.models.issue import Issue | ||
|
|
@@ -13,9 +17,64 @@ | |
| from apps.github.models.user import User | ||
| from apps.github.utils import check_owasp_site_repository | ||
|
|
||
| INVALID_ISSUE_LINK_FORMAT = "Invalid GitHub issue link format." | ||
| FETCH_ISSUE_ERROR = "Failed to fetch issue from GitHub: {error}" | ||
| MIN_PARTS_LENGTH = 4 | ||
|
|
||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def sync_issue(issue_link): | ||
| """Sync GitHub issue data.""" | ||
| try: | ||
| return Issue.objects.get(url=issue_link) | ||
| except Issue.DoesNotExist: | ||
| pass | ||
|
|
||
| parsed_url = urlparse(issue_link) | ||
| path_parts = parsed_url.path.strip("/").split("/") | ||
| if len(path_parts) < MIN_PARTS_LENGTH or path_parts[2] != "issues": | ||
| raise ValidationError(INVALID_ISSUE_LINK_FORMAT) | ||
|
||
|
|
||
| github_token = os.getenv("GITHUB_TOKEN") | ||
| github_client = Github(github_token) | ||
abhayymishraa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| issue_number = int(path_parts[3]) | ||
| owner = path_parts[0] | ||
| repo_name = path_parts[1] | ||
|
|
||
| gh_repo = github_client.get_repo(f"{owner}/{repo_name}") | ||
| gh_issue = gh_repo.get_issue(issue_number) | ||
| try: | ||
| author = User.objects.get(login=gh_issue.user.login) | ||
| except User.DoesNotExist: | ||
| author = User.update_data(gh_issue.user) | ||
|
|
||
| try: | ||
| repository = Repository.objects.get(node_id=gh_repo.id) | ||
| except Repository.DoesNotExist: | ||
| try: | ||
| repo_owner = User.objects.get(login=gh_repo.owner.login) | ||
| except User.DoesNotExist: | ||
| repo_owner = User.update_data(gh_repo.owner) | ||
| try: | ||
| organization = Organization.objects.get(node_id=gh_repo.organization.id) | ||
| except Organization.DoesNotExist: | ||
| organization = Organization.update_data(gh_repo.organization) | ||
abhayymishraa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| repository = Repository.update_data( | ||
| gh_repository=gh_repo, | ||
| commits=gh_repo.get_commits(), | ||
| contributors=gh_repo.get_contributors(), | ||
| languages=gh_repo.get_languages(), | ||
| organization=organization, | ||
| user=repo_owner, | ||
| ) | ||
|
|
||
| return Issue.update_data(gh_issue, author=author, repository=repository) | ||
|
|
||
|
|
||
| def sync_repository(gh_repository, organization=None, user=None): | ||
| """Sync GitHub repository data.""" | ||
| entity_key = gh_repository.name.lower() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -151,7 +151,7 @@ def generate_summary(self, open_ai=None, max_tokens=500): | |
|
|
||
| prompt = ( | ||
| Prompt.get_github_issue_documentation_project_summary() | ||
| if self.project.is_documentation_type | ||
| if self.project and self.project.is_documentation_type | ||
|
||
| else Prompt.get_github_issue_project_summary() | ||
| ) | ||
| if not prompt: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from django.contrib import admin | ||
|
|
||
| from apps.nest.models.sponsorship import Sponsorship | ||
|
|
||
|
|
||
| class SponsorshipAdmin(admin.ModelAdmin): | ||
| list_display = ("issue", "price_usd", "slack_user_id") | ||
| search_fields = ("issue__title", "slack_user_id") | ||
|
|
||
|
|
||
| admin.site.register(Sponsorship, SponsorshipAdmin) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from django.apps import AppConfig | ||
|
|
||
|
|
||
| class NestConfig(AppConfig): | ||
| name = "apps.nest" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| """Nest app constants.""" | ||
|
|
||
| ISSUES_INDEX = 5 | ||
| GITHUB_COM_INDEX = 2 | ||
| MIN_PARTS_LENGTH = 4 | ||
|
|
||
| DEADLINE_FORMAT_ERROR = "Invalid deadline format. Use YYYY-MM-DD or YYYY-MM-DD HH:MM." | ||
| DEADLINE_FUTURE_ERROR = "Deadline must be in the future." | ||
| FETCH_ISSUE_ERROR = "Failed to fetch issue from GitHub: {error}" | ||
| INVALID_ISSUE_LINK_FORMAT = "Invalid GitHub issue link format." | ||
| ISSUE_LINK_ERROR = "Issue link must belong to an OWASP repository." | ||
| PRICE_POSITIVE_ERROR = "Price must be a positive value." | ||
| PRICE_VALID_ERROR = "Price must be a valid number." | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # Generated by Django 5.1.5 on 2025-02-27 05:33 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| initial = True | ||
|
|
||
| dependencies = [ | ||
| ("github", "0016_user_is_bot"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="Sponsorship", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| verbose_name="ID", | ||
| ), | ||
| ), | ||
| ("nest_created_at", models.DateTimeField(auto_now_add=True)), | ||
| ("nest_updated_at", models.DateTimeField(auto_now=True)), | ||
| ("deadline_at", models.DateTimeField(blank=True, null=True)), | ||
| ("price_usd", models.FloatField()), | ||
| ("slack_user_id", models.CharField(max_length=100)), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainValidate the slack_user_id field. Consider adding validation for the slack_user_id field format to ensure it matches Slack's user ID pattern. 🏁 Script executed: #!/bin/bash
# Check if the Sponsorship model has validation for slack_user_id in the model definition
# Find the Sponsorship model definition
fd -e py -t f "sponsorship.py" | xargs grep -A 20 "class Sponsorship"Length of output: 686 Action Required: Implement Slack User ID Validation The Sponsorship model currently defines the |
||
| ( | ||
| "issue", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="sponsorships", | ||
| to="github.issue", | ||
| ), | ||
| ), | ||
| ], | ||
| options={ | ||
| "verbose_name_plural": "Sponsorships", | ||
| "db_table": "nest_sponsorships", | ||
| }, | ||
| ), | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| """Nest app sponsorship model.""" | ||
|
|
||
| from datetime import datetime | ||
|
|
||
| from django.core.exceptions import ValidationError | ||
| from django.db import models | ||
| from django.utils import timezone | ||
|
|
||
| from apps.common.models import BulkSaveModel, TimestampedModel | ||
| from apps.github.models.issue import Issue | ||
| from apps.nest.constants import ( | ||
| DEADLINE_FORMAT_ERROR, | ||
| DEADLINE_FUTURE_ERROR, | ||
| ISSUE_LINK_ERROR, | ||
| PRICE_POSITIVE_ERROR, | ||
| PRICE_VALID_ERROR, | ||
| ) | ||
|
|
||
|
|
||
| class Sponsorship(BulkSaveModel, TimestampedModel): | ||
| """Sponsorship model.""" | ||
|
|
||
| class Meta: | ||
| db_table = "nest_sponsorships" | ||
| verbose_name_plural = "Sponsorships" | ||
|
|
||
| deadline_at = models.DateTimeField(null=True, blank=True) | ||
| price_usd = models.FloatField() | ||
|
||
| slack_user_id = models.CharField(max_length=100) | ||
|
|
||
| issue = models.ForeignKey( | ||
| Issue, | ||
| on_delete=models.CASCADE, | ||
| related_name="sponsorships", | ||
| ) | ||
|
|
||
| def __str__(self): | ||
| """Sponsorship human readable representation.""" | ||
| return f"Sponsorship for {self.issue.title} by {self.slack_user_id}" | ||
|
|
||
| @staticmethod | ||
| def update_data(sponsorship, **kwargs): | ||
| """Update sponsorship data with the provided fields.""" | ||
| fields_to_update = ["price_usd", "deadline_at", "slack_user_id"] | ||
| for field in fields_to_update: | ||
| if field in kwargs: | ||
| setattr(sponsorship, field, kwargs[field]) | ||
| sponsorship.save() | ||
| return sponsorship | ||
|
|
||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| @staticmethod | ||
| def validate_deadline(deadline_str): | ||
| """Validate that the deadline is in a valid datetime format.""" | ||
| try: | ||
| # Try parsing the deadline in YYYY-MM-DD format | ||
| deadline = datetime.strptime(deadline_str, "%Y-%m-%d").replace( | ||
|
||
| tzinfo=timezone.get_current_timezone() | ||
| ) | ||
| except ValueError: | ||
| try: | ||
| # Try parsing the deadline in YYYY-MM-DD HH:MM format | ||
| deadline = datetime.strptime(deadline_str, "%Y-%m-%d %H:%M").replace( | ||
| tzinfo=timezone.get_current_timezone() | ||
| ) | ||
| except ValueError as e: | ||
| raise ValidationError(DEADLINE_FORMAT_ERROR) from e | ||
|
|
||
| if deadline < timezone.now(): | ||
| raise ValidationError(DEADLINE_FUTURE_ERROR) | ||
|
|
||
| return deadline | ||
|
|
||
| @staticmethod | ||
| def validate_github_issue_link(issue_link): | ||
| """Validate that the issue link belongs to a valid OWASP-related repository.""" | ||
| if not issue_link.startswith("https://github.com/OWASP"): | ||
| raise ValidationError(ISSUE_LINK_ERROR) | ||
| return issue_link | ||
|
|
||
| @staticmethod | ||
| def validate_price(price): | ||
| """Validate that the price is a positive float value.""" | ||
| try: | ||
| price = float(price) | ||
| if price <= 0: | ||
| raise ValidationError(PRICE_POSITIVE_ERROR) | ||
| except ValueError as e: | ||
| raise ValidationError(PRICE_VALID_ERROR) from e | ||
| return price | ||
Uh oh!
There was an error while loading. Please reload this page.