Commit graph

663 commits

Author SHA1 Message Date
Paulo Castellano
757a215d0d Back run/node status and node type with TS consts
Add NodeRunStatus and RunStatus consts mirroring the backend enums, and route
the test panel and invocations list through them (plus the existing NodeType)
instead of bare 'completed'/'failed'/'fetch_rss' string checks. Type the run/
node-run interfaces with the value unions, extract the status tile classes into
a helper, and render the node type via i18n like the invocations list already does.
2026-06-13 17:50:23 -03:00
Paulo Castellano
19f331741c Use Inertia's useHttp for automation JSON calls
The test-run and invocation-detail requests used raw fetch with a hand-rolled
CSRF header. Switch to Inertia v3's useHttp (the standalone XHR client already
used across the app), which handles CSRF, headers and error payloads — dropping
the manual plumbing and reading the 422 message from error.response.data.
2026-06-13 17:41:48 -03:00
Paulo Castellano
cf3cbfba9e Simplify the cron-to-schedule inference
Rewrite inferScheduleFromCron: bail once on a pinned month, group the branches
by cadence (sub-hourly / hourly / daily / weekly / monthly), and share the
hour+minute via one `clock` object instead of repeating the same guards in
eight stacked conditionals. Behavior-preserving — verified the generate↔infer
round-trip across every preset.
2026-06-13 17:31:02 -03:00
Paulo Castellano
c79b4a4a84 Type node enum props with their value unions
The required method/unit/operator/mode props were typed as plain string after
dropping their defaults. Use the matching *Value union types (HttpMethodValue,
DelayUnitValue, ConditionOperatorValue, PublishModeValue) so the renderers are
constrained to real enum values, matching the config panels.
2026-06-13 17:15:20 -03:00
Paulo Castellano
1ac4795575 Drop redundant method default in webhook/http nodes
Webhook and HTTP nodes are always created with a method, so the ?? default was
dead. Make the prop required and remove the now-unused HttpMethod import,
matching the unit/operator/mode cleanup.
2026-06-13 17:13:32 -03:00
Paulo Castellano
9b3e9e8307 Translate node summary labels in the canvas
The Generate/Delay/Condition/Publish node summaries rendered English prose and
raw enum values (account/accounts, single, hours, contains, scheduled · +N min).
Route them through i18n: pluralize the account count, and reuse the existing
unit/operator/mode/format labels in all three locales. Drop the redundant
?? defaults where the node is always created with the value (unit, operator,
mode) and tighten those prop types to required.
2026-06-13 17:10:38 -03:00
Paulo Castellano
90e9e4bee6 Centralize timezone display in the date helper
Add date.getTimezoneAbbr() and route every "GMT-x" abbreviation through it,
removing the three inline dayjs().format('z') copies (schedule-summary,
TriggerNodeConfig, PickTimePopover) and the duplicate userTimezone helper.
Drop the pointless computed wrapper for a value that never changes, and rename
snap5 to snapToFiveMinutes.
2026-06-13 16:46:34 -03:00
Paulo Castellano
ff8fd9de40 Tidy automation frontend: extract CSS, use the Label component
- Move the automation-node and json-viewer styles out of app.css into their
  own imported stylesheets.
- Replace raw <label> elements across the node config panels and the test
  panel with the shared ui/label component.
2026-06-13 16:30:39 -03:00
Paulo Castellano
730cb9d156 Tidy automation backend: run duration, folded migrations, imports
- Add AutomationRun::durationInMilliseconds() as the single source of truth
  for the Invocations list and metrics, replacing the duplicated inline diff.
