Commit graph

721 commits

Author SHA1 Message Date
Paulo Castellano
c71234337d
Merge pull request #96 from trypostit/fix/past-due-no-forced-subscribe
Past-due access, reliable checkout.completed, and complete morph map
2026-06-14 16:18:51 -03:00
Paulo Castellano
65116de859 Register all models in the morph map + lock it with a test
Add the five automation models to Relation::enforceMorphMap(), document the
all-models-must-be-mapped rule in CLAUDE.md, and add MorphMapTest to fail
when any app/Models class is missing from the map.
2026-06-14 16:12:49 -03:00
Paulo Castellano
f223cf14ce Fire checkout.completed reliably for trial-with-card checkouts
A trial-with-card subscription is already subscribed() (status trialing)
by the time the webhook lands, so /billing/processing usually mounts
already-active and the false->true poll transition the event depended on
never happened — only 3 of 66 real subscriptions emitted checkout.completed.

Complete the purchase from whichever path runs first (onMounted when already
active, or the poll transition), de-duplicated per checkout session via a
one-time Cache::add gate on session_id so back-button/refresh can't re-fire.
2026-06-14 16:12:49 -03:00
Paulo Castellano
8e334cd676 Keep past_due subscribers in-app instead of forcing re-subscribe
A past_due subscription made subscribed() return false (Cashier default),
so EnsureAccountReady redirected to /subscribe — which starts a brand-new
checkout and creates a second subscription. Past-due users already have a
subscription; they only need to update their payment method.

