159 lines
4.4 KiB
Python
159 lines
4.4 KiB
Python
from enum import StrEnum
|
|
|
|
from typing import Annotated, Self
|
|
|
|
from httpx import HTTPStatusError
|
|
|
|
from fastapi import APIRouter, Query, Response
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
from nyekiapi.lib import settings
|
|
from nyekiapi.api.v2.errors import NotFoundError, BadGatewayError, InternalServerError
|
|
from nyekiapi.api.v2.schemas import ErrorSchema
|
|
from nyekiapi.api.v2.profiles.schemas import ProfileSchema
|
|
from nyekiapi.lib.integrations import discord, github, Profile
|
|
|
|
|
|
router = APIRouter(tags=["Profiles"])
|
|
|
|
|
|
sources = []
|
|
|
|
if settings.DISCORD_TOKEN:
|
|
sources.append(("DISCORD", "discord"))
|
|
|
|
if settings.GITHUB_TOKEN:
|
|
sources.append(("GITHUB", "github"))
|
|
|
|
|
|
ProfileSource = StrEnum("ProfileSource", sources)
|
|
|
|
|
|
class ProfileQuerySchema(BaseModel):
|
|
source: ProfileSource = Field(..., description="The source of the profile.")
|
|
source_id: str | None = Field(
|
|
None,
|
|
description='The ID of the profile in the source.\nSupported by: `"discord"` `"github"`',
|
|
)
|
|
source_name: str | None = Field(
|
|
None,
|
|
description='The name of the profile in the source.\nSupported by: `"github"`',
|
|
)
|
|
|
|
@model_validator(mode="after")
|
|
def validate_source(self) -> Self:
|
|
if self.source_id and self.source_name:
|
|
raise ValueError(
|
|
"Both source_id and source_name cannot be provided at the same time."
|
|
)
|
|
|
|
if self.source.value == "discord" and not self.source_id:
|
|
raise ValueError("source_id is required for Discord profiles.")
|
|
|
|
if self.source.value == "github" and not (self.source_name or self.source_id):
|
|
raise ValueError(
|
|
"Either source_name or source_id is required for GitHub profiles."
|
|
)
|
|
|
|
return self
|
|
|
|
|
|
ProfileQuery = Annotated[ProfileQuerySchema, Query()]
|
|
|
|
|
|
async def _get_profile(profile: ProfileQuerySchema) -> Profile:
|
|
try:
|
|
if profile.source.value == "discord":
|
|
return await discord.get_profile_by_id(profile.source_id)
|
|
|
|
elif profile.source.value == "github":
|
|
if profile.source_id:
|
|
return await github.get_profile_by_id(profile.source_id)
|
|
else:
|
|
return await github.get_profile_by_name(profile.source_name)
|
|
|
|
except HTTPStatusError as e:
|
|
if e.response.status_code == 404:
|
|
raise NotFoundError("profile") from e
|
|
|
|
elif e.response.status_code >= 500:
|
|
raise BadGatewayError() from e
|
|
|
|
else:
|
|
raise InternalServerError() from e
|
|
|
|
|
|
@router.get(
|
|
"/profile",
|
|
responses={
|
|
404: {"model": ErrorSchema},
|
|
422: {"model": ErrorSchema},
|
|
500: {"model": ErrorSchema},
|
|
502: {"model": ErrorSchema},
|
|
},
|
|
)
|
|
async def get_profile(profile: ProfileQuery) -> ProfileSchema:
|
|
"""Returns basic information about a profile from a given source."""
|
|
|
|
profile = await _get_profile(profile)
|
|
|
|
return ProfileSchema.from_integration(profile)
|
|
|
|
|
|
@router.get(
|
|
"/profile/avatar",
|
|
responses={
|
|
404: {"model": ErrorSchema},
|
|
422: {"model": ErrorSchema},
|
|
500: {"model": ErrorSchema},
|
|
502: {"model": ErrorSchema},
|
|
},
|
|
status_code=302,
|
|
)
|
|
async def get_profile_avatar(response: Response, profile: ProfileQuery) -> None:
|
|
"""Redirects to the avatar URL of a profile from a given source."""
|
|
|
|
profile = await _get_profile(profile)
|
|
|
|
response.headers["Location"] = profile.avatar_url
|
|
|
|
|
|
@router.get(
|
|
"/profile/banner",
|
|
responses={
|
|
404: {"model": ErrorSchema},
|
|
422: {"model": ErrorSchema},
|
|
500: {"model": ErrorSchema},
|
|
502: {"model": ErrorSchema},
|
|
},
|
|
status_code=302,
|
|
)
|
|
async def get_profile_banner(response: Response, profile: ProfileQuery) -> None:
|
|
"""Redirects to the banner URL of a profile from a given source."""
|
|
|
|
profile = await _get_profile(profile)
|
|
|
|
if not profile.banner_url:
|
|
raise NotFoundError("profile banner")
|
|
|
|
response.headers["Location"] = profile.banner_url
|
|
|
|
|
|
@router.get(
|
|
"/profile/url",
|
|
responses={
|
|
404: {"model": ErrorSchema},
|
|
422: {"model": ErrorSchema},
|
|
500: {"model": ErrorSchema},
|
|
502: {"model": ErrorSchema},
|
|
},
|
|
status_code=302,
|
|
)
|
|
async def get_profile_url(response: Response, profile: ProfileQuery) -> None:
|
|
"""Redirects to the profile URL of a profile from a given source."""
|
|
|
|
profile = await _get_profile(profile)
|
|
|
|
response.headers["Location"] = profile.url
|