Feature: Multi-user request system (#615)

- Adds a comprehensive multi-user request system to the existing
download flow
- Request configuration is policy based. Configure global settings for
content type, or narrow down policy for specific sources (E.g. allow
direct downloads, set prowlarr to request only, block IRC completely,
etc).
- Global policy configuration and per-user overrides for tailored
configs
- Replaced downloads sidebar with ActivitySidebar, combining active
downloads with requests. Admin management of user requests is done here,
and admins have view of downloads from all users. Sidebar can now be
pinned.
- Request either a standard book or a specific release. Release-requests
are used if you permit one source differently than the other. On
book-level requests, admins pick the specific file to be attached to the
fulfilled request.
- Users can request books with a note

This is WIP so some features are still not complete (notifications, more
automatic release selection, among others).
This commit is contained in:
Alex
2026-02-14 11:08:20 +00:00
committed by GitHub
parent af9d9ec8db
commit 68608b6162
102 changed files with 14991 additions and 843 deletions
+114
View File
@@ -491,6 +491,59 @@ class TestAdminUserUpdateEndpoint:
settings = user_db.get_user_settings(user["id"])
assert settings["DESTINATION_AUDIOBOOK"] == "/audiobooks/alice"
def test_update_user_settings_accepts_valid_request_policy_rule(self, admin_client, user_db):
user = user_db.create_user(username="alice")
resp = admin_client.put(
f"/api/admin/users/{user['id']}",
json={
"settings": {
"REQUEST_POLICY_RULES": [
{
"source": "prowlarr",
"content_type": "audiobook",
"mode": "request_release",
}
]
}
},
)
assert resp.status_code == 200
settings = user_db.get_user_settings(user["id"])
assert settings["REQUEST_POLICY_RULES"] == [
{
"source": "prowlarr",
"content_type": "audiobook",
"mode": "request_release",
}
]
def test_update_user_settings_rejects_invalid_source_content_type_pair(self, admin_client, user_db):
user = user_db.create_user(username="alice")
resp = admin_client.put(
f"/api/admin/users/{user['id']}",
json={
"settings": {
"REQUEST_POLICY_RULES": [
{
"source": "direct_download",
"content_type": "audiobook",
"mode": "request_release",
}
]
}
},
)
assert resp.status_code == 400
assert resp.json["error"] == "Invalid settings payload"
assert any(
"does not support content_type 'audiobook'" in msg
for msg in resp.json["details"]
)
def test_update_settings_merges(self, admin_client, user_db):
user = user_db.create_user(username="alice")
user_db.set_user_settings(user["id"], {"DESTINATION": "/books/alice"})
@@ -515,6 +568,67 @@ class TestAdminUserUpdateEndpoint:
assert "settings" in resp.json
assert resp.json["settings"]["DESTINATION"] == "/books/alice"
def test_update_user_settings_null_clears_override(self, admin_client, user_db):
user = user_db.create_user(username="alice")
user_db.set_user_settings(user["id"], {"DESTINATION": "/books/alice"})
resp = admin_client.put(
f"/api/admin/users/{user['id']}",
json={"settings": {"DESTINATION": None}},
)
assert resp.status_code == 200
settings = user_db.get_user_settings(user["id"])
assert settings.get("DESTINATION") is None
def test_update_user_settings_null_policy_mode_accepted(self, admin_client, user_db):
user = user_db.create_user(username="alice")
user_db.set_user_settings(user["id"], {"REQUEST_POLICY_DEFAULT_EBOOK": "request_book"})
resp = admin_client.put(
f"/api/admin/users/{user['id']}",
json={"settings": {"REQUEST_POLICY_DEFAULT_EBOOK": None}},
)
assert resp.status_code == 200
settings = user_db.get_user_settings(user["id"])
assert settings.get("REQUEST_POLICY_DEFAULT_EBOOK") is None
def test_update_user_settings_null_policy_rules_accepted(self, admin_client, user_db):
user = user_db.create_user(username="alice")
user_db.set_user_settings(user["id"], {
"REQUEST_POLICY_RULES": [{"source": "prowlarr", "content_type": "audiobook", "mode": "request_release"}],
})
resp = admin_client.put(
f"/api/admin/users/{user['id']}",
json={"settings": {"REQUEST_POLICY_RULES": None}},
)
assert resp.status_code == 200
settings = user_db.get_user_settings(user["id"])
assert settings.get("REQUEST_POLICY_RULES") is None
def test_update_user_settings_mixed_null_and_values(self, admin_client, user_db):
user = user_db.create_user(username="alice")
user_db.set_user_settings(user["id"], {
"DESTINATION": "/books/alice",
"REQUEST_POLICY_DEFAULT_EBOOK": "request_book",
})
resp = admin_client.put(
f"/api/admin/users/{user['id']}",
json={"settings": {
"DESTINATION": None,
"BOOKLORE_LIBRARY_ID": "5",
"REQUEST_POLICY_DEFAULT_EBOOK": None,
"REQUEST_POLICY_DEFAULT_AUDIOBOOK": "download",
}},
)
assert resp.status_code == 200
settings = user_db.get_user_settings(user["id"])
assert settings.get("DESTINATION") is None
assert settings["BOOKLORE_LIBRARY_ID"] == "5"
assert settings.get("REQUEST_POLICY_DEFAULT_EBOOK") is None
assert settings["REQUEST_POLICY_DEFAULT_AUDIOBOOK"] == "download"
def test_update_user_settings_rejects_unknown_key(self, admin_client, user_db):
user = user_db.create_user(username="alice")