Repository: sissbruecker/linkding
Pull request #993 (2025-02-19 05:39:24)

Return web archive fallback URL from REST API
Fixes https://github.com/sissbruecker/linkding/issues/885


Patch:
diff --git a/bookmarks/api/serializers.py b/bookmarks/api/serializers.py
index 153341e..124a55b 100644
--- a/bookmarks/api/serializers.py
+++ b/bookmarks/api/serializers.py
@@ -10,6 +10,7 @@ from bookmarks.services.bookmarks import (
     enhance_with_website_metadata,
 )
 from bookmarks.services.tags import get_or_create_tag
+from bookmarks.services.wayback import generate_fallback_webarchive_url
 
 
 class TagListField(serializers.ListField):
@@ -59,9 +60,10 @@ class BookmarkSerializer(serializers.ModelSerializer):
 
     # Custom tag_names field to allow passing a list of tag names to create/update
     tag_names = TagListField(required=False)
-    # Custom fields to return URLs for favicon and preview image
+    # Custom fields to generate URLs for favicon, preview image, and web archive snapshot
     favicon_url = serializers.SerializerMethodField()
     preview_image_url = serializers.SerializerMethodField()
+    web_archive_snapshot_url = serializers.SerializerMethodField()
     # Add dummy website title and description fields for backwards compatibility but keep them empty
     website_title = serializers.SerializerMethodField()
     website_description = serializers.SerializerMethodField()
@@ -82,6 +84,12 @@ class BookmarkSerializer(serializers.ModelSerializer):
         preview_image_url = request.build_absolute_uri(preview_image_file_path)
         return preview_image_url
 
+    def get_web_archive_snapshot_url(self, obj: Bookmark):
+        if obj.web_archive_snapshot_url:
+            return obj.web_archive_snapshot_url
+
+        return generate_fallback_webarchive_url(obj.url, obj.date_added)
+
     def get_website_title(self, obj: Bookmark):
         return None
 


Test patch:
diff --git a/bookmarks/tests/test_bookmarks_api.py b/bookmarks/tests/test_bookmarks_api.py
index 2166773..c53967f 100644
--- a/bookmarks/tests/test_bookmarks_api.py
+++ b/bookmarks/tests/test_bookmarks_api.py
@@ -1,15 +1,18 @@
+import datetime
 import urllib.parse
 from collections import OrderedDict
 from unittest.mock import patch
 
 from django.contrib.auth.models import User
 from django.urls import reverse
+from django.utils import timezone
 from rest_framework import status
 from rest_framework.authtoken.models import Token
 from rest_framework.response import Response
 
 from bookmarks.models import Bookmark, BookmarkSearch, UserProfile
 from bookmarks.services import website_loader
+from bookmarks.services.wayback import generate_fallback_webarchive_url
 from bookmarks.services.website_loader import WebsiteMetadata
 from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
 
@@ -33,7 +36,10 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
             expectation["title"] = bookmark.title
             expectation["description"] = bookmark.description
             expectation["notes"] = bookmark.notes
-            expectation["web_archive_snapshot_url"] = bookmark.web_archive_snapshot_url
+            expectation["web_archive_snapshot_url"] = (
+                bookmark.web_archive_snapshot_url
+                or generate_fallback_webarchive_url(bookmark.url, bookmark.date_added)
+            )
             expectation["favicon_url"] = (
                 f"http://testserver/static/{bookmark.favicon_file}"
                 if bookmark.favicon_file
@@ -590,6 +596,23 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
         response = self.get(url, expected_status_code=status.HTTP_200_OK)
         self.assertBookmarkListEqual([response.data], [bookmark])
 
+    def test_get_bookmark_returns_fallback_webarchive_url(self):
+        self.authenticate()
+        bookmark = self.setup_bookmark(
+            web_archive_snapshot_url="",
+            url="https://example.com/",
+            added=timezone.datetime(
+                2023, 8, 11, 21, 45, 11, tzinfo=datetime.timezone.utc
+            ),
+        )
+
+        url = reverse("bookmarks:bookmark-detail", args=[bookmark.id])
+        response = self.get(url, expected_status_code=status.HTTP_200_OK)
+        self.assertEqual(
+            response.data["web_archive_snapshot_url"],
+            "https://web.archive.org/web/20230811214511/https://example.com/",
+        )
+
     def test_update_bookmark(self):
         self.authenticate()
         bookmark = self.setup_bookmark()