1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543 |
- import textwrap
- from typing import Optional, Union
- from uuid import UUID
- from fastapi import Body, Depends, Path, Query
- from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
- from pydantic import EmailStr
- from core.base import R2RException
- from core.base.api.models import (
- GenericBooleanResponse,
- GenericMessageResponse,
- WrappedAPIKeyResponse,
- WrappedAPIKeysResponse,
- WrappedBooleanResponse,
- WrappedCollectionsResponse,
- WrappedGenericMessageResponse,
- WrappedTokenResponse,
- WrappedUserResponse,
- WrappedUsersResponse,
- )
- from core.base.providers.database import LimitSettings
- from ...abstractions import R2RProviders, R2RServices
- from .base_router import BaseRouterV3
- oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
- class UsersRouter(BaseRouterV3):
- def __init__(self, providers: R2RProviders, services: R2RServices):
- super().__init__(providers, services)
- def _setup_routes(self):
- @self.router.post(
- "/users",
- # dependencies=[Depends(self.rate_limit_dependency)],
- response_model=WrappedUserResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- new_user = client.users.create(
- email="jane.doe@example.com",
- password="secure_password123"
- )"""
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.create({
- email: "jane.doe@example.com",
- password: "secure_password123"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "CLI",
- "source": textwrap.dedent(
- """
- r2r users create jane.doe@example.com secure_password123
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users" \\
- -H "Content-Type: application/json" \\
- -d '{
- "email": "jane.doe@example.com",
- "password": "secure_password123"
- }'"""
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def register(
- email: EmailStr = Body(..., description="User's email address"),
- password: str = Body(..., description="User's password"),
- name: str | None = Body(
- None, description="The name for the new user"
- ),
- bio: str | None = Body(
- None, description="The bio for the new user"
- ),
- profile_picture: str | None = Body(
- None, description="Updated user profile picture"
- ),
- # auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedUserResponse:
- """Register a new user with the given email and password."""
- # TODO: Do we really want this validation? The default password for the superuser would not pass...
- def validate_password(password: str) -> bool:
- if len(password) < 10:
- return False
- if not any(c.isupper() for c in password):
- return False
- if not any(c.islower() for c in password):
- return False
- if not any(c.isdigit() for c in password):
- return False
- if not any(c in "!@#$%^&*" for c in password):
- return False
- return True
- validate_password(password)
- registration_response = await self.services.auth.register(
- email, password
- )
- if name or bio or profile_picture:
- return await self.services.auth.update_user(
- user_id=registration_response.id,
- name=name,
- bio=bio,
- profile_picture=profile_picture,
- )
- return registration_response
- # TODO: deprecated, remove in next release
- @self.router.post(
- "/users/register",
- # dependencies=[Depends(self.rate_limit_dependency)],
- response_model=WrappedUserResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- new_user = client.users.register(
- email="jane.doe@example.com",
- password="secure_password123"
- )"""
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.register({
- email: "jane.doe@example.com",
- password: "secure_password123"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "CLI",
- "source": textwrap.dedent(
- """
- r2r users register jane.doe@example.com secure_password123
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users/register" \\
- -H "Content-Type: application/json" \\
- -d '{
- "email": "jane.doe@example.com",
- "password": "secure_password123"
- }'"""
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def register(
- email: EmailStr = Body(..., description="User's email address"),
- password: str = Body(..., description="User's password"),
- ):
- """Register a new user with the given email and password."""
- return await self.services.auth.register(email, password)
- @self.router.post(
- "/users/verify-email",
- # dependencies=[Depends(self.rate_limit_dependency)],
- response_model=WrappedGenericMessageResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- tokens = client.users.verify_email(
- email="jane.doe@example.com",
- verification_code="1lklwal!awdclm"
- )"""
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.verifyEmail({
- email: jane.doe@example.com",
- verificationCode: "1lklwal!awdclm"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users/login" \\
- -H "Content-Type: application/x-www-form-urlencoded" \\
- -d "email=jane.doe@example.com&verification_code=1lklwal!awdclm"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def verify_email(
- email: EmailStr = Body(..., description="User's email address"),
- verification_code: str = Body(
- ..., description="Email verification code"
- ),
- ) -> WrappedGenericMessageResponse:
- """Verify a user's email address."""
- user = (
- await self.providers.database.users_handler.get_user_by_email(
- email
- )
- )
- if user and user.is_verified:
- raise R2RException(
- status_code=400,
- message="This email is already verified. Please log in.",
- )
- result = await self.services.auth.verify_email(
- email, verification_code
- )
- return GenericMessageResponse(message=result["message"]) # type: ignore
- @self.router.post(
- "/users/login",
- # dependencies=[Depends(self.rate_limit_dependency)],
- response_model=WrappedTokenResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- tokens = client.users.login(
- email="jane.doe@example.com",
- password="secure_password123"
- )
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.login({
- email: jane.doe@example.com",
- password: "secure_password123"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users/login" \\
- -H "Content-Type: application/x-www-form-urlencoded" \\
- -d "username=jane.doe@example.com&password=secure_password123"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def login(form_data: OAuth2PasswordRequestForm = Depends()):
- """Authenticate a user and provide access tokens."""
- return await self.services.auth.login(
- form_data.username, form_data.password
- )
- @self.router.post(
- "/users/logout",
- response_model=WrappedGenericMessageResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- result = client.users.logout()
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.logout();
- }
- main();
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users/logout" \\
- -H "Authorization: Bearer YOUR_API_KEY"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def logout(
- token: str = Depends(oauth2_scheme),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedGenericMessageResponse:
- """Log out the current user."""
- result = await self.services.auth.logout(token)
- return GenericMessageResponse(message=result["message"]) # type: ignore
- @self.router.post(
- "/users/refresh-token",
- dependencies=[Depends(self.rate_limit_dependency)],
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- new_tokens = client.users.refresh_token()
- # New tokens are automatically stored in the client"""
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.refreshAccessToken();
- }
- main();
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users/refresh-token" \\
- -H "Content-Type: application/json" \\
- -d '{
- "refresh_token": "YOUR_REFRESH_TOKEN"
- }'"""
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def refresh_token(
- refresh_token: str = Body(..., description="Refresh token")
- ) -> WrappedTokenResponse:
- """Refresh the access token using a refresh token."""
- result = await self.services.auth.refresh_access_token(
- refresh_token=refresh_token
- )
- return result
- @self.router.post(
- "/users/change-password",
- dependencies=[Depends(self.rate_limit_dependency)],
- response_model=WrappedGenericMessageResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- result = client.users.change_password(
- current_password="old_password123",
- new_password="new_secure_password456"
- )"""
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.changePassword({
- currentPassword: "old_password123",
- newPassword: "new_secure_password456"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users/change-password" \\
- -H "Authorization: Bearer YOUR_API_KEY" \\
- -H "Content-Type: application/json" \\
- -d '{
- "current_password": "old_password123",
- "new_password": "new_secure_password456"
- }'"""
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def change_password(
- current_password: str = Body(..., description="Current password"),
- new_password: str = Body(..., description="New password"),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> GenericMessageResponse:
- """Change the authenticated user's password."""
- result = await self.services.auth.change_password(
- auth_user, current_password, new_password
- )
- return GenericMessageResponse(message=result["message"]) # type: ignore
- @self.router.post(
- "/users/request-password-reset",
- dependencies=[
- Depends(self.providers.auth.auth_wrapper(public=True))
- ],
- response_model=WrappedGenericMessageResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- result = client.users.request_password_reset(
- email="jane.doe@example.com"
- )"""
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.requestPasswordReset({
- email: jane.doe@example.com",
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users/request-password-reset" \\
- -H "Content-Type: application/json" \\
- -d '{
- "email": "jane.doe@example.com"
- }'"""
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def request_password_reset(
- email: EmailStr = Body(..., description="User's email address"),
- ) -> WrappedGenericMessageResponse:
- """Request a password reset for a user."""
- result = await self.services.auth.request_password_reset(email)
- return GenericMessageResponse(message=result["message"]) # type: ignore
- @self.router.post(
- "/users/reset-password",
- dependencies=[Depends(self.rate_limit_dependency)],
- response_model=WrappedGenericMessageResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- result = client.users.reset_password(
- reset_token="reset_token_received_via_email",
- new_password="new_secure_password789"
- )"""
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.resetPassword({
- resestToken: "reset_token_received_via_email",
- newPassword: "new_secure_password789"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/v3/users/reset-password" \\
- -H "Content-Type: application/json" \\
- -d '{
- "reset_token": "reset_token_received_via_email",
- "new_password": "new_secure_password789"
- }'"""
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def reset_password(
- reset_token: str = Body(..., description="Password reset token"),
- new_password: str = Body(..., description="New password"),
- ) -> WrappedGenericMessageResponse:
- """Reset a user's password using a reset token."""
- result = await self.services.auth.confirm_password_reset(
- reset_token, new_password
- )
- return GenericMessageResponse(message=result["message"]) # type: ignore
- @self.router.get(
- "/users",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="List Users",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- # List users with filters
- users = client.users.list(
- offset=0,
- limit=100,
- )
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.list();
- }
- main();
- """
- ),
- },
- {
- "lang": "CLI",
- "source": textwrap.dedent(
- """
- r2r users list
- """
- ),
- },
- {
- "lang": "Shell",
- "source": textwrap.dedent(
- """
- curl -X GET "https://api.example.com/users?offset=0&limit=100&username=john&email=john@example.com&is_active=true&is_superuser=false" \\
- -H "Authorization: Bearer YOUR_API_KEY"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def list_users(
- # TODO - Implement the following parameters
- # offset: int = Query(0, ge=0, example=0),
- # limit: int = Query(100, ge=1, le=1000, example=100),
- # username: Optional[str] = Query(None, example="john"),
- # email: Optional[str] = Query(None, example="john@example.com"),
- # is_active: Optional[bool] = Query(None, example=True),
- # is_superuser: Optional[bool] = Query(None, example=False),
- # auth_user=Depends(self.providers.auth.auth_wrapper()),
- ids: list[str] = Query(
- [], description="List of user IDs to filter by"
- ),
- offset: int = Query(
- 0,
- ge=0,
- description="Specifies the number of objects to skip. Defaults to 0.",
- ),
- limit: int = Query(
- 100,
- ge=1,
- le=1000,
- description="Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.",
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedUsersResponse:
- """
- List all users with pagination and filtering options.
- Only accessible by superusers.
- """
- if not auth_user.is_superuser:
- raise R2RException(
- "Only a superuser can call the `users_overview` endpoint.",
- 403,
- )
- user_uuids = [UUID(user_id) for user_id in ids]
- users_overview_response = (
- await self.services.management.users_overview(
- user_ids=user_uuids, offset=offset, limit=limit
- )
- )
- return users_overview_response["results"], { # type: ignore
- "total_entries": users_overview_response["total_entries"]
- }
- @self.router.get(
- "/users/me",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Get the Current User",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- # Get user details
- users = client.users.me()
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.retrieve();
- }
- main();
- """
- ),
- },
- {
- "lang": "CLI",
- "source": textwrap.dedent(
- """
- r2r users me
- """
- ),
- },
- {
- "lang": "Shell",
- "source": textwrap.dedent(
- """
- curl -X GET "https://api.example.com/users/me" \\
- -H "Authorization: Bearer YOUR_API_KEY"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def get_current_user(
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedUserResponse:
- """
- Get detailed information about the currently authenticated user.
- """
- return auth_user
- @self.router.get(
- "/users/{id}",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Get User Details",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- # Get user details
- users = client.users.retrieve(
- id="b4ac4dd6-5f27-596e-a55b-7cf242ca30aa"
- )
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.retrieve({
- id: "b4ac4dd6-5f27-596e-a55b-7cf242ca30aa"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "CLI",
- "source": textwrap.dedent(
- """
- r2r users retrieve b4ac4dd6-5f27-596e-a55b-7cf242ca30aa
- """
- ),
- },
- {
- "lang": "Shell",
- "source": textwrap.dedent(
- """
- curl -X GET "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000" \\
- -H "Authorization: Bearer YOUR_API_KEY"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def get_user(
- id: UUID = Path(
- ..., example="550e8400-e29b-41d4-a716-446655440000"
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedUserResponse:
- """
- Get detailed information about a specific user.
- Users can only access their own information unless they are superusers.
- """
- if not auth_user.is_superuser and auth_user.id != id:
- raise R2RException(
- "Only a superuser can call the get `user` endpoint for other users.",
- 403,
- )
- users_overview_response = (
- await self.services.management.users_overview(
- offset=0,
- limit=1,
- user_ids=[id],
- )
- )
- return users_overview_response["results"][0]
- @self.router.delete(
- "/users/{id}",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Delete User",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- # Delete user
- client.users.delete(id="550e8400-e29b-41d4-a716-446655440000", password="secure_password123")
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.delete({
- id: "550e8400-e29b-41d4-a716-446655440000",
- password: "secure_password123"
- });
- }
- main();
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def delete_user(
- id: UUID = Path(
- ..., example="550e8400-e29b-41d4-a716-446655440000"
- ),
- password: Optional[str] = Body(
- None, description="User's current password"
- ),
- delete_vector_data: Optional[bool] = Body(
- False,
- description="Whether to delete the user's vector data",
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedBooleanResponse:
- """
- Delete a specific user.
- Users can only delete their own account unless they are superusers.
- """
- if not auth_user.is_superuser and auth_user.id != id:
- raise R2RException(
- "Only a superuser can delete other users.",
- 403,
- )
- await self.services.auth.delete_user(
- user_id=id,
- password=password,
- delete_vector_data=delete_vector_data,
- is_superuser=auth_user.is_superuser,
- )
- return GenericBooleanResponse(success=True) # type: ignore
- @self.router.get(
- "/users/{id}/collections",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Get User Collections",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- # Get user collections
- collections = client.user.list_collections(
- "550e8400-e29b-41d4-a716-446655440000",
- offset=0,
- limit=100
- )
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.listCollections({
- id: "550e8400-e29b-41d4-a716-446655440000",
- offset: 0,
- limit: 100
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "CLI",
- "source": textwrap.dedent(
- """
- r2r users list-collections 550e8400-e29b-41d4-a716-446655440000
- """
- ),
- },
- {
- "lang": "Shell",
- "source": textwrap.dedent(
- """
- curl -X GET "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/collections?offset=0&limit=100" \\
- -H "Authorization: Bearer YOUR_API_KEY"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def get_user_collections(
- id: UUID = Path(
- ..., example="550e8400-e29b-41d4-a716-446655440000"
- ),
- offset: int = Query(
- 0,
- ge=0,
- description="Specifies the number of objects to skip. Defaults to 0.",
- ),
- limit: int = Query(
- 100,
- ge=1,
- le=1000,
- description="Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.",
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedCollectionsResponse:
- """
- Get all collections associated with a specific user.
- Users can only access their own collections unless they are superusers.
- """
- if auth_user.id != id and not auth_user.is_superuser:
- raise R2RException(
- "The currently authenticated user does not have access to the specified collection.",
- 403,
- )
- user_collection_response = (
- await self.services.management.collections_overview(
- offset=offset,
- limit=limit,
- user_ids=[id],
- )
- )
- return user_collection_response["results"], { # type: ignore
- "total_entries": user_collection_response["total_entries"]
- }
- @self.router.post(
- "/users/{id}/collections/{collection_id}",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Add User to Collection",
- response_model=WrappedBooleanResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- # Add user to collection
- client.users.add_to_collection(
- id="550e8400-e29b-41d4-a716-446655440000",
- collection_id="750e8400-e29b-41d4-a716-446655440000"
- )
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.addToCollection({
- id: "550e8400-e29b-41d4-a716-446655440000",
- collectionId: "750e8400-e29b-41d4-a716-446655440000"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "CLI",
- "source": textwrap.dedent(
- """
- r2r users add-to-collection 550e8400-e29b-41d4-a716-446655440000 750e8400-e29b-41d4-a716-446655440000
- """
- ),
- },
- {
- "lang": "Shell",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/collections/750e8400-e29b-41d4-a716-446655440000" \\
- -H "Authorization: Bearer YOUR_API_KEY"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def add_user_to_collection(
- id: UUID = Path(
- ..., example="550e8400-e29b-41d4-a716-446655440000"
- ),
- collection_id: UUID = Path(
- ..., example="750e8400-e29b-41d4-a716-446655440000"
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedBooleanResponse:
- if auth_user.id != id and not auth_user.is_superuser:
- raise R2RException(
- "The currently authenticated user does not have access to the specified collection.",
- 403,
- )
- # TODO - Do we need a check on user access to the collection?
- await self.services.management.add_user_to_collection( # type: ignore
- id, collection_id
- )
- return GenericBooleanResponse(success=True) # type: ignore
- @self.router.delete(
- "/users/{id}/collections/{collection_id}",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Remove User from Collection",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- # Remove user from collection
- client.users.remove_from_collection(
- id="550e8400-e29b-41d4-a716-446655440000",
- collection_id="750e8400-e29b-41d4-a716-446655440000"
- )
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.removeFromCollection({
- id: "550e8400-e29b-41d4-a716-446655440000",
- collectionId: "750e8400-e29b-41d4-a716-446655440000"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "CLI",
- "source": textwrap.dedent(
- """
- r2r users remove-from-collection 550e8400-e29b-41d4-a716-446655440000 750e8400-e29b-41d4-a716-446655440000
- """
- ),
- },
- {
- "lang": "Shell",
- "source": textwrap.dedent(
- """
- curl -X DELETE "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/collections/750e8400-e29b-41d4-a716-446655440000" \\
- -H "Authorization: Bearer YOUR_API_KEY"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def remove_user_from_collection(
- id: UUID = Path(
- ..., example="550e8400-e29b-41d4-a716-446655440000"
- ),
- collection_id: UUID = Path(
- ..., example="750e8400-e29b-41d4-a716-446655440000"
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedBooleanResponse:
- """
- Remove a user from a collection.
- Requires either superuser status or access to the collection.
- """
- if auth_user.id != id and not auth_user.is_superuser:
- raise R2RException(
- "The currently authenticated user does not have access to the specified collection.",
- 403,
- )
- # TODO - Do we need a check on user access to the collection?
- await self.services.management.remove_user_from_collection( # type: ignore
- id, collection_id
- )
- return GenericBooleanResponse(success=True) # type: ignore
- @self.router.post(
- "/users/{id}",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Update User",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- # Update user
- updated_user = client.update_user(
- "550e8400-e29b-41d4-a716-446655440000",
- name="John Doe"
- )
- """
- ),
- },
- {
- "lang": "JavaScript",
- "source": textwrap.dedent(
- """
- const { r2rClient } = require("r2r-js");
- const client = new r2rClient();
- function main() {
- const response = await client.users.update({
- id: "550e8400-e29b-41d4-a716-446655440000",
- name: "John Doe"
- });
- }
- main();
- """
- ),
- },
- {
- "lang": "Shell",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000" \\
- -H "Authorization: Bearer YOUR_API_KEY" \\
- -H "Content-Type: application/json" \\
- -d '{
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "name": "John Doe",
- }'
- """
- ),
- },
- ]
- },
- )
- # TODO - Modify update user to have synced params with user object
- @self.base_endpoint
- async def update_user(
- id: UUID = Path(..., description="ID of the user to update"),
- email: EmailStr | None = Body(
- None, description="Updated email address"
- ),
- is_superuser: bool | None = Body(
- None, description="Updated superuser status"
- ),
- name: str | None = Body(None, description="Updated user name"),
- bio: str | None = Body(None, description="Updated user bio"),
- profile_picture: str | None = Body(
- None, description="Updated profile picture URL"
- ),
- limits_overrides: dict = Body(
- None,
- description="Updated limits overrides",
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedUserResponse:
- """
- Update user information.
- Users can only update their own information unless they are superusers.
- Superuser status can only be modified by existing superusers.
- """
- if is_superuser is not None and not auth_user.is_superuser:
- raise R2RException(
- "Only superusers can update the superuser status of a user",
- 403,
- )
- if not auth_user.is_superuser and auth_user.id != id:
- raise R2RException(
- "Only superusers can update other users' information",
- 403,
- )
- if not auth_user.is_superuser and limits_overrides is not None:
- raise R2RException(
- "Only superusers can update other users' limits overrides",
- 403,
- )
- return await self.services.auth.update_user(
- user_id=id,
- email=email,
- is_superuser=is_superuser,
- name=name,
- bio=bio,
- profile_picture=profile_picture,
- limits_overrides=limits_overrides,
- )
- @self.router.post(
- "/users/{id}/api-keys",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Create User API Key",
- response_model=WrappedAPIKeyResponse,
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- result = client.users.create_api_key(
- id="550e8400-e29b-41d4-a716-446655440000",
- )
- # result["api_key"] contains the newly created API key
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X POST "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/api-keys" \\
- -H "Authorization: Bearer YOUR_API_TOKEN"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def create_user_api_key(
- id: UUID = Path(
- ..., description="ID of the user for whom to create an API key"
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedAPIKeyResponse:
- """
- Create a new API key for the specified user.
- Only superusers or the user themselves may create an API key.
- """
- if auth_user.id != id and not auth_user.is_superuser:
- raise R2RException(
- "Only the user themselves or a superuser can create API keys for this user.",
- 403,
- )
- api_key = await self.services.auth.create_user_api_key(id)
- return api_key # type: ignore
- @self.router.get(
- "/users/{id}/api-keys",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="List User API Keys",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- client = R2RClient()
- # client.login(...)
- keys = client.users.list_api_keys(
- id="550e8400-e29b-41d4-a716-446655440000"
- )
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X GET "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/api-keys" \\
- -H "Authorization: Bearer YOUR_API_TOKEN"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def list_user_api_keys(
- id: UUID = Path(
- ..., description="ID of the user whose API keys to list"
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedAPIKeysResponse:
- """
- List all API keys for the specified user.
- Only superusers or the user themselves may list the API keys.
- """
- if auth_user.id != id and not auth_user.is_superuser:
- raise R2RException(
- "Only the user themselves or a superuser can list API keys for this user.",
- 403,
- )
- keys = (
- await self.providers.database.users_handler.get_user_api_keys(
- id
- )
- )
- return keys, {"total_entries": len(keys)} # type: ignore
- @self.router.delete(
- "/users/{id}/api-keys/{key_id}",
- dependencies=[Depends(self.rate_limit_dependency)],
- summary="Delete User API Key",
- openapi_extra={
- "x-codeSamples": [
- {
- "lang": "Python",
- "source": textwrap.dedent(
- """
- from r2r import R2RClient
- from uuid import UUID
- client = R2RClient()
- # client.login(...)
- response = client.users.delete_api_key(
- id="550e8400-e29b-41d4-a716-446655440000",
- key_id="d9c562d4-3aef-43e8-8f08-0cf7cd5e0a25"
- )
- """
- ),
- },
- {
- "lang": "cURL",
- "source": textwrap.dedent(
- """
- curl -X DELETE "https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000/api-keys/d9c562d4-3aef-43e8-8f08-0cf7cd5e0a25" \\
- -H "Authorization: Bearer YOUR_API_TOKEN"
- """
- ),
- },
- ]
- },
- )
- @self.base_endpoint
- async def delete_user_api_key(
- id: UUID = Path(..., description="ID of the user"),
- key_id: UUID = Path(
- ..., description="ID of the API key to delete"
- ),
- auth_user=Depends(self.providers.auth.auth_wrapper()),
- ) -> WrappedBooleanResponse:
- """
- Delete a specific API key for the specified user.
- Only superusers or the user themselves may delete the API key.
- """
- if auth_user.id != id and not auth_user.is_superuser:
- raise R2RException(
- "Only the user themselves or a superuser can delete this API key.",
- 403,
- )
- success = (
- await self.providers.database.users_handler.delete_api_key(
- id, key_id
- )
- )
- if not success:
- raise R2RException(
- "API key not found or could not be deleted", 400
- )
- return {"success": True} # type: ignore
|