Skip to content

Commit

Permalink
packaging work, testing.
Browse files Browse the repository at this point in the history
  • Loading branch information
andreasofthings committed Aug 13, 2024
1 parent ad70bab commit 87ce6af
Show file tree
Hide file tree
Showing 18 changed files with 150 additions and 92 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@
## Fediverse Following

## Fediverse Followers

### Build the package
```
python3 -m build --wheel
```
27 changes: 0 additions & 27 deletions old-tox.ini

This file was deleted.

86 changes: 63 additions & 23 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build]
reproducible = false
reproducible = true

[tool.hatch.build.targets.wheel]
packages = ["webapp/"]


[project]
name = "pramari-webapp"
version = "1.1.23"
version = "1.1.24"
authors = [
{ name="Andreas Neumeier", email="[email protected]" },
]
Expand Down Expand Up @@ -44,52 +44,92 @@ classifiers = [
"Homepage" = "https://www.pramari.de"
"Bug Tracker" = "https://github.com/pramari/npc/issues"

# [tool.pytest.ini_options]
# addopts = "" #--cov --cov-report html --cov-report term-missing --cov-fail-under 95"

[tool.tox]
min_version = 4.0
legacy_tox_ini = """
[tox]
min_version = 4.0
isolated_build = True
env_list =
py312
type
[testenv]
setenv =
PYTHONPATH = ".";{toxinidir}:{toxinidir}
DJANGO_SETTINGS_MODULE=tests.settings
PYTHONPATH = ".";{toxinidir}:{toxinidir}
deps =
pytest
# behave
coverage
ruff
commands =
ruff check webapp
coverage run --source webapp tests/manage.py test --settings=tests.settings
coverage xml
# behave
# echo "success"
[testenv:lint]
deps = flake8
commands = flake8 webapp
[testenv:pep8]
show-source = True
commands = {envbindir}/flake8 --max-line-length=80 --exclude=.tox,docs,sample_app/tests/settings.py,sample_app/__init__.py,sample_app/migrations sample_app
# Flake8 only needed when linting.
# Do not care about other dependencies, it's just for linting.
deps = flake8
changedir = {toxinidir}
"""

[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "tests.settings"

python_files = ["test_*.py"]

[tool.coverage.run]
source = ["webapp"]

[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
]

# Same as Black.
line-length = 88
indent-width = 4

[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F"]
ignore = []

# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = false
docstring-code-line-length = "dynamic"

File renamed without changes.
2 changes: 1 addition & 1 deletion tests/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path
SITE_ID = 1
DEBUG = False
DEBUG = True
SECRET_KEY = "fake-key"
BASE_DIR = Path(__file__).resolve().parent.parent
ROOT_URLCONF = "tests.urls"
Expand Down
3 changes: 3 additions & 0 deletions webapp/activities/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .inbound import follow, accept, undo, create, delete

__all__ = ["follow", "accept", "undo", "create", "delete"]
53 changes: 31 additions & 22 deletions webapp/activities/inbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,21 @@ def wrapper(target, activity: ActivityObject, *args, **kwargs):
localactor = Actor.objects.get(id=activity.actor)
except Actor.DoesNotExist:
logger.error(f"Actor not found: '{activity.actor}'")
return JsonResponse({"error": "Actor f'{activity.actor}' not found"}, status=404)
return JsonResponse(
{"error": "Actor f'{activity.actor}' not found"}, status=404
)

try:
action_object = Actor.objects.get(id=activity.object)
except Actor.DoesNotExist:
logger.error(f"Object not found: '{activity.object}'")
logger.error(f"{activity.type}: Object not found: '{activity.object}'")
action_object = None

action.send(
sender=localactor, verb=activity.type, action_object=action_object, target=target
sender=localactor,
verb=activity.type,
action_object=action_object,
target=target,
) # noqa: E501

return f(target, activity, *args, **kwargs)
Expand Down Expand Up @@ -105,11 +110,11 @@ def accept(target: Actor, activity: ActivityObject) -> JsonResponse:
:param target: The target of the activity
:param activity: The :py:class:webapp.activity.Activityobject`
"""
from webapp.models.activitypub.actor import Fllwng
from webapp.models.activitypub.actor import Follow

fllwng = Fllwng.objects.get(actor=activity.actor)
fllwng.accepted = activity.id # remember the accept-id
fllwng.save()
follow = Follow.objects.get(actor=activity.actor)
follow.accepted = activity.id # remember the accept-id
follow.save()

return JsonResponse({"status": "accepted."})

Expand All @@ -127,6 +132,14 @@ def delete(target: Actor, activity: ActivityObject) -> JsonResponse:
def undo(target: Actor, activity: ActivityObject):
"""
Undo an activity.
Object: (example)
{
'id': 'https://23.social/b271295c-7a1b-4da8-ae58-927fea32bb60',
'type': 'Follow',
'actor': 'https://23.social/users/andreasofthings',
'object': 'https://pramari.de/@andreas'
}
"""
logger.error(f"Activity Object: {activity}")

Expand All @@ -137,22 +150,18 @@ def undo(target: Actor, activity: ActivityObject):
return JsonResponse({"status": "missing object"})

if (
not activity.actor
and activity.type.lower() != "follow" # noqa: E501
not activity.actor and activity.object.get('type').lower() != "follow" # noqa: E501
): # noqa: E501
return JsonResponse({"status": "invalid object"})
return JsonResponse({"status": "invalid object/unsupported activity"})

try:
followers = ( # noqa: F841, E501
Actor.objects.filter(id=activity.object)
.get()
.followers # noqa: E501
)
except Actor.DoesNotExist:
return JsonResponse({"status": "no followers"})

from webapp.models.activitypub.actor import Follow
try:
follow = Follow.objects.get(accepted=activity.object.get('id'))
except Follow.DoesNotExist:
return JsonResponse({"status": "follow not found"})
follow.delete()
logger.error(f"{activity.actor} has undone {activity.object}")
# followers.delete()

return JsonResponse({"status": "undone"})

Expand Down Expand Up @@ -186,7 +195,7 @@ def follow(target: Actor, activity: ActivityObject):
remoteActor = getRemoteActor(activity.actor)
remoteActorObject = Actor.objects.get(id=remoteActor.get("id"))
localActorObject = Actor.objects.get(id=activity.object)
remoteActorObject.follows.add(localActorObject)
remoteActorObject.follows.add(localActorObject)

# Step 2:
# Confirm the follow request to message.actor
Expand All @@ -203,9 +212,9 @@ def follow(target: Actor, activity: ActivityObject):
) # noqa: E501, BLK100

if settings.DEBUG:
acceptFollow(remoteActor.get('inbox'), activity, action_id[0][1].id)
acceptFollow(remoteActor.get("inbox"), activity, action_id[0][1].id)
else:
acceptFollow.delay(remoteActor.get('inbox'), activity, action_id[0][1].id)
acceptFollow.delay(remoteActor.get("inbox"), activity, action_id[0][1].id)

return JsonResponse(
{
Expand Down
1 change: 0 additions & 1 deletion webapp/activitypub/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from django.contrib import admin

# Register your models here.
1 change: 0 additions & 1 deletion webapp/activitypub/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from django.db import models

# Create your models here.
1 change: 0 additions & 1 deletion webapp/activitypub/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from django.test import TestCase

# Create your tests here.
1 change: 0 additions & 1 deletion webapp/activitypub/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from django.shortcuts import render

# Create your views here.
1 change: 1 addition & 0 deletions webapp/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ class ActorAdmin(admin.ModelAdmin):
"id",
"type",
"profile",
"remote"
)
inlines = [FollowInline]

Expand Down
14 changes: 12 additions & 2 deletions webapp/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def dispatch(self, *args, **kwargs):
)


class JsonLDMixin(object):
class RequireContentTypeMixinMixin(object):
"""
A mixin that returns a JSON-LD response if the request
specifies 'application/activity+json' in the Accept header.
Expand All @@ -32,11 +32,21 @@ class JsonLDMixin(object):
to_jsonld() is a method that should be implemented by the class.
:param content_type: The content type to check for in the Accept header. Defaults to 'application/activity+json'.
Example usage:
class
class SomeView(JsonLDMixin, View):
content_type = "application/activity+json"
def to_jsonld(self, request, *args, **kwargs):
return {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"content": "This is a note",
}
"""

content_type = "application/activity+json"

def to_jsonld(self, *args, **kwargs):
raise NotImplementedError()

Expand Down
5 changes: 4 additions & 1 deletion webapp/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,13 @@ def imgurl(self):
user=self.user, verified=True, primary=True
)
else:
"""
No Image for unverified users
"""
return "user/default.png" # noqa: E501

# construct the url
if self.gravatar is True
if self.gravatar:
hashvalue = hashlib.md5(
str(email).lower().encode("utf-8")
).hexdigest() # noqa: E501
Expand Down
11 changes: 8 additions & 3 deletions webapp/tasks/activitypub.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def requestFollow(localID: str, remoteID: str) -> bool:

localActor = Actor.objects.get(id=localID)
remoteActor = getRemoteActor(remoteID)
remoteActorObject = Actor.objects.get(id=remoteActor.get('id'))
remoteActorObject = Actor.objects.get(id=remoteActor.get("id"))

activity_id = action.send(
sender=localActor, verb="Follow", target=remoteActorObject
Expand All @@ -159,7 +159,6 @@ def requestFollow(localID: str, remoteID: str) -> bool:
print("Actor Following: ", localActor.follows)
print("Type: ", type(localActor.follows))


message = json.dumps(
{
"@context": "https://www.w3.org/ns/activitystreams",
Expand All @@ -172,7 +171,7 @@ def requestFollow(localID: str, remoteID: str) -> bool:
localActor.follows.add(remoteActorObject) # remember we follow this actor

signed = signedRequest( # noqa: F841,E501
"POST", remoteActor.get('inbox'), message, f"{localActor.id}#main-key"
"POST", remoteActor.get("inbox"), message, f"{localActor.id}#main-key"
) # noqa: F841,E501
return True

Expand Down Expand Up @@ -205,6 +204,12 @@ def acceptFollow(inbox: str, activity: ActivityObject, accept_id: str) -> bool:
logger.error(f"acceptFollow to {activity.actor}")
logger.error(f"with message: {message=}")

# remember we accepted this follow
from webapp.models.activitypub.actor import Follow
follow = Follow.objects.get(actor=activity.actor)
follow.accepted = accept_id
follow.save()

signed = signedRequest( # noqa: F841,E501
"POST",
inbox,
Expand Down
Loading

0 comments on commit 87ce6af

Please sign in to comment.