- Fold the variables and root_run_id columns into their create migrations
  (this branch isn't in production) and drop the standalone alters.
- Import Illuminate\Http\Response (aliased) instead of referencing it inline.
2026-06-13 16:30:32 -03:00
Paulo Castellano
2bd2e72656 Validate webhook payload template is JSON before it can run
A webhook node parses its payload template as JSON before resolving
placeholders, so a template with unquoted {{ }} placeholders or any malformed
JSON could be saved, tested, and activated — only to fail midway through a run.

Reject it up front instead: AutomationConfigValidator is the single source of
truth for per-node config issues (keyed to the field the editor surfaces them
under), enforced on save (field errors), on activate, and before a test run.
The editor mirrors the check to disable Test/Activate with a clear reason, and
the test panel now surfaces the server's message instead of a generic toast.
2026-06-13 16:03:19 -03:00
Paulo Castellano
867c8d8acf Merge schedule summary and timezone into one line in trigger config
Show the user's timezone inline next to clock-pinned schedules
(Runs every day at 09:00 (GMT-3)) instead of on a separate hint line, and
only when the schedule pins a time of day — minute/hour intervals omit it.
2026-06-13 15:26:54 -03:00
Paulo Castellano
a129b6b5cd Denormalize automation trigger_type into an indexed column
The scheduler command ran every minute and loaded all active automations,
then filtered by trigger_type in PHP because that value lived buried in the
nodes JSON array — effectively a full-table scan plus a JSON decode per row
each minute, discarding every non-schedule automation.

Derive trigger_type into a real, indexed column on save (recomputed in the
existing saving() hook so it can never drift from nodes) and filter on it in
SQL. Applies to both the schedule firer and the post-trigger dispatcher.
2026-06-13 15:26:54 -03:00
Paulo Castellano
605261e1b8 Back fixed-set automation strings with enums and consts
Replace magic strings across the automation domain with backed PHP enums
(HttpMethod, AuthType, DelayUnit, ScheduleField) and mirrored TS consts
(http-method, auth-type, delay-unit, schedule-field, condition-operator,
publish-mode), plus the existing Condition\Handle / Operator / Publish\Mode.

Also:
- require scheduled_offset via concrete-index required_if instead of
  defaulting to 60 when the publish mode is scheduled
- fail the webhook node explicitly when the resolved url is empty
- localize node failure messages (fetch_rss/http/webhook)
- cast resolver/strtoupper inputs to string so a present-null config value
  degrades gracefully instead of crashing
- list automations with config('app.pagination.default'), drop the perPage param
2026-06-13 15:04:24 -03:00
Paulo Castellano
ed5a757c4f Drop no-op log in Generate node
The no-active-accounts branch only logged and took no action; the
platforms loop already handles an empty active-account set.
2026-06-13 14:03:59 -03:00
Paulo Castellano
e77ba84f5d Drop redundant malformed-feed log in Fetch RSS node
The failed() result already surfaces the error; the Log::warning was
duplicate noise. Removes the now-unused Log import.
2026-06-13 14:02:42 -03:00
Paulo Castellano
478cad9f27 Tighten automation conventions: pagination, imports, i18n, enums
- Pagination: drop the perPage override from ListAutomations and the
  hardcoded page size from GetAutomationInvocations; both use
  config('app.pagination.default'). Document the rule in CLAUDE.md.
- Imports: import DomainException / InvalidArgumentException / Throwable
  instead of inline backslash references across the automation actions.
- i18n: move the hardcoded Fetch RSS and HTTP Request failure strings to
  automations.errors.* in all three locales.
- Publish: require scheduled_offset when mode is scheduled (validation)
  and drop the magic 60-minute default in the node. Fixes the required_if
  rules to reference the concrete node index instead of a wildcard that
  never resolved (also repairs the trigger cron rule).
- Condition handles: back the yes/no output handles with a shared
  Condition\Handle enum (PHP) and ConditionHandle const (TS).
2026-06-13 13:59:52 -03:00
Paulo Castellano
1b2ff23dff Guard expression resolver against malformed-UTF-8 json_encode
resolveVariable() declares a string return type, but json_encode returns
false on malformed UTF-8 (plausible for scraped feed/HTTP payloads),
which throws a TypeError under strict_types and fails the node. Encode
with JSON_PARTIAL_OUTPUT_ON_ERROR and fall back to an empty string.
2026-06-13 10:25:25 -03:00
Paulo Castellano
fe8cd8a970 Fix no-unused-expressions lint error in trigger weekday toggle
Replace the ternary-as-statement with an explicit if/else so
@typescript-eslint/no-unused-expressions stops flagging it.
2026-06-13 10:10:34 -03:00
Paulo Castellano
37a7a64ff0 Drop the custom-cron schedule option from automations
Remove the "Custom (Cron)" schedule field — too technical for the editor.
The remaining presets (minutes/hours/days/weeks/months) cover the need and
still build the cron string under the hood.

Removed at the root: the ScheduleField.Custom enum case, the custom-cron
input and select option, the schedule_custom_cron type field and its
schedule-summary handling, the backend validation (Rule::in and the
schedule_custom_cron rule), the i18n keys, and the custom round-trip test
case. Existing automations keep firing — the scheduler runs off the stored
cron string, not schedule_field.
2026-06-13 10:06:38 -03:00
Paulo Castellano
31b6544c57 Cover node-run error paths and align webhook failure handling
Wrap the webhook HTTP send in a try/catch so a connection error returns
a clean failed result (reason: request_failed) instead of bubbling up as
a job failure — matching the HTTP request node.

Add tests for the gaps in node-run coverage:
- HTTP request: basic auth, PUT/PATCH/DELETE, non-2xx responses,
  connection exceptions, and an items_path that doesn't resolve to a list.
- Webhook: every HTTP method, header expression resolution, and the new
  connection-failure path.
- Fetch RSS: non-2xx feed responses, malformed XML, items without a
  publish date (skipped), and the link fallback when an item has no guid.
- Delay: unknown unit throws.
- Publish: dry runs don't publish or queue.
2026-06-13 09:56:18 -03:00
Paulo Castellano
cda197adf8 Update automation guides and UI components
- Updated the 'guide' label in English, Spanish, and Portuguese translations to provide clearer context: changed from 'Guide' to 'Learn how it works' and its translations.
- Removed the EditorGuide component from the automation form, replacing it with a tooltip that links to the documentation.
- Adjusted the icon used for the help button from IconHelp to IconLifebuoy for better visual representation.
- Enhanced the tooltip functionality to provide guidance on automation actions directly within the UI.
2026-06-12 19:41:25 -03:00
Paulo Castellano
3e43da29e0 Add Workflow/Invocations/Metrics/Settings tabs to automations
Split the automation detail screen into four route-based tabs behind a
shared AutomationHeader:

- Workflow: the existing editor canvas.
- Invocations: a paginated, filterable run log with expandable per-node
  detail, a refresh control, and a loading state.
- Metrics: KPI cards, a runs-over-time @unovis chart with locale-aware
  date labels, and a posts-by-platform breakdown over a date range.
- Settings: rename, an activate/pause switch, and a danger-zone delete.

Invocations and Metrics report only real executions via a new
productionRuns scope, so manual test runs (dry or with real data) never
leak into the log or the charts. The now-unused excludingDryRuns scope
is removed.

Generated copy now flows the most-restrictive platform context through
the humanizer too, and the editor guide documents every available
expression grouped by source node.
2026-06-12 19:19:46 -03:00
Paulo Castellano
a09b1f45c2 Structure brand voice and make generated copy platform-aware
Replace free-text brand_tone/brand_voice_notes with a single structured
brand_voice_traits JSON column backed by the BrandVoiceTrait enum, exposed
as choice-chip pills in the brand settings UI and autofillable from a site.
Brand voice and visuals become per-automation toggles on the Generate node.

Unify the image controls into one 0-10 picker (0 = text-only, 1 = single,
2+ = carousel) and feed the generator the most restrictive selected network
so copy fits every platform. Pass that same platform context through the
humanizer pass — extracted into a shared ResolvesPlatformCopyBudget trait —
so the rewrite can no longer drift past the character cap the generator
respected, in both the automation and manual creation flows.

Persist the trigger node's schedule editor fields on save (they were
silently dropped by validated() for lacking validation rules).
2026-06-12 17:09:39 -03:00
Paulo Castellano
448ae73389 Add expression autocomplete, side-panel editor, and richer HTTP fetch
Automations editor:
- {{ }} expression autocomplete in CodeMirror, scoped to the braces and
  graph-aware (suggests only what upstream nodes provide + variables + now);
  migrate the Generate prompt to CodeMirror so it shares the same completions
