Skip to content
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

feat: [AXM-1227, AXM-1235, AXM-1242] accredible models, client and sy… #187

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions credentials/apps/badges/accredible/api_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import logging
from django.conf import settings
from django.contrib.sites.models import Site
from attrs import asdict

from credentials.apps.badges.models import AccredibleAPIConfig, AccredibleGroup
from credentials.apps.badges.base_api_client import BaseBadgeProviderClient
from credentials.apps.badges.accredible.data import AccredibleBadgeData, AccredibleExpireBadgeData
from credentials.apps.badges.accredible.utils import get_accredible_api_base_url
from credentials.apps.badges.accredible.exceptions import AccredibleError


logger = logging.getLogger(__name__)


class AccredibleAPIClient(BaseBadgeProviderClient):
"""
A client for interacting with the Accredible API.

This class provides methods for performing various operations on the Accredible API.
"""

PROVIDER_NAME = "Accredible"

def __init__(self, api_config_id: int):
"""
Initializes a AccredibleAPIClient object.

Args:
api_config (AccredibleAPIConfig): Configuration object for the Accredible API.
"""

self.api_config_id = api_config_id
self.api_config = self.get_api_config()

def get_api_config(self) -> AccredibleAPIConfig:
"""
Returns the API configuration object for the Accredible API.
"""
try:
return AccredibleAPIConfig.objects.get(id=self.api_config_id)
except AccredibleAPIConfig.DoesNotExist:
raise AccredibleError(f"AccredibleAPIConfig with the id {self.api_config_id} does not exist!")

def _get_base_api_url(self) -> str:
return get_accredible_api_base_url(settings)

def _get_headers(self) -> dict:
"""
Returns the headers for making API requests to Accredible.
"""
return {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_config.api_key}",
}

def fetch_all_groups(self) -> dict:
"""
Fetch all groups.
"""
return self.perform_request("get", "issuer/all_groups")

def fetch_design_image(self, design_id: int) -> str:
"""
Fetches the design and return the URL of image.
"""
design_raw = self.perform_request("get", f"designs/{design_id}")
return design_raw.get("design", {}).get("rasterized_content_url")

def issue_badge(self, issue_badge_data: AccredibleBadgeData) -> dict:
"""
Issues a badge using the Accredible REST API.

Args:
issue_badge_data (IssueBadgeData): Data required to issue the badge.
"""
return self.perform_request("post", "credentials", asdict(issue_badge_data))

def revoke_badge(self, badge_id, data: AccredibleExpireBadgeData) -> dict:
"""
Revoke a badge with the given badge ID.

Args:
badge_id (str): ID of the badge to revoke.
data (dict): Additional data for the revocation.
"""
return self.perform_request("patch", f"credentials/{badge_id}", asdict(data))

def sync_groups(self, site_id: int) -> int:
"""
Pull all groups for a given Accredible API config.

Args:
site_id (int): ID of the site.

Returns:
int | None: processed items.
"""
try:
site = Site.objects.get(id=site_id)
except Site.DoesNotExist:
logger.error(f"Site with the id {site_id} does not exist!")
raise

groups_data = self.fetch_all_groups()
raw_groups = groups_data.get("groups", [])

all_group_ids = [group.get("id") for group in raw_groups]
AccredibleGroup.objects.exclude(id__in=all_group_ids).delete()

for raw_group in raw_groups:
kyrylo-kh marked this conversation as resolved.
Show resolved Hide resolved
AccredibleGroup.objects.update_or_create(
id=raw_group.get("id"),
api_config=self.api_config,
defaults={
"site": site,
"name": raw_group.get("course_name"),
"description": raw_group.get("course_description"),
"icon": self.fetch_design_image(raw_group.get("primary_design_id")),
},
)

return len(raw_groups)
61 changes: 61 additions & 0 deletions credentials/apps/badges/accredible/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import attr
from datetime import datetime


@attr.s(auto_attribs=True, frozen=True)
class AccredibleRecipient:
"""
Represents the recipient data in the credential.

Attributes:
name (str): The recipient's name.
email (str): The recipient's email address.
"""

name: str
email: str

@attr.s(auto_attribs=True, frozen=True)
class AccredibleCredential:
"""
Represents the credential data.

Attributes:
recipient (RecipientData): Information about the recipient.
group_id (int): ID of the credential group.
name (str): Title of the credential.
description (str): Description of the credential.
issued_on (datetime): Date when the credential was issued.
complete (bool): Whether the credential process is complete.
"""

kyrylo-kh marked this conversation as resolved.
Show resolved Hide resolved
recipient: AccredibleRecipient
group_id: int
name: str
description: str
issued_on: datetime
complete: bool

@attr.s(auto_attribs=True, frozen=True)
class AccredibleExpiredCredential:
"""
Represents the data required to expire a credential.
"""

expired_on: datetime

@attr.s(auto_attribs=True, frozen=True)
class AccredibleBadgeData:
"""
Represents the data required to issue a badge.
"""

credential: AccredibleCredential

@attr.s(auto_attribs=True, frozen=True)
class AccredibleExpireBadgeData:
"""
Represents the data required to expire a badge.
"""

credential: AccredibleExpiredCredential
11 changes: 11 additions & 0 deletions credentials/apps/badges/accredible/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Specific for Accredible exceptions.
"""

from credentials.apps.badges.exceptions import BadgesError


class AccredibleError(BadgesError):
"""
Accredible backend generic error.
"""
44 changes: 44 additions & 0 deletions credentials/apps/badges/accredible/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Accredible utility functions.
"""

def get_accredible_api_base_url(settings) -> str:
NiedielnitsevIvan marked this conversation as resolved.
Show resolved Hide resolved
"""
Determines the base URL for the Accredible service based on application settings.

Parameters:
- settings: A configuration object containing the application's settings.

Returns:
- str: The base URL for the Accredible service (web site).
This will be the URL for the sandbox environment if `USE_SANDBOX` is
set to a truthy value in the configuration;
otherwise, it will be the production environment's URL.
"""
accredible_config = settings.BADGES_CONFIG["accredible"]

if accredible_config.get("USE_SANDBOX"):
return accredible_config["ACCREDIBLE_SANDBOX_API_BASE_URL"]

return accredible_config["ACCREDIBLE_API_BASE_URL"]


def get_accredible_base_url(settings) -> str:
"""
Determines the base URL for the Accredible service based on application settings.

Parameters:
- settings: A configuration object containing the application's settings.

Returns:
- str: The base URL for the Accredible service (web site).
This will be the URL for the sandbox environment if `USE_SANDBOX` is
set to a truthy value in the configuration;
otherwise, it will be the production environment's URL.
"""
credly_config = settings.BADGES_CONFIG["accredible"]

if credly_config.get("USE_SANDBOX"):
return credly_config["ACCREDIBLE_SANDBOX_BASE_URL"]

return credly_config["ACCREDIBLE_BASE_URL"]
Loading
Loading