Skip to content

Commit

Permalink
ATProto.send: populate Object.copies with local record's AT URI
Browse files Browse the repository at this point in the history
  • Loading branch information
snarfed committed Sep 14, 2023
1 parent d3b3ff4 commit 077b5a9
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 20 deletions.
17 changes: 13 additions & 4 deletions atproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
)
import flask_app
import hub
from models import Object, PROTOCOLS, User
from models import Object, PROTOCOLS, Target, User
from protocol import Protocol

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -242,9 +242,18 @@ def update_user_create_repo():
assert repo

# create record
writes.append(Write(action=Action.CREATE, collection='app.bsky.feed.post',
rkey=next_tid(), record=obj.as_bsky()))
repo.apply_writes(writes)
ndb.transactional()
def write():
tid = next_tid()
writes.append(Write(action=Action.CREATE, collection='app.bsky.feed.post',
rkey=tid, record=obj.as_bsky()))
repo.apply_writes(writes)

at_uri = f'at://{user.atproto_did}/app.bsky.feed.post/{tid}'
add(obj.copies, Target(uri=at_uri, protocol=to_cls.ABBREV))
obj.put()

write()

return True

Expand Down
41 changes: 27 additions & 14 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,11 @@ def user_page_link(self):


class Target(ndb.Model):
"""Delivery destinations. ActivityPub inboxes, webmention targets, etc.
"""Protocol + URI pairs for identifying objects.
These are currently used for:
* delivery destinations, eg ActivityPub inboxes, webmention targets, etc.
* copies of objects stored elsewhere, eg at:// URIs for ATProto records
Used in StructuredPropertys inside Object; not stored directly in the
datastore.
Expand All @@ -352,9 +356,9 @@ class Target(ndb.Model):
property on the parent entity, prefixed by the StructuredProperty name
below, eg delivered.uri, delivered.protocol, etc.
For repeated StructuredPropertys, the hoisted properties are all
repeated on the parent entity, and reconstructed into
StructuredPropertys based on their order.
For repeated StructuredPropertys, the hoisted properties are all repeated on
the parent entity, and reconstructed into StructuredPropertys based on their
order.
https://googleapis.dev/python/python-ndb/latest/model.html#google.cloud.ndb.model.StructuredProperty
"""
Expand All @@ -363,8 +367,12 @@ class Target(ndb.Model):
# subclasses are created, so that PROTOCOLS is fully populated
protocol = ndb.StringProperty(choices=[], required=True)

def __eq__(self, other):
"""Equality excludes Targets' :class:`Key`."""
return self.uri == other.uri and self.protocol == other.protocol

def __hash__(self):
"""Override Model and allow hashing so these can be dict keys."""
"""Allow hashing so these can be dict keys."""
return hash((self.protocol, self.uri))


Expand Down Expand Up @@ -410,6 +418,18 @@ class Object(StringIdModel):
our_as1 = JsonProperty() # AS1 for activities that we generate or modify ourselves
raw = JsonProperty() # other standalone data format, eg DID document

deleted = ndb.BooleanProperty()

delivered = ndb.StructuredProperty(Target, repeated=True)
undelivered = ndb.StructuredProperty(Target, repeated=True)
failed = ndb.StructuredProperty(Target, repeated=True)

# Copies of this object elsewhere, eg at:// URIs for ATProto records
copies = ndb.StructuredProperty(Target, repeated=True)

created = ndb.DateTimeProperty(auto_now_add=True)
updated = ndb.DateTimeProperty(auto_now=True)

new = None
changed = None
"""
Expand Down Expand Up @@ -460,16 +480,8 @@ def type(self): # AS1 objectType, or verb if it's an activity
def _object_ids(self): # id(s) of inner objects
if self.as1:
return redirect_unwrap(as1.get_ids(self.as1, 'object'))
object_ids = ndb.ComputedProperty(_object_ids, repeated=True)

deleted = ndb.BooleanProperty()

delivered = ndb.StructuredProperty(Target, repeated=True)
undelivered = ndb.StructuredProperty(Target, repeated=True)
failed = ndb.StructuredProperty(Target, repeated=True)

created = ndb.DateTimeProperty(auto_now_add=True)
updated = ndb.DateTimeProperty(auto_now=True)
object_ids = ndb.ComputedProperty(_object_ids, repeated=True)

def _expire(self):
"""Maybe automatically delete this Object after 90d using a TTL policy.
Expand All @@ -481,6 +493,7 @@ def _expire(self):
"""
if self.type in OBJECT_EXPIRE_TYPES:
return (self.updated or util.now()) + OBJECT_EXPIRE_AGE

expire = ndb.ComputedProperty(_expire, indexed=False)

def _pre_put_hook(self):
Expand Down
17 changes: 15 additions & 2 deletions tests/test_atproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import atproto
from atproto import ATProto
import common
from models import Object
from models import Object, Target
import protocol
from .testutil import Fake, TestCase

Expand Down Expand Up @@ -240,6 +240,10 @@ def test_send_new_repo(self, mock_post, mock_create_task):
record = repo.get_record('app.bsky.feed.post', arroba.util._tid_last)
self.assertEqual(POST_BSKY, record)

at_uri = f'at://{user.atproto_did}/app.bsky.feed.post/{arroba.util._tid_last}'
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
Object.get_by_id(id='fake:post').copies)

# check PLC directory call to create did:plc
self.assertEqual((f'https://plc.local/{user.atproto_did}',),
mock_post.call_args.args)
Expand Down Expand Up @@ -284,12 +288,17 @@ def test_send_new_repo_includes_user_profile(self, mock_post, mock_create_task):
self.assertTrue(ATProto.send(obj, 'http://localhost/'))

# check profile, record
repo = self.storage.load_repo(user.key.get().atproto_did)
did = user.key.get().atproto_did
repo = self.storage.load_repo(did)
profile = repo.get_record('app.bsky.actor.profile', 'self')
self.assertEqual(ACTOR_PROFILE_VIEW_BSKY, profile)
record = repo.get_record('app.bsky.feed.post', arroba.util._tid_last)
self.assertEqual(POST_BSKY, record)

at_uri = f'at://{did}/app.bsky.feed.post/{arroba.util._tid_last}'
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
Object.get_by_id(id='fake:post').copies)

mock_create_task.assert_called()

@patch.object(tasks_client, 'create_task', return_value=Task(name='my task'))
Expand All @@ -312,6 +321,10 @@ def test_send_existing_repo(self, mock_create_task):
record = repo.get_record('app.bsky.feed.post', arroba.util._tid_last)
self.assertEqual(POST_BSKY, record)

at_uri = f'at://{user.atproto_did}/app.bsky.feed.post/{arroba.util._tid_last}'
self.assertEqual([Target(uri=at_uri, protocol='atproto')],
Object.get_by_id(id='fake:post').copies)

mock_create_task.assert_called()

@patch.object(tasks_client, 'create_task')
Expand Down

0 comments on commit 077b5a9

Please sign in to comment.