129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
"""
|
|
This blueprint deals with everything under /api/projects/.
|
|
|
|
Used environment variables:
|
|
GITEA_URL: The URL (either intenral or external) to the Gitea instance to pull data from.
|
|
GITEA_TOKEN: The API token for the crawler user. Only requires repository.read.
|
|
"""
|
|
|
|
from urllib.parse import urlencode, urlparse, urlunparse, ParseResult
|
|
from io import BytesIO
|
|
import logging
|
|
import string
|
|
import os
|
|
|
|
import requests
|
|
import flask
|
|
import magic
|
|
|
|
log = logging.getLogger('blueprints.projects')
|
|
|
|
class Blueprint(flask.Blueprint):
|
|
"""
|
|
Holds the required data for this blueprint to work.
|
|
Automatically gets the UID for the logged in user,
|
|
and gives access to a simple constructURL() function
|
|
to make API calls without specifying the token each time.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self._baseURL = urlparse(os.getenv('GITEA_URL'))
|
|
self._apiKey = os.getenv('GITEA_TOKEN')
|
|
self.giteaUID = None
|
|
|
|
r = requests.get(self.constructURL('/api/v1/user'), timeout=5)
|
|
|
|
if r.ok:
|
|
self.giteaUID = r.json().get('id')
|
|
else:
|
|
log.error('Cannot get Gitea user ID. This blueprint will not work.')
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def constructURL(self, apiPath: str, query: dict=None) -> str:
|
|
"""
|
|
Construcs an API url from the path and queries.
|
|
"""
|
|
|
|
if query is None:
|
|
query = {}
|
|
|
|
query.update({'token': self._apiKey})
|
|
|
|
newURL = ParseResult(scheme=self._baseURL.scheme, netloc=self._baseURL.netloc,
|
|
path=apiPath, params='', query=urlencode(query), fragment='')
|
|
return urlunparse(newURL)
|
|
|
|
projectsBP = Blueprint('projectsAPI', __name__)
|
|
|
|
def generateStrippedProjectData(data: dict) -> dict:
|
|
"""
|
|
Generates the stripped down project data that the front-end expects.
|
|
"""
|
|
|
|
# ----- Languages -----
|
|
# Get the languages path and request it
|
|
path = urlparse(data['languages_url']).path
|
|
r = requests.get(projectsBP.constructURL(path), timeout=5)
|
|
languages = r.json()
|
|
# Convert all languages from bytes to percentages
|
|
languages = {lang: size / sum(languages.values()) for lang, size in languages.items()}
|
|
|
|
# ----- Image -----
|
|
if not data.get('avatar_url', '') == '':
|
|
# Get the avatar URL
|
|
avatarUrl = urlparse(data['avatar_url'])
|
|
# Parse the URL to get just the hash
|
|
imgHash = avatarUrl.path.split('/')[-1:][0]
|
|
# Create an API path for that image
|
|
imgPath = f'/api/projects/img/{imgHash}'
|
|
else:
|
|
imgPath = None
|
|
|
|
# ----- Git URL -----
|
|
if not data.get('private'):
|
|
gitUrl = data.get('html_url')
|
|
else:
|
|
gitUrl = None
|
|
|
|
newData = {}
|
|
newData.update({'name': data.get('name')})
|
|
newData.update({'desc': data.get('description')})
|
|
newData.update({'issues': data.get('open_issues_count')})
|
|
newData.update({'langs': languages})
|
|
newData.update({'gitUrl': gitUrl})
|
|
newData.update({'imgPath': imgPath})
|
|
return newData
|
|
|
|
@projectsBP.route('/api/projects', strict_slashes=False)
|
|
def projectsList():
|
|
"""
|
|
Returns a filtered list of Gitea projects.
|
|
"""
|
|
|
|
# Request for all Gitea projects that the crawler account is a contributor in
|
|
r = requests.get(projectsBP.constructURL('/api/v1/repos/search',
|
|
{'exclusive': False, 'uid': projectsBP.giteaUID}), timeout=5)
|
|
if r.ok:
|
|
# Filter down every repo with generateStrippedProjectData()
|
|
projects = [generateStrippedProjectData(data) for data in r.json().get('data')]
|
|
return projects
|
|
else:
|
|
flask.abort(500)
|
|
|
|
@projectsBP.route('/api/projects/img/<imgHash>', strict_slashes=False)
|
|
def img(imgHash: str):
|
|
"""
|
|
Forwards images from Gitea's /repo-avatars.
|
|
"""
|
|
|
|
# Filter the hash
|
|
imgHash = ''.join([c for c in imgHash if c in string.ascii_lowercase+string.digits])
|
|
# Make the request the Gitea
|
|
r = requests.get(projectsBP.constructURL(f'/repo-avatars/{imgHash}'), timeout=5)
|
|
# Get the MIME type
|
|
mime = magic.from_buffer(BytesIO(r.content).read(128), mime=True)
|
|
# Send the image
|
|
content = BytesIO(r.content)
|
|
return flask.send_file(content, mime)
|