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

badges: updated badges to new architecture #1

Closed
wants to merge 7 commits into from
Closed
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
171 changes: 109 additions & 62 deletions invenio_github/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"""Invenio module that adds GitHub integration to the platform."""

import json
from abc import abstractmethod
from contextlib import contextmanager
from copy import deepcopy

import github3
import humanize
Expand All @@ -42,21 +44,25 @@
from werkzeug.local import LocalProxy
from werkzeug.utils import cached_property

from invenio_github.models import Release, ReleaseStatus, Repository
from invenio_github.proxies import current_github
from invenio_github.tasks import sync_hooks as sync_hooks_task
from invenio_github.utils import iso_utcnow, parse_timestamp, utcnow

from .errors import (
RemoteAccountDataNotSet,
RemoteAccountNotFound,
RepositoryAccessError,
RepositoryNotFoundError,
)
from .models import ReleaseStatus, Repository
from .tasks import sync_hooks as sync_hooks_task
from .utils import iso_utcnow, parse_timestamp, utcnow


def check_repo_access_permissions(repo, user_id, repo_id, repo_name):
def check_repo_access_permissions(repo, user_id):
"""Checks permissions from user on repo."""
if repo and repo.user_id and repo.user_id != int(user_id):
raise RepositoryAccessError(user=user_id, repo=repo_name, repo_id=repo_id)
raise RepositoryAccessError(
user=user_id, repo=repo.name, repo_id=repo.github_id
)


class GitHubAPI(object):
Expand All @@ -73,10 +79,12 @@ def api(self):

@cached_property
def access_token(self):
"""Return OAuth access token."""
if self.user_id:
return RemoteToken.get(self.user_id, self.remote.consumer_key).access_token
return self.remote.get_request_token()[0]
"""Return OAuth access token's value."""
token = RemoteToken.get(self.user_id, self.remote.consumer_key)
if not token:
# The token is not yet in DB, it is retrieved from the request session.
return self.remote.get_request_token()[0]
return token.access_token

