search endpoint (user part)

This commit is contained in:
Mouse Reeve 2020-01-27 19:57:17 -08:00
parent d2d278b475
commit f7242452fa
6 changed files with 44 additions and 17 deletions

View file

@ -19,11 +19,11 @@ def webfinger(request):
if not resource and not resource.startswith('acct:'):
return HttpResponseBadRequest()
ap_id = resource.replace('acct:', '')
user = models.User.objects.filter(full_username=ap_id).first()
user = models.User.objects.filter(username=ap_id).first()
if not user:
return HttpResponseNotFound('No account found')
return JsonResponse({
'subject': 'acct:%s' % (user.full_username),
'subject': 'acct:%s' % (user.username),
'links': [
{
'rel': 'self',
@ -88,6 +88,22 @@ def inbox(request, username):
return HttpResponse()
def handle_account_search(query):
''' webfingerin' other servers '''
domain = query.split('@')[1]
try:
user = models.User.objects.get(username=query)
except models.User.DoesNotExist:
url = 'https://%s/.well-known/webfinger' % domain
params = {'resource': 'acct:%s' % query}
response = requests.get(url, params=params)
data = response.json()
for link in data['links']:
if link['rel'] == 'self':
user = get_or_create_remote_user(link['href'])
return user
def handle_add(activity):
''' receiving an Add activity (to shelve a book) '''
# TODO what happens here? If it's a remote over, then I think
@ -156,7 +172,7 @@ def handle_outgoing_follow(user, to_follow):
'summary': '',
'type': 'Follow',
'actor': user.actor,
'object': to_follow,
'object': to_follow.actor,
}
broadcast(user, activity, [format_inbox(to_follow)])
@ -281,6 +297,7 @@ def broadcast(sender, action, recipients):
for recipient in recipients:
sign_and_send(sender, action, recipient)
def sign_and_send(sender, action, destination):
''' crpyto whatever and http junk '''
inbox_fragment = '/api/u/%s/inbox' % (sender.username)
@ -291,7 +308,7 @@ date: %s''' % (inbox_fragment, DOMAIN, now)
signer = pkcs1_15.new(RSA.import_key(sender.private_key))
signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8')))
signature = 'keyId="%s",' % sender.full_username
signature = 'keyId="%s",' % sender.username
signature += 'headers="(request-target) host date",'
signature += 'signature="%s"' % b64encode(signed_message)
response = requests.post(
@ -306,9 +323,9 @@ date: %s''' % (inbox_fragment, DOMAIN, now)
if not response.ok:
response.raise_for_status()
def get_or_create_remote_user(activity):
def get_or_create_remote_user(actor):
''' wow, a foreigner '''
actor = activity['actor']
try:
user = models.User.objects.get(actor=actor)
except models.User.DoesNotExist:

View file

@ -1,4 +1,4 @@
# Generated by Django 3.0.2 on 2020-01-28 02:46
# Generated by Django 3.0.2 on 2020-01-28 03:40
from django.conf import settings
import django.contrib.auth.models
@ -32,7 +32,6 @@ class Migration(migrations.Migration):
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('full_username', models.CharField(blank=True, max_length=255, null=True, unique=True)),
('private_key', models.TextField(blank=True, null=True)),
('public_key', models.TextField(blank=True, null=True)),
('api_key', models.CharField(blank=True, max_length=255, null=True)),

View file

@ -10,12 +10,6 @@ import re
class User(AbstractUser):
''' a user who wants to read books '''
full_username = models.CharField(
max_length=255,
blank=True,
null=True,
unique=True
)
private_key = models.TextField(blank=True, null=True)
public_key = models.TextField(blank=True, null=True)
api_key = models.CharField(max_length=255, blank=True, null=True)
@ -36,8 +30,8 @@ class User(AbstractUser):
if self.local and not self.actor:
self.actor = 'https://%s/api/u/%s' % (DOMAIN, self.username)
if self.local and not self.full_username:
self.full_username = '%s@%s' % (self.username, DOMAIN)
if self.local and not re.match(r'\w+@\w+.\w+', self.username):
self.username = '%s@%s' % (self.username, DOMAIN)
super().save(*args, **kwargs)

View file

@ -38,7 +38,7 @@
{% endif %}
</div>
<div id="search">
<form action="search">
<form action="/search/">
<input type="text" name="q"></input>
<input type="submit" value="🔍"></input>
</form>

View file

@ -24,10 +24,13 @@ urlpatterns = [
path('logout/', views.user_logout),
path('user/<str:username>', views.user_profile),
path('book/<str:book_identifier>', views.book_page),
path('review/', views.review),
path('shelve/<str:shelf_id>/<int:book_id>', views.shelve),
path('follow/', views.follow),
path('unfollow/', views.unfollow),
path('search/', views.search),
path('api/u/<str:username>', federation.get_actor),
path('api/u/<str:username>/inbox', federation.inbox),
path('api/u/<str:username>/outbox', federation.outbox),

View file

@ -7,6 +7,7 @@ from django.template.response import TemplateResponse
from django.views.decorators.csrf import csrf_exempt
from fedireads import models, openlibrary
from fedireads import federation as api
import re
@login_required
def home(request):
@ -133,3 +134,16 @@ def unfollow(request):
followed.followers.remove(request.user)
return redirect('/user/%s' % followed.username)
@csrf_exempt
@login_required
def search(request):
''' that search bar up top '''
query = request.GET.get('q')
if re.match(r'\w+@\w+.\w+', query):
results = [api.handle_account_search(query)]
else:
results = []
return TemplateResponse(request, 'results.html', {'results': results})