Skip to content

Commit

Permalink
Merged @JD557 upkeeping and improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
nigini committed Jul 23, 2024
2 parents 6364785 + bbdb66f commit 6e6ad36
Show file tree
Hide file tree
Showing 89 changed files with 3,032 additions and 1,721 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[flake8]
max-line-length = 88
max-line-length = 120
extend-ignore = E203
exclude = alembic/versions
31 changes: 31 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Unit Tests
on: push

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Check out repository code
uses: actions/checkout@v2

- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: "3.11"

- name: Setup poetry
uses: Gr1N/setup-poetry@v8

- name: Install dependencies
run: |
poetry install --no-root --no-interaction
- name: Run lint
run: |
poetry run inv lint
- name: Run test suite
run: |
poetry run inv tests
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.db
*.log
__pycache__/
.mypy_cache/
.pytest_cache/
Expand Down
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ Kevin Wallace <[email protected]>
Miguel Jacq <[email protected]>
Alexey Shpakovsky <[email protected]>
Josh Washburne <[email protected]>
João Costa <[email protected]>
Sam <[email protected]>
Ash McAllan <[email protected]>
Cassio Zen <[email protected]>
Cocoa <[email protected]>
Jane <[email protected]>
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.10-slim as python-base
FROM python:3.11-slim as python-base
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
POETRY_HOME="/opt/poetry" \
Expand All @@ -14,7 +14,7 @@ RUN apt-get install -y --no-install-recommends curl build-essential gcc libffi-d
# rustc is needed to compile Python packages
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
RUN curl -sSL https://install.python-poetry.org | python3 -
RUN curl -sSL https://install.python-poetry.org | python3 -
WORKDIR $PYSETUP_PATH
COPY poetry.lock pyproject.toml ./
RUN poetry install --only main
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ move-to:

.PHONY: self-destruct
self-destruct:
-docker run --rm --volume `pwd`/data:/app/data --volume `pwd`/app/static:/app/app/static microblogpub/microblogpub inv self-destruct
-docker run --rm --it --volume `pwd`/data:/app/data --volume `pwd`/app/static:/app/app/static microblogpub/microblogpub inv self-destruct

.PHONY: reset-password
reset-password:
Expand All @@ -41,3 +41,7 @@ check-config:
.PHONY: compile-scss
compile-scss:
-docker run --rm --volume `pwd`/data:/app/data --volume `pwd`/app/static:/app/app/static microblogpub/microblogpub inv compile-scss

.PHONY: import-mastodon-following-accounts
import-mastodon-following-accounts:
-docker run --rm --volume `pwd`/data:/app/data --volume `pwd`/app/static:/app/app/static microblogpub/microblogpub inv import-mastodon-following-accounts $(path)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Add option to hide announces from actor
Revision ID: 9b404c47970a
Revises: fadfd359ce78
Create Date: 2022-12-12 19:26:36.912763+00:00
"""
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision = '9b404c47970a'
down_revision = 'fadfd359ce78'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('actor', schema=None) as batch_op:
batch_op.add_column(sa.Column('are_announces_hidden_from_stream', sa.Boolean(), server_default='0', nullable=False))

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('actor', schema=None) as batch_op:
batch_op.drop_column('are_announces_hidden_from_stream')

# ### end Alembic commands ###
48 changes: 48 additions & 0 deletions alembic/versions/2022_12_16_1730-4ab54becec04_add_oauth_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Add OAuth client
Revision ID: 4ab54becec04
Revises: 9b404c47970a
Create Date: 2022-12-16 17:30:54.520477+00:00
"""
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision = '4ab54becec04'
down_revision = '9b404c47970a'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('oauth_client',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('client_name', sa.String(), nullable=False),
sa.Column('redirect_uris', sa.JSON(), nullable=True),
sa.Column('client_uri', sa.String(), nullable=True),
sa.Column('logo_uri', sa.String(), nullable=True),
sa.Column('scope', sa.String(), nullable=True),
sa.Column('client_id', sa.String(), nullable=False),
sa.Column('client_secret', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('client_secret')
)
with op.batch_alter_table('oauth_client', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_oauth_client_client_id'), ['client_id'], unique=True)
batch_op.create_index(batch_op.f('ix_oauth_client_id'), ['id'], unique=False)

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oauth_client', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_oauth_client_id'))
batch_op.drop_index(batch_op.f('ix_oauth_client_client_id'))