@property
def session_token(self):
Expand Down Expand Up @@ -162,43 +170,41 @@ def sync(self, hooks=True, async_hooks=True):
consider the information on GitHub as valid, and we overwrite our
own state based on this information.
"""
active_repos = {}
github_repos = {
repo.id: repo
for repo in self.api.repositories()
if repo.permissions["admin"]
}
for gh_repo_id, gh_repo in github_repos.items():
active_repos[gh_repo_id] = {
"id": gh_repo_id,
"full_name": gh_repo.full_name,
"description": gh_repo.description,
"default_branch": gh_repo.default_branch,
}
github_repos = {}
for repo in self.api.repositories():
if repo.permissions["admin"]:
github_repos[repo.id] = {
"id": repo.id,
"full_name": repo.full_name,
"description": repo.description,
"default_branch": repo.default_branch,
}

if hooks:
self._sync_hooks(list(active_repos.keys()), asynchronous=async_hooks)
self._sync_hooks(list(github_repos.keys()), asynchronous=async_hooks)

# Update changed names for repositories stored in DB
db_repos = Repository.query.filter(
Repository.user_id == self.user_id,
)

for repo in db_repos:
if gh_repo and repo.name != gh_repo.full_name:
repo.name = gh_repo.full_name
gh_repo = github_repos.get(repo.github_id)
if gh_repo and repo.name != gh_repo["full_name"]:
repo.name = gh_repo["full_name"]
db.session.add(repo)

# Remove ownership from repositories that the user has no longer
# 'admin' permissions, or have been deleted.
Repository.query.filter(
Repository.user_id == self.user_id,
~Repository.github_id.in_(github_repos.keys()),
).update(dict(user_id=None, hook=None), synchronize_session=False)

# Update repos and last sync
self.account.extra_data.update(
dict(
repos=active_repos,
repos=github_repos,
last_sync=iso_utcnow(),
)
)
Expand Down Expand Up @@ -240,9 +246,9 @@ def sync_repo_hook(self, repo_id):
repo = Repository.create(self.user_id, repo_id, gh_repo.full_name)

if hook:
Repository.enable(repo, self.user_id, hook.id)
self.enable_repo(repo, hook.id)
else:
Repository.disable(repo)
self.disable_repo(repo)

def check_sync(self):
"""Check if sync is required based on last sync date."""
Expand All @@ -261,9 +267,7 @@ def create_hook(self, repo_id, repo_name):
if not repo:
repo = Repository.create(self.user_id, repo_id, repo_name)

check_repo_access_permissions(
repo, self.user_id, repo_id=repo_id, repo_name=repo_name
)
check_repo_access_permissions(repo, self.user_id)

# Create hook
hook_config = dict(
Expand Down Expand Up @@ -292,7 +296,7 @@ def create_hook(self, repo_id, repo_name):
else:
hook.edit(config=hook_config, events=["release"])

Repository.enable(repo, self.user_id, hook.id)
self.enable_repo(repo, hook.id)
return True

return False
Expand All @@ -304,9 +308,7 @@ def remove_hook(self, repo_id, name):
if not repo:
raise RepositoryNotFoundError(repo_id)

check_repo_access_permissions(
repo, self.user_id, repo_id=repo_id, repo_name=name
)
check_repo_access_permissions(repo, self.user_id)

ghrepo = self.api.repository_with_id(repo_id)
if ghrepo:
Expand All @@ -315,43 +317,65 @@ def remove_hook(self, repo_id, name):
)
hook = next(hooks, None)
if not hook or hook.delete():
Repository.disable(repo)
self.disable_repo(repo)
return True
return False

def get_repository_releases(self, repo_name="", repo=None):
"""Retrieve repository releases."""
if not (repo or repo_name):
raise ValueError("At least one of (repo, repo_name) is required")
def repo_last_published_release(self, repo):
"""Retrieves the repository last release."""
return repo.latest_release(ReleaseStatus.PUBLISHED)

if not repo:
repo = Repository.get(name=repo_name)
def get_repository_releases(self, repo):
"""Retrieve repository releases. Returns API release objects."""

check_repo_access_permissions(repo, self.user_id, repo.github_id, repo_name)
check_repo_access_permissions(repo, self.user_id, repo.github_id, repo.name)

# Retrieve releases and sort them by creation date
release_objects = sorted(repo.releases.all(), key=lambda r: r.created)
release_instances = []
for release_object in repo.releases.order_by(Release.created):
release_instance = current_github.release_api_class(release_object)
release_instances.append(release_instance)

return release_objects
return release_instances

def get_user_repositories(self):
"""Retrieves user repositories."""
token = self.session_token
if token:
extra_data = self.account.extra_data
repos = extra_data.get("repos", [])
"""Retrieves user repositories, containing db repositories plus remote repositories."""
repos = deepcopy(self.user_available_repositories)
if repos:
# 'Enhance' our repos dict, from our database model
db_repos = Repository.query.filter(
Repository.github_id.in_([int(k) for k in repos.keys()]),
).all()
for repo in db_repos:
repos[str(repo.github_id)]["instance"] = repo
repos[str(repo.github_id)]["latest"] = GitHubRelease(
repo.latest_release()
)
for repo in self.user_enabled_repositories:
if str(repo.github_id) in repos:
release_instance = current_github.release_api_class(
repo.latest_release()
)
repos[str(repo.github_id)]["instance"] = repo
repos[str(repo.github_id)]["latest"] = release_instance
return repos

@property
def user_enabled_repositories(self):
"""Retrieve user repositories from the model."""
return Repository.query.filter(Repository.user_id == self.user_id)

@property
def user_available_repositories(self):
"""Retrieve user repositories from user's remote data."""
return self.account.extra_data.get("repos", [])

def disable_repo(self, repo):
"""Disables an user repository if the user has permission to do so."""
check_repo_access_permissions(repo, self.user_id, repo.github_id, repo.name)

repo.hook = None
repo.user_id = None

def enable_repo(self, repo, hook):
"""Enables an user repository if the user has permission to do so."""
check_repo_access_permissions(repo, self.user_id, repo.github_id, repo.name)

