diff --git a/ROADMAP.md b/ROADMAP.md index 7aa0033..b20d732 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,10 +15,12 @@ they land. - [x] Delete by J-ID with preview grid and bulk selection (plus per-copy deletion of duplicate locations) - [x] Temp folder cleanup -- [ ] **Library search upgrades** - - [ ] Search by tags (custom + e621 tags), not just filename - - [ ] Tag cloud from the library - - [ ] Status filter (matched / not found / deleted / unknown / custom) +- [x] **Library search upgrades** + - [x] Search by tags (custom + e621 tags) with a Filename / Tags / Both selector + - [x] Tag cloud in the sidebar (click to search, hidden from guests for + blacklisted items) + - [x] Status filter (matched / custom / unknown — not_found/deleted arrive with + the e621 match cache) ## 2. e621 integration diff --git a/backend/apps/library/serializers.py b/backend/apps/library/serializers.py index df6b193..82a8cb6 100644 --- a/backend/apps/library/serializers.py +++ b/backend/apps/library/serializers.py @@ -5,7 +5,12 @@ from django.core import signing from rest_framework import serializers from .models import MediaItem, MediaLocation, TempUpload, DownloadTask -from .services import MEDIA_FILE_SALT, UPLOAD_FILE_SALT, VIDEO_EXTENSIONS +from .services import ( + MEDIA_FILE_SALT, + UPLOAD_FILE_SALT, + VIDEO_EXTENSIONS, + signed_media_url, +) class MediaLocationSerializer(serializers.ModelSerializer): @@ -22,6 +27,8 @@ class MediaItemSerializer(serializers.ModelSerializer): locations = MediaLocationSerializer(many=True, read_only=True) j_id = serializers.SerializerMethodField() display_rating = serializers.SerializerMethodField() + raw_url = serializers.SerializerMethodField() + thumbnail_url = serializers.SerializerMethodField() filename = serializers.SerializerMethodField() extension = serializers.SerializerMethodField() kind = serializers.SerializerMethodField() @@ -42,6 +49,8 @@ class MediaItemSerializer(serializers.ModelSerializer): "filename", "extension", "kind", + "raw_url", + "thumbnail_url", "uploaded_by", "uploaded_by_id", "e621_post_id", @@ -59,6 +68,8 @@ class MediaItemSerializer(serializers.ModelSerializer): "filename", "extension", "kind", + "raw_url", + "thumbnail_url", "uploaded_by", "uploaded_by_id", "e621_post_id", @@ -68,9 +79,22 @@ class MediaItemSerializer(serializers.ModelSerializer): "updated_at", ] + def _request_user(self): + request = self.context.get("request") + user = getattr(request, "user", None) + if user is None or not getattr(user, "is_authenticated", False): + return None + return user + def get_j_id(self, obj): return f"J-{obj.id}" + def get_raw_url(self, obj): + return signed_media_url(obj, self._request_user(), "raw") + + def get_thumbnail_url(self, obj): + return signed_media_url(obj, self._request_user(), "thumbnail") + def get_display_rating(self, obj): if obj.rating: return obj.rating diff --git a/backend/apps/library/services.py b/backend/apps/library/services.py index 32250cf..d45b7b1 100644 --- a/backend/apps/library/services.py +++ b/backend/apps/library/services.py @@ -10,6 +10,7 @@ from pathlib import Path import imagehash from django.conf import settings +from django.core import signing from django.http import FileResponse, Http404, HttpResponse from django.utils.text import get_valid_filename from PIL import Image @@ -72,6 +73,18 @@ def index_file(path, folder): return item, created_item, location, created_location +def signed_media_url(item, user, action="raw"): + """Media URL that /