oauth_data_source.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import urllib.parse
  2. import requests
  3. from flask_login import current_user
  4. from extensions.ext_database import db
  5. from models.source import DataSourceBinding
  6. class OAuthDataSource:
  7. def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
  8. self.client_id = client_id
  9. self.client_secret = client_secret
  10. self.redirect_uri = redirect_uri
  11. def get_authorization_url(self):
  12. raise NotImplementedError()
  13. def get_access_token(self, code: str):
  14. raise NotImplementedError()
  15. class NotionOAuth(OAuthDataSource):
  16. _AUTH_URL = 'https://api.notion.com/v1/oauth/authorize'
  17. _TOKEN_URL = 'https://api.notion.com/v1/oauth/token'
  18. _NOTION_PAGE_SEARCH = "https://api.notion.com/v1/search"
  19. _NOTION_BLOCK_SEARCH = "https://api.notion.com/v1/blocks"
  20. _NOTION_BOT_USER = "https://api.notion.com/v1/users/me"
  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 save_internal_access_token(self, access_token: str):
  76. workspace_name = self.notion_workspace_name(access_token)
  77. workspace_icon = None
  78. workspace_id = current_user.current_tenant_id
  79. # get all authorized pages
  80. pages = self.get_authorized_pages(access_token)
  81. source_info = {
  82. 'workspace_name': workspace_name,
  83. 'workspace_icon': workspace_icon,
  84. 'workspace_id': workspace_id,
  85. 'pages': pages,
  86. 'total': len(pages)
  87. }
  88. # save data source binding
  89. data_source_binding = DataSourceBinding.query.filter(
  90. db.and_(
  91. DataSourceBinding.tenant_id == current_user.current_tenant_id,
  92. DataSourceBinding.provider == 'notion',
  93. DataSourceBinding.access_token == access_token
  94. )
  95. ).first()
  96. if data_source_binding:
  97. data_source_binding.source_info = source_info
  98. data_source_binding.disabled = False
  99. db.session.commit()
  100. else:
  101. new_data_source_binding = DataSourceBinding(
  102. tenant_id=current_user.current_tenant_id,
  103. access_token=access_token,
  104. source_info=source_info,
  105. provider='notion'
  106. )
  107. db.session.add(new_data_source_binding)
  108. db.session.commit()
  109. def sync_data_source(self, binding_id: str):
  110. # save data source binding
  111. data_source_binding = DataSourceBinding.query.filter(
  112. db.and_(
  113. DataSourceBinding.tenant_id == current_user.current_tenant_id,
  114. DataSourceBinding.provider == 'notion',
  115. DataSourceBinding.id == binding_id,
  116. DataSourceBinding.disabled == False
  117. )
  118. ).first()
  119. if data_source_binding:
  120. # get all authorized pages
  121. pages = self.get_authorized_pages(data_source_binding.access_token)
  122. source_info = data_source_binding.source_info
  123. new_source_info = {
  124. 'workspace_name': source_info['workspace_name'],
  125. 'workspace_icon': source_info['workspace_icon'],
  126. 'workspace_id': source_info['workspace_id'],
  127. 'pages': pages,
  128. 'total': len(pages)
  129. }
  130. data_source_binding.source_info = new_source_info
  131. data_source_binding.disabled = False
  132. db.session.commit()
  133. else:
  134. raise ValueError('Data source binding not found')
  135. def get_authorized_pages(self, access_token: str):
  136. pages = []
  137. page_results = self.notion_page_search(access_token)
  138. database_results = self.notion_database_search(access_token)
  139. # get page detail
  140. for page_result in page_results:
  141. page_id = page_result['id']
  142. page_name = 'Untitled'
  143. for key in ['Name', 'title', 'Title', 'Page']:
  144. if key in page_result['properties']:
  145. if len(page_result['properties'][key].get('title', [])) > 0:
  146. page_name = page_result['properties'][key]['title'][0]['plain_text']
  147. break
  148. page_icon = page_result['icon']
  149. if page_icon:
  150. icon_type = page_icon['type']
  151. if icon_type == 'external' or icon_type == 'file':
  152. url = page_icon[icon_type]['url']
  153. icon = {
  154. 'type': 'url',
  155. 'url': url if url.startswith('http') else f'https://www.notion.so{url}'
  156. }
  157. else:
  158. icon = {
  159. 'type': 'emoji',
  160. 'emoji': page_icon[icon_type]
  161. }
  162. else:
  163. icon = None
  164. parent = page_result['parent']
  165. parent_type = parent['type']
  166. if parent_type == 'block_id':
  167. parent_id = self.notion_block_parent_page_id(access_token, parent[parent_type])
  168. elif parent_type == 'workspace':
  169. parent_id = 'root'
  170. else:
  171. parent_id = parent[parent_type]
  172. page = {
  173. 'page_id': page_id,
  174. 'page_name': page_name,
  175. 'page_icon': icon,
  176. 'parent_id': parent_id,
  177. 'type': 'page'
  178. }
  179. pages.append(page)
  180. # get database detail
  181. for database_result in database_results:
  182. page_id = database_result['id']
  183. if len(database_result['title']) > 0:
  184. page_name = database_result['title'][0]['plain_text']
  185. else:
  186. page_name = 'Untitled'
  187. page_icon = database_result['icon']
  188. if page_icon:
  189. icon_type = page_icon['type']
  190. if icon_type == 'external' or icon_type == 'file':
  191. url = page_icon[icon_type]['url']
  192. icon = {
  193. 'type': 'url',
  194. 'url': url if url.startswith('http') else f'https://www.notion.so{url}'
  195. }
  196. else:
  197. icon = {
  198. 'type': icon_type,
  199. icon_type: page_icon[icon_type]
  200. }
  201. else:
  202. icon = None
  203. parent = database_result['parent']
  204. parent_type = parent['type']
  205. if parent_type == 'block_id':
  206. parent_id = self.notion_block_parent_page_id(access_token, parent[parent_type])
  207. elif parent_type == 'workspace':
  208. parent_id = 'root'
  209. else:
  210. parent_id = parent[parent_type]
  211. page = {
  212. 'page_id': page_id,
  213. 'page_name': page_name,
  214. 'page_icon': icon,
  215. 'parent_id': parent_id,
  216. 'type': 'database'
  217. }
  218. pages.append(page)
  219. return pages
  220. def notion_page_search(self, access_token: str):
  221. data = {
  222. 'filter': {
  223. "value": "page",
  224. "property": "object"
  225. }
  226. }
  227. headers = {
  228. 'Content-Type': 'application/json',
  229. 'Authorization': f"Bearer {access_token}",
  230. 'Notion-Version': '2022-06-28',
  231. }
  232. response = requests.post(url=self._NOTION_PAGE_SEARCH, json=data, headers=headers)
  233. response_json = response.json()
  234. if 'results' in response_json:
  235. results = response_json['results']
  236. else:
  237. results = []
  238. return results
  239. def notion_block_parent_page_id(self, access_token: str, block_id: str):
  240. headers = {
  241. 'Authorization': f"Bearer {access_token}",
  242. 'Notion-Version': '2022-06-28',
  243. }
  244. response = requests.get(url=f'{self._NOTION_BLOCK_SEARCH}/{block_id}', headers=headers)
  245. response_json = response.json()
  246. parent = response_json['parent']
  247. parent_type = parent['type']
  248. if parent_type == 'block_id':
  249. return self.notion_block_parent_page_id(access_token, parent[parent_type])
  250. return parent[parent_type]
  251. def notion_workspace_name(self, access_token: str):
  252. headers = {
  253. 'Authorization': f"Bearer {access_token}",
  254. 'Notion-Version': '2022-06-28',
  255. }
  256. response = requests.get(url=self._NOTION_BOT_USER, headers=headers)
  257. response_json = response.json()
  258. if 'object' in response_json and response_json['object'] == 'user':
  259. user_type = response_json['type']
  260. user_info = response_json[user_type]
  261. if 'workspace_name' in user_info:
  262. return user_info['workspace_name']
  263. return 'workspace'
  264. def notion_database_search(self, access_token: str):
  265. data = {
  266. 'filter': {
  267. "value": "database",
  268. "property": "object"
  269. }
  270. }
  271. headers = {
  272. 'Content-Type': 'application/json',
  273. 'Authorization': f"Bearer {access_token}",
  274. 'Notion-Version': '2022-06-28',
  275. }
  276. response = requests.post(url=self._NOTION_PAGE_SEARCH, json=data, headers=headers)
  277. response_json = response.json()
  278. if 'results' in response_json:
  279. results = response_json['results']
  280. else:
  281. results = []
  282. return results