repo.hook = hook
repo.user_id = self.user_id

def get_last_sync_time(self):
"""Retrieves the last sync delta time from github's client extra data.

Expand All @@ -368,17 +392,17 @@ def get_last_sync_time(self):
)
return last_sync

def get_repository(self, repo_name):
def get_repository(self, repo_name=None, repo_github_id=None):
"""Retrieves one repository.

Checks for access permission.
"""
repo = Repository.get(name=repo_name)
repo = Repository.get(name=repo_name, github_id=repo_github_id)
if not repo:
raise RepositoryNotFoundError(repo_name)

# Might raise a RepositoryAccessError
check_repo_access_permissions(repo, self.user_id, repo.github_id, repo_name)
check_repo_access_permissions(repo, self.user_id)

return repo

Expand Down Expand Up @@ -551,7 +575,6 @@ def test_zipball(self):

# High level API

# TODO split maybe
def release_failed(self):
"""Set release status to FAILED."""
self.release_object.status = ReleaseStatus.FAILED
Expand Down Expand Up @@ -600,3 +623,27 @@ def process_release(self):
def resolve_record(self):
"""Resolves a record from the release. To be implemented by the API class implementation."""
raise NotImplementedError

def serialize_record(self):
"""Serializes the release record."""
raise NotImplementedError

def release_url(self):
"""Returns the release url."""

@property
@abstractmethod
def badge_title(self):
"""Stores a string to render in the record badge title (e.g. 'DOI')."""
return None

@property
@abstractmethod
def badge_value(self):
"""Stores a string to render in the record badge value (e.g. '10.1234/invenio.1234')."""
return None

@property
def self_url(self):
"""Release self url (e.g. github HTML url)."""
return self.release_payload.get("html_url")
9 changes: 2 additions & 7 deletions invenio_github/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@

"""Configuration for GitHub module."""

from copy import deepcopy
from datetime import timedelta

from invenio_oauthclient.contrib.github import REMOTE_APP

GITHUB_WEBHOOK_RECEIVER_ID = "github"
"""Local name of webhook receiver."""

Expand Down Expand Up @@ -77,7 +74,5 @@
GITHUB_MAX_CONTRIBUTORS_NUMBER = 30
"""Max number of contributors of a release to be retrieved from Github."""

# Copy the default GitHub OAuth application configuration, and update disconnect handlers and scope.
"""OAuth Client configuration."""
REMOTE_APP["disconnect_handler"] = "invenio_github.handlers:disconnect"
REMOTE_APP["params"]["request_token_params"]["scope"] = "user,admin:repo_hook,read:org"
GITHUB_INTEGRATION_ENABLED = False
"""Enables the github integration."""
28 changes: 3 additions & 25 deletions invenio_github/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@

from flask import current_app
from six import string_types
from sqlalchemy import event
from werkzeug.utils import cached_property, import_string

from invenio_github.api import GitHubRelease
from invenio_github.utils import obj_or_import_string

from . import config
from .api import GitHubRelease
from .utils import obj_or_import_string


class InvenioGitHub(object):
Expand Down Expand Up @@ -65,30 +65,8 @@ def init_app(self, app):
self.init_config(app)
app.extensions["invenio-github"] = self

@app.before_first_request
def connect_signals():
"""Connect OAuthClient signals."""
from invenio_oauthclient.models import RemoteAccount
from invenio_oauthclient.signals import account_setup_committed

from .api import GitHubAPI
from .handlers import account_post_init

account_setup_committed.connect(
account_post_init, sender=GitHubAPI.remote._get_current_object()
)

@event.listens_for(RemoteAccount, "before_delete")
def receive_before_delete(mapper, connection, target):
"""Listen for the 'before_delete' event."""

def init_config(self, app):
"""Initialize configuration."""
app.config.setdefault(
"GITHUB_BASE_TEMPLATE",
app.config.get("BASE_TEMPLATE", "invenio_github/base.html"),
)

app.config.setdefault(
"GITHUB_SETTINGS_TEMPLATE",
app.config.get(
Expand Down
Loading