oauth_data_source.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import json
  2. import urllib.parse
  3. import requests
  4. from flask_login import current_user
  5. from extensions.ext_database import db
  6. from models.source import DataSourceBinding
  7. class OAuthDataSource:
  8. def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
  9. self.client_id = client_id
  10. self.client_secret = client_secret
  11. self.redirect_uri = redirect_uri
  12. def get_authorization_url(self):
  13. raise NotImplementedError()
  14. def get_access_token(self, code: str):
  15. raise NotImplementedError()
  16. class NotionOAuth(OAuthDataSource):
  17. _AUTH_URL = 'https://api.notion.com/v1/oauth/authorize'
  18. _TOKEN_URL = 'https://api.notion.com/v1/oauth/token'
  19. _NOTION_PAGE_SEARCH = "https://api.notion.com/v1/search"
  20. _NOTION_BLOCK_SEARCH = "https://api.notion.com/v1/blocks"
  21. def get_authorization_url(self):
  22. params = {
  23. 'client_id': self.client_id,
  24. 'response_type': 'code',
  25. 'redirect_uri': self.redirect_uri,
  26. 'owner': 'user'
  27. }
  28. return f"{self._AUTH_URL}?{urllib.parse.urlencode(params)}"
  29. def get_access_token(self, code: str):
  30. data = {
  31. 'code': code,
  32. 'grant_type': 'authorization_code',
  33. 'redirect_uri': self.redirect_uri
  34. }
  35. headers = {'Accept': 'application/json'}
  36. auth = (self.client_id, self.client_secret)
  37. response = requests.post(self._TOKEN_URL, data=data, auth=auth, headers=headers)
  38. response_json = response.json()
  39. access_token = response_json.get('access_token')
  40. if not access_token:
  41. raise ValueError(f"Error in Notion OAuth: {response_json}")
  42. workspace_name = response_json.get('workspace_name')
  43. workspace_icon = response_json.get('workspace_icon')
  44. workspace_id = response_json.get('workspace_id')
  45. # get all authorized pages
  46. pages = self.get_authorized_pages(access_token)
  47. source_info = {
  48. 'workspace_name': workspace_name,
  49. 'workspace_icon': workspace_icon,
  50. 'workspace_id': workspace_id,
  51. 'pages': pages,
  52. 'total': len(pages)
  53. }
  54. # save data source binding
  55. data_source_binding = DataSourceBinding.query.filter(
  56. db.and_(
  57. DataSourceBinding.tenant_id == current_user.current_tenant_id,
  58. DataSourceBinding.provider == 'notion',
  59. DataSourceBinding.access_token == access_token
  60. )
  61. ).first()
  62. if data_source_binding:
  63. data_source_binding.source_info = source_info
  64. data_source_binding.disabled = False
  65. db.session.commit()
  66. else:
  67. new_data_source_binding = DataSourceBinding(
  68. tenant_id=current_user.current_tenant_id,
  69. access_token=access_token,
  70. source_info=source_info,
  71. provider='notion'
  72. )
  73. db.session.add(new_data_source_binding)
  74. db.session.commit()
  75. def sync_data_source(self, binding_id: str):
  76. # save data source binding
  77. data_source_binding = DataSourceBinding.query.filter(
  78. db.and_(
  79. DataSourceBinding.tenant_id == current_user.current_tenant_id,
  80. DataSourceBinding.provider == 'notion',
  81. DataSourceBinding.id == binding_id,
  82. DataSourceBinding.disabled == False
  83. )
  84. ).first()
  85. if data_source_binding:
  86. # get all authorized pages
  87. pages = self.get_authorized_pages(data_source_binding.access_token)
  88. source_info = data_source_binding.source_info
  89. new_source_info = {
  90. 'workspace_name': source_info['workspace_name'],
  91. 'workspace_icon': source_info['workspace_icon'],
  92. 'workspace_id': source_info['workspace_id'],
  93. 'pages': pages,
  94. 'total': len(pages)
  95. }
  96. data_source_binding.source_info = new_source_info
  97. data_source_binding.disabled = False
  98. db.session.commit()
  99. else:
  100. raise ValueError('Data source binding not found')
  101. def get_authorized_pages(self, access_token: str):
  102. pages = []
  103. page_results = self.notion_page_search(access_token)
  104. database_results = self.notion_database_search(access_token)
  105. # get page detail
  106. for page_result in page_results:
  107. page_id = page_result['id']
  108. if 'Name' in page_result['properties']:
  109. if len(page_result['properties']['Name']['title']) > 0:
  110. page_name = page_result['properties']['Name']['title'][0]['plain_text']
  111. else:
  112. page_name = 'Untitled'
  113. elif 'title' in page_result['properties']:
  114. if len(page_result['properties']['title']['title']) > 0:
  115. page_name = page_result['properties']['title']['title'][0]['plain_text']
  116. else:
  117. page_name = 'Untitled'
  118. elif 'Title' in page_result['properties']:
  119. if len(page_result['properties']['Title']['title']) > 0:
  120. page_name = page_result['properties']['Title']['title'][0]['plain_text']
  121. else:
  122. page_name = 'Untitled'
  123. else:
  124. page_name = 'Untitled'
  125. page_icon = page_result['icon']
  126. if page_icon:
  127. icon_type = page_icon['type']
  128. if icon_type == 'external' or icon_type == 'file':
  129. url = page_icon[icon_type]['url']
  130. icon = {
  131. 'type': 'url',
  132. 'url': url if url.startswith('http') else f'https://www.notion.so{url}'
  133. }
  134. else:
  135. icon = {
  136. 'type': 'emoji',
  137. 'emoji': page_icon[icon_type]
  138. }
  139. else:
  140. icon = None
  141. parent = page_result['parent']
  142. parent_type = parent['type']
  143. if parent_type == 'block_id':
  144. parent_id = self.notion_block_parent_page_id(access_token, parent[parent_type])
  145. elif parent_type == 'workspace':
  146. parent_id = 'root'
  147. else:
  148. parent_id = parent[parent_type]
  149. page = {
  150. 'page_id': page_id,
  151. 'page_name': page_name,
  152. 'page_icon': icon,
  153. 'parent_id': parent_id,
  154. 'type': 'page'
  155. }
  156. pages.append(page)
  157. # get database detail
  158. for database_result in database_results:
  159. page_id = database_result['id']
  160. if len(database_result['title']) > 0:
  161. page_name = database_result['title'][0]['plain_text']
  162. else:
  163. page_name = 'Untitled'
  164. page_icon = database_result['icon']
  165. if page_icon:
  166. icon_type = page_icon['type']
  167. if icon_type == 'external' or icon_type == 'file':
  168. url = page_icon[icon_type]['url']
  169. icon = {
  170. 'type': 'url',
  171. 'url': url if url.startswith('http') else f'https://www.notion.so{url}'
  172. }
  173. else:
  174. icon = {
  175. 'type': icon_type,
  176. icon_type: page_icon[icon_type]
  177. }
  178. else:
  179. icon = None
  180. parent = database_result['parent']
  181. parent_type = parent['type']
  182. if parent_type == 'block_id':
  183. parent_id = self.notion_block_parent_page_id(access_token, parent[parent_type])
  184. elif parent_type == 'workspace':
  185. parent_id = 'root'
  186. else:
  187. parent_id = parent[parent_type]
  188. page = {
  189. 'page_id': page_id,
  190. 'page_name': page_name,
  191. 'page_icon': icon,
  192. 'parent_id': parent_id,
  193. 'type': 'database'
  194. }
  195. pages.append(page)
  196. return pages
  197. def notion_page_search(self, access_token: str):
  198. data = {
  199. 'filter': {
  200. "value": "page",
  201. "property": "object"
  202. }
  203. }
  204. headers = {
  205. 'Content-Type': 'application/json',
  206. 'Authorization': f"Bearer {access_token}",
  207. 'Notion-Version': '2022-06-28',
  208. }
  209. response = requests.post(url=self._NOTION_PAGE_SEARCH, json=data, headers=headers)
  210. response_json = response.json()
  211. results = response_json['results']
  212. return results
  213. def notion_block_parent_page_id(self, access_token: str, block_id: str):
  214. headers = {
  215. 'Authorization': f"Bearer {access_token}",
  216. 'Notion-Version': '2022-06-28',
  217. }
  218. response = requests.get(url=f'{self._NOTION_BLOCK_SEARCH}/{block_id}', headers=headers)
  219. response_json = response.json()
  220. parent = response_json['parent']
  221. parent_type = parent['type']
  222. if parent_type == 'block_id':
  223. return self.notion_block_parent_page_id(access_token, parent[parent_type])
  224. return parent[parent_type]
  225. def notion_database_search(self, access_token: str):
  226. data = {
  227. 'filter': {
  228. "value": "database",
  229. "property": "object"
  230. }
  231. }
  232. headers = {
  233. 'Content-Type': 'application/json',
  234. 'Authorization': f"Bearer {access_token}",
  235. 'Notion-Version': '2022-06-28',
  236. }
  237. response = requests.post(url=self._NOTION_PAGE_SEARCH, json=data, headers=headers)
  238. response_json = response.json()
  239. results = response_json['results']
  240. return results