- Expandable editors: an expand button slides out a side-by-side panel
  (matching the sidebar card), with a minimize control; the inline field
  collapses to a hint while editing in the panel
- Hover-revealed editor toolbar (expand/copy) with styled tooltips so the
  buttons no longer obscure the text while reading
- Beta badge on the Automations sidebar item
- Delete a single connection with Backspace/Delete (edge selection)
- Re-key node config so switching between same-type nodes refreshes the form

HTTP fetch node — cover every JSON response shape:
- Top-level array, object map (items_path=*), array of primitives, and NDJSON
- Key-based dedup via item_key_path (seen-set, FIFO-capped) for feeds without
  dates; first poll records a baseline and emits nothing (date path too)

Fan-out test visibility:
- root_run_id links every forked branch back to the run that started a test,
  so the test panel aggregates all branches instead of one

Fix a few pre-existing type issues (ScheduleData import, padded minute,
optional created_at).
2026-06-12 11:31:58 -03:00
Paulo Castellano
9a692b4608 Enhance automation functionality: Introduce workflow variables and improve node validation
- Added support for workflow variables in automations, allowing users to define reusable values.
- Implemented validation for Generate nodes to ensure intended image counts align with selected accounts.
- Updated automation models and requests to handle new variables, including encryption for sensitive data.
- Enhanced UI to display variables and their management within the automation editor.
- Improved error handling for webhook and HTTP nodes to prevent requests to invalid URLs.
- Refactored various components for better context resolution during automation runs.
2026-06-11 15:47:29 -03:00
Paulo Castellano
4efaa0bf99 Harden automations module: full-post generation, reliable runs, editor UX
Generation
- Generate node now produces the full post (text + AI image + carousel)
  via a shared PostImagePipeline extracted from StreamPostCreation
