Add user profile endpoint (#541)

* feat: Implement UserProfile serializer and add API endpoint per #457

* chore: Document API addition

* Address review comments

---------

Co-authored-by: fkulla <mail@florian.direct>
This commit is contained in:
Sascha Ißbrücker 2023-10-01 21:57:32 +02:00 committed by GitHub
parent 41f79e35a0
commit 3e4f08f51b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 3 deletions

View file

@ -5,7 +5,7 @@ from rest_framework.response import Response
from rest_framework.routers import DefaultRouter
from bookmarks import queries
from bookmarks.api.serializers import BookmarkSerializer, TagSerializer
from bookmarks.api.serializers import BookmarkSerializer, TagSerializer, UserProfileSerializer
from bookmarks.models import Bookmark, BookmarkSearch, Tag, User
from bookmarks.services.bookmarks import archive_bookmark, unarchive_bookmark, website_loader
from bookmarks.services.website_loader import WebsiteMetadata
@ -108,6 +108,13 @@ class TagViewSet(viewsets.GenericViewSet,
return {'user': self.request.user}
class UserViewSet(viewsets.GenericViewSet):
@action(methods=['get'], detail=False)
def profile(self, request):
return Response(UserProfileSerializer(request.user.profile).data)
router = DefaultRouter()
router.register(r'bookmarks', BookmarkViewSet, basename='bookmark')
router.register(r'tags', TagViewSet, basename='tag')
router.register(r'user', UserViewSet, basename='user')

View file

@ -2,7 +2,7 @@ from django.db.models import prefetch_related_objects
from rest_framework import serializers
from rest_framework.serializers import ListSerializer
from bookmarks.models import Bookmark, Tag, build_tag_string
from bookmarks.models import Bookmark, Tag, build_tag_string, UserProfile
from bookmarks.services.bookmarks import create_bookmark, update_bookmark
from bookmarks.services.tags import get_or_create_tag
@ -89,3 +89,21 @@ class TagSerializer(serializers.ModelSerializer):
def create(self, validated_data):
return get_or_create_tag(validated_data['name'], self.context['user'])
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = [
"theme",
"bookmark_date_display",
"bookmark_link_target",
"web_archive_integration",
"tag_search",
"enable_sharing",
"enable_public_sharing",
"enable_favicons",
"display_url",
"permanent_notes",
"search_preferences",
]

View file

@ -6,8 +6,9 @@ from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework import status
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from bookmarks.models import Bookmark
from bookmarks.models import Bookmark, BookmarkSearch, UserProfile
from bookmarks.services import website_loader
from bookmarks.services.website_loader import WebsiteMetadata
from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
@ -644,3 +645,49 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
check_url = urllib.parse.quote_plus(inaccessible_bookmark.url)
response = self.get(f'{url}?url={check_url}', expected_status_code=status.HTTP_200_OK)
self.assertIsNone(response.data['bookmark'])
def assertUserProfile(self, response: Response, profile: UserProfile):
self.assertEqual(response.data['theme'], profile.theme)
self.assertEqual(response.data['bookmark_date_display'], profile.bookmark_date_display)
self.assertEqual(response.data['bookmark_link_target'], profile.bookmark_link_target)
self.assertEqual(response.data['web_archive_integration'], profile.web_archive_integration)
self.assertEqual(response.data['tag_search'], profile.tag_search)
self.assertEqual(response.data['enable_sharing'], profile.enable_sharing)
self.assertEqual(response.data['enable_public_sharing'], profile.enable_public_sharing)
self.assertEqual(response.data['enable_favicons'], profile.enable_favicons)
self.assertEqual(response.data['display_url'], profile.display_url)
self.assertEqual(response.data['permanent_notes'], profile.permanent_notes)
self.assertEqual(response.data['search_preferences'], profile.search_preferences)
def test_user_profile(self):
self.authenticate()
# default profile
profile = self.user.profile
url = reverse('bookmarks:user-profile')
response = self.get(url, expected_status_code=status.HTTP_200_OK)
self.assertUserProfile(response, profile)
# update profile
profile.theme = 'dark'
profile.bookmark_date_display = 'absolute'
profile.bookmark_link_target = '_self'
profile.web_archive_integration = 'enabled'
profile.tag_search = 'lax'
profile.enable_sharing = True
profile.enable_public_sharing = True
profile.enable_favicons = True
profile.display_url = True
profile.permanent_notes = True
profile.search_preferences = {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
profile.save()
url = reverse('bookmarks:user-profile')
response = self.get(url, expected_status_code=status.HTTP_200_OK)
self.assertUserProfile(response, profile)

View file

@ -111,3 +111,11 @@ class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
self.authenticate()
self.get(f'{url}?url={check_url}', expected_status_code=status.HTTP_200_OK)
def test_user_profile_requires_authentication(self):
url = reverse('bookmarks:user-profile')
self.get(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
self.authenticate()
self.get(url, expected_status_code=status.HTTP_200_OK)

View file

@ -236,3 +236,35 @@ Example payload:
"name": "example"
}
```
### User
**Profile**
```
GET /api/user/profile/
```
User preferences.
Example response:
```json
{
"theme": "auto",
"bookmark_date_display": "relative",
"bookmark_link_target": "_blank",
"web_archive_integration": "enabled",
"tag_search": "lax",
"enable_sharing": true,
"enable_public_sharing": true,
"enable_favicons": false,
"display_url": false,
"permanent_notes": false,
"search_preferences": {
"sort": "title_asc",
"shared": "off",
"unread": "off"
}
}
```