users_router.py 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313
  1. import textwrap
  2. from typing import Optional
  3. from uuid import UUID
  4. from fastapi import Body, Depends, Path, Query
  5. from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
  6. from pydantic import EmailStr
  7. from core.base import R2RException
  8. from core.base.api.models import (
  9. GenericBooleanResponse,
  10. GenericMessageResponse,
  11. WrappedBooleanResponse,
  12. WrappedCollectionsResponse,
  13. WrappedGenericMessageResponse,
  14. WrappedTokenResponse,
  15. WrappedUserResponse,
  16. WrappedUsersResponse,
  17. )
  18. from .base_router import BaseRouterV3
  19. oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
  20. class UsersRouter(BaseRouterV3):
  21. def __init__(
  22. self, providers, services, orchestration_provider=None, run_type=None
  23. ):
  24. super().__init__(providers, services, orchestration_provider, run_type)
  25. def _setup_routes(self):
  26. @self.router.post(
  27. "/users",
  28. response_model=WrappedUserResponse,
  29. openapi_extra={
  30. "x-codeSamples": [
  31. {
  32. "lang": "Python",
  33. "source": textwrap.dedent(
  34. """
  35. from r2r import R2RClient
  36. client = R2RClient("http://localhost:7272")
  37. new_user = client.users.create(
  38. email="jane.doe@example.com",
  39. password="secure_password123"
  40. )"""
  41. ),
  42. },
  43. {
  44. "lang": "JavaScript",
  45. "source": textwrap.dedent(
  46. """
  47. const { r2rClient } = require("r2r-js");
  48. const client = new r2rClient("http://localhost:7272");
  49. function main() {
  50. const response = await client.users.create({
  51. email: "jane.doe@example.com",
  52. password: "secure_password123"
  53. });
  54. }
  55. main();
  56. """
  57. ),
  58. },
  59. {
  60. "lang": "CLI",
  61. "source": textwrap.dedent(
  62. """
  63. r2r users create jane.doe@example.com secure_password123
  64. """
  65. ),
  66. },
  67. {
  68. "lang": "cURL",
  69. "source": textwrap.dedent(
  70. """
  71. curl -X POST "https://api.example.com/v3/users" \\
  72. -H "Content-Type: application/json" \\
  73. -d '{
  74. "email": "jane.doe@example.com",
  75. "password": "secure_password123"
  76. }'"""
  77. ),
  78. },
  79. ]
  80. },
  81. )
  82. @self.base_endpoint
  83. async def register(
  84. email: EmailStr = Body(..., description="User's email address"),
  85. password: str = Body(..., description="User's password"),
  86. name: str | None = Body(
  87. None, description="The name for the new user"
  88. ),
  89. bio: str | None = Body(
  90. None, description="The bio for the new user"
  91. ),
  92. profile_picture: str | None = Body(
  93. None, description="Updated user profile picture"
  94. ),
  95. auth_user=Depends(self.providers.auth.auth_wrapper),
  96. ) -> WrappedUserResponse:
  97. """Register a new user with the given email and password."""
  98. print('email = ', email)
  99. print('making request.....')
  100. registration_response = await self.services["auth"].register(
  101. email, password
  102. )
  103. print('registration_response = ', registration_response)
  104. if name or bio or profile_picture:
  105. return await self.services["auth"].update_user(
  106. user_id=registration_response.id,
  107. name=name,
  108. bio=bio,
  109. profile_picture=profile_picture,
  110. )
  111. return registration_response
  112. # TODO: deprecated, remove in next release
  113. @self.router.post(
  114. "/users/register",
  115. response_model=WrappedUserResponse,
  116. openapi_extra={
  117. "x-codeSamples": [
  118. {
  119. "lang": "Python",
  120. "source": textwrap.dedent(
  121. """
  122. from r2r import R2RClient
  123. client = R2RClient("http://localhost:7272")
  124. new_user = client.users.register(
  125. email="jane.doe@example.com",
  126. password="secure_password123"
  127. )"""
  128. ),
  129. },
  130. {
  131. "lang": "JavaScript",
  132. "source": textwrap.dedent(
  133. """
  134. const { r2rClient } = require("r2r-js");
  135. const client = new r2rClient("http://localhost:7272");
  136. function main() {
  137. const response = await client.users.register({
  138. email: "jane.doe@example.com",
  139. password: "secure_password123"
  140. });
  141. }
  142. main();
  143. """
  144. ),
  145. },
  146. {
  147. "lang": "CLI",
  148. "source": textwrap.dedent(
  149. """
  150. r2r users register jane.doe@example.com secure_password123
  151. """
  152. ),
  153. },
  154. {
  155. "lang": "cURL",
  156. "source": textwrap.dedent(
  157. """
  158. curl -X POST "https://api.example.com/v3/users/register" \\
  159. -H "Content-Type: application/json" \\
  160. -d '{
  161. "email": "jane.doe@example.com",
  162. "password": "secure_password123"
  163. }'"""
  164. ),
  165. },
  166. ]
  167. },
  168. )
  169. @self.base_endpoint
  170. async def register(
  171. email: EmailStr = Body(..., description="User's email address"),
  172. password: str = Body(..., description="User's password"),
  173. ):
  174. """Register a new user with the given email and password."""
  175. return await self.services["auth"].register(email, password)
  176. @self.router.post(
  177. "/users/verify-email",
  178. response_model=WrappedGenericMessageResponse,
  179. openapi_extra={
  180. "x-codeSamples": [
  181. {
  182. "lang": "Python",
  183. "source": textwrap.dedent(
  184. """
  185. from r2r import R2RClient
  186. client = R2RClient("http://localhost:7272")
  187. tokens = client.users.verify_email(
  188. email="jane.doe@example.com",
  189. verification_code="1lklwal!awdclm"
  190. )"""
  191. ),
  192. },
  193. {
  194. "lang": "JavaScript",
  195. "source": textwrap.dedent(
  196. """
  197. const { r2rClient } = require("r2r-js");
  198. const client = new r2rClient("http://localhost:7272");
  199. function main() {
  200. const response = await client.users.verifyEmail({
  201. email: jane.doe@example.com",
  202. verificationCode: "1lklwal!awdclm"
  203. });
  204. }
  205. main();
  206. """
  207. ),
  208. },
  209. {
  210. "lang": "cURL",
  211. "source": textwrap.dedent(
  212. """
  213. curl -X POST "https://api.example.com/v3/users/login" \\
  214. -H "Content-Type: application/x-www-form-urlencoded" \\
  215. -d "email=jane.doe@example.com&verification_code=1lklwal!awdclm"
  216. """
  217. ),
  218. },
  219. ]
  220. },
  221. )
  222. @self.base_endpoint
  223. async def verify_email(
  224. email: EmailStr = Body(..., description="User's email address"),
  225. verification_code: str = Body(
  226. ..., description="Email verification code"
  227. ),
  228. ) -> WrappedGenericMessageResponse:
  229. """Verify a user's email address."""
  230. result = await self.services["auth"].verify_email(
  231. email, verification_code
  232. )
  233. return GenericMessageResponse(message=result["message"]) # type: ignore
  234. @self.router.post(
  235. "/users/login",
  236. response_model=WrappedTokenResponse,
  237. openapi_extra={
  238. "x-codeSamples": [
  239. {
  240. "lang": "Python",
  241. "source": textwrap.dedent(
  242. """
  243. from r2r import R2RClient
  244. client = R2RClient("http://localhost:7272")
  245. tokens = client.users.login(
  246. email="jane.doe@example.com",
  247. password="secure_password123"
  248. )
  249. """
  250. ),
  251. },
  252. {
  253. "lang": "JavaScript",
  254. "source": textwrap.dedent(
  255. """
  256. const { r2rClient } = require("r2r-js");
  257. const client = new r2rClient("http://localhost:7272");
  258. function main() {
  259. const response = await client.users.login({
  260. email: jane.doe@example.com",
  261. password: "secure_password123"
  262. });
  263. }
  264. main();
  265. """
  266. ),
  267. },
  268. {
  269. "lang": "cURL",
  270. "source": textwrap.dedent(
  271. """
  272. curl -X POST "https://api.example.com/v3/users/login" \\
  273. -H "Content-Type: application/x-www-form-urlencoded" \\
  274. -d "username=jane.doe@example.com&password=secure_password123"
  275. """
  276. ),
  277. },
  278. ]
  279. },
  280. )
  281. @self.base_endpoint
  282. async def login(form_data: OAuth2PasswordRequestForm = Depends()):
  283. """Authenticate a user and provide access tokens."""
  284. return await self.services["auth"].login(
  285. form_data.username, form_data.password
  286. )
  287. @self.router.post(
  288. "/users/logout",
  289. response_model=WrappedGenericMessageResponse,
  290. openapi_extra={
  291. "x-codeSamples": [
  292. {
  293. "lang": "Python",
  294. "source": textwrap.dedent(
  295. """
  296. from r2r import R2RClient
  297. client = R2RClient("http://localhost:7272")
  298. # client.login(...)
  299. result = client.users.logout()
  300. """
  301. ),
  302. },
  303. {
  304. "lang": "JavaScript",
  305. "source": textwrap.dedent(
  306. """
  307. const { r2rClient } = require("r2r-js");
  308. const client = new r2rClient("http://localhost:7272");
  309. function main() {
  310. const response = await client.users.logout();
  311. }
  312. main();
  313. """
  314. ),
  315. },
  316. {
  317. "lang": "cURL",
  318. "source": textwrap.dedent(
  319. """
  320. curl -X POST "https://api.example.com/v3/users/logout" \\
  321. -H "Authorization: Bearer YOUR_API_KEY"
  322. """
  323. ),
  324. },
  325. ]
  326. },
  327. )
  328. @self.base_endpoint
  329. async def logout(
  330. token: str = Depends(oauth2_scheme),
  331. auth_user=Depends(self.providers.auth.auth_wrapper),
  332. ) -> WrappedGenericMessageResponse:
  333. """Log out the current user."""
  334. result = await self.services["auth"].logout(token)
  335. return GenericMessageResponse(message=result["message"]) # type: ignore
  336. @self.router.post(
  337. "/users/refresh-token",
  338. openapi_extra={
  339. "x-codeSamples": [
  340. {
  341. "lang": "Python",
  342. "source": textwrap.dedent(
  343. """
  344. from r2r import R2RClient
  345. client = R2RClient("http://localhost:7272")
  346. # client.login(...)
  347. new_tokens = client.users.refresh_token()
  348. # New tokens are automatically stored in the client"""
  349. ),
  350. },
  351. {
  352. "lang": "JavaScript",
  353. "source": textwrap.dedent(
  354. """
  355. const { r2rClient } = require("r2r-js");
  356. const client = new r2rClient("http://localhost:7272");
  357. function main() {
  358. const response = await client.users.refreshAccessToken();
  359. }
  360. main();
  361. """
  362. ),
  363. },
  364. {
  365. "lang": "cURL",
  366. "source": textwrap.dedent(
  367. """
  368. curl -X POST "https://api.example.com/v3/users/refresh-token" \\
  369. -H "Content-Type: application/json" \\
  370. -d '{
  371. "refresh_token": "YOUR_REFRESH_TOKEN"
  372. }'"""
  373. ),
  374. },
  375. ]
  376. },
  377. )
  378. @self.base_endpoint
  379. async def refresh_token(
  380. refresh_token: str = Body(..., description="Refresh token")
  381. ) -> WrappedTokenResponse:
  382. """Refresh the access token using a refresh token."""
  383. result = await self.services["auth"].refresh_access_token(
  384. refresh_token=refresh_token
  385. )
  386. return result
  387. @self.router.post(
  388. "/users/change-password",
  389. response_model=WrappedGenericMessageResponse,
  390. openapi_extra={
  391. "x-codeSamples": [
  392. {
  393. "lang": "Python",
  394. "source": textwrap.dedent(
  395. """
  396. from r2r import R2RClient
  397. client = R2RClient("http://localhost:7272")
  398. # client.login(...)
  399. result = client.users.change_password(
  400. current_password="old_password123",
  401. new_password="new_secure_password456"
  402. )"""
  403. ),
  404. },
  405. {
  406. "lang": "JavaScript",
  407. "source": textwrap.dedent(
  408. """
  409. const { r2rClient } = require("r2r-js");
  410. const client = new r2rClient("http://localhost:7272");
  411. function main() {
  412. const response = await client.users.changePassword({
  413. currentPassword: "old_password123",
  414. newPassword: "new_secure_password456"
  415. });
  416. }
  417. main();
  418. """
  419. ),
  420. },
  421. {
  422. "lang": "cURL",
  423. "source": textwrap.dedent(
  424. """
  425. curl -X POST "https://api.example.com/v3/users/change-password" \\
  426. -H "Authorization: Bearer YOUR_API_KEY" \\
  427. -H "Content-Type: application/json" \\
  428. -d '{
  429. "current_password": "old_password123",
  430. "new_password": "new_secure_password456"
  431. }'"""
  432. ),
  433. },
  434. ]
  435. },
  436. )
  437. @self.base_endpoint
  438. async def change_password(
  439. current_password: str = Body(..., description="Current password"),
  440. new_password: str = Body(..., description="New password"),
  441. auth_user=Depends(self.providers.auth.auth_wrapper),
  442. ) -> GenericMessageResponse:
  443. """Change the authenticated user's password."""
  444. result = await self.services["auth"].change_password(
  445. auth_user, current_password, new_password
  446. )
  447. return GenericMessageResponse(message=result["message"]) # type: ignore
  448. @self.router.post(
  449. "/users/request-password-reset",
  450. response_model=WrappedGenericMessageResponse,
  451. openapi_extra={
  452. "x-codeSamples": [
  453. {
  454. "lang": "Python",
  455. "source": textwrap.dedent(
  456. """
  457. from r2r import R2RClient
  458. client = R2RClient("http://localhost:7272")
  459. result = client.users.request_password_reset(
  460. email="jane.doe@example.com"
  461. )"""
  462. ),
  463. },
  464. {
  465. "lang": "JavaScript",
  466. "source": textwrap.dedent(
  467. """
  468. const { r2rClient } = require("r2r-js");
  469. const client = new r2rClient("http://localhost:7272");
  470. function main() {
  471. const response = await client.users.requestPasswordReset({
  472. email: jane.doe@example.com",
  473. });
  474. }
  475. main();
  476. """
  477. ),
  478. },
  479. {
  480. "lang": "cURL",
  481. "source": textwrap.dedent(
  482. """
  483. curl -X POST "https://api.example.com/v3/users/request-password-reset" \\
  484. -H "Content-Type: application/json" \\
  485. -d '{
  486. "email": "jane.doe@example.com"
  487. }'"""
  488. ),
  489. },
  490. ]
  491. },
  492. )
  493. @self.base_endpoint
  494. async def request_password_reset(
  495. email: EmailStr = Body(..., description="User's email address")
  496. ) -> WrappedGenericMessageResponse:
  497. """Request a password reset for a user."""
  498. result = await self.services["auth"].request_password_reset(email)
  499. return GenericMessageResponse(message=result["message"]) # type: ignore
  500. @self.router.post(
  501. "/users/reset-password",
  502. response_model=WrappedGenericMessageResponse,
  503. openapi_extra={
  504. "x-codeSamples": [
  505. {
  506. "lang": "Python",
  507. "source": textwrap.dedent(
  508. """
  509. from r2r import R2RClient
  510. client = R2RClient("http://localhost:7272")
  511. result = client.users.reset_password(
  512. reset_token="reset_token_received_via_email",
  513. new_password="new_secure_password789"
  514. )"""
  515. ),
  516. },
  517. {
  518. "lang": "JavaScript",
  519. "source": textwrap.dedent(
  520. """
  521. const { r2rClient } = require("r2r-js");
  522. const client = new r2rClient("http://localhost:7272");
  523. function main() {
  524. const response = await client.users.resetPassword({
  525. resestToken: "reset_token_received_via_email",
  526. newPassword: "new_secure_password789"
  527. });
  528. }
  529. main();
  530. """
  531. ),
  532. },
  533. {
  534. "lang": "cURL",
  535. "source": textwrap.dedent(
  536. """
  537. curl -X POST "https://api.example.com/v3/users/reset-password" \\
  538. -H "Content-Type: application/json" \\
  539. -d '{
  540. "reset_token": "reset_token_received_via_email",
  541. "new_password": "new_secure_password789"
  542. }'"""
  543. ),
  544. },
  545. ]
  546. },
  547. )
  548. @self.base_endpoint
  549. async def reset_password(
  550. reset_token: str = Body(..., description="Password reset token"),
  551. new_password: str = Body(..., description="New password"),
  552. ) -> WrappedGenericMessageResponse:
  553. """Reset a user's password using a reset token."""
  554. result = await self.services["auth"].confirm_password_reset(
  555. reset_token, new_password
  556. )
  557. return GenericMessageResponse(message=result["message"]) # type: ignore
  558. @self.router.get(
  559. "/users",
  560. summary="List Users",
  561. openapi_extra={
  562. "x-codeSamples": [
  563. {
  564. "lang": "Python",
  565. "source": textwrap.dedent(
  566. """
  567. from r2r import R2RClient
  568. client = R2RClient("http://localhost:7272")
  569. # client.login(...)
  570. # List users with filters
  571. users = client.users.list(
  572. offset=0,
  573. limit=100,
  574. )
  575. """
  576. ),
  577. },
  578. {
  579. "lang": "JavaScript",
  580. "source": textwrap.dedent(
  581. """
  582. const { r2rClient } = require("r2r-js");
  583. const client = new r2rClient("http://localhost:7272");
  584. function main() {
  585. const response = await client.users.list();
  586. }
  587. main();
  588. """
  589. ),
  590. },
  591. {
  592. "lang": "CLI",
  593. "source": textwrap.dedent(
  594. """
  595. r2r users list
  596. """
  597. ),
  598. },
  599. {
  600. "lang": "Shell",
  601. "source": textwrap.dedent(
  602. """
  603. curl -X GET "https://api.example.com/users?offset=0&limit=100&username=john&email=john@example.com&is_active=true&is_superuser=false" \\
  604. -H "Authorization: Bearer YOUR_API_KEY"
  605. """
  606. ),
  607. },
  608. ]
  609. },
  610. )
  611. @self.base_endpoint
  612. async def list_users(
  613. # TODO - Implement the following parameters
  614. # offset: int = Query(0, ge=0, example=0),
  615. # limit: int = Query(100, ge=1, le=1000, example=100),
  616. # username: Optional[str] = Query(None, example="john"),
  617. # email: Optional[str] = Query(None, example="john@example.com"),
  618. # is_active: Optional[bool] = Query(None, example=True),
  619. # is_superuser: Optional[bool] = Query(None, example=False),
  620. # auth_user=Depends(self.providers.auth.auth_wrapper),
  621. ids: list[str] = Query(
  622. [], description="List of user IDs to filter by"
  623. ),
  624. offset: int = Query(
  625. 0,
  626. ge=0,
  627. description="Specifies the number of objects to skip. Defaults to 0.",
  628. ),
  629. limit: int = Query(
  630. 100,
  631. ge=1,
  632. le=1000,
  633. description="Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.",
  634. ),
  635. auth_user=Depends(self.providers.auth.auth_wrapper),
  636. ) -> WrappedUsersResponse:
  637. """
  638. List all users with pagination and filtering options.
  639. Only accessible by superusers.
  640. """
  641. if not auth_user.is_superuser:
  642. raise R2RException(
  643. "Only a superuser can call the `users_overview` endpoint.",
  644. 403,
  645. )
  646. user_uuids = [UUID(user_id) for user_id in ids]
  647. users_overview_response = await self.services[
  648. "management"
  649. ].users_overview(user_ids=user_uuids, offset=offset, limit=limit)
  650. return users_overview_response["results"], { # type: ignore
  651. "total_entries": users_overview_response["total_entries"]
  652. }
  653. @self.router.get(
  654. "/users/me",
  655. summary="Get the Current User",
  656. openapi_extra={
  657. "x-codeSamples": [
  658. {
  659. "lang": "Python",
  660. "source": textwrap.dedent(
  661. """
  662. from r2r import R2RClient
  663. client = R2RClient("http://localhost:7272")
  664. # client.login(...)
  665. # Get user details
  666. users = client.users.me()
  667. """
  668. ),
  669. },
  670. {
  671. "lang": "JavaScript",
  672. "source": textwrap.dedent(
  673. """
  674. const { r2rClient } = require("r2r-js");
  675. const client = new r2rClient("http://localhost:7272");
  676. function main() {
  677. const response = await client.users.retrieve();
  678. }
  679. main();
  680. """
  681. ),
  682. },
  683. {
  684. "lang": "CLI",
  685. "source": textwrap.dedent(
  686. """
  687. r2r users me
  688. """
  689. ),
  690. },
  691. {
  692. "lang": "Shell",
  693. "source": textwrap.dedent(
  694. """
  695. curl -X GET "https://api.example.com/users/me" \\
  696. -H "Authorization: Bearer YOUR_API_KEY"
  697. """
  698. ),
  699. },
  700. ]
  701. },
  702. )
  703. @self.base_endpoint
  704. async def get_current_user(
  705. auth_user=Depends(self.providers.auth.auth_wrapper),
  706. ) -> WrappedUserResponse:
  707. """
  708. Get detailed information about the currently authenticated user.
  709. """
  710. return auth_user
  711. @self.router.get(
  712. "/users/{id}",
  713. summary="Get User Details",
  714. openapi_extra={
  715. "x-codeSamples": [
  716. {
  717. "lang": "Python",
  718. "source": textwrap.dedent(
  719. """
  720. from r2r import R2RClient
  721. client = R2RClient("http://localhost:7272")
  722. # client.login(...)
  723. # Get user details
  724. users = client.users.retrieve(
  725. id="b4ac4dd6-5f27-596e-a55b-7cf242ca30aa"
  726. )
  727. """
  728. ),
  729. },
  730. {
  731. "lang": "JavaScript",
  732. "source": textwrap.dedent(
  733. """
  734. const { r2rClient } = require("r2r-js");
  735. const client = new r2rClient("http://localhost:7272");
  736. function main() {
  737. const response = await client.users.retrieve({
  738. id: "b4ac4dd6-5f27-596e-a55b-7cf242ca30aa"
  739. });
  740. }
  741. main();
  742. """
  743. ),
  744. },
  745. {
  746. "lang": "CLI",
  747. "source": textwrap.dedent(
  748. """
  749. r2r users retrieve b4ac4dd6-5f27-596e-a55b-7cf242ca30aa
  750. """
  751. ),
  752. },
  753. {
  754. "lang": "Shell",
  755. "source": textwrap.dedent(
  756. """
  757. curl -X GET "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000" \\
  758. -H "Authorization: Bearer YOUR_API_KEY"
  759. """
  760. ),
  761. },
  762. ]
  763. },
  764. )
  765. @self.base_endpoint
  766. async def get_user(
  767. id: UUID = Path(
  768. ..., example="550e8400-e29b-41d4-a716-446655440000"
  769. ),
  770. auth_user=Depends(self.providers.auth.auth_wrapper),
  771. ) -> WrappedUserResponse:
  772. """
  773. Get detailed information about a specific user.
  774. Users can only access their own information unless they are superusers.
  775. """
  776. if not auth_user.is_superuser and auth_user.id != id:
  777. raise R2RException(
  778. "Only a superuser can call the get `user` endpoint for other users.",
  779. 403,
  780. )
  781. users_overview_response = await self.services[
  782. "management"
  783. ].users_overview(
  784. offset=0,
  785. limit=1,
  786. user_ids=[id],
  787. )
  788. return users_overview_response["results"][0]
  789. @self.router.delete(
  790. "/users/{id}",
  791. summary="Delete User",
  792. openapi_extra={
  793. "x-codeSamples": [
  794. {
  795. "lang": "Python",
  796. "source": textwrap.dedent(
  797. """
  798. from r2r import R2RClient
  799. client = R2RClient("http://localhost:7272")
  800. # client.login(...)
  801. # Delete user
  802. client.users.delete(id="550e8400-e29b-41d4-a716-446655440000", password="secure_password123")
  803. """
  804. ),
  805. },
  806. {
  807. "lang": "JavaScript",
  808. "source": textwrap.dedent(
  809. """
  810. const { r2rClient } = require("r2r-js");
  811. const client = new r2rClient("http://localhost:7272");
  812. function main() {
  813. const response = await client.users.delete({
  814. id: "550e8400-e29b-41d4-a716-446655440000",
  815. password: "secure_password123"
  816. });
  817. }
  818. main();
  819. """
  820. ),
  821. },
  822. ]
  823. },
  824. )
  825. @self.base_endpoint
  826. async def delete_user(
  827. id: UUID = Path(
  828. ..., example="550e8400-e29b-41d4-a716-446655440000"
  829. ),
  830. password: Optional[str] = Body(
  831. None, description="User's current password"
  832. ),
  833. delete_vector_data: Optional[bool] = Body(
  834. False,
  835. description="Whether to delete the user's vector data",
  836. ),
  837. auth_user=Depends(self.providers.auth.auth_wrapper),
  838. ) -> WrappedBooleanResponse:
  839. """
  840. Delete a specific user.
  841. Users can only delete their own account unless they are superusers.
  842. """
  843. if not auth_user.is_superuser and auth_user.id != id:
  844. raise R2RException(
  845. "Only a superuser can delete other users.",
  846. 403,
  847. )
  848. await self.services["auth"].delete_user(
  849. user_id=id,
  850. password=password,
  851. delete_vector_data=delete_vector_data,
  852. is_superuser=auth_user.is_superuser,
  853. )
  854. return GenericBooleanResponse(success=True) # type: ignore
  855. @self.router.get(
  856. "/users/{id}/collections",
  857. summary="Get User Collections",
  858. openapi_extra={
  859. "x-codeSamples": [
  860. {
  861. "lang": "Python",
  862. "source": textwrap.dedent(
  863. """
  864. from r2r import R2RClient
  865. client = R2RClient("http://localhost:7272")
  866. # client.login(...)
  867. # Get user collections
  868. collections = client.user.list_collections(
  869. "550e8400-e29b-41d4-a716-446655440000",
  870. offset=0,
  871. limit=100
  872. )
  873. """
  874. ),
  875. },
  876. {
  877. "lang": "JavaScript",
  878. "source": textwrap.dedent(
  879. """
  880. const { r2rClient } = require("r2r-js");
  881. const client = new r2rClient("http://localhost:7272");
  882. function main() {
  883. const response = await client.users.listCollections({
  884. id: "550e8400-e29b-41d4-a716-446655440000",
  885. offset: 0,
  886. limit: 100
  887. });
  888. }
  889. main();
  890. """
  891. ),
  892. },
  893. {
  894. "lang": "CLI",
  895. "source": textwrap.dedent(
  896. """
  897. r2r users list-collections 550e8400-e29b-41d4-a716-446655440000
  898. """
  899. ),
  900. },
  901. {
  902. "lang": "Shell",
  903. "source": textwrap.dedent(
  904. """
  905. curl -X GET "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/collections?offset=0&limit=100" \\
  906. -H "Authorization: Bearer YOUR_API_KEY"
  907. """
  908. ),
  909. },
  910. ]
  911. },
  912. )
  913. @self.base_endpoint
  914. async def get_user_collections(
  915. id: UUID = Path(
  916. ..., example="550e8400-e29b-41d4-a716-446655440000"
  917. ),
  918. offset: int = Query(
  919. 0,
  920. ge=0,
  921. description="Specifies the number of objects to skip. Defaults to 0.",
  922. ),
  923. limit: int = Query(
  924. 100,
  925. ge=1,
  926. le=1000,
  927. description="Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.",
  928. ),
  929. auth_user=Depends(self.providers.auth.auth_wrapper),
  930. ) -> WrappedCollectionsResponse:
  931. """
  932. Get all collections associated with a specific user.
  933. Users can only access their own collections unless they are superusers.
  934. """
  935. if auth_user.id != id and not auth_user.is_superuser:
  936. raise R2RException(
  937. "The currently authenticated user does not have access to the specified collection.",
  938. 403,
  939. )
  940. user_collection_response = await self.services[
  941. "management"
  942. ].collections_overview(
  943. offset=offset,
  944. limit=limit,
  945. user_ids=[id],
  946. )
  947. return user_collection_response["results"], { # type: ignore
  948. "total_entries": user_collection_response["total_entries"]
  949. }
  950. @self.router.post(
  951. "/users/{id}/collections/{collection_id}",
  952. summary="Add User to Collection",
  953. response_model=WrappedBooleanResponse,
  954. openapi_extra={
  955. "x-codeSamples": [
  956. {
  957. "lang": "Python",
  958. "source": textwrap.dedent(
  959. """
  960. from r2r import R2RClient
  961. client = R2RClient("http://localhost:7272")
  962. # client.login(...)
  963. # Add user to collection
  964. client.users.add_to_collection(
  965. id="550e8400-e29b-41d4-a716-446655440000",
  966. collection_id="750e8400-e29b-41d4-a716-446655440000"
  967. )
  968. """
  969. ),
  970. },
  971. {
  972. "lang": "JavaScript",
  973. "source": textwrap.dedent(
  974. """
  975. const { r2rClient } = require("r2r-js");
  976. const client = new r2rClient("http://localhost:7272");
  977. function main() {
  978. const response = await client.users.addToCollection({
  979. id: "550e8400-e29b-41d4-a716-446655440000",
  980. collectionId: "750e8400-e29b-41d4-a716-446655440000"
  981. });
  982. }
  983. main();
  984. """
  985. ),
  986. },
  987. {
  988. "lang": "CLI",
  989. "source": textwrap.dedent(
  990. """
  991. r2r users add-to-collection 550e8400-e29b-41d4-a716-446655440000 750e8400-e29b-41d4-a716-446655440000
  992. """
  993. ),
  994. },
  995. {
  996. "lang": "Shell",
  997. "source": textwrap.dedent(
  998. """
  999. curl -X POST "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/collections/750e8400-e29b-41d4-a716-446655440000" \\
  1000. -H "Authorization: Bearer YOUR_API_KEY"
  1001. """
  1002. ),
  1003. },
  1004. ]
  1005. },
  1006. )
  1007. @self.base_endpoint
  1008. async def add_user_to_collection(
  1009. id: UUID = Path(
  1010. ..., example="550e8400-e29b-41d4-a716-446655440000"
  1011. ),
  1012. collection_id: UUID = Path(
  1013. ..., example="750e8400-e29b-41d4-a716-446655440000"
  1014. ),
  1015. auth_user=Depends(self.providers.auth.auth_wrapper),
  1016. ) -> WrappedBooleanResponse:
  1017. if auth_user.id != id and not auth_user.is_superuser:
  1018. raise R2RException(
  1019. "The currently authenticated user does not have access to the specified collection.",
  1020. 403,
  1021. )
  1022. # TODO - Do we need a check on user access to the collection?
  1023. await self.services["management"].add_user_to_collection( # type: ignore
  1024. id, collection_id
  1025. )
  1026. return GenericBooleanResponse(success=True) # type: ignore
  1027. @self.router.delete(
  1028. "/users/{id}/collections/{collection_id}",
  1029. summary="Remove User from Collection",
  1030. openapi_extra={
  1031. "x-codeSamples": [
  1032. {
  1033. "lang": "Python",
  1034. "source": textwrap.dedent(
  1035. """
  1036. from r2r import R2RClient
  1037. client = R2RClient("http://localhost:7272")
  1038. # client.login(...)
  1039. # Remove user from collection
  1040. client.users.remove_from_collection(
  1041. id="550e8400-e29b-41d4-a716-446655440000",
  1042. collection_id="750e8400-e29b-41d4-a716-446655440000"
  1043. )
  1044. """
  1045. ),
  1046. },
  1047. {
  1048. "lang": "JavaScript",
  1049. "source": textwrap.dedent(
  1050. """
  1051. const { r2rClient } = require("r2r-js");
  1052. const client = new r2rClient("http://localhost:7272");
  1053. function main() {
  1054. const response = await client.users.removeFromCollection({
  1055. id: "550e8400-e29b-41d4-a716-446655440000",
  1056. collectionId: "750e8400-e29b-41d4-a716-446655440000"
  1057. });
  1058. }
  1059. main();
  1060. """
  1061. ),
  1062. },
  1063. {
  1064. "lang": "CLI",
  1065. "source": textwrap.dedent(
  1066. """
  1067. r2r users remove-from-collection 550e8400-e29b-41d4-a716-446655440000 750e8400-e29b-41d4-a716-446655440000
  1068. """
  1069. ),
  1070. },
  1071. {
  1072. "lang": "Shell",
  1073. "source": textwrap.dedent(
  1074. """
  1075. curl -X DELETE "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/collections/750e8400-e29b-41d4-a716-446655440000" \\
  1076. -H "Authorization: Bearer YOUR_API_KEY"
  1077. """
  1078. ),
  1079. },
  1080. ]
  1081. },
  1082. )
  1083. @self.base_endpoint
  1084. async def remove_user_from_collection(
  1085. id: UUID = Path(
  1086. ..., example="550e8400-e29b-41d4-a716-446655440000"
  1087. ),
  1088. collection_id: UUID = Path(
  1089. ..., example="750e8400-e29b-41d4-a716-446655440000"
  1090. ),
  1091. auth_user=Depends(self.providers.auth.auth_wrapper),
  1092. ) -> WrappedBooleanResponse:
  1093. """
  1094. Remove a user from a collection.
  1095. Requires either superuser status or access to the collection.
  1096. """
  1097. if auth_user.id != id and not auth_user.is_superuser:
  1098. raise R2RException(
  1099. "The currently authenticated user does not have access to the specified collection.",
  1100. 403,
  1101. )
  1102. # TODO - Do we need a check on user access to the collection?
  1103. await self.services["management"].remove_user_from_collection( # type: ignore
  1104. id, collection_id
  1105. )
  1106. return GenericBooleanResponse(success=True) # type: ignore
  1107. @self.router.post(
  1108. "/users/{id}",
  1109. summary="Update User",
  1110. openapi_extra={
  1111. "x-codeSamples": [
  1112. {
  1113. "lang": "Python",
  1114. "source": textwrap.dedent(
  1115. """
  1116. from r2r import R2RClient
  1117. client = R2RClient("http://localhost:7272")
  1118. # client.login(...)
  1119. # Update user
  1120. updated_user = client.update_user(
  1121. "550e8400-e29b-41d4-a716-446655440000",
  1122. name="John Doe"
  1123. )
  1124. """
  1125. ),
  1126. },
  1127. {
  1128. "lang": "JavaScript",
  1129. "source": textwrap.dedent(
  1130. """
  1131. const { r2rClient } = require("r2r-js");
  1132. const client = new r2rClient("http://localhost:7272");
  1133. function main() {
  1134. const response = await client.users.update({
  1135. id: "550e8400-e29b-41d4-a716-446655440000",
  1136. name: "John Doe"
  1137. });
  1138. }
  1139. main();
  1140. """
  1141. ),
  1142. },
  1143. {
  1144. "lang": "Shell",
  1145. "source": textwrap.dedent(
  1146. """
  1147. curl -X POST "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000" \\
  1148. -H "Authorization: Bearer YOUR_API_KEY" \\
  1149. -H "Content-Type: application/json" \\
  1150. -d '{
  1151. "id": "550e8400-e29b-41d4-a716-446655440000",
  1152. "name": "John Doe",
  1153. }'
  1154. """
  1155. ),
  1156. },
  1157. ]
  1158. },
  1159. )
  1160. # TODO - Modify update user to have synced params with user object
  1161. @self.base_endpoint
  1162. async def update_user(
  1163. id: UUID = Path(..., description="ID of the user to update"),
  1164. email: EmailStr | None = Body(
  1165. None, description="Updated email address"
  1166. ),
  1167. is_superuser: bool | None = Body(
  1168. None, description="Updated superuser status"
  1169. ),
  1170. name: str | None = Body(None, description="Updated user name"),
  1171. bio: str | None = Body(None, description="Updated user bio"),
  1172. profile_picture: str | None = Body(
  1173. None, description="Updated profile picture URL"
  1174. ),
  1175. auth_user=Depends(self.providers.auth.auth_wrapper),
  1176. ) -> WrappedUserResponse:
  1177. """
  1178. Update user information.
  1179. Users can only update their own information unless they are superusers.
  1180. Superuser status can only be modified by existing superusers.
  1181. """
  1182. if is_superuser is not None and not auth_user.is_superuser:
  1183. raise R2RException(
  1184. "Only superusers can update the superuser status of a user",
  1185. 403,
  1186. )
  1187. if not auth_user.is_superuser and auth_user.id != id:
  1188. raise R2RException(
  1189. "Only superusers can update other users' information",
  1190. 403,
  1191. )
  1192. return await self.services["auth"].update_user(
  1193. user_id=id,
  1194. email=email,
  1195. is_superuser=is_superuser,
  1196. name=name,
  1197. bio=bio,
  1198. profile_picture=profile_picture,
  1199. )