Coverage for polar/integrations/github_repository_benefit/endpoints.py: 52%
63 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 17:15 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 17:15 +0000
1from typing import Any 1a
3from fastapi import Depends, Request, Response 1a
4from fastapi.responses import RedirectResponse 1a
5from httpx_oauth.integrations.fastapi import OAuth2AuthorizeCallback 1a
6from httpx_oauth.oauth2 import OAuth2Token 1a
8from polar.auth.dependencies import WebUserWrite 1a
9from polar.config import settings 1a
10from polar.eventstream.service import publish 1a
11from polar.exceptions import ( 1a
12 NotPermitted,
13 PolarRedirectionError,
14 ResourceAlreadyExists,
15 Unauthorized,
16)
17from polar.integrations.github_repository_benefit.schemas import ( 1a
18 GitHubInvitesBenefitRepositories,
19)
20from polar.kit import jwt 1a
21from polar.kit.http import ReturnTo, add_query_parameters, get_safe_return_url 1a
22from polar.openapi import APITag 1a
23from polar.postgres import AsyncSession, get_db_session 1a
24from polar.redis import Redis, get_redis 1a
25from polar.routing import APIRouter 1a
27from .service import github_oauth_client, github_repository_benefit_user_service 1a
29router = APIRouter( 1a
30 prefix="/integrations/github_repository_benefit",
31 tags=["integrations_github_repository_benefit", APITag.private],
32)
35###############################################################################
36# OAUTH2
37###############################################################################
40def get_decoded_token_state( 1a
41 access_token_state: tuple[OAuth2Token, str | None],
42) -> tuple[OAuth2Token, dict[str, Any]]:
43 token_data, state = access_token_state
44 if not state:
45 raise Unauthorized("No state")
47 try:
48 state_data = jwt.decode(
49 token=state,
50 secret=settings.SECRET,
51 type="github_repository_benefit_oauth",
52 )
53 except jwt.DecodeError as e:
54 raise Unauthorized("Invalid state") from e
56 return (token_data, state_data)
59###############################################################################
60# User OAuth
61###############################################################################
64oauth2_authorize_callback = OAuth2AuthorizeCallback( 1a
65 github_oauth_client,
66 route_name="integrations.github_repository_benefit.user_callback",
67)
70class OAuthCallbackError(PolarRedirectionError): ... 1a
73class NotPermittedOrganizationBillingPlan(NotPermitted): 1a
74 def __init__(self) -> None: 1a
75 message = "Organization billing plan not accessible."
76 super().__init__(message)
79@router.get( 1a
80 "/user/authorize", name="integrations.github_repository_benefit.user_authorize"
81)
82async def user_authorize( 1a
83 request: Request, return_to: ReturnTo, auth_subject: WebUserWrite
84) -> RedirectResponse:
85 state = {"return_to": return_to}
87 encoded_state = jwt.encode(
88 data=state, secret=settings.SECRET, type="github_repository_benefit_oauth"
89 )
91 authorization_url = await github_oauth_client.get_authorization_url(
92 redirect_uri=str(
93 request.url_for("integrations.github_repository_benefit.user_callback")
94 ),
95 state=encoded_state,
96 scope=["user", "user.email"],
97 )
98 return RedirectResponse(authorization_url, 303)
101@router.get( 1a
102 "/user/callback", name="integrations.github_repository_benefit.user_callback"
103)
104async def user_callback( 1a
105 auth_subject: WebUserWrite,
106 session: AsyncSession = Depends(get_db_session),
107 access_token_state: tuple[OAuth2Token, str | None] = Depends(
108 oauth2_authorize_callback
109 ),
110) -> RedirectResponse:
111 token_data, state = get_decoded_token_state(access_token_state)
113 try:
114 await github_repository_benefit_user_service.create_oauth_account(
115 session,
116 auth_subject.subject,
117 token_data,
118 )
119 except ResourceAlreadyExists:
120 await github_repository_benefit_user_service.update_oauth_account(
121 session, auth_subject.subject, token_data
122 )
124 return_to = state["return_to"]
125 redirect_url = get_safe_return_url(add_query_parameters(return_to))
127 return RedirectResponse(redirect_url, 303)
130@router.get( 1a
131 "/user/repositories",
132 name="integrations.github_repository_benefit.user_repositories",
133 description="Lists available repositories for this user",
134)
135async def user_repositories( 1a
136 auth_subject: WebUserWrite,
137 session: AsyncSession = Depends(get_db_session),
138 redis: Redis = Depends(get_redis),
139) -> GitHubInvitesBenefitRepositories:
140 oauth = await github_repository_benefit_user_service.get_oauth_account(
141 session, auth_subject.subject
142 )
144 installations = (
145 await github_repository_benefit_user_service.list_user_installations(oauth)
146 )
148 orgs = await github_repository_benefit_user_service.list_orgs_with_billing_plans(
149 redis, oauth, installations
150 )
152 repos = await github_repository_benefit_user_service.list_repositories(
153 oauth,
154 installations,
155 )
157 return GitHubInvitesBenefitRepositories(
158 organizations=orgs,
159 repositories=repos,
160 )
163###############################################################################
164# Installation
165###############################################################################
168@router.get( 1a
169 "/installation/install",
170 name="integrations.github_repository_benefit.installation_install",
171)
172async def installation_install( 1a
173 request: Request, auth_subject: WebUserWrite
174) -> RedirectResponse:
175 return RedirectResponse(
176 f"https://github.com/apps/{settings.GITHUB_REPOSITORY_BENEFITS_APP_NAMESPACE}/installations/new",
177 303,
178 )
181@router.get( 1a
182 "/installation/callback",
183 name="integrations.github_repository_benefit.installation_callback",
184)
185async def installation_callback( 1a
186 request: Request, auth_subject: WebUserWrite
187) -> Response:
188 await publish(
189 "integrations.github_repository_benefit.installed",
190 payload={},
191 user_id=auth_subject.subject.id,
192 )
194 return Response("Installation successful, you can close this page.")