- Generate config UI mirrors the /posts/create wizard (carousel slide
  count, include-image toggle); drop the decorative format/unsplash keys

Flow correctness
- RSS/HTTP nodes expose named has-items (default) and no-items output
  handles, labeled and colored like the Condition node
- AdvanceAutomationRun records a no_matching_edge terminal instead of
  completing silently; "0 new items" feedback in the test panel
- Manual/test runs no longer persist the production dedup watermark

Run reliability
- Pause truly halts in-flight runs (production only; manual test runs
  always run regardless of automation status)
- ProcessAutomationNode::failed() marks the run failed
- automation:recover-stuck-runs and automation:prune-dry-runs commands

Webhook / HTTP
- Branded User-Agent (config-driven) on outbound webhook + http_request
- Webhook fails on invalid JSON instead of silently sending {}
- HTTP custom headers editor; CodeMirror-based CodeEditor for JSON

Editor UX
- Header Test button only opens the panel; the panel has a Run button
  (saves first) and owns the with-real-data toggle
- Clicking a node closes the test panel and opens its config
- Node cards: max-width + truncate so long URLs don't grow the node
2026-06-10 20:45:01 -03:00
Paulo Castellano
af4d83190e Move automation reads and delete into actions 2026-06-10 18:10:13 -03:00
Paulo Castellano
ab7c7c8bdf Dispatch post-trigger automations asynchronously via job 2026-06-10 17:46:46 -03:00
Paulo Castellano
6d3a375add Add strict_types to automation files and AutomationNodeState factory 2026-06-10 17:26:50 -03:00
Paulo Castellano
41edf3123d Register observers via #[ObservedBy] attribute instead of the service provider
Move PostObserver, AutomationRunObserver, and AutomationNodeRunObserver onto
their models with the #[ObservedBy] attribute (the Laravel-preferred way) and
drop AppServiceProvider::configureObservers() plus its now-unused imports.
2026-06-10 17:19:32 -03:00
Paulo Castellano
21b14f7893 Merge main into feat/automations-module
Conflict resolutions + integration fixes:
- CreatePost: kept the branch's merge-into-existing meta persistence (equivalent
  to main's #86 replace on create, and what the automations flow was built on).