op.drop_table('oauth_client')
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Add OAuth refresh token support
Revision ID: a209f0333f5a
Revises: 4ab54becec04
Create Date: 2022-12-18 11:26:31.976348+00:00
"""
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision = 'a209f0333f5a'
down_revision = '4ab54becec04'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('indieauth_access_token', schema=None) as batch_op:
batch_op.add_column(sa.Column('refresh_token', sa.String(), nullable=True))
batch_op.add_column(sa.Column('was_refreshed', sa.Boolean(), server_default='0', nullable=False))
batch_op.create_index(batch_op.f('ix_indieauth_access_token_refresh_token'), ['refresh_token'], unique=True)

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('indieauth_access_token', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_indieauth_access_token_refresh_token'))
batch_op.drop_column('was_refreshed')
batch_op.drop_column('refresh_token')

# ### end Alembic commands ###
78 changes: 69 additions & 9 deletions app/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
from typing import Union
from urllib.parse import urlparse

import httpx
from loguru import logger
from sqlalchemy import select
from sqlalchemy.orm import joinedload

from app import activitypub as ap
from app import media
from app.config import BASE_URL
from app.config import USER_AGENT
from app.config import USERNAME
from app.config import WEBFINGER_DOMAIN
from app.database import AsyncSession
from app.utils.datetime import as_utc
from app.utils.datetime import now
Expand All @@ -27,7 +31,38 @@ def _handle(raw_actor: ap.RawObject) -> str:
if not domain.hostname:
raise ValueError(f"Invalid actor ID {ap_id}")

return f'@{raw_actor["preferredUsername"]}@{domain.hostname}' # type: ignore
handle = f'@{raw_actor["preferredUsername"]}@{domain.hostname}' # type: ignore

# TODO: cleanup this
# Next, check for custom webfinger domains
resp: httpx.Response | None = None
for url in {
f"https://{domain.hostname}/.well-known/webfinger",
f"http://{domain.hostname}/.well-known/webfinger",
}:
try:
logger.info(f"Webfinger {handle} at {url}")
resp = httpx.get(
url,
params={"resource": f"acct:{handle[1:]}"},
headers={
"User-Agent": USER_AGENT,
},
follow_redirects=True,
)
resp.raise_for_status()
break
except Exception:
logger.exception(f"Failed to webfinger {handle}")

if resp:
try:
json_resp = resp.json()
if json_resp.get("subject", "").startswith("acct:"):
return "@" + json_resp["subject"].removeprefix("acct:")
except Exception:
logger.exception(f"Failed to parse webfinger response for {handle}")
return handle


class Actor:
Expand Down Expand Up @@ -61,7 +96,7 @@ def display_name(self) -> str:
return self.name
return self.preferred_username

@property
@cached_property
def handle(self) -> str:
return _handle(self.ap_actor)

Expand Down Expand Up @@ -143,13 +178,18 @@ def server(self) -> str:


class RemoteActor(Actor):
def __init__(self, ap_actor: ap.RawObject) -> None:
def __init__(self, ap_actor: ap.RawObject, handle: str | None = None) -> None:
if (ap_type := ap_actor.get("type")) not in ap.ACTOR_TYPES:
raise ValueError(f"Unexpected actor type: {ap_type}")

self._ap_actor = ap_actor
self._ap_type = ap_type

if handle is None:
handle = _handle(ap_actor)

self._handle = handle

@property
def ap_actor(self) -> ap.RawObject:
return self._ap_actor
Expand All @@ -162,8 +202,12 @@ def ap_type(self) -> str:
def is_from_db(self) -> bool:
return False

@property
def handle(self) -> str:
return self._handle

LOCAL_ACTOR = RemoteActor(ap_actor=ap.ME)

LOCAL_ACTOR = RemoteActor(ap_actor=ap.ME, handle=f"@{USERNAME}@{WEBFINGER_DOMAIN}")


async def save_actor(db_session: AsyncSession, ap_actor: ap.RawObject) -> "ActorModel":
Expand Down Expand Up @@ -248,6 +292,22 @@ async def fetch_actor(
raise ap.ObjectNotFoundError(actor_id)


async def list_actors(db_session: AsyncSession, limit: int = 100) -> list["ActorModel"]:
from app import models

return (
(
await db_session.scalars(
select(models.Actor)
.where(models.Actor.is_deleted.is_(False))
.limit(limit)
)
)
.unique()
.all()
)


async def update_actor_if_needed(
db_session: AsyncSession,
actor_in_db: "ActorModel",
Expand Down Expand Up @@ -358,11 +418,11 @@ async def get_actors_metadata(
is_following=actor.ap_id in following,
is_follower=actor.ap_id in followers,
is_follow_request_sent=actor.ap_id in sent_follow_requests,
is_follow_request_rejected=bool(
sent_follow_requests[actor.ap_id] in rejected_follow_requests
)
if actor.ap_id in sent_follow_requests
else False,
is_follow_request_rejected=(
bool(sent_follow_requests[actor.ap_id] in rejected_follow_requests)
if actor.ap_id in sent_follow_requests
else False
),
outbox_follow_ap_id=sent_follow_requests.get(actor.ap_id),
inbox_follow_ap_id=followers.get(actor.ap_id),
moved_to=moved_to,
Expand Down
Loading

0 comments on commit 6e6ad36

Please sign in to comment.