Enable Cashier::keepPastDueSubscriptionsActive() so past_due counts as
active and users keep navigating. Surface a past-due notice in the sidebar
footer linking to the Stripe billing portal (not /subscribe). Both trial
modes (subscription trial / generic trial) are unaffected.
2026-06-14 15:46:17 -03:00
Paulo Castellano
11c27dfd62
Merge pull request #95 from trypostit/feat/telegram-channel
Add Telegram publishing channel
2026-06-14 14:46:33 -03:00
Paulo Castellano
c9f086ea94 Use formatNumberCompact helper in post platform metrics 2026-06-14 14:09:39 -03:00
Paulo Castellano
389177c25b Soften Telegram preview bubble shadow (drop trypost ink shadow) 2026-06-14 14:07:36 -03:00
Paulo Castellano
0c3c837bd3 Polish Telegram preview: simulate channel reactions 2026-06-14 13:58:14 -03:00
Paulo Castellano
548138f277 Render Telegram subscribers as an icon pill with divider before reactions 2026-06-14 13:53:57 -03:00
Paulo Castellano
a5b33f5b0a Show Telegram subscribers on post metrics; render reactions as compact pills 2026-06-14 13:50:12 -03:00
Paulo Castellano
fc1d51ca60 Telegram connect dialog: drop footer Close button, top-align icon 2026-06-14 13:35:03 -03:00
Paulo Castellano
b9fa8be513 Group Telegram service classes under Services/Social/Telegram 2026-06-14 13:32:03 -03:00
Paulo Castellano
80b68088a0 Type Telegram media kinds with a TelegramMediaType enum 2026-06-14 13:23:05 -03:00
Paulo Castellano
b6a3cb886c Centralize Telegram Bot API URL/token construction in a TelegramApi helper 2026-06-14 12:41:19 -03:00
Paulo Castellano
e907ad087f Lock Telegram @mention passthrough in sanitizer test 2026-06-14 11:40:55 -03:00
Paulo Castellano
c719480cf1 Harden Telegram reactions/analytics from PR review; add scoping + edge-case tests 2026-06-14 11:37:04 -03:00
Paulo Castellano
ba246a3ddd Add Telegram analytics: per-post reactions (webhook) + channel subscribers 2026-06-14 11:18:40 -03:00
Paulo Castellano
ce5d59a149 Detect Telegram connection via broadcast instead of polling 2026-06-14 10:58:13 -03:00
Paulo Castellano
f5d6c4a186 Fetch Telegram channel photo synchronously in the connect action (drop avatar job) 2026-06-14 10:30:17 -03:00
Paulo Castellano
f194bde58d Fall back to display_name for disconnect confirmation when username is null (Telegram) 2026-06-14 10:21:52 -03:00
Paulo Castellano
b9cc12aef7 Fetch Telegram channel photo as avatar; extract ConnectTelegramChannel action 2026-06-14 10:18:44 -03:00
Paulo Castellano
58eab0619b Use TelegramConnectStatus enum for connect status instead of magic strings 2026-06-14 09:59:36 -03:00
Paulo Castellano
92e6425906 Use Platform enum instead of magic string in AddSocialDialog 2026-06-14 09:55:50 -03:00
Paulo Castellano
ec5a3a3b16 Add cursor-pointer and tooltip to Telegram copy button 2026-06-14 09:42:31 -03:00
Paulo Castellano
d5da44fd75 Fix Telegram dialog overflow (min-w-0 grid item), round icon, dedupe copy toast 2026-06-14 09:36:42 -03:00
Paulo Castellano
33bc001977 Truncate the long Telegram connect code so the dialog doesn't overflow 2026-06-14 09:32:09 -03:00
Paulo Castellano
e72bf51c38 Make webhook host configurable via WEBHOOK_URL (sendkit-style domain group) 2026-06-14 09:31:00 -03:00
Paulo Castellano
cc61adbac2 Move webhook routes into dedicated routes/webhook.php 2026-06-14 09:23:46 -03:00
Paulo Castellano
d729c34c0e Harden Telegram connect: one-time code, drop session, decode validation; switch icon to png; widen add-social dialog 2026-06-14 09:18:24 -03:00
Paulo Castellano
2284596f9d Make Telegram connect codes stateless: drop the model/table, use a signed code + session 2026-06-14 08:57:38 -03:00
Paulo Castellano
816d367dbe Add return type to telegram exception test helper 2026-06-13 22:58:04 -03:00
Paulo Castellano
c096092c7b Polish PR: status FormRequest, sanitizer anchor guard, webhook limit, dedupe OAuth popup 2026-06-13 22:48:21 -03:00
Paulo Castellano
2585d89cb4 Address PR review: connect-status enum, publisher cleanup, fill test gaps 2026-06-13 22:38:23 -03:00
Paulo Castellano
deb1c6fa69 Extract Telegram webhook registration into an action 2026-06-13 22:20:52 -03:00
Paulo Castellano
f378469842 Wire Telegram into the post composer (preview, content type, logo) 2026-06-13 22:10:12 -03:00
Paulo Castellano
b504243212 Document Telegram env vars in docker .env example 2026-06-13 22:02:02 -03:00
Paulo Castellano
b6605ea228 Document Telegram env vars in .env.example 2026-06-13 22:01:37 -03:00
Paulo Castellano
79acdccc45 Add Telegram connection UI (connect dialog + code polling) 2026-06-13 21:58:49 -03:00
Paulo Castellano
a4cf8aa4ce Add Telegram connection flow (controller + webhook)
Connect a channel by issuing a one-time code the user posts as /connect <code>
in their channel. A secret-token-guarded webhook matches the code, links the
channel as a SocialAccount (chat_id in meta), and records it on the request so
the connect endpoint can poll for completion. Adds the TelegramConnectRequest
model + migration, the connect/status endpoints, the public webhook route (CSRF
exempt), a ConnectionVerifier branch (getChat liveness), and a telegram:set-webhook
command. Tests cover the code issue, webhook link, secret rejection, expired/
unknown codes, status polling, and the command.
2026-06-13 21:39:03 -03:00
Paulo Castellano
9634e88e5d Add Telegram publishing (backend foundation)
Register Telegram as a platform: Platform/ContentType enum cases, a
platforms.telegram config block (shared bot token via env), TelegramPublisher
(sendMessage / sendPhoto|Video|Document / sendMediaGroup over the Bot API, HTML
parse mode, 4096 limit with long text split off a 1024 caption), wired into the
publisher dispatch. Add a Telegram ContentSanitizer branch (Telegram-allowed
HTML + ampersand escaping), MediaOptimizer/profile-url/factory support, and the
TelegramPublishException. Tests cover text, single media, album, long-text split,
overflow, API errors, private-channel URLs, and sanitization.
2026-06-13 21:25:31 -03:00
Paulo Castellano
986e2661f9
Merge pull request #94 from trypostit/fix/bluesky-publisher-hardening
Harden BlueskyPublisher: link facets, conventions, NSID lexicon, tests
2026-06-13 20:38:09 -03:00
Paulo Castellano
9864ea96b6 Cover handle-resolution errors and configured web-app host
Add a test that a network error during handle resolution degrades the mention
to plain text instead of failing the post (exercises the resolveHandleToDid
try/catch), and a test asserting the post URL is built from the configured
web_app host (a revert to a hardcoded host would now fail).
2026-06-13 20:30:41 -03:00
Paulo Castellano
444d522012 Lock lexicon values and the URL paren-keep branch in tests
Add a unit test asserting each BlueskyLexicon constant equals its AT Protocol
NSID — a wrong value (not just a typo'd name) would otherwise fail silently at
runtime. Add a test that a URL with a matching '(' keeps its trailing ')'
(Wikipedia-style), covering the previously-untested keep branch of the trim.
2026-06-13 20:25:05 -03:00
Paulo Castellano
8cfdd4c128 Centralize AT Protocol NSIDs in BlueskyLexicon
The Bluesky lexicon identifiers (createRecord, createSession, feed.post, facet
types, etc.) were repeated as magic strings across BlueskyPublisher,
BlueskyAnalytics, ConnectionVerifier and BlueskyController, where a typo fails
silently at runtime as "Invalid request". Define them once as named constants
so a typo is an undefined-constant error instead. Tests keep the literal NSIDs
as the independent contract.
2026-06-13 20:18:39 -03:00
Paulo Castellano
c86596ada3 Assert image embed and 4-image cap in tests
The image tests only checked that createRecord was sent — the faked empty
download meant no blob was ever attached, so they never verified the embed.
Drive a real (mocked) optimize + upload and assert the createRecord record
carries an app.bsky.embed.images embed, and that six images upload exactly four.
2026-06-13 20:07:25 -03:00
Paulo Castellano
84052a2f1c Tidy BlueskyPublisher conventions
Import Exception instead of referencing it inline, read the blob via data_get,
source the web-app host from config (new bluesky.web_app key) instead of
hardcoding bsky.app, and drop the getUtf8ByteOffset no-op — the PREG /u offset
is already in bytes, so the call sites use it directly.
2026-06-13 20:04:40 -03:00
Paulo Castellano
2ff34369e0 Stop URL facets swallowing trailing punctuation
The URL regex captured trailing sentence punctuation and an unmatched closing
paren ("see https://x.com).") into the link facet, so Bluesky rendered the
punctuation as part of a malformed link. Trim it like the official atproto
tokenizer. Add tests for the trim and for byte-offset correctness after
multibyte characters (emoji/accents).
2026-06-13 20:02:54 -03:00
Paulo Castellano
7baf1b7df5
Merge pull request #93 from Schrall/fix/bluesky-mention-facet-resolve-did
Fix: resolve Bluesky @mention handles to DIDs (mentions caused "Invalid post data")
2026-06-13 19:57:17 -03:00
Paulo Castellano
60094c9316 Simplify handle resolution to a single AppView call
resolveHandle is a public read served by the AppView, so drop the PDS-first
auth, the multi-endpoint fallback loop and the threaded service/account args in
favor of one unauthenticated GET against config public_appview — matching how
BlueskyAnalytics already reads from the AppView. The try/catch stays so a
network error degrades the mention to plain text instead of failing the post.
Remove the now-obsolete fallback test.
2026-06-13 18:41:43 -03:00
Paulo Castellano
5227cde5c7 Cache unresolvable handles and tighten offset types
Memoize by key (array_key_exists) so a handle that fails to resolve is tried
once per post instead of on every repeat occurrence, and add a test that a
repeated handle resolves once. Cast the PREG_OFFSET_CAPTURE offsets to int to
match getUtf8ByteOffset's signature, and reword the resolver docblock to say
"configured default service" instead of naming bsky.social.
2026-06-13 18:35:30 -03:00