-
-
Notifications
You must be signed in to change notification settings - Fork 647
Implement certificate metadata issuance system #4922
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
Open
anurag2787
wants to merge
18
commits into
OWASP:feature/contributor-recognition-program
Choose a base branch
from
anurag2787:certificate-metadata
base: feature/contributor-recognition-program
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+208
−4
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
db9356c
Implement contribution score calculation
anurag2787 ab44181
Fixed coderabbit review
anurag2787 6059dca
fix error logging
anurag2787 19df2cb
Merge branch 'feature/contributor-recognition-program' into score-cal…
anurag2787 e75c035
Address the review
anurag2787 034b419
fixed pre-commit check
anurag2787 3461f2c
Address coderabbit comment
anurag2787 839d772
Create Certificate metadata generation
anurag2787 0359e86
fixed check
anurag2787 362b013
address coderabbit comment
anurag2787 d651eed
update
anurag2787 32353e5
fix error handling
anurag2787 2839c93
update
anurag2787 31e69d7
Merge branch 'feature/contributor-recognition-program' into certifica…
anurag2787 abc9b21
Merge branch 'feature/contributor-recognition-program' into certifica…
anurag2787 425ee43
Fixed file naming
anurag2787 b069ab8
Address the review
anurag2787 67388ee
Fixed exception
anurag2787 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Service layer for Contributor Recognition Program.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| """Certificate provider classes for Contributor Recognition Program.""" | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from abc import ABC, abstractmethod | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from django.conf import settings | ||
|
|
||
| from apps.owasp.models.crp.certificate import Certificate | ||
|
|
||
| if TYPE_CHECKING: | ||
| from apps.github.models.user import User | ||
| from apps.owasp.models.crp.recognition_enums import TierChoices | ||
|
|
||
|
|
||
| class BaseCertificateProvider(ABC): | ||
| """Abstract base class for certificate providers.""" | ||
|
|
||
| @abstractmethod | ||
| def issue_certificate(self, user: User, score: int, tier: TierChoices) -> None: | ||
| """Issue a certificate. | ||
|
|
||
| Args: | ||
| user (User): The user for whom the certificate is issued. | ||
| score (int): The score when the certificate is issued. | ||
| tier (TierChoices): The tier for which the certificate is issued. | ||
|
|
||
| """ | ||
|
|
||
|
|
||
| class LocalCertificateProvider(BaseCertificateProvider): | ||
| """Certificate provider that records certificate metadata in local database.""" | ||
|
|
||
| def issue_certificate(self, user: User, score: int, tier: TierChoices) -> None: | ||
| """Record the certificate metadata in the local database. | ||
|
|
||
| Args: | ||
| user (User): The user for whom the certificate is issued. | ||
| score (int): The score when the certificate is issued. | ||
| tier (TierChoices): The tier for which the certificate is issued. | ||
|
|
||
| """ | ||
| Certificate.objects.create( | ||
| github_user=user, | ||
| score=score, | ||
| tier=tier, | ||
| ) | ||
|
|
||
|
|
||
| class CertificateProviderFactory: | ||
| """Factory for resolving and instantiating certificate providers.""" | ||
|
|
||
| PROVIDERS: dict[str, type[BaseCertificateProvider]] = { | ||
| "local": LocalCertificateProvider, | ||
| } | ||
|
|
||
| @classmethod | ||
| def get_provider(cls) -> BaseCertificateProvider: | ||
| """Resolve and instantiate the configured certificate provider. | ||
|
|
||
| Returns: | ||
| BaseCertificateProvider: The active certificate provider instance. | ||
|
|
||
| Raises: | ||
| ValueError: If the configured provider is unknown. | ||
|
|
||
| """ | ||
| provider_type = settings.CERTIFICATE_PROVIDER | ||
| provider_class = cls.PROVIDERS.get(provider_type) | ||
| if not provider_class: | ||
| error_msg = f"Unknown certificate provider: {provider_type}" | ||
| raise ValueError(error_msg) | ||
|
|
||
| return provider_class() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| """Certificate service layer for Contributor Recognition Program.""" | ||
|
anurag2787 marked this conversation as resolved.
|
||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from django.db import transaction | ||
|
|
||
| from apps.github.models.user import User | ||
| from apps.owasp.exceptions import CertificateIssuanceError | ||
| from apps.owasp.models.crp.certificate import Certificate | ||
| from apps.owasp.services.certificate_providers import CertificateProviderFactory | ||
|
|
||
| if TYPE_CHECKING: | ||
| from apps.owasp.models.crp.recognition_enums import TierChoices | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class CertificateService: | ||
| """Service layer for orchestrating contributor certificate workflows.""" | ||
|
|
||
| @classmethod | ||
| @transaction.atomic | ||
| def issue_certificate(cls, user: User, score: int, tier: TierChoices) -> None: | ||
| """Issue the certificate for the user's current tier if it does not already exist. | ||
|
|
||
| Args: | ||
| user (User): The user to issue certificates for. | ||
| score (int): The current contribution score of the user. | ||
| tier (TierChoices): The current tier the user qualifies for. | ||
|
|
||
| """ | ||
| # Lock the User row to serialize concurrent certificate issuances for this user | ||
| user = User.objects.select_for_update().get(id=user.id) | ||
|
|
||
| # Check if user already has an active certificate for this specific tier | ||
| if Certificate.objects.filter( | ||
| github_user=user, | ||
| tier=tier, | ||
| is_revoked=False, | ||
| ).exists(): | ||
| return | ||
|
|
||
| try: | ||
| provider = CertificateProviderFactory.get_provider() | ||
| except ValueError as e: | ||
| logger.exception("Failed to resolve certificate provider") | ||
| raise CertificateIssuanceError from e | ||
|
|
||
| logger.info( | ||
| "Issuing %s certificate to user %s with score %s", | ||
| tier, | ||
| user.login, | ||
| score, | ||
| ) | ||
| try: | ||
| provider.issue_certificate(user, score, tier) | ||
| except Exception as e: | ||
| logger.exception( | ||
| "Failed to issue %s certificate for user %s", | ||
| tier, | ||
| user.login, | ||
| ) | ||
| raise CertificateIssuanceError from e | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.