Repository: bookwyrm-social/bookwyrm

Issue:
deal with requests for /@username
### Problem
Mastodon puts public accounts at `example.org/@username`. People might expect bookwyrm to do the same.

### Solution
Redirect `/@mouse` to `/user/mouse`. With regards to code, this sounds pretty easy. Keep in mind that this uses up any URL path starting with "@".

### Other Stuff
Come to think of it, why _is_ the user profile at `/user/mouse`? The at-symbol ("@")  is rarely used in URL paths otherwise. It's a nifty way of shortening the URL while keeping other URL paths safe.


Discussion:
Is there any particular reason we need to follow the way Mastodon does things? There are many possibilities and many ActivityPub apps.

This is a little more nuanced than it may initially appear. The canonical url for a Mastodon user is `/users/{name}`, whilst `/@{name}` is nominated as a "profile" URL. Mastodon redirects from `/users/{name}` to `/@{name}`

You can check the options used by various ActivityPub applications using webfinger:

* [Mastodon](https://tech.lgbt/.well-known/webfinger?resource=acct%3Abookwyrm@tech.lgbt) - `/users/{name}` and `/@{name}`
* [Pixelfed](https://pixelfed.social/.well-known/webfinger?resource=acct%3Aquokka@pixelfed.social) - `/users/{name}` and `/{name}`
* [write.as](https://write.as/.well-known/webfinger?resource=acct%3Aa-pattern-person@write.as) - `/api/collections/{name}` and `/{name}` 
* [Bookwyrm](https://bookwyrm.social/.well-known/webfinger?resource=acct%3Ahugh@bookwyrm.social) `/user/{name}` (no specific profile URL nominated)

@hughrun I guess my writing was too condensed; I agree with everything you wrote. It would be silly to copy Mastodon's style, for the sake of it.

My main concern is that users may _expect_ `/@mouse` to work[^1]. In terms of UX, it's important that a user completes their goal (eg. ["Great Design is Invisible"](http://bokardo.com/principles-of-user-interface-design/#great-design-is-invisible)). Of course, you've got to make sure this doesn't interfere with some other aspect, but I don't see that happening here.

[^1]: Case in point, Mastodon, Pixelfed, and write.as each play it safe, and serve or redirect requests for `/@binyamin`.
Yep that's totally valid. 

I guess my concern is that every time other fediverse apps do something to match what Mastodon has chosen to do in the name of "reducing user friction", it reinforces the soft power of the core Mastodon devs in directing standards because of their sheer weight of users. They've made some good decisions (like remote following using the ostatus subscribe protocol) but also some dubious ones. 

I feel like `/user/{name}` is a pretty intuitive place for a user profile to be, but unless @mouse-reeve knows of some potential or existing problem it would cause, allowing for both patterns isn't intrinsically bad.
I think it would be super straightforward to add /@username paths, should just be a matter of adding another line to urls.py

Resolving patch:
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py
--- a/bookwyrm/urls.py
+++ b/bookwyrm/urls.py
@@ -276,6 +276,7 @@
     # users
     re_path(rf"{USER_PATH}\.json$", views.User.as_view()),
     re_path(rf"{USER_PATH}/?$", views.User.as_view(), name="user-feed"),
+    re_path(rf"^@(?P<username>{regex.USERNAME})$", views.user_redirect),
     re_path(rf"{USER_PATH}/rss/?$", views.rss_feed.RssFeed(), name="user-rss"),
     re_path(
         rf"{USER_PATH}/followers(.json)?/?$",
diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py
--- a/bookwyrm/views/__init__.py
+++ b/bookwyrm/views/__init__.py
@@ -94,7 +94,7 @@
 from .status import CreateStatus, EditStatus, DeleteStatus, update_progress
 from .status import edit_readthrough
 from .updates import get_notification_count, get_unread_status_count
-from .user import User, Followers, Following, hide_suggestions
+from .user import User, Followers, Following, hide_suggestions, user_redirect
 from .wellknown import *
 from .annual_summary import (
     AnnualSummary,
diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py
--- a/bookwyrm/views/user.py
+++ b/bookwyrm/views/user.py
@@ -151,3 +151,9 @@ def hide_suggestions(request):
     request.user.show_suggested_users = False
     request.user.save(broadcast=False, update_fields=["show_suggested_users"])
     return redirect(request.headers.get("Referer", "/"))
+
+
+# pylint: disable=unused-argument
+def user_redirect(request, username):
+    """redirect to a user's feed"""
+    return redirect("user-feed", username=username)


Test patch:
diff --git a/bookwyrm/tests/views/test_user.py b/bookwyrm/tests/views/test_user.py
--- a/bookwyrm/tests/views/test_user.py
+++ b/bookwyrm/tests/views/test_user.py
@@ -186,3 +186,11 @@ def test_hide_suggestions(self):
 
         self.local_user.refresh_from_db()
         self.assertFalse(self.local_user.show_suggested_users)
+
+    def test_user_redirect(self):
+        """test the basic redirect"""
+        request = self.factory.get("@mouse")
+        request.user = self.anonymous_user
+        result = views.user_redirect(request, "mouse")
+
+        self.assertEqual(result.status_code, 302)