New config item to hide followers/following
parent
567595bb4b
commit
b2f268682c
|
@ -102,6 +102,9 @@ class Config(pydantic.BaseModel):
|
|||
emoji: str | None = None
|
||||
also_known_as: str | None = None
|
||||
|
||||
hides_followers: bool = False
|
||||
hides_following: bool = False
|
||||
|
||||
inbox_retention_days: int = 15
|
||||
|
||||
# Config items to make tests easier
|
||||
|
@ -144,6 +147,8 @@ _SCHEME = "https" if CONFIG.https else "http"
|
|||
ID = f"{_SCHEME}://{DOMAIN}"
|
||||
USERNAME = CONFIG.username
|
||||
MANUALLY_APPROVES_FOLLOWERS = CONFIG.manually_approves_followers
|
||||
HIDES_FOLLOWERS = CONFIG.hides_followers
|
||||
HIDES_FOLLOWING = CONFIG.hides_following
|
||||
PRIVACY_REPLACE = None
|
||||
if CONFIG.privacy_replace:
|
||||
PRIVACY_REPLACE = {pr.domain: pr.replace_by for pr in CONFIG.privacy_replace}
|
||||
|
|
38
app/main.py
38
app/main.py
|
@ -403,6 +403,20 @@ async def _build_followx_collection(
|
|||
return collection_page
|
||||
|
||||
|
||||
async def _empty_followx_collection(
|
||||
db_session: AsyncSession,
|
||||
model_cls: Type[models.Following | models.Follower],
|
||||
path: str,
|
||||
) -> ap.RawObject:
|
||||
total_items = await db_session.scalar(select(func.count(model_cls.id)))
|
||||
return {
|
||||
"@context": ap.AS_CTX,
|
||||
"id": ID + path,
|
||||
"type": "OrderedCollection",
|
||||
"totalItems": total_items,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/followers")
|
||||
async def followers(
|
||||
request: Request,
|
||||
|
@ -413,6 +427,15 @@ async def followers(
|
|||
_: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker),
|
||||
) -> ActivityPubResponse | templates.TemplateResponse:
|
||||
if is_activitypub_requested(request):
|
||||
if config.HIDES_FOLLOWERS:
|
||||
return ActivityPubResponse(
|
||||
await _empty_followx_collection(
|
||||
db_session=db_session,
|
||||
model_cls=models.Follower,
|
||||
path="/followers",
|
||||
)
|
||||
)
|
||||
else:
|
||||
return ActivityPubResponse(
|
||||
await _build_followx_collection(
|
||||
db_session=db_session,
|
||||
|
@ -423,6 +446,9 @@ async def followers(
|
|||
)
|
||||
)
|
||||
|
||||
if config.HIDES_FOLLOWERS:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
# We only show the most recent 20 followers on the public website
|
||||
followers_result = await db_session.scalars(
|
||||
select(models.Follower)
|
||||
|
@ -460,6 +486,15 @@ async def following(
|
|||
_: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker),
|
||||
) -> ActivityPubResponse | templates.TemplateResponse:
|
||||
if is_activitypub_requested(request):
|
||||
if config.HIDES_FOLLOWING:
|
||||
return ActivityPubResponse(
|
||||
await _empty_followx_collection(
|
||||
db_session=db_session,
|
||||
model_cls=models.Following,
|
||||
path="/following",
|
||||
)
|
||||
)
|
||||
else:
|
||||
return ActivityPubResponse(
|
||||
await _build_followx_collection(
|
||||
db_session=db_session,
|
||||
|
@ -470,6 +505,9 @@ async def following(
|
|||
)
|
||||
)
|
||||
|
||||
if config.HIDES_FOLLOWING:
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
# We only show the most recent 20 follows on the public website
|
||||
following = (
|
||||
(
|
||||
|
|
|
@ -419,3 +419,5 @@ _templates.env.filters["privacy_replace_url"] = privacy_replace.replace_url
|
|||
_templates.env.globals["JS_HASH"] = config.JS_HASH
|
||||
_templates.env.globals["CSS_HASH"] = config.CSS_HASH
|
||||
_templates.env.globals["BASE_URL"] = config.BASE_URL
|
||||
_templates.env.globals["HIDES_FOLLOWERS"] = config.HIDES_FOLLOWERS
|
||||
_templates.env.globals["HIDES_FOLLOWING"] = config.HIDES_FOLLOWING
|
||||
|
|
|
@ -36,8 +36,12 @@
|
|||
{% if articles_count %}
|
||||
<li>{{ header_link("articles", "Articles") }}</li>
|
||||
{% endif %}
|
||||
{% if not HIDES_FOLLOWERS %}
|
||||
<li>{{ header_link("followers", "Followers") }} <span class="counter">{{ followers_count }}</span></li>
|
||||
{% endif %}
|
||||
{% if not HIDES_FOLLOWING %}
|
||||
<li>{{ header_link("following", "Following") }} <span class="counter">{{ following_count }}</span></li>
|
||||
{% endif %}
|
||||
<li>{{ header_link("get_remote_follow", "Remote follow") }}</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
|
@ -31,7 +33,19 @@ def test_followers__ap(client, db) -> None:
|
|||
response = client.get("/followers", headers={"Accept": ap.AP_CONTENT_TYPE})
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == ap.AP_CONTENT_TYPE
|
||||
assert response.json()["id"].endswith("/followers")
|
||||
json_resp = response.json()
|
||||
assert json_resp["id"].endswith("/followers")
|
||||
assert "first" in json_resp
|
||||
|
||||
|
||||
def test_followers__ap_hides_followers(client, db) -> None:
|
||||
with mock.patch("app.main.config.HIDES_FOLLOWERS", True):
|
||||
response = client.get("/followers", headers={"Accept": ap.AP_CONTENT_TYPE})
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == ap.AP_CONTENT_TYPE
|
||||
json_resp = response.json()
|
||||
assert json_resp["id"].endswith("/followers")
|
||||
assert "first" not in json_resp
|
||||
|
||||
|
||||
def test_followers__html(client, db) -> None:
|
||||
|
@ -40,14 +54,40 @@ def test_followers__html(client, db) -> None:
|
|||
assert response.headers["content-type"].startswith("text/html")
|
||||
|
||||
|
||||
def test_followers__html_hides_followers(client, db) -> None:
|
||||
with mock.patch("app.main.config.HIDES_FOLLOWERS", True):
|
||||
response = client.get("/followers", headers={"Accept": "text/html"})
|
||||
assert response.status_code == 404
|
||||
assert response.headers["content-type"].startswith("text/html")
|
||||
|
||||
|
||||
def test_following__ap(client, db) -> None:
|
||||
response = client.get("/following", headers={"Accept": ap.AP_CONTENT_TYPE})
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == ap.AP_CONTENT_TYPE
|
||||
assert response.json()["id"].endswith("/following")
|
||||
json_resp = response.json()
|
||||
assert json_resp["id"].endswith("/following")
|
||||
assert "first" in json_resp
|
||||
|
||||
|
||||
def test_following__ap_hides_following(client, db) -> None:
|
||||
with mock.patch("app.main.config.HIDES_FOLLOWING", True):
|
||||
response = client.get("/following", headers={"Accept": ap.AP_CONTENT_TYPE})
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == ap.AP_CONTENT_TYPE
|
||||
json_resp = response.json()
|
||||
assert json_resp["id"].endswith("/following")
|
||||
assert "first" not in json_resp
|
||||
|
||||
|
||||
def test_following__html(client, db) -> None:
|
||||
response = client.get("/following")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"].startswith("text/html")
|
||||
|
||||
|
||||
def test_following__html_hides_following(client, db) -> None:
|
||||
with mock.patch("app.main.config.HIDES_FOLLOWING", True):
|
||||
response = client.get("/following", headers={"Accept": "text/html"})
|
||||
assert response.status_code == 404
|
||||
assert response.headers["content-type"].startswith("text/html")
|
||||
|
|
Loading…
Reference in New Issue