- FacebookSettings.vue: kept both new defaults (previewOnly + meta).
- RunGenerateNode + GenerateNodeConfig.vue: ContentType::InstagramCarousel was
  removed on main (#80); an IG carousel is now a multi-image instagram_feed, so
  the carousel-capable list uses InstagramFeed.
- GenerateNodeTest: fixtures use the ContentType enum and the new instagram_feed
  carousel signal.
2026-06-10 17:09:54 -03:00
Paulo Castellano
d6a82c03ae
Merge pull request #87 from trypostit/chore/rename-channels-tab
Rename post editor 'Schedule' tab to 'Channels'
2026-06-10 16:32:48 -03:00
Paulo Castellano
8dad00d4df
Merge branch 'main' into chore/rename-channels-tab 2026-06-10 16:30:02 -03:00
Paulo Castellano
7178f8ecc8
Merge pull request #68 from Falconiere/fix/linkedin-drop-r_basicprofile
fix(linkedin): drop deprecated r_basicprofile from default scopes
2026-06-10 16:29:45 -03:00
Paulo Castellano
2b75820544 chore: small improvements 2026-06-10 16:20:00 -03:00
Paulo Castellano
8b8b59f52e
Merge branch 'main' into fix/linkedin-drop-r_basicprofile 2026-06-10 16:07:52 -03:00
Paulo Castellano
bac93adaab
Merge branch 'main' into chore/rename-channels-tab 2026-06-10 16:07:38 -03:00
Paulo Castellano
525cf5f6b3
Merge pull request #86 from trypostit/feat/api-mcp-post-meta-parity
Post platform meta (aspect ratio) parity across API and MCP
2026-06-10 16:06:55 -03:00
Paulo Castellano
aadb525af0 Rename post editor 'Schedule' tab to 'Channels'
The tab holds publish targets, per-platform settings, and labels — not
scheduling (the date/time control lives in the editor header). Renames the
i18n key posts.edit.tabs.schedule -> tabs.channels (Channels / Canais /
Canales) and the single $t reference. The scheduling button string and the
tab's internal value are untouched.
2026-06-10 16:06:02 -03:00
Paulo Castellano
efe49c34de Make AspectRatio the single source for crop math; fill test gaps
Review follow-ups:
- AspectRatio::toFloat() now owns the crop ratio math; CropsImageForAspectRatio
  delegates to it so the enum is the single source of truth for both validation
  and cropping (no more parallel literal map).
- API update controller now reloads postPlatforms before returning, so the
  update response reflects the persisted platform meta/content_type (was stale).
- Tests: AspectRatio enum unit test; API valid-update read-back + 'original'
  on create; MCP response read-back + valid update.
2026-06-10 15:52:41 -03:00
Paulo Castellano
ce6ee04883 Bring post platform meta (aspect ratio) to parity across API and MCP
PR #82 made `meta.aspect_ratio` crop Facebook (and already Instagram) feed
images at publish time, but the API and MCP surfaces only half-supported it:
you couldn't set meta at creation, the value wasn't validated, and responses
never returned it. This closes those gaps.

- New `AspectRatio` enum is the single source of truth for the allowed ratios
  (1:1, 4:5, 16:9, original). App/API/MCP requests now validate via
  `Rule::enum(AspectRatio::class)` — an invalid ratio is rejected everywhere
  instead of silently center-cropping to square.
- API `StorePostRequest` and MCP `CreatePostTool` now accept `platforms.*.meta`;
  `CreatePost` persists it. MCP create documents `meta` in its schema.
- `Api\PostPlatformResource` now exposes `meta`, so API and MCP responses return
  the aspect_ratio (and other per-platform meta) a client set.
2026-06-10 15:36:47 -03:00
Falconiere Barbosa
a2a7f44c9a
refactor(linkedin): drive OAuth scopes from LINKEDIN_SCOPES env via config
Replace the additive LINKEDIN_EXTRA_SCOPES approach with a single
full-override env var per flow, exploded into an array at the config
layer (config/trypost.php -> platforms.linkedin{,-page}.scopes). This
keeps env values as plain comma-separated strings, lets self-hosters
override the entire set in one place, and removes the controller-side
scope-merge logic.

- config/trypost.php: explode LINKEDIN_SCOPES / LINKEDIN_PAGE_SCOPES
  into the scopes arrays (deprecated r_basicprofile stays out of the
  personal default)
- LinkedInController: drop resolveScopes(), read config scopes directly
- LinkedInPageController: drop the hardcoded $scopes property, read
  config scopes at both call sites
- tests: drive the connect scope assertions from config overrides
- .env.example, docker/.env.docker.example: document LINKEDIN_SCOPES
  and LINKEDIN_PAGE_SCOPES
2026-06-10 15:04:57 -03:00
Falconiere Barbosa
8e164bda36
refactor(linkedin): dedupe connect-scope test setup into shared closure 2026-06-10 15:04:57 -03:00
Falconiere Barbosa
21bbe96d6b
refactor(linkedin): move OAuth scopes into config/trypost.php platforms
Consolidate the default scope set alongside the existing LinkedIn host
config under config/trypost.php -> platforms.linkedin, matching the
project convention that per-platform service config lives there. The
default still drops the deprecated r_basicprofile scope, and
LINKEDIN_EXTRA_SCOPES stays additive (merged onto the defaults rather
than replacing them) so operators can opt back into legacy scopes
without risking a misconfigured full-replacement.

- config/trypost.php: add scopes + extra_scopes to platforms.linkedin
- config/services.php: drop the moved extra_scopes key
- LinkedInController::resolveScopes(): read both from trypost config
- tests: repoint config() overrides to the new key
2026-06-10 15:04:57 -03:00
Paulo Castellano
4987a31e8a
Merge branch 'main' into fix/linkedin-drop-r_basicprofile 2026-06-10 14:57:40 -03:00
Paulo Castellano
693e9c9b40
Merge pull request #82 from trypostit/69-feature-aspect-ratio-selection-for-facebook-posts-like-instagram
Aspect ratio selection for Facebook posts (like Instagram)
2026-06-10 14:56:14 -03:00
Paulo Castellano
03e129f401
Merge branch 'main' into 69-feature-aspect-ratio-selection-for-facebook-posts-like-instagram 2026-06-10 14:37:30 -03:00
Paulo Castellano
03a3d9eb7e
Merge branch 'main' into fix/linkedin-drop-r_basicprofile 2026-06-10 14:31:56 -03:00
Paulo Castellano
2b620124d2
Merge pull request #85 from trypostit/chore/copilot-review-instructions
Add Copilot code review instructions
2026-06-10 14:31:44 -03:00
Paulo Castellano
bcefe66da2 Enrich Copilot review instructions with examples and missing conventions 2026-06-10 14:26:54 -03:00