Skip to content

Commit

Permalink
feat: [AXM-1227, AXM-1235, AXM-1242] Accredible models, client and sy… (
Browse files Browse the repository at this point in the history
#187)

* feat: [AXM-1227] Accredible models

* feat: [AXM-1235] Accredible API client

* feat: [AXM-1242] Accredible syncing groups
  • Loading branch information
kyrylo-kh authored Dec 23, 2024
1 parent da2b598 commit dd1a65e
Show file tree
Hide file tree
Showing 14 changed files with 776 additions and 3 deletions.
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:
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.
"""

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:
"""
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

0 comments on commit dd1a65e

Please sign in to comment.