Switch to aiosqlite

This commit is contained in:
Thomas Sileo 2022-06-29 20:43:17 +02:00
parent 18bd2cb664
commit 1f54a6a6ac
21 changed files with 698 additions and 549 deletions

View file

@ -4,11 +4,11 @@ from typing import Union
from urllib.parse import urlparse
from sqlalchemy import select
from sqlalchemy.orm import Session
from sqlalchemy.orm import joinedload
from app import activitypub as ap
from app import media
from app.database import AsyncSession
if typing.TYPE_CHECKING:
from app.models import Actor as ActorModel
@ -131,7 +131,7 @@ class RemoteActor(Actor):
LOCAL_ACTOR = RemoteActor(ap_actor=ap.ME)
def save_actor(db: Session, ap_actor: ap.RawObject) -> "ActorModel":
async def save_actor(db_session: AsyncSession, ap_actor: ap.RawObject) -> "ActorModel":
from app import models
if ap_type := ap_actor.get("type") not in ap.ACTOR_TYPES:
@ -143,23 +143,25 @@ def save_actor(db: Session, ap_actor: ap.RawObject) -> "ActorModel":
ap_type=ap_actor["type"],
handle=_handle(ap_actor),
)
db.add(actor)
db.commit()
db.refresh(actor)
db_session.add(actor)
await db_session.commit()
await db_session.refresh(actor)
return actor
def fetch_actor(db: Session, actor_id: str) -> "ActorModel":
async def fetch_actor(db_session: AsyncSession, actor_id: str) -> "ActorModel":
from app import models
existing_actor = db.execute(
select(models.Actor).where(models.Actor.ap_id == actor_id)
).scalar_one_or_none()
existing_actor = (
await db_session.scalars(
select(models.Actor).where(models.Actor.ap_id == actor_id)
)
).one_or_none()
if existing_actor:
return existing_actor
ap_actor = ap.get(actor_id)
return save_actor(db, ap_actor)
return await save_actor(db_session, ap_actor)
@dataclass
@ -175,8 +177,8 @@ class ActorMetadata:
ActorsMetadata = dict[str, ActorMetadata]
def get_actors_metadata(
db: Session,
async def get_actors_metadata(
db_session: AsyncSession,
actors: list[Union["ActorModel", "RemoteActor"]],
) -> ActorsMetadata:
from app import models
@ -184,17 +186,19 @@ def get_actors_metadata(
ap_actor_ids = [actor.ap_id for actor in actors]
followers = {
follower.ap_actor_id: follower.inbox_object.ap_id
for follower in db.scalars(
select(models.Follower)
.where(models.Follower.ap_actor_id.in_(ap_actor_ids))
.options(joinedload(models.Follower.inbox_object))
for follower in (
await db_session.scalars(
select(models.Follower)
.where(models.Follower.ap_actor_id.in_(ap_actor_ids))
.options(joinedload(models.Follower.inbox_object))
)
)
.unique()
.all()
}
following = {
following.ap_actor_id
for following in db.execute(
for following in await db_session.execute(
select(models.Following.ap_actor_id).where(
models.Following.ap_actor_id.in_(ap_actor_ids)
)
@ -202,7 +206,7 @@ def get_actors_metadata(
}
sent_follow_requests = {
follow_req.ap_object["object"]: follow_req.ap_id
for follow_req in db.execute(
for follow_req in await db_session.execute(
select(models.OutboxObject.ap_object, models.OutboxObject.ap_id).where(
models.OutboxObject.ap_type == "Follow",
models.OutboxObject.undone_by_outbox_object_id.is_(None),

View file

@ -8,7 +8,6 @@ from fastapi.exceptions import HTTPException
from fastapi.responses import RedirectResponse
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy.orm import Session
from sqlalchemy.orm import joinedload
from app import activitypub as ap
@ -25,7 +24,8 @@ from app.config import generate_csrf_token
from app.config import session_serializer
from app.config import verify_csrf_token
from app.config import verify_password
from app.database import get_db
from app.database import AsyncSession
from app.database import get_db_session
from app.lookup import lookup
from app.uploads import save_upload
from app.utils import pagination
@ -62,30 +62,36 @@ unauthenticated_router = APIRouter()
@router.get("/")
def admin_index(
async def admin_index(
request: Request,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> templates.TemplateResponse:
return templates.render_template(db, request, "index.html", {"request": request})
return await templates.render_template(
db_session, request, "index.html", {"request": request}
)
@router.get("/lookup")
def get_lookup(
async def get_lookup(
request: Request,
query: str | None = None,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> templates.TemplateResponse:
ap_object = None
actors_metadata = {}
if query:
ap_object = lookup(db, query)
ap_object = await lookup(db_session, query)
if ap_object.ap_type in ap.ACTOR_TYPES:
actors_metadata = get_actors_metadata(db, [ap_object]) # type: ignore
actors_metadata = await get_actors_metadata(
db_session, [ap_object] # type: ignore
)
else:
actors_metadata = get_actors_metadata(db, [ap_object.actor]) # type: ignore
actors_metadata = await get_actors_metadata(
db_session, [ap_object.actor] # type: ignore
)
print(ap_object)
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"lookup.html",
{
@ -97,16 +103,18 @@ def get_lookup(
@router.get("/new")
def admin_new(
async def admin_new(
request: Request,
query: str | None = None,
in_reply_to: str | None = None,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> templates.TemplateResponse:
content = ""
in_reply_to_object = None
if in_reply_to:
in_reply_to_object = boxes.get_anybox_object_by_ap_id(db, in_reply_to)
in_reply_to_object = await boxes.get_anybox_object_by_ap_id(
db_session, in_reply_to
)
# Add mentions to the initial note content
if not in_reply_to_object:
@ -117,8 +125,8 @@ def admin_new(
if tag.get("type") == "Mention" and tag["name"] != LOCAL_ACTOR.handle:
content += f'{tag["name"]} '
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"admin_new.html",
{
@ -138,28 +146,30 @@ def admin_new(
@router.get("/bookmarks")
def admin_bookmarks(
async def admin_bookmarks(
request: Request,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> templates.TemplateResponse:
stream = (
db.scalars(
select(models.InboxObject)
.where(
models.InboxObject.ap_type.in_(
["Note", "Article", "Video", "Announce"]
),
models.InboxObject.is_hidden_from_stream.is_(False),
models.InboxObject.undone_by_inbox_object_id.is_(None),
models.InboxObject.is_bookmarked.is_(True),
(
await db_session.scalars(
select(models.InboxObject)
.where(
models.InboxObject.ap_type.in_(
["Note", "Article", "Video", "Announce"]
),
models.InboxObject.is_hidden_from_stream.is_(False),
models.InboxObject.undone_by_inbox_object_id.is_(None),
models.InboxObject.is_bookmarked.is_(True),
)
.order_by(models.InboxObject.ap_published_at.desc())
.limit(20)
)
.order_by(models.InboxObject.ap_published_at.desc())
.limit(20)
).all()
# TODO: joinedload + unique
)
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"admin_stream.html",
{
@ -169,9 +179,9 @@ def admin_bookmarks(
@router.get("/inbox")
def admin_inbox(
async def admin_inbox(
request: Request,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
filter_by: str | None = None,
cursor: str | None = None,
) -> templates.TemplateResponse:
@ -184,17 +194,22 @@ def admin_inbox(
)
page_size = 20
remaining_count = db.scalar(select(func.count(models.InboxObject.id)).where(*where))
remaining_count = await db_session.scalar(
select(func.count(models.InboxObject.id)).where(*where)
)
q = select(models.InboxObject).where(*where)
inbox = (
db.scalars(
q.options(
joinedload(models.InboxObject.relates_to_inbox_object),
joinedload(models.InboxObject.relates_to_outbox_object),
(
await db_session.scalars(
q.options(
joinedload(models.InboxObject.relates_to_inbox_object),
joinedload(models.InboxObject.relates_to_outbox_object),
joinedload(models.InboxObject.actor),
)
.order_by(models.InboxObject.ap_published_at.desc())
.limit(20)
)
.order_by(models.InboxObject.ap_published_at.desc())
.limit(20)
)
.unique()
.all()
@ -206,8 +221,8 @@ def admin_inbox(
else None
)
actors_metadata = get_actors_metadata(
db,
actors_metadata = await get_actors_metadata(
db_session,
[
inbox_object.actor
for inbox_object in inbox
@ -215,8 +230,8 @@ def admin_inbox(
],
)
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"admin_inbox.html",
{
@ -228,9 +243,9 @@ def admin_inbox(
@router.get("/outbox")
def admin_outbox(
async def admin_outbox(
request: Request,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
filter_by: str | None = None,
cursor: str | None = None,
) -> templates.TemplateResponse:
@ -243,20 +258,22 @@ def admin_outbox(
)
page_size = 20
remaining_count = db.scalar(
remaining_count = await db_session.scalar(
select(func.count(models.OutboxObject.id)).where(*where)
)
q = select(models.OutboxObject).where(*where)
outbox = (
db.scalars(
q.options(
joinedload(models.OutboxObject.relates_to_inbox_object),
joinedload(models.OutboxObject.relates_to_outbox_object),
joinedload(models.OutboxObject.relates_to_actor),
(
await db_session.scalars(
q.options(
joinedload(models.OutboxObject.relates_to_inbox_object),
joinedload(models.OutboxObject.relates_to_outbox_object),
joinedload(models.OutboxObject.relates_to_actor),
)
.order_by(models.OutboxObject.ap_published_at.desc())
.limit(page_size)
)
.order_by(models.OutboxObject.ap_published_at.desc())
.limit(page_size)
)
.unique()
.all()
@ -268,8 +285,8 @@ def admin_outbox(
else None
)
actors_metadata = get_actors_metadata(
db,
actors_metadata = await get_actors_metadata(
db_session,
[
outbox_object.relates_to_actor
for outbox_object in outbox
@ -277,8 +294,8 @@ def admin_outbox(
],
)
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"admin_outbox.html",
{
@ -290,32 +307,34 @@ def admin_outbox(
@router.get("/notifications")
def get_notifications(
request: Request, db: Session = Depends(get_db)
async def get_notifications(
request: Request, db_session: AsyncSession = Depends(get_db_session)
) -> templates.TemplateResponse:
notifications = (
db.scalars(
select(models.Notification)
.options(
joinedload(models.Notification.actor),
joinedload(models.Notification.inbox_object),
joinedload(models.Notification.outbox_object),
(
await db_session.scalars(
select(models.Notification)
.options(
joinedload(models.Notification.actor),
joinedload(models.Notification.inbox_object),
joinedload(models.Notification.outbox_object),
)
.order_by(models.Notification.created_at.desc())
)
.order_by(models.Notification.created_at.desc())
)
.unique()
.all()
)
actors_metadata = get_actors_metadata(
db, [notif.actor for notif in notifications if notif.actor]
actors_metadata = await get_actors_metadata(
db_session, [notif.actor for notif in notifications if notif.actor]
)
for notif in notifications:
notif.is_new = False
db.commit()
await db_session.commit()
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"notifications.html",
{
@ -326,19 +345,19 @@ def get_notifications(
@router.get("/object")
def admin_object(
async def admin_object(
request: Request,
ap_id: str,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> templates.TemplateResponse:
requested_object = boxes.get_anybox_object_by_ap_id(db, ap_id)
requested_object = await boxes.get_anybox_object_by_ap_id(db_session, ap_id)
if not requested_object:
raise HTTPException(status_code=404)
replies_tree = boxes.get_replies_tree(db, requested_object)
replies_tree = await boxes.get_replies_tree(db_session, requested_object)
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"object.html",
{"replies_tree": replies_tree},
@ -346,30 +365,34 @@ def admin_object(
@router.get("/profile")
def admin_profile(
async def admin_profile(
request: Request,
actor_id: str,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> templates.TemplateResponse:
actor = db.execute(
select(models.Actor).where(models.Actor.ap_id == actor_id)
actor = (
await db_session.execute(
select(models.Actor).where(models.Actor.ap_id == actor_id)
)
).scalar_one_or_none()
if not actor:
raise HTTPException(status_code=404)
actors_metadata = get_actors_metadata(db, [actor])
actors_metadata = await get_actors_metadata(db_session, [actor])
inbox_objects = db.scalars(
select(models.InboxObject)
.where(
models.InboxObject.actor_id == actor.id,
models.InboxObject.ap_type.in_(["Note", "Article", "Video"]),
inbox_objects = (
await db_session.scalars(
select(models.InboxObject)
.where(
models.InboxObject.actor_id == actor.id,
models.InboxObject.ap_type.in_(["Note", "Article", "Video"]),
)
.order_by(models.InboxObject.ap_published_at.desc())
)
.order_by(models.InboxObject.ap_published_at.desc())
).all()
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"admin_profile.html",
{
@ -381,120 +404,120 @@ def admin_profile(
@router.post("/actions/follow")
def admin_actions_follow(
async def admin_actions_follow(
request: Request,
ap_actor_id: str = Form(),
redirect_url: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
print(f"Following {ap_actor_id}")
send_follow(db, ap_actor_id)
await send_follow(db_session, ap_actor_id)
return RedirectResponse(redirect_url, status_code=302)
@router.post("/actions/like")
def admin_actions_like(
async def admin_actions_like(
request: Request,
ap_object_id: str = Form(),
redirect_url: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
boxes.send_like(db, ap_object_id)
await boxes.send_like(db_session, ap_object_id)
return RedirectResponse(redirect_url, status_code=302)
@router.post("/actions/undo")
def admin_actions_undo(
async def admin_actions_undo(
request: Request,
ap_object_id: str = Form(),
redirect_url: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
boxes.send_undo(db, ap_object_id)
await boxes.send_undo(db_session, ap_object_id)
return RedirectResponse(redirect_url, status_code=302)
@router.post("/actions/announce")
def admin_actions_announce(
async def admin_actions_announce(
request: Request,
ap_object_id: str = Form(),
redirect_url: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
boxes.send_announce(db, ap_object_id)
await boxes.send_announce(db_session, ap_object_id)
return RedirectResponse(redirect_url, status_code=302)
@router.post("/actions/bookmark")
def admin_actions_bookmark(
async def admin_actions_bookmark(
request: Request,
ap_object_id: str = Form(),
redirect_url: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
inbox_object = get_inbox_object_by_ap_id(db, ap_object_id)
inbox_object = await get_inbox_object_by_ap_id(db_session, ap_object_id)
if not inbox_object:
raise ValueError("Should never happen")
inbox_object.is_bookmarked = True
db.commit()
await db_session.commit()
return RedirectResponse(redirect_url, status_code=302)
@router.post("/actions/unbookmark")
def admin_actions_unbookmark(
async def admin_actions_unbookmark(
request: Request,
ap_object_id: str = Form(),
redirect_url: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
inbox_object = get_inbox_object_by_ap_id(db, ap_object_id)
inbox_object = await get_inbox_object_by_ap_id(db_session, ap_object_id)
if not inbox_object:
raise ValueError("Should never happen")
inbox_object.is_bookmarked = False
db.commit()
await db_session.commit()
return RedirectResponse(redirect_url, status_code=302)
@router.post("/actions/pin")
def admin_actions_pin(
async def admin_actions_pin(
request: Request,
ap_object_id: str = Form(),
redirect_url: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
outbox_object = get_outbox_object_by_ap_id(db, ap_object_id)
outbox_object = await get_outbox_object_by_ap_id(db_session, ap_object_id)
if not outbox_object:
raise ValueError("Should never happen")
outbox_object.is_pinned = True
db.commit()
await db_session.commit()
return RedirectResponse(redirect_url, status_code=302)
@router.post("/actions/unpin")
def admin_actions_unpin(
async def admin_actions_unpin(
request: Request,
ap_object_id: str = Form(),
redirect_url: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
outbox_object = get_outbox_object_by_ap_id(db, ap_object_id)
outbox_object = await get_outbox_object_by_ap_id(db_session, ap_object_id)
if not outbox_object:
raise ValueError("Should never happen")
outbox_object.is_pinned = False
db.commit()
await db_session.commit()
return RedirectResponse(redirect_url, status_code=302)
@router.post("/actions/new")
def admin_actions_new(
async def admin_actions_new(
request: Request,
files: list[UploadFile] = [],
content: str = Form(),
@ -504,16 +527,16 @@ def admin_actions_new(
is_sensitive: bool = Form(False),
visibility: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> RedirectResponse:
# XXX: for some reason, no files restuls in an empty single file
uploads = []
if len(files) >= 1 and files[0].filename:
for f in files:
upload = save_upload(db, f)
upload = await save_upload(db_session, f)
uploads.append((upload, f.filename))
public_id = boxes.send_create(
db,
public_id = await boxes.send_create(
db_session,
source=content,
uploads=uploads,
in_reply_to=in_reply_to or None,
@ -528,12 +551,12 @@ def admin_actions_new(
@unauthenticated_router.get("/login")
def login(
async def login(
request: Request,
db: Session = Depends(get_db),
db_session: AsyncSession = Depends(get_db_session),
) -> templates.TemplateResponse:
return templates.render_template(
db,
return await templates.render_template(
db_session,
request,
"login.html",
{"csrf_token": generate_csrf_token()},
@ -541,7 +564,7 @@ def login(
@unauthenticated_router.post("/login")
def login_validation(
async def login_validation(
request: Request,
password: str = Form(),
csrf_check: None = Depends(verify_csrf_token),
@ -556,7 +579,7 @@ def login_validation(
@router.get("/logout")
def logout(
async def logout(
request: Request,
) -> RedirectResponse:
resp = RedirectResponse(request.url_for("index"), status_code=302)

View file

@ -12,7 +12,6 @@ from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy import update
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from sqlalchemy.orm import joinedload
from app import activitypub as ap
@ -26,6 +25,7 @@ from app.actor import save_actor
from app.ap_object import RemoteObject
from app.config import BASE_URL
from app.config import ID
from app.database import AsyncSession
from app.database import now
from app.outgoing_activities import new_outgoing_activity
from app.source import markdownify
@ -42,8 +42,8 @@ def outbox_object_id(outbox_id) -> str:
return f"{BASE_URL}/o/{outbox_id}"
def save_outbox_object(
db: Session,
async def save_outbox_object(
db_session: AsyncSession,
public_id: str,
raw_object: ap.RawObject,
relates_to_inbox_object_id: int | None = None,
@ -68,15 +68,15 @@ def save_outbox_object(
is_hidden_from_homepage=True if ra.in_reply_to else False,
source=source,
)
db.add(outbox_object)
db.commit()
db.refresh(outbox_object)
db_session.add(outbox_object)
await db_session.commit()
await db_session.refresh(outbox_object)
return outbox_object
def send_like(db: Session, ap_object_id: str) -> None:
inbox_object = get_inbox_object_by_ap_id(db, ap_object_id)
async def send_like(db_session: AsyncSession, ap_object_id: str) -> None:
inbox_object = await get_inbox_object_by_ap_id(db_session, ap_object_id)
if not inbox_object:
raise ValueError(f"{ap_object_id} not found in the inbox")
@ -88,20 +88,22 @@ def send_like(db: Session, ap_object_id: str) -> None:
"actor": ID,
"object": ap_object_id,
}
outbox_object = save_outbox_object(
db, like_id, like, relates_to_inbox_object_id=inbox_object.id
outbox_object = await save_outbox_object(
db_session, like_id, like, relates_to_inbox_object_id=inbox_object.id
)
if not outbox_object.id:
raise ValueError("Should never happen")
inbox_object.liked_via_outbox_object_ap_id = outbox_object.ap_id
db.commit()
await db_session.commit()
new_outgoing_activity(db, inbox_object.actor.inbox_url, outbox_object.id)
await new_outgoing_activity(
db_session, inbox_object.actor.inbox_url, outbox_object.id
)
def send_announce(db: Session, ap_object_id: str) -> None:
inbox_object = get_inbox_object_by_ap_id(db, ap_object_id)
async def send_announce(db_session: AsyncSession, ap_object_id: str) -> None:
inbox_object = await get_inbox_object_by_ap_id(db_session, ap_object_id)
if not inbox_object:
raise ValueError(f"{ap_object_id} not found in the inbox")
@ -118,22 +120,22 @@ def send_announce(db: Session, ap_object_id: str) -> None:
inbox_object.ap_actor_id,
],
}
outbox_object = save_outbox_object(
db, announce_id, announce, relates_to_inbox_object_id=inbox_object.id
outbox_object = await save_outbox_object(
db_session, announce_id, announce, relates_to_inbox_object_id=inbox_object.id
)
if not outbox_object.id:
raise ValueError("Should never happen")
inbox_object.announced_via_outbox_object_ap_id = outbox_object.ap_id
db.commit()
await db_session.commit()
recipients = _compute_recipients(db, announce)
recipients = await _compute_recipients(db_session, announce)
for rcp in recipients:
new_outgoing_activity(db, rcp, outbox_object.id)
await new_outgoing_activity(db_session, rcp, outbox_object.id)
def send_follow(db: Session, ap_actor_id: str) -> None:
actor = fetch_actor(db, ap_actor_id)
async def send_follow(db_session: AsyncSession, ap_actor_id: str) -> None:
actor = await fetch_actor(db_session, ap_actor_id)
follow_id = allocate_outbox_id()
follow = {
@ -144,17 +146,17 @@ def send_follow(db: Session, ap_actor_id: str) -> None:
"object": ap_actor_id,
}
outbox_object = save_outbox_object(
db, follow_id, follow, relates_to_actor_id=actor.id
outbox_object = await save_outbox_object(
db_session, follow_id, follow, relates_to_actor_id=actor.id
)
if not outbox_object.id:
raise ValueError("Should never happen")
new_outgoing_activity(db, actor.inbox_url, outbox_object.id)
await new_outgoing_activity(db_session, actor.inbox_url, outbox_object.id)
def send_undo(db: Session, ap_object_id: str) -> None:
outbox_object_to_undo = get_outbox_object_by_ap_id(db, ap_object_id)
async def send_undo(db_session: AsyncSession, ap_object_id: str) -> None:
outbox_object_to_undo = await get_outbox_object_by_ap_id(db_session, ap_object_id)
if not outbox_object_to_undo:
raise ValueError(f"{ap_object_id} not found in the outbox")
@ -172,8 +174,8 @@ def send_undo(db: Session, ap_object_id: str) -> None:
"object": ap.remove_context(outbox_object_to_undo.ap_object),
}
outbox_object = save_outbox_object(
db,
outbox_object = await save_outbox_object(
db_session,
undo_id,
undo,
relates_to_outbox_object_id=outbox_object_to_undo.id,
@ -186,31 +188,33 @@ def send_undo(db: Session, ap_object_id: str) -> None:
if outbox_object_to_undo.ap_type == "Follow":
if not outbox_object_to_undo.activity_object_ap_id:
raise ValueError("Should never happen")
followed_actor = fetch_actor(db, outbox_object_to_undo.activity_object_ap_id)
new_outgoing_activity(
db,
followed_actor = await fetch_actor(
db_session, outbox_object_to_undo.activity_object_ap_id
)
await new_outgoing_activity(
db_session,
followed_actor.inbox_url,
outbox_object.id,
)
# Also remove the follow from the following collection
db.execute(
await db_session.execute(
delete(models.Following).where(
models.Following.ap_actor_id == followed_actor.ap_id
)
)
db.commit()
await db_session.commit()
elif outbox_object_to_undo.ap_type == "Like":
liked_object_ap_id = outbox_object_to_undo.activity_object_ap_id
if not liked_object_ap_id:
raise ValueError("Should never happen")
liked_object = get_inbox_object_by_ap_id(db, liked_object_ap_id)
liked_object = await get_inbox_object_by_ap_id(db_session, liked_object_ap_id)
if not liked_object:
raise ValueError(f"Cannot find liked object {liked_object_ap_id}")
liked_object.liked_via_outbox_object_ap_id = None
# Send the Undo to the liked object's actor
new_outgoing_activity(
db,
await new_outgoing_activity(
db_session,
liked_object.actor.inbox_url, # type: ignore
outbox_object.id,
)
@ -218,21 +222,23 @@ def send_undo(db: Session, ap_object_id: str) -> None:
announced_object_ap_id = outbox_object_to_undo.activity_object_ap_id
if not announced_object_ap_id:
raise ValueError("Should never happen")
announced_object = get_inbox_object_by_ap_id(db, announced_object_ap_id)
announced_object = await get_inbox_object_by_ap_id(
db_session, announced_object_ap_id
)
if not announced_object:
raise ValueError(f"Cannot find announced object {announced_object_ap_id}")
announced_object.announced_via_outbox_object_ap_id = None
# Send the Undo to the original recipients
recipients = _compute_recipients(db, outbox_object.ap_object)
recipients = await _compute_recipients(db_session, outbox_object.ap_object)
for rcp in recipients:
new_outgoing_activity(db, rcp, outbox_object.id)
await new_outgoing_activity(db_session, rcp, outbox_object.id)
else:
raise ValueError("Should never happen")
def send_create(
db: Session,
async def send_create(
db_session: AsyncSession,
source: str,
uploads: list[tuple[models.Upload, str]],
in_reply_to: str | None,
@ -243,11 +249,11 @@ def send_create(
note_id = allocate_outbox_id()
published = now().replace(microsecond=0).isoformat().replace("+00:00", "Z")
context = f"{ID}/contexts/" + uuid.uuid4().hex
content, tags, mentioned_actors = markdownify(db, source)
content, tags, mentioned_actors = await markdownify(db_session, source)
attachments = []
if in_reply_to:
in_reply_to_object = get_anybox_object_by_ap_id(db, in_reply_to)
in_reply_to_object = await get_anybox_object_by_ap_id(db_session, in_reply_to)
if not in_reply_to_object:
raise ValueError(f"Invalid in reply to {in_reply_to=}")
if not in_reply_to_object.ap_context:
@ -255,7 +261,7 @@ def send_create(
context = in_reply_to_object.ap_context
if in_reply_to_object.is_from_outbox:
db.execute(
await db_session.execute(
update(models.OutboxObject)
.where(
models.OutboxObject.ap_id == in_reply_to,
@ -302,7 +308,7 @@ def send_create(
"sensitive": is_sensitive,
"attachment": attachments,
}
outbox_object = save_outbox_object(db, note_id, note, source=source)
outbox_object = await save_outbox_object(db_session, note_id, note, source=source)
if not outbox_object.id:
raise ValueError("Should never happen")
@ -312,24 +318,26 @@ def send_create(
tag=tag["name"][1:],
outbox_object_id=outbox_object.id,
)
db.add(tagged_object)
db_session.add(tagged_object)
for (upload, filename) in uploads:
outbox_object_attachment = models.OutboxObjectAttachment(
filename=filename, outbox_object_id=outbox_object.id, upload_id=upload.id
)
db.add(outbox_object_attachment)
db_session.add(outbox_object_attachment)
db.commit()
await db_session.commit()
recipients = _compute_recipients(db, note)
recipients = await _compute_recipients(db_session, note)
for rcp in recipients:
new_outgoing_activity(db, rcp, outbox_object.id)
await new_outgoing_activity(db_session, rcp, outbox_object.id)
return note_id
def _compute_recipients(db: Session, ap_object: ap.RawObject) -> set[str]:
async def _compute_recipients(
db_session: AsyncSession, ap_object: ap.RawObject
) -> set[str]:
_recipients = []
for field in ["to", "cc", "bto", "bcc"]:
if field in ap_object:
@ -343,15 +351,17 @@ def _compute_recipients(db: Session, ap_object: ap.RawObject) -> set[str]:
# If we got a local collection, assume it's a collection of actors
if r.startswith(BASE_URL):
for actor in fetch_actor_collection(db, r):
for actor in await fetch_actor_collection(db_session, r):
recipients.add(actor.shared_inbox_url or actor.inbox_url)
continue
# Is it a known actor?
known_actor = db.execute(
select(models.Actor).where(models.Actor.ap_id == r)
).scalar_one_or_none()
known_actor = (
await db_session.execute(
select(models.Actor).where(models.Actor.ap_id == r)
)
).scalar_one_or_none() # type: ignore
if known_actor:
recipients.add(known_actor.shared_inbox_url or known_actor.inbox_url)
continue
@ -359,7 +369,7 @@ def _compute_recipients(db: Session, ap_object: ap.RawObject) -> set[str]:
# Fetch the object
raw_object = ap.fetch(r)
if raw_object.get("type") in ap.ACTOR_TYPES:
saved_actor = save_actor(db, raw_object)
saved_actor = await save_actor(db_session, raw_object)
recipients.add(saved_actor.shared_inbox_url or saved_actor.inbox_url)
else:
# Assume it's a collection of actors
@ -370,27 +380,43 @@ def _compute_recipients(db: Session, ap_object: ap.RawObject) -> set[str]:
return recipients
def get_inbox_object_by_ap_id(db: Session, ap_id: str) -> models.InboxObject | None:
return db.execute(
select(models.InboxObject).where(models.InboxObject.ap_id == ap_id)
).scalar_one_or_none()
async def get_inbox_object_by_ap_id(
db_session: AsyncSession, ap_id: str
) -> models.InboxObject | None:
return (
await db_session.execute(
select(models.InboxObject)
.where(models.InboxObject.ap_id == ap_id)
.options(
joinedload(models.InboxObject.actor),
joinedload(models.InboxObject.relates_to_inbox_object),
joinedload(models.InboxObject.relates_to_outbox_object),
)
)
).scalar_one_or_none() # type: ignore
def get_outbox_object_by_ap_id(db: Session, ap_id: str) -> models.OutboxObject | None:
return db.execute(
select(models.OutboxObject).where(models.OutboxObject.ap_id == ap_id)
).scalar_one_or_none()
async def get_outbox_object_by_ap_id(
db_session: AsyncSession, ap_id: str
) -> models.OutboxObject | None:
return (
await db_session.execute(
select(models.OutboxObject).where(models.OutboxObject.ap_id == ap_id)
)
).scalar_one_or_none() # type: ignore
def get_anybox_object_by_ap_id(db: Session, ap_id: str) -> AnyboxObject | None:
async def get_anybox_object_by_ap_id(
db_session: AsyncSession, ap_id: str
) -> AnyboxObject | None:
if ap_id.startswith(BASE_URL):
return get_outbox_object_by_ap_id(db, ap_id)
return await get_outbox_object_by_ap_id(db_session, ap_id)
else:
return get_inbox_object_by_ap_id(db, ap_id)
return await get_inbox_object_by_ap_id(db_session, ap_id)
def _handle_delete_activity(
db: Session,
async def _handle_delete_activity(
db_session: AsyncSession,
from_actor: models.Actor,
ap_object_to_delete: models.InboxObject,
) -> None:
@ -404,12 +430,12 @@ def _handle_delete_activity(
# TODO(ts): do we need to delete related activities? should we keep
# bookmarked objects with a deleted flag?
logger.info(f"Deleting {ap_object_to_delete.ap_type}/{ap_object_to_delete.ap_id}")
db.delete(ap_object_to_delete)
db.flush()
await db_session.delete(ap_object_to_delete)
await db_session.flush()
def _handle_follow_follow_activity(
db: Session,
async def _handle_follow_follow_activity(
db_session: AsyncSession,
from_actor: models.Actor,
inbox_object: models.InboxObject,
) -> None:
@ -419,8 +445,8 @@ def _handle_follow_follow_activity(
ap_actor_id=from_actor.ap_id,
)
try:
db.add(follower)
db.flush()
db_session.add(follower)
await db_session.flush()
except IntegrityError:
pass # TODO update the existing followe
@ -433,20 +459,20 @@ def _handle_follow_follow_activity(
"actor": ID,
"object": inbox_object.ap_id,
}
outbox_activity = save_outbox_object(db, reply_id, reply)
outbox_activity = await save_outbox_object(db_session, reply_id, reply)
if not outbox_activity.id:
raise ValueError("Should never happen")
new_outgoing_activity(db, from_actor.inbox_url, outbox_activity.id)
await new_outgoing_activity(db_session, from_actor.inbox_url, outbox_activity.id)
notif = models.Notification(
notification_type=models.NotificationType.NEW_FOLLOWER,
actor_id=from_actor.id,
)
db.add(notif)
db_session.add(notif)
def _handle_undo_activity(
db: Session,
async def _handle_undo_activity(
db_session: AsyncSession,
from_actor: models.Actor,
undo_activity: models.InboxObject,
ap_activity_to_undo: models.InboxObject,
@ -462,7 +488,7 @@ def _handle_undo_activity(
if ap_activity_to_undo.ap_type == "Follow":
logger.info(f"Undo follow from {from_actor.ap_id}")
db.execute(
await db_session.execute(
delete(models.Follower).where(
models.Follower.inbox_object_id == ap_activity_to_undo.id
)
@ -471,13 +497,13 @@ def _handle_undo_activity(
notification_type=models.NotificationType.UNFOLLOW,
actor_id=from_actor.id,
)
db.add(notif)
db_session.add(notif)
elif ap_activity_to_undo.ap_type == "Like":
if not ap_activity_to_undo.activity_object_ap_id:
raise ValueError("Like without object")
liked_obj = get_outbox_object_by_ap_id(
db,
liked_obj = await get_outbox_object_by_ap_id(
db_session,
ap_activity_to_undo.activity_object_ap_id,
)
if not liked_obj:
@ -494,7 +520,7 @@ def _handle_undo_activity(
outbox_object_id=liked_obj.id,
inbox_object_id=ap_activity_to_undo.id,
)
db.add(notif)
db_session.add(notif)
elif ap_activity_to_undo.ap_type == "Announce":
if not ap_activity_to_undo.activity_object_ap_id:
@ -504,8 +530,8 @@ def _handle_undo_activity(
f"Undo for announce {ap_activity_to_undo.ap_id}/{announced_obj_ap_id}"
)
if announced_obj_ap_id.startswith(BASE_URL):
announced_obj_from_outbox = get_outbox_object_by_ap_id(
db, announced_obj_ap_id
announced_obj_from_outbox = await get_outbox_object_by_ap_id(
db_session, announced_obj_ap_id
)
if announced_obj_from_outbox:
logger.info("Found in the oubox")
@ -518,7 +544,7 @@ def _handle_undo_activity(
outbox_object_id=announced_obj_from_outbox.id,
inbox_object_id=ap_activity_to_undo.id,
)
db.add(notif)
db_session.add(notif)
# FIXME(ts): what to do with ap_activity_to_undo? flag? delete?
else:
@ -527,8 +553,8 @@ def _handle_undo_activity(
# commit will be perfomed in save_to_inbox
def _handle_create_activity(
db: Session,
async def _handle_create_activity(
db_session: AsyncSession,
from_actor: models.Actor,
created_object: models.InboxObject,
) -> None:
@ -544,7 +570,7 @@ def _handle_create_activity(
return None
if created_object.in_reply_to and created_object.in_reply_to.startswith(BASE_URL):
db.execute(
await db_session.execute(
update(models.OutboxObject)
.where(
models.OutboxObject.ap_id == created_object.in_reply_to,
@ -559,12 +585,12 @@ def _handle_create_activity(
actor_id=from_actor.id,
inbox_object_id=created_object.id,
)
db.add(notif)
db_session.add(notif)
def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
async def save_to_inbox(db_session: AsyncSession, raw_object: ap.RawObject) -> None:
try:
actor = fetch_actor(db, ap.get_id(raw_object["actor"]))
actor = await fetch_actor(db_session, ap.get_id(raw_object["actor"]))
except httpx.HTTPStatusError:
logger.exception("Failed to fetch actor")
return
@ -576,7 +602,7 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
ra = RemoteObject(ap.unwrap_activity(raw_object), actor=actor)
if (
db.scalar(
await db_session.scalar(
select(func.count(models.InboxObject.id)).where(
models.InboxObject.ap_id == ra.ap_id
)
@ -590,13 +616,13 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
relates_to_outbox_object: models.OutboxObject | None = None
if ra.activity_object_ap_id:
if ra.activity_object_ap_id.startswith(BASE_URL):
relates_to_outbox_object = get_outbox_object_by_ap_id(
db,
relates_to_outbox_object = await get_outbox_object_by_ap_id(
db_session,
ra.activity_object_ap_id,
)
else:
relates_to_inbox_object = get_inbox_object_by_ap_id(
db,
relates_to_inbox_object = await get_inbox_object_by_ap_id(
db_session,
ra.activity_object_ap_id,
)
@ -625,27 +651,29 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
), # TODO: handle mentions
)
db.add(inbox_object)
db.flush()
db.refresh(inbox_object)
db_session.add(inbox_object)
await db_session.flush()
await db_session.refresh(inbox_object)
if ra.ap_type == "Note": # TODO: handle create better
_handle_create_activity(db, actor, inbox_object)
await _handle_create_activity(db_session, actor, inbox_object)
elif ra.ap_type == "Update":
pass
elif ra.ap_type == "Delete":
if relates_to_inbox_object:
_handle_delete_activity(db, actor, relates_to_inbox_object)
await _handle_delete_activity(db_session, actor, relates_to_inbox_object)
else:
# TODO(ts): handle delete actor
logger.info(
f"Received a Delete for an unknown object: {ra.activity_object_ap_id}"
)
elif ra.ap_type == "Follow":
_handle_follow_follow_activity(db, actor, inbox_object)
await _handle_follow_follow_activity(db_session, actor, inbox_object)
elif ra.ap_type == "Undo":
if relates_to_inbox_object:
_handle_undo_activity(db, actor, inbox_object, relates_to_inbox_object)
await _handle_undo_activity(
db_session, actor, inbox_object, relates_to_inbox_object
)
else:
logger.info("Received Undo for an unknown activity")
elif ra.ap_type in ["Accept", "Reject"]:
@ -661,7 +689,7 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
outbox_object_id=relates_to_outbox_object.id,
ap_actor_id=actor.ap_id,
)
db.add(following)
db_session.add(following)
else:
logger.info(
"Received an Accept for an unsupported activity: "
@ -689,7 +717,7 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
outbox_object_id=relates_to_outbox_object.id,
inbox_object_id=inbox_object.id,
)
db.add(notif)
db_session.add(notif)
elif raw_object["type"] == "Announce":
if relates_to_outbox_object:
# This is an announce for a local object
@ -703,7 +731,7 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
outbox_object_id=relates_to_outbox_object.id,
inbox_object_id=inbox_object.id,
)
db.add(notif)
db_session.add(notif)
else:
# This is announce for a maybe unknown object
if relates_to_inbox_object:
@ -713,7 +741,9 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
if not ra.activity_object_ap_id:
raise ValueError("Should never happen")
announced_raw_object = ap.fetch(ra.activity_object_ap_id)
announced_actor = fetch_actor(db, ap.get_actor_id(announced_raw_object))
announced_actor = await fetch_actor(
db_session, ap.get_actor_id(announced_raw_object)
)
announced_object = RemoteObject(announced_raw_object, announced_actor)
announced_inbox_object = models.InboxObject(
server=urlparse(announced_object.ap_id).netloc,
@ -727,8 +757,8 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
visibility=announced_object.visibility,
is_hidden_from_stream=True,
)
db.add(announced_inbox_object)
db.flush()
db_session.add(announced_inbox_object)
await db_session.flush()
inbox_object.relates_to_inbox_object_id = announced_inbox_object.id
elif ra.ap_type in ["Like", "Announce"]:
if not relates_to_outbox_object:
@ -749,7 +779,7 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
outbox_object_id=relates_to_outbox_object.id,
inbox_object_id=inbox_object.id,
)
db.add(notif)
db_session.add(notif)
elif raw_object["type"] == "Announce":
# TODO(ts): notification
relates_to_outbox_object.announces_count = (
@ -762,18 +792,18 @@ def save_to_inbox(db: Session, raw_object: ap.RawObject) -> None:
outbox_object_id=relates_to_outbox_object.id,
inbox_object_id=inbox_object.id,
)
db.add(notif)