Updating a property we don't support returned a 200 with no change, so
clients believed the update had worked. Return invalid_request instead,
and apply nothing if any part of the request is unsupported.
This also covers empty operations, syndication URLs we have nowhere to
store, and photos added without an https URL, all of which were being
silently ignored.
Mastodon and Bluesky URLs can now be added and deleted through updates,
as they are our active syndication targets and q=source returns them.
Mastodon can be on any host, so accept the instances we have syndication
targets for rather than hard-coding one.
Stop reading instagram_url in q=source, as notes have no such column.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Servers supporting updates must support the source content query. It
was falling through to the token response, so clients got a 200 with no
post in it. Return the raw note as microformats2 JSON, honouring
properties[].
Malformed requests could cause a 500 or worse. A replace value sent as a
string overwrote the note with its first character, because $value[0]
indexes the string. Validate update operations before applying any of
them, and return invalid_request for wrongly shaped properties, unknown
or malformed q values, and missing update URLs.
Share URL resolution between updates and q=source in NoteResolver, which
also rejects URLs on other hosts and invalid note IDs. Those IDs used to
decode to a real note because invalid characters were skipped.
Allow photo objects without alt text, and parse the published date
before saving so that an invalid date doesn't leave a stray note behind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Detect form-encoded action=... requests, so form-encoded updates work
and unhandled actions are no longer created as notes
- Default to h-entry when no type is given
- Read form-encoded photos from `photo` rather than `photos`
- Pass `category` through EntryData so bookmark tags are saved
- Accept single values for form-encoded multi-value properties
- Return 403 for insufficient_scope and 401 for invalid tokens
- Return 400 invalid_request for unsupported post types, update
operations and models instead of reporting them as 500s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Likes returned `url`, which is the liked post rather than our page. Use
`uri` instead. Bookmarks already asked for `uri`, which didn't exist until
the previous commit, so their Location header was empty.
Neither case had a test, so assert the header value for both.
Refs #148
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`uri` is the attribute every other postable model uses for its absolute
permalink on this site. Add it to Like, which had none, and rename
Bookmark's `local_uri` to match. `url` stays as the thing being liked or
bookmarked.
Refs #148
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Constrain the likes/{like} and bookmarks/{bookmark} routes to numeric
IDs. Previously a junk URL such as /likes/twcamp^share|twsrc^ios was
passed to route model binding, and Postgres rejected the text-vs-integer
comparison with a QueryException, returning a 500 instead of a 404.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save an explicit light/dark choice to localStorage and clear it when
switching back to system. An inline head script applies the saved
colour scheme before first paint to avoid a flash of the system theme.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>