Compare commits

...
Author SHA1 Message Date
cc2ff2b2a2
Drop the unused notes.shorturl column
The short domain is gone and nothing reads or writes this column any more.

Refs #148

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:29:05 +00:00
73b9668088
Link to likes through the uri attribute in the like template
Refs #148

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:29:00 +00:00
e8f2535cd1
Return our permalink in the Micropub Location header for likes
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>
2026-11-01 15:29:00 +00:00
5481956d78
Use uri for the like and bookmark permalinks
`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>
2026-11-01 15:28:30 +00:00
7ce256baa5
Return 404 for non-numeric like and bookmark IDs
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>
2026-10-29 19:01:28 +00:00
c3cf597950
Persist the theme selector choice across page loads
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>
2026-10-24 20:23:02 +01:00
d2bc91794b
Use self-hosted Libron and Cal Sans fonts
Libron is the body serif and Cal Sans is used for headings and the header
nav, both under the SIL OFL with licences alongside the font files. Both
are preloaded so the header doesn't shift while Cal Sans loads. Also
nudge note text up and note/article metadata down in size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:02:52 +01:00
a31c218784
Hide draft articles from the front page and mark drafts when viewed directly
Add an Article::published() scope and use it on the front page (which
previously showed drafts), the /blog listing and the feeds. Draft
articles are still reachable at their URL, but now show a draft notice,
a "[Draft]" title prefix and a noindex robots meta tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 16:20:50 +01:00
eb35a0aa2d
Update existing draft articles instead of erroring on a repeat Micropub post
If a Micropub h-entry post's title matches an existing article that's
still a draft, update that article in place rather than trying to
insert a duplicate. If it matches one that's already published,
reject the request with a clear error instead of silently colliding.

Also set includeTrashed on Article's slug config as a safety net: this
model soft-deletes, and Sluggable's uniqueness check ignores trashed
rows by default, so a previously-deleted article's title could crash
new inserts with a raw unique constraint violation (this is exactly
what surfaced in Flare as a UniqueConstraintViolationException on
articles_titleurl_unique once the prior swallowed-exception fix
shipped).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:16:11 +01:00
6727138f43
Report exceptions from Micropub 500 error paths instead of swallowing them
MicropubController's catch-all handlers returned a generic 500 without
ever calling report(), so failures never reached laravel.log or Flare
(Flare is already wired up via bootstrap/app.php). Widened the final
catch to \Throwable so PHP Errors (e.g. TypeError) get the same
Micropub-shaped error response and are also reported.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 12:22:33 +01:00
4aa93d63bb
Add Article::uri accessor for the absolute post URL
Follow the uri/link convention already used by Note, Bookmark, and
Place, rather than concatenating config('app.url') inline where the
absolute URL is needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017USyUg8PwuoDcHP8pv5xjy
2026-09-13 12:12:36 +01:00
77998a963e
Fix relative Location header when Micropub creates an article
EntryHandler used Article::link, which is deliberately a site-relative
path elsewhere in the app, directly as the Micropub response's
Location URL. Every other post type it returns (notes, bookmarks,
places) already prepends the site URL, so articles were the only case
where clients received a relative Location - iA Writer appears to
treat that as a local file path and fails to open it after posting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017USyUg8PwuoDcHP8pv5xjy
2026-09-13 11:42:28 +01:00
568ae78864
Add manual Micropub token generation for non-PKCE clients
iA Writer's IndieAuth client predates PKCE support in the spec, so it
can't complete the normal authorization flow. Add an admin form to
mint a token directly (reusing the existing TokenService), so it can
be pasted into clients that support manual token setup instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017USyUg8PwuoDcHP8pv5xjy
2026-09-13 10:40:27 +01:00
9e9fbdc127
Add admin-editable About page
Mirrors the existing Bio singleton pattern: a new `about` table/model,
admin CRUD at /admin/about, and a public page at /about linked from
both the header nav and the admin homepage. Content is wrapped in
.e-content so it lays out correctly within the site's CSS grid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bzYwaDD8p3XNDMYKvXKrs
2026-08-28 14:49:53 +01:00
9532aae37e
Bring back winter snow effect, gated by an admin setting
The site has flip-flopped on the snow effect every winter (add it,
remove it, repeat), meaning a PR round-trip each time. Instead, restore
the winter.js/is-land assets and gate them behind a new admin-toggleable
setting so the effect can be switched on/off from /admin/settings
without touching code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113BVPXDpEk9HPEqXvSG2WQ
2026-08-23 10:17:26 +01:00
e9d8f4c74b
Redesign theme toggle as a two-icon light/dark switch
Replace the single-icon popover with a dropdown of radio inputs with a
compact two-button toggle (light/dark), inspired by vale.rocks' design:
clicking an icon forces that theme, clicking the active icon again
returns to following the system preference. Keeps the existing
light-dark() colour-scheme mechanism and view-transition wipe
animation, and reuses the project's existing Fluent UI sun/moon icons.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn4aNjE3ofpDM78F4qmdCV
2026-08-22 18:14:08 +01:00
be2bc267ad
Remove snow-fall 2026-08-22 17:39:31 +01:00
a76c84b40f
Respect Micropub post-status when creating articles
Articles created via Micropub were always published immediately,
ignoring the client-supplied post-status property. Clients like iA
Writer that publish drafts (post-status: draft) had no way to land
an article as unpublished.

EntryData now extracts post-status from the request, and
ArticleService sets published accordingly, defaulting to true when
the property is omitted or set to anything other than "draft".

Fixes #123

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXyzNvQZPQgBoSZwW7cLG8
2026-08-22 17:16:55 +01:00
99f306ede1
Replace RSS icon with a JSON Feed icon in the header
The nav's feed link already points at the JSON feed, but was using
the feather RSS icon. Adds an SVG recreation of the official JSON
Feed mark (traced from jsonfeed.org's icon) and swaps it in, using a
single dark-green fill throughout so it stays legible against both
the light and dark theme backgrounds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXyzNvQZPQgBoSZwW7cLG8
2026-08-22 10:42:23 +01:00
714cd95d79
Drop RSS/Atom feeds, support JSON feeds only
Removes the RSS and Atom feed routes, controller methods, and views
for both the blog and notes feeds, keeping JSON (and JF2) as the only
supported feed formats. Also serves the JSON feeds with the
spec-required application/feed+json MIME type instead of the generic
application/json.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXyzNvQZPQgBoSZwW7cLG8
2026-08-22 10:42:16 +01:00
6a0bb623fc
Restyle the Micropub tokens admin table
Breaks the table out of the centred content column to full page
width via a subgrid on <main> (adds a reusable .full-bleed utility),
and reworks the table itself to match a Claude Design mockup: a
rounded card wrapper, scope values as pill chips, a dotted status
pill, and proportional column widths. Colours use the site's
existing --primary-hue/light-dark() tokens so it stays correct in
both themes. The card scrolls horizontally on narrow viewports
instead of clipping content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHN7V9kyZxrsxaspMSqoGJ
2026-08-21 16:05:48 +01:00
30ec2ec0e8
Initial work on better admin page styling 2026-08-21 15:36:18 +01:00
4ed98c30b2
Fix undefined property error on passkey login
Webauthn\PublicKeyCredential has no $id property (only $rawId,
inherited from Credential). Every passkey login attempt was throwing
Undefined property: Webauthn\PublicKeyCredential::$id and failing
with a 500. Encode $rawId the same way it's stored during
registration to look up the matching Passkey record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHN7V9kyZxrsxaspMSqoGJ
2026-08-21 15:32:09 +01:00
faf8e5c1ec
Fix CSRF exemption and array-input crash on revocation/introspection
An Opus code review of the branch caught two real bugs the test suite
structurally couldn't see:

- /revocation and /introspect were never added to bootstrap/app.php's
  CSRF except list, so both were fully broken (403) for any real
  external client, despite every feature test passing — CSRF
  verification is short-circuited entirely while running tests.
  Verified live against the running app before and after the fix, and
  added a regression test that asserts against the actual configured
  exemptions rather than relying on request-time behavior that tests
  can't exercise.

- An array-shaped `token` param (e.g. token[]=a&token[]=b) crashed
  both endpoints with a 500, since this app promotes PHP warnings
  ("Array to string conversion") to exceptions. Fixed at the shared
  root, MicropubToken::findActive(), which also closes the same latent
  hole in VerifyMicropubToken's access_token param that predates this
  branch. Verified live and covered with regression tests.

Also applied the review's lower-severity findings: added the missing
introspection_endpoint Link header and metadata test assertions,
removed the now-dead is_string($scopes) array branch in the Micropub
handlers and media controller (scope is unconditionally a string from
the DB now, this guarded against a JWT-array-claim shape that can no
longer occur), dropped a redundant #[Table] model attribute, sized
token_hash to its actual 64-char length, and removed a one-off inline
style in the admin view.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014625MfkGZ7GVdbqKme4a8L
2026-08-13 17:03:43 +01:00
24da24a677
Add IndieAuth token introspection endpoint (RFC 7662)
Lets a resource server (or a client checking its own token, via
self-introspection) verify a token's active/me/client_id/scope without
needing to be tightly coupled to this token endpoint. Requires the
caller to present their own currently-active token as authorization,
per spec's requirement that the endpoint MUST require some form of
authorization. Inactive tokens get back only {"active": false}, no
detail on why, matching the privacy stance already used for
revocation.

Pulled the hash-and-lookup-active-token logic (now needed a third
time) into MicropubToken::findActive(), used by this, the revocation
endpoint, and VerifyMicropubToken.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014625MfkGZ7GVdbqKme4a8L
2026-08-13 16:44:31 +01:00
9d6cf6c815
Add admin page to list and revoke Micropub tokens
Gives a way to actually use the revocation capability built up over
the last few commits from the admin side, not just self-service via
the client. Lists client_id/scope/issue time per token (never the raw
token itself, since only its hash is stored) with a revoke button for
active ones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014625MfkGZ7GVdbqKme4a8L
2026-08-13 16:18:52 +01:00
9c9a6392c8
Add IndieAuth token revocation endpoint (RFC 7009)
Implements the current IndieAuth spec's dedicated /revocation endpoint
so clients can self-revoke a token (e.g. on user sign-out), rather than
only supporting revocation via the admin side. Always responds 200 per
spec, whether the token was found or not, so callers can't use it to
probe token validity. Skips the legacy action=revoke-on-/token fallback
the spec mentions for older clients, since the only real client here is
already being updated to use the current endpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014625MfkGZ7GVdbqKme4a8L
2026-08-13 16:13:11 +01:00
d5706b5f8f
Replace JWT Micropub tokens with revocable opaque tokens
Tokens now store a hashed row in micropub_tokens instead of being
self-contained signed JWTs, so a leaked or unwanted token can actually
be revoked. Since revocation already requires a DB lookup on every
request, JWT's stateless-verification benefit was gone anyway, so this
also drops the lcobucci/jwt dependency entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014625MfkGZ7GVdbqKme4a8L
2026-08-13 16:07:07 +01:00
f9f2744fad
Send Brrr push notifications for new webmentions
Dispatches a queued job to POST to a configured Brrr webhook whenever
a brand-new webmention is saved, so replies/likes/reposts show up as
push notifications instead of requiring a manual check of the site.
2026-08-02 18:07:39 +01:00
31c49ac3fc
Adopt Laravel's Image facade for media processing, upgrade Intervention to v4
Laravel 13's Image facade wraps Intervention Image v4 internally, so
switching our upload width probe and resize job/command to it required
bumping intervention/image ^3 -> ^4 (and its intervention/gif ^5
dependency). Removes our own ImageManager container binding and
config/image.php in favour of Laravel's built-in driver resolution.

Also fixes a latent filename mismatch in ProcessMediaJobTest that Pint's
stricter typing on the new Image API turned into a hard TypeError, and
tidies config/flare.php to use imported class names instead of FQCNs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Sorsgn85nw7uQyRMNvzyD
2026-08-01 17:07:38 +01:00
287520ad7b
Force HTTPS URL generation in production
Uses Laravel's built-in URL::forceHttps() so route(), url(), and asset()
always generate https:// links in production, without affecting local dev.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RP5TeZbk9KS754xuJMobjE
2026-07-28 19:20:18 +01:00
2eab5749ea
Update dependencies 2026-07-26 10:57:20 +01:00
e08763c526
Migrate outbound HTTP calls from Guzzle to the Http facade
Replaces every raw GuzzleHttp\Client usage (Nominatim, web.archive.org,
webmention fetch/discover/send, Bridgy syndication, IndieAuth client_id
lookup, contact avatar/h-card fetch, profile image download, and the
CloudConvert screenshot pipeline) with Illuminate\Support\Facades\Http,
and enables Http::preventStrayRequests() globally in tests so any
un-faked outbound call now fails loudly instead of silently hitting the
network.

The CloudConvert retry-until-finished middleware in AppServiceProvider
is replaced by a plain polling loop in SaveScreenshot, which also fixes
a latent bug where the old middleware decoded a response object instead
of its body. Bridgy syndication jobs keep their tries=1/no-retry
semantics unchanged to avoid duplicate publishes. Guzzle's PSR-7 helpers
(Header, UriResolver, Utils) stay in SendWebMentions since Http has no
equivalent for them.

Every affected test file's Guzzle MockHandler/HandlerStack boilerplate
is replaced with Http::fake()/Http::sequence().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8j7cJhCiYDsGUgB7obKNQ
2026-07-26 09:12:01 +01:00
c420e19f08
Replace abandoned spatie/commonmark-highlighter with tempest/highlight
Swap the syntax highlighting library and its CSS theme (zenburn -> nord,
matching tempest's class names), add padding to code blocks, and commit
the missing compressed winter.js assets from the earlier asset rename.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 18:37:05 +01:00
f7e13976e0
Rename snow-fall.js asset to winter.js
Some ad/tracker blocklists were matching the "snow-fall" filename and blocking the script.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 18:01:57 +01:00
db09718a96
Switch Bridgy syndication from Micropub to webmention
Bridgy's Micropub endpoint required per-silo access tokens and posted
raw markdown content directly. Switch to Bridgy's webmention publish
endpoint instead: POST source/target to /publish/webmention and let
Bridgy re-fetch the note page, which now carries hidden verification
links for silos not yet syndicated. Removes the now-unused
config/bridgy.php and BRIDGY_MASTODON_TOKEN env var, and caps both
jobs to a single try since retries would send Bridgy a duplicate
publish webmention.
2026-07-14 17:36:25 +01:00
11e2c469cb
Fix images overflowing on mobile by preserving aspect ratio
max-width: 100% alone lets img/picture height stay at intrinsic size,
so on narrow viewports images could still overflow vertically or look
squashed. Adding height: auto keeps the aspect ratio correct as width
scales down.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 20:02:49 +01:00
4569abc351
Add command to reprocess existing media with correct aspect-ratio scaling
ProcessMedia was using resize(), which distorts images that aren't the
target aspect ratio; scale() preserves it. Existing medium/small
variants generated before this fix need regenerating, so add an
artisan command (with --dry-run) to do that, plus feature tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 20:00:30 +01:00
dd203f6f31
Update dependencies 2026-06-29 20:13:11 +01:00
9f012b01e4
Upgrade to Laravel 13 2026-04-07 09:01:19 +01:00
e8e2bff5e4
Fix UpdateHandler crash when replace.content is a rich-text object
OwnYourSwarm sends content as an array of objects with `value` and `html`
keys per the Micropub spec. The handler now handles both forms: plain
strings and rich objects, preferring `html` over `value`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 17:43:57 +00:00
4451fc247e
Fix OwnYourSwarm checkin parsing to read from properties.checkin
Real OwnYourSwarm requests send the checkin h-card inside
properties.checkin (as an array), not at the top level. The code
was reading from the top-level key so no Place was ever created,
leaving checkin notes blank.

Update tests to match the real request format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 19:18:00 +00:00
0a13d27d6f
Expand webmentions source and target columns to text
Long brid.gy Bluesky source URLs exceed 255 characters, causing
SQLSTATE[22001] errors. Changed source and target from varchar(255)
to text (no performance impact in PostgreSQL).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 16:32:22 +00:00
1137d2a07e
Move request parsing into DTOs via fromRequest(), expand update handler
Each DTO now owns its own parsing logic via fromRequest(), removing
the normalization layer from MicropubRequest entirely. UpdateHandler
gains delete support and allows replace/add/delete to compose in a
single request rather than being mutually exclusive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 20:39:50 +00:00
ad15b36f4d
Introduce typed DTOs and sub-namespaces for Micropub handlers
Each handler now declares the data class it needs via dataClass(). The
controller builds the appropriate typed DTO from the raw request array
before calling handle(), giving handlers typed property access instead
of raw array lookups.

Handlers moved to App\Services\Micropub\Handlers, data objects to
App\Services\Micropub\Data. MicropubHandlerRegistry and the interface
are documented with flow diagrams and guidance for adding new types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 10:41:33 +00:00
24cb6d61aa
Implement micropub update support with clean exception-based error handling
- Add MicropubUnsupportedModelException for non-note update attempts
- Refactor UpdateHandler to throw exceptions instead of returning JsonResponse objects; extract applySyndication() to DRY up duplicated syndication URL mapping
- Fix MicropubController: InvalidTokenScopeException now returns 401 + insufficient_scope; add catches for ModelNotFoundException (404) and MicropubUnsupportedModelException (500); updates return 200 not 201
- Slim MicropubRequest.normalizeMicropubJson() to branch on action type, keeping update and create fields cleanly separated
- Update tests to match corrected status codes and error keys; remove markTestSkipped() from all update tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 10:15:35 +00:00
a342b432d4
Update dependencies 2026-02-21 17:06:37 +00:00
a5f146125b
Allow migrate media command to be re-run 2026-01-09 14:21:37 +00:00
4dd4977dd9
Fix issue with migrating old S3 media 2026-01-08 18:21:28 +00:00
d31a59cbbe Merge branch 'main' into develop 2026-01-04 11:29:54 +01:00
51780523b4
Fix unit tests 2026-01-04 10:22:32 +00:00
9e788e0fe8
Images can be uploaded with alt text
This means associating images to notes via a pivot table, we also finish
off the migration of medai from S3.
2026-01-04 10:07:25 +00:00
76db869af5
Allow files to be added to notes
This is specifically when a Micropub client is using the JSON syntax and
initially uploads the photo via the media endpoint
2025-12-27 17:58:43 +00:00
d3d6913474
Switch local setup to PHP8.5
Also pull in config changes from upstream laravel/laravel
2025-12-13 15:53:00 +00:00
69e11d0799 Merge pull request 'MTM Small mobile layout tweaks' (#63) from develop into main
Reviewed-on: #63
2025-12-11 21:13:47 +01:00
6b4607e4f9
Small mobile layout tweaks 2025-12-11 19:58:17 +00:00
aef76e023a Merge pull request 'MTM Fix layout of note metadata' (#61) from develop into main
Reviewed-on: #61
2025-12-11 18:54:12 +01:00
4409e42029 Merge pull request 'MTM Redesign 2025.1' (#57) from develop into main
Reviewed-on: #57
2025-12-11 18:20:26 +01:00
6183a065e9 Merge pull request 'MTM Micropub fixes' (#52) from develop into main
Reviewed-on: #52
2025-08-17 12:18:06 +02:00
0ef822de0c Merge pull request 'MTM No snow' (#49) from develop into main
Reviewed-on: #49
2025-07-01 17:57:22 +02:00
07db68882b Merge pull request 'MTM Laravel v12' (#31) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #31
2025-04-04 18:10:46 +02:00
13786584a2 Merge pull request 'MTM WebMention username fix & snow fall' (#21) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #21
2024-11-30 17:01:53 +01:00
6d867fcc71 Merge pull request 'MTM Host images locally' (#12) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #12
2024-10-26 15:02:56 +02:00
7dd85a3988 Merge pull request 'MTM Improve customisability' (#8) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #8
2024-08-29 19:25:33 +02:00
863db8e491 Merge pull request 'MTM Fix how scopes are stored in the IndieAuth flow' (#4) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #4
2024-08-03 14:28:48 +02:00
b7d7db60e7
Merge pull request #1474 from jonnybarnes/develop
MTM Fix scope checks
2024-07-13 15:20:46 +01:00
0336744322
Merge pull request #1454 from jonnybarnes/develop
MTM Fix issuing of tokens
2024-06-30 11:20:41 +01:00
7b13f91cc4
Merge pull request #1445 from jonnybarnes/develop
MTM Parse scope value from authorization URL correctly
2024-06-22 21:01:34 +01:00
f689128afc
Merge pull request #1442 from jonnybarnes/develop
MTM Save full url during login
2024-06-22 20:33:36 +01:00
2e624d9565
Merge pull request #1439 from jonnybarnes/develop
Improve IndieAuth links in the html head
2024-06-22 18:46:16 +01:00
d60117cd1d
Merge pull request #1436 from jonnybarnes/develop
New IndieAuth endpoint
2024-06-22 18:29:29 +01:00
fabe9d662b
Merge pull request #1371 from jonnybarnes/develop
MTM Bluesky Syndication
2024-03-23 21:33:39 +00:00
507bac08f2
Merge pull request #1363 from jonnybarnes/develop
MTM Remove CSP header
2024-03-23 15:13:55 +00:00
c0802f8792
Merge pull request #1361 from jonnybarnes/develop
MTM Fix some endpoints
2024-03-23 14:29:33 +00:00
c907bcde0c
Merge pull request #1357 from jonnybarnes/develop
MTM Update CSP
2024-03-22 19:17:22 +00:00
784f4cb44a
Merge pull request #1354 from jonnybarnes/develop
MTM Fix showing webmentions
2024-03-22 19:00:39 +00:00
528e981d7c
Merge pull request #1352 from jonnybarnes/develop
MTM Add Flare config back
2024-03-22 18:33:45 +00:00
5f8d6ca882
Merge pull request #1348 from jonnybarnes/develop
MTM Fix WebMentions
2024-03-22 17:53:24 +00:00
d56306e57b
Merge pull request #1346 from jonnybarnes/develop
MTM Laravel 11
2024-03-22 15:31:33 +00:00
43c654d9e7
Merge pull request #1316 from jonnybarnes/develop
MTM Fix error in SendWebMention job
2024-03-02 10:21:49 +00:00
a7c8e58abd
Merge pull request #1303 from jonnybarnes/develop
MTM Fix fonts
2024-02-23 16:50:55 +00:00
b40f10cada
Merge pull request #1300 from jonnybarnes/develop
MTM Switch fonts
2024-02-23 16:41:54 +00:00
deba175f8f
Merge pull request #1288 from jonnybarnes/develop
MTM: Remove snow effect
2024-02-17 18:28:13 +00:00
c0c8e5262b
Merge pull request #1206 from jonnybarnes/develop
MTM Simplify build steps
2023-12-22 08:03:01 +00:00
469d11ca7a
Merge pull request #1191 from jonnybarnes/develop
MTM Hopefully fix snow
2023-12-17 15:01:17 +00:00
6ff247d58b
Merge pull request #1188 from jonnybarnes/develop
MTM Add snow
2023-12-17 14:45:57 +00:00
d5bac002c4
Merge pull request #1155 from jonnybarnes/develop
MTM Show Webmentions
2023-12-02 14:53:51 +00:00
481d9e22bb
Merge pull request #1123 from jonnybarnes/develop
MTM Updated CSS and Deploy script
2023-11-11 19:25:04 +00:00
335acb130e
Merge pull request #1103 from jonnybarnes/develop
MTM Passkey Support
2023-10-27 20:17:14 +00:00
f375e4217f
Merge pull request #922 from jonnybarnes/develop
MTM Fix error in SaveProfileImage
2023-06-17 20:01:52 +01:00
3dfe99aac7
Merge pull request #919 from jonnybarnes/develop
MTM Fix routing for Article links
2023-06-16 13:18:28 +01:00
577d821709
Merge pull request #909 from jonnybarnes/develop
MTM Fix use of config variables
2023-06-11 17:01:56 +01:00
66f4f856a6
Merge pull request #906 from jonnybarnes/develop
MTM Stop note IDs being too large
2023-06-11 15:22:55 +01:00
73bc0b5338
Merge pull request #901 from jonnybarnes/develop
MTM Add Flare support
2023-06-09 14:15:30 +01:00
2f40e83568
Merge pull request #874 from jonnybarnes/develop
MTM Use separate CSS file in production
2023-05-29 13:04:38 +01:00
b2ef970810
Merge pull request #865 from jonnybarnes/develop
MTM Fix CSP for webpack added style rules
2023-05-27 19:40:02 +01:00
2b2826212f
Merge pull request #863 from jonnybarnes/develop
MTM Fix compiled assets for prod
2023-05-27 18:08:09 +01:00
94bb8c204b
Merge pull request #860 from jonnybarnes/develop
MTM Fix schema update
2023-05-27 16:58:22 +01:00
87d49bddde
Merge pull request #857 from jonnybarnes/develop
MTM: Some small tweaks and dependency updates
2023-05-27 15:41:41 +01:00
9383ed8d0b
Merge pull request #817 from jonnybarnes/develop
MTM Remove Mapbox links
2023-05-04 18:49:21 +01:00
2962675f9d
Merge pull request #770 from jonnybarnes/develop
MTM Re-add search functionality
2023-04-12 10:19:14 +01:00
8532ee36d3
Merge pull request #766 from jonnybarnes/develop
MTM Switch bio to be stored in the database
2023-04-11 17:50:34 +01:00
22447b6027
Merge pull request #758 from jonnybarnes/develop
MTM 2023 Redesign v1
2023-04-08 17:07:18 +01:00
fcf0b4a778
Merge pull request #670 from jonnybarnes/develop
MTM: Laravel 10
2023-02-18 10:03:06 +00:00
cc782bc39a
Merge pull request #641 from jonnybarnes/develop
MTM: Add PHP 8.2 Support
2023-02-04 12:45:44 +00:00
054bba1da9
Merge pull request #639 from jonnybarnes/develop
MTM: Recent work
2023-02-04 12:22:23 +00:00
22e3eaab23
Merge pull request #559 from jonnybarnes/develop
MTM Update Failed Jobs table
2022-11-21 19:15:09 +00:00
e0efd19ef9
Merge pull request #553 from jonnybarnes/develop
MTM More Media Endpoint Improvements
2022-11-20 17:30:25 +00:00
0cd6f81a09
Merge pull request #551 from jonnybarnes/develop
MTM Improve Media Endpoint
2022-11-18 17:32:39 +00:00
30c9b2e248
Merge pull request #535 from jonnybarnes/develop
MTM Handle finding webmention targets better
2022-11-09 20:10:55 +00:00
52ac67ba7a
Merge pull request #525 from jonnybarnes/develop
MTM Post Markdown content to Mastodon
2022-11-06 10:32:03 +00:00
272a5ea2fd
Merge pull request #522 from jonnybarnes/develop
MTM Mastodon syndication
2022-11-04 17:22:22 +00:00
0bea4a1e95
Merge pull request #499 from jonnybarnes/develop
MTM Simplify migrations
2022-10-24 21:18:58 +01:00
8ec738a765
Merge pull request #497 from jonnybarnes/develop
MTM Fix error saving syndication data
2022-10-24 17:45:22 +01:00
2f548725db
Merge pull request #494 from jonnybarnes/develop
MTM Store synidaction targets in the database
2022-10-24 14:28:06 +01:00
f6546e2335
Merge pull request #477 from jonnybarnes/develop
MTM Update Twitter syndication logo
2022-10-16 18:16:38 +01:00
d52b8a3e83
Merge pull request #473 from jonnybarnes/develop
MTM Fix IndieAuth sign in
2022-10-15 16:24:35 +01:00
46133be181
Merge pull request #445 from jonnybarnes/develop
MTM Reworked indieauth checking in token endpoint
2022-09-24 19:23:18 +01:00
0faf896e8f
Merge pull request #443 from jonnybarnes/develop
MTM Fix IndieAuth sign in
2022-09-24 18:42:27 +01:00
374e231a6b
Merge pull request #420 from jonnybarnes/develop
MTM Use env for cookie prefix
2022-09-10 13:50:49 +01:00
729c52df2f
Merge pull request #418 from jonnybarnes/develop
MTM JWT and Cookie fixes
2022-09-10 13:31:24 +01:00
ac19c02e76
Merge pull request #380 from jonnybarnes/develop
MTM More link fixes
2022-08-21 09:00:38 +01:00
0289f9f0b0
Merge pull request #378 from jonnybarnes/develop
MTM Fixing links
2022-08-20 15:31:34 +01:00
14a10472a6
Merge pull request #365 from jonnybarnes/develop
MTM Add tagged bookmarks page
2022-08-14 18:03:30 +01:00
b74bdd01ef
Merge pull request #362 from jonnybarnes/develop
MTM: Fix some links
2022-08-13 20:30:08 +01:00
9c059f822f
Merge pull request #293 from jonnybarnes/develop
MTM Mentions Improvements
2022-07-09 12:03:07 +01:00
75e369992d
Merge pull request #268 from jonnybarnes/develop
MTM Deploy tags
2022-06-04 17:24:37 +01:00
a18f7b2d03
Merge pull request #265 from jonnybarnes/develop
MTM Dependency updates and HTML Sanitizer
2022-06-04 11:16:23 +01:00
7d1738964a
Merge pull request #261 from jonnybarnes/develop
MTM: Laravel 9
2022-05-15 17:20:12 +01:00
77004ec7d4
Merge pull request #246 from jonnybarnes/develop
MTM: Syndication links style fix
2022-01-08 21:25:14 +00:00
3f891b580c
Merge pull request #244 from jonnybarnes/develop
MTM: Various dependency updates and new deployment code
2022-01-08 19:58:35 +00:00
38f3244e31
Merge pull request #220 from jonnybarnes/develop
MTM: PHP8 support, and PostCSS Webpack work
2020-12-31 14:55:46 +00:00
650d54292e
Merge pull request #215 from jonnybarnes/develop
MTM: Get main branch up to date
2020-11-08 11:14:29 +00:00
c40548be73
Merge pull request #114 from jonnybarnes/develop
MTM: recent work on Horizon
2019-03-23 16:10:06 +00:00
225 changed files with 8140 additions and 5779 deletions

View file

@ -70,11 +70,6 @@ ADMIN_USER=admin# pick something better, this is used for `/admin`
ADMIN_PASS=password ADMIN_PASS=password
DISPLAY_NAME='Joe Bloggs'# This is used for example in the header and titles DISPLAY_NAME='Joe Bloggs'# This is used for example in the header and titles
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=
SCOUT_DRIVER=database SCOUT_DRIVER=database
SCOUT_QUEUE=false SCOUT_QUEUE=false
@ -83,8 +78,8 @@ SESSION_SAME_SITE=strict
LOG_SLACK_WEBHOOK_URL= LOG_SLACK_WEBHOOK_URL=
BRRR_WEBHOOK_URL=
FLARE_KEY= FLARE_KEY=
IGNITION_OPEN_AI_KEY= IGNITION_OPEN_AI_KEY=
BRIDGY_MASTODON_TOKEN=

View file

@ -0,0 +1,101 @@
<?php
namespace App\Console\Commands;
use App\Models\Media;
use App\Models\Note;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class MigrateMedia extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:migrate-media';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Migrate media';
/**
* Execute the console command.
*/
public function handle(): void
{
// First check new `media_note` table exists
if (! DB::getSchemaBuilder()->hasTable('media_note')) {
$this->error('The table "media_note" does not exist.');
exit(1);
}
// Load all media already saved in `media_endpoint` table
$this->line('Updating existing local media');
$mediaEndpointMedia = Media::all();
// Save relationship in new `media_note` table based on `media_endpoint.note_id`
$this->withProgressBar($mediaEndpointMedia, function (Media $mediaEndpointMediaItem) {
$note = Note::find($mediaEndpointMediaItem->note_id);
if ($note) {
$note->media()->syncWithoutDetaching($mediaEndpointMediaItem->id);
}
});
// Load all media records from `media` table
$this->line('');
$this->line('Migrating old media from S3');
$oldMedia = DB::table('media')->get();
foreach ($oldMedia as $oldMediaItem) {
// We only want to process the S3 media
if ($oldMediaItem->disk !== 's3') {
$this->warn('Original media item never stored in S3');
continue;
}
// Check media exists in S3
if (! Storage::disk('s3')->exists($oldMediaItem->id.DIRECTORY_SEPARATOR.$oldMediaItem->file_name)) {
$this->warn('Original media item not found in S3');
continue;
}
// We want to just copy the file, check it does not already exist locally
if (Storage::disk('public')->exists('media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name)) {
$this->warn('File already exists locally with filename of original media item');
continue;
}
// Save relationship based on `media.model_id`
// I have already checked they are all notes
$noteId = $oldMediaItem->model_id;
$note = Note::find($noteId);
if (! $note) {
$this->warn('Note no longer exists');
continue;
}
// Create media entry in database and attach to note
$newMediaItem = Media::create([
'path' => 'media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name,
'type' => 'image',
]);
$note->media()->syncWithoutDetaching($newMediaItem->id);
// Copy the file
Storage::disk('public')->writeStream('media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name, Storage::disk('s3')->readStream($oldMediaItem->id.DIRECTORY_SEPARATOR.$oldMediaItem->file_name));
$this->info('Media item migrated from S3');
}
$this->line('');
$this->line('Migration finished');
}
}

View file

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\Media;
use Illuminate\Console\Command;
use Illuminate\Image\ImageException;
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage;
class ReprocessMediaImages extends Command
{
protected $signature = 'app:reprocess-media-images {--dry-run : List images that would be processed without making changes}';
protected $description = 'Regenerate medium and small image variants using correct aspect-ratio scaling';
public function handle(): void
{
$media = Media::where('type', 'image')
->whereNotNull('image_widths')
->where('image_widths', '>', 1000)
->get();
$dryRun = $this->option('dry-run');
$this->info("Found {$media->count()} images to reprocess.".($dryRun ? ' (dry run)' : ''));
foreach ($media as $item) {
$path = $item->path;
if (! Storage::disk('public')->exists($path)) {
$this->warn("{$path}: original not found on public disk, skipping.");
continue;
}
if ($dryRun) {
$this->line("{$path} ({$item->image_widths}px wide)");
continue;
}
$this->info("Processing: {$path}");
$image = Image::fromStorage($path, 'public');
try {
$image->width();
} catch (ImageException) {
$this->warn(' Could not decode image, skipping.');
continue;
}
$filenameParts = explode('.', $path);
$extension = array_pop($filenameParts);
$basename = trim(implode('.', $filenameParts), '.');
Storage::disk('public')->put($basename.'-medium.'.$extension, $image->scale(width: 1000)->toBytes());
Storage::disk('public')->put($basename.'-small.'.$extension, $image->scale(width: 500)->toBytes());
$this->info(' Done.');
}
$this->info('Reprocessing complete.'.($dryRun ? ' (dry run — no files were changed)' : ''));
}
}

View file

@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Exceptions;
class MicropubUnsupportedModelException extends \Exception {}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers;
use App\Models\About;
use Illuminate\View\View;
class AboutPageController extends Controller
{
public function show(): View
{
return view('about', [
'about' => About::first()?->content,
]);
}
}

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\About;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class AboutController extends Controller
{
public function show(): View
{
$about = About::first();
return view('admin.about.show', [
'aboutEntry' => $about,
]);
}
public function update(Request $request): RedirectResponse
{
$about = About::firstOrNew();
$about->content = $request->input('content');
$about->save();
return redirect()->route('admin.about.show');
}
}

View file

@ -6,10 +6,11 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Contact; use App\Models\Contact;
use GuzzleHttp\Client;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\View\View; use Illuminate\View\View;
class ContactsController extends Controller class ContactsController extends Controller
@ -103,7 +104,7 @@ class ContactsController extends Controller
* This method attempts to find the microformat marked-up profile image * This method attempts to find the microformat marked-up profile image
* from a given homepage and save it accordingly * from a given homepage and save it accordingly
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View * @return RedirectResponse|View
*/ */
public function getAvatar(int $contactId) public function getAvatar(int $contactId)
{ {
@ -112,14 +113,13 @@ class ContactsController extends Controller
$avatar = null; $avatar = null;
$contact = Contact::findOrFail($contactId); $contact = Contact::findOrFail($contactId);
if ($contact->homepage !== null && mb_strlen($contact->homepage) !== 0) { if ($contact->homepage !== null && mb_strlen($contact->homepage) !== 0) {
$client = resolve(Client::class);
try { try {
$response = $client->get($contact->homepage); $response = Http::throw()->get($contact->homepage);
} catch (\GuzzleHttp\Exception\BadResponseException $e) { } catch (RequestException $e) {
return redirect('/admin/contacts/'.$contactId.'/edit') return redirect('/admin/contacts/'.$contactId.'/edit')
->with('error', 'Bad resposne from contact’s homepage'); ->with('error', 'Bad resposne from contact’s homepage');
} }
$mf2 = \Mf2\parse((string) $response->getBody(), $contact->homepage); $mf2 = \Mf2\parse($response->body(), $contact->homepage);
foreach ($mf2['items'] as $microformat) { foreach ($mf2['items'] as $microformat) {
if (Arr::get($microformat, 'type.0') === 'h-card') { if (Arr::get($microformat, 'type.0') === 'h-card') {
$avatarURL = Arr::get($microformat, 'properties.photo.0.value'); $avatarURL = Arr::get($microformat, 'properties.photo.0.value');
@ -128,8 +128,8 @@ class ContactsController extends Controller
} }
if ($avatarURL !== null) { if ($avatarURL !== null) {
try { try {
$avatar = $client->get($avatarURL); $avatar = Http::throw()->get($avatarURL);
} catch (\GuzzleHttp\Exception\BadResponseException $e) { } catch (RequestException $e) {
return redirect('/admin/contacts/'.$contactId.'/edit') return redirect('/admin/contacts/'.$contactId.'/edit')
->with('error', 'Unable to download avatar'); ->with('error', 'Unable to download avatar');
} }
@ -140,7 +140,7 @@ class ContactsController extends Controller
if ($filesystem->isDirectory($directory) === false) { if ($filesystem->isDirectory($directory) === false) {
$filesystem->makeDirectory($directory); $filesystem->makeDirectory($directory);
} }
$filesystem->put($directory . '/image', $avatar->getBody()); $filesystem->put($directory.'/image', $avatar->body());
return view('admin.contacts.getavatarsuccess', [ return view('admin.contacts.getavatarsuccess', [
'homepage' => parse_url($contact->homepage, PHP_URL_HOST), 'homepage' => parse_url($contact->homepage, PHP_URL_HOST),

View file

@ -128,7 +128,7 @@ class PasskeysController extends Controller
// Unset session data to mitigate replay attacks // Unset session data to mitigate replay attacks
$request->session()->forget('create_options'); $request->session()->forget('create_options');
if (empty($publicKeyCredentialCreationOptionsData)) { if (empty($publicKeyCredentialCreationOptionsData)) {
throw new WebAuthnException('No public key credential request options found'); throw new WebauthnException('No public key credential request options found');
} }
$attestationStatementSupportManager = new AttestationStatementSupportManager; $attestationStatementSupportManager = new AttestationStatementSupportManager;
@ -145,7 +145,7 @@ class PasskeysController extends Controller
); );
if (! $publicKeyCredential->response instanceof AuthenticatorAttestationResponse) { if (! $publicKeyCredential->response instanceof AuthenticatorAttestationResponse) {
throw new WebAuthnException('Invalid response type'); throw new WebauthnException('Invalid response type');
} }
$algorithmManager = new Manager; $algorithmManager = new Manager;
@ -254,7 +254,7 @@ class PasskeysController extends Controller
], 400); ], 400);
} }
$passkey = Passkey::firstWhere('passkey_id', $publicKeyCredential->id); $passkey = Passkey::firstWhere('passkey_id', Base64UrlSafe::encodeUnpadded($publicKeyCredential->rawId));
if (! $passkey) { if (! $passkey) {
return response()->json([ return response()->json([
'success' => false, 'success' => false,

View file

@ -120,6 +120,7 @@ class PlacesController extends Controller
foreach ($place1->notes as $note) { foreach ($place1->notes as $note) {
$note->place()->dissociate(); $note->place()->dissociate();
$note->place()->associate($place2->id); $note->place()->associate($place2->id);
$note->save();
} }
$place1->delete(); $place1->delete();
} }
@ -127,6 +128,7 @@ class PlacesController extends Controller
foreach ($place2->notes as $note) { foreach ($place2->notes as $note) {
$note->place()->dissociate(); $note->place()->dissociate();
$note->place()->associate($place1->id); $note->place()->associate($place1->id);
$note->save();
} }
$place2->delete(); $place2->delete();
} }

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class SettingsController extends Controller
{
public function show(): View
{
$settings = Setting::first();
return view('admin.settings.show', [
'settings' => $settings,
]);
}
public function update(Request $request): RedirectResponse
{
$settings = Setting::firstOrNew();
$settings->winter_effect_enabled = $request->boolean('winter_effect_enabled');
$settings->save();
return redirect()->route('admin.settings.show');
}
}

View file

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\MicropubToken;
use App\Services\TokenService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class TokensController extends Controller
{
/**
* Show a list of issued Micropub tokens.
*/
public function index(): View
{
$tokens = MicropubToken::latest()->get();
return view('admin.tokens.index', compact('tokens'));
}
/**
* Show the form to manually generate a new Micropub token.
*
* This is for clients (e.g. iA Writer) that don't support the IndieAuth
* PKCE flow and instead expect to be given a token directly.
*/
public function create(): View
{
return view('admin.tokens.create');
}
/**
* Manually generate a new Micropub token.
*/
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'client_id' => 'required|string',
'scope' => 'required|array|min:1',
]);
$token = resolve(TokenService::class)->getNewToken([
'me' => config('app.url'),
'client_id' => $validated['client_id'],
'scope' => implode(' ', $validated['scope']),
]);
return redirect('/admin/tokens')->with('new_token', $token);
}
/**
* Revoke a Micropub token.
*/
public function revoke(MicropubToken $token): RedirectResponse
{
$token->revoke();
return redirect('/admin/tokens');
}
}

View file

@ -17,7 +17,7 @@ class ArticlesController extends Controller
*/ */
public function index(?int $year = null, ?int $month = null): View public function index(?int $year = null, ?int $month = null): View
{ {
$articles = Article::where('published', '1') $articles = Article::published()
->date($year, $month) ->date($year, $month)
->orderBy('updated_at', 'desc') ->orderBy('updated_at', 'desc')
->simplePaginate(5); ->simplePaginate(5);

View file

@ -7,68 +7,15 @@ namespace App\Http\Controllers;
use App\Models\Article; use App\Models\Article;
use App\Models\Note; use App\Models\Note;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
class FeedsController extends Controller class FeedsController extends Controller
{ {
/**
* Returns the blog RSS feed.
*/
public function blogRss(): Response
{
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
$buildDate = $articles->first()->updated_at->toRssString();
return response()
->view('articles.rss', compact('articles', 'buildDate'))
->header('Content-Type', 'application/rss+xml; charset=utf-8');
}
/**
* Returns the blog Atom feed.
*/
public function blogAtom(): Response
{
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
return response()
->view('articles.atom', compact('articles'))
->header('Content-Type', 'application/atom+xml; charset=utf-8');
}
/**
* Returns the notes RSS feed.
*/
public function notesRss(): Response
{
$notes = Note::latest()->take(20)->get();
$buildDate = $notes->first()->updated_at->toRssString();
return response()
->view('notes.rss', compact('notes', 'buildDate'))
->header('Content-Type', 'application/rss+xml; charset=utf-8');
}
/**
* Returns the notes Atom feed.
*/
public function notesAtom(): Response
{
$notes = Note::latest()->take(20)->get();
return response()
->view('notes.atom', compact('notes'))
->header('Content-Type', 'application/atom+xml; charset=utf-8');
}
/** @todo sort out return type for json responses */
/** /**
* Returns the blog JSON feed. * Returns the blog JSON feed.
*/ */
public function blogJson(): array public function blogJson(): JsonResponse
{ {
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get(); $articles = Article::published()->latest('updated_at')->take(20)->get();
$data = [ $data = [
'version' => 'https://jsonfeed.org/version/1.1', 'version' => 'https://jsonfeed.org/version/1.1',
'title' => 'The JSON Feed for '.config('user.display_name').'’s blog', 'title' => 'The JSON Feed for '.config('user.display_name').'’s blog',
@ -94,13 +41,15 @@ class FeedsController extends Controller
]; ];
} }
return $data; return response()->json($data, 200, [
'Content-Type' => 'application/feed+json',
]);
} }
/** /**
* Returns the notes JSON feed. * Returns the notes JSON feed.
*/ */
public function notesJson(): array public function notesJson(): JsonResponse
{ {
$notes = Note::latest()->with('media', 'place', 'tags')->take(20)->get(); $notes = Note::latest()->with('media', 'place', 'tags')->take(20)->get();
$data = [ $data = [
@ -130,7 +79,9 @@ class FeedsController extends Controller
} }
} }
return $data; return response()->json($data, 200, [
'Content-Type' => 'application/feed+json',
]);
} }
/** /**
@ -138,7 +89,7 @@ class FeedsController extends Controller
*/ */
public function blogJf2(): JsonResponse public function blogJf2(): JsonResponse
{ {
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get(); $articles = Article::published()->latest('updated_at')->take(20)->get();
$items = []; $items = [];
foreach ($articles as $article) { foreach ($articles as $article) {
$items[] = [ $items[] = [

View file

@ -26,7 +26,7 @@ class FrontPageController extends Controller
->withCount(['webmentions AS reposts' => function ($query) { ->withCount(['webmentions AS reposts' => function ($query) {
$query->where('type', 'repost-of'); $query->where('type', 'repost-of');
}])->get(); }])->get();
$articles = Article::latest()->get(); $articles = Article::published()->latest()->get();
$bookmarks = Bookmark::latest()->with('tags')->get(); $bookmarks = Bookmark::latest()->with('tags')->get();
$likes = Like::latest()->get(); $likes = Like::latest()->get();

Some files were not shown because too many files have changed in this diff Show more