Commit graph

36 commits

Author SHA1 Message Date
Paulo Castellano
44127c4890 Remove the Vitest frontend unit-test layer
The project's test strategy is backend + e2e only, so drop the Vitest setup that had been added for the crop math: the imageCrop.test.ts suite, vitest.config.ts, the dependency and test:unit script, the CI step, and the test:all reference. The crop math is exercised through the Pest browser test.
2026-07-04 10:53:08 -03:00
Paulo Castellano
ef81ea74a8 Add frontend unit tests and harden the cropper's test coverage
Add Vitest (test:unit) with exact-value tests for the crop math (containScale, defaultSelection, clampSelection, all resizeSelection corners, and mime/filename resolution), and wire it plus the Pest browser test into CI. The browser test now samples output pixels against a four-quadrant fixture, so it detects a blank, flipped, or wrong crop rather than only asserting a 512x512 canvas. Add composer test:all to run PHP + Vitest + browser tests locally.
2026-07-04 09:56:38 -03:00
Paulo Castellano
c6369a6ddb Crop the avatar/logo before upload with a dependency-free cropper
Selecting an avatar or workspace logo now opens a crop dialog (drag + zoom)
before uploading, so the image is framed the way it renders. The crop is
performed client-side and the resized 512x512 result is what gets uploaded.

This reworks the idea from #131 without its cropper dependency: vue-advanced-cropper
was last released ~2 years ago and we did not want an unmaintained package for
something this load-bearing. What we need is narrow (fixed 1:1, a circle/square
mask, fixed-size output), so a small canvas-based cropper covers it:

- imageCrop.ts: pure transform math (cover-fit, clamp, zoom, viewport->source).
- ImageCropperDialog.vue: CSS-transform preview, pointer drag, wheel/button zoom,
  a ResizeObserver to measure the modal (no requestAnimationFrame timing hacks),
  and a canvas toBlob only on save.
- PhotoUpload.vue: opens the cropper on file select; the mask shape follows the
  display shape (round avatar / square logo) instead of always being round.
- crop_* strings added to all 15 locales.

Also installs Pest browser testing (pest-plugin-browser + Playwright) and adds a
browser test for the crop flow. TestCase only calls withoutVite() for non-browser
tests, since browser tests need the real Vite assets to boot the SPA. The Pest
browser server does not parse multipart uploads, so the test asserts the crop
dispatches the correct upload request; endpoint persistence stays covered by
ProfileUpdateTest.
2026-07-03 21:46:19 -03:00
Paulo Castellano
3e15144399 chore: remove unused Pennant package and dead user-timezone handling
Pennant: all feature flags were replaced by BillingCycle, so remove the package
(composer), the now-empty app/Features discovery, the pennant-development skill,
and replace the create_features_table migration with a drop_features_table.

Timezone: the registration flow collected a user timezone (seeder, request
validation, hidden field) but no timezone column ever existed and CreateUser
discarded it. Remove the dead handling, the orphaned Timezone rule, and the
tests that covered the now-removed validation.
2026-06-22 13:37:38 -03:00
Paulo Castellano
246a159f34 feat(automations): multi-format RSS/Atom feeds with dynamic variables
Replace the RSS-2.0-only SimpleXML parser with SimplePie so the Fetch RSS
node reads Atom 1.0 (YouTube, GitHub, The Verge…) and RSS 2.0 + namespace
extensions (dc:, content:, media:, yt:, itunes:). Each item exposes stable
cross-format aliases (title, link, date, content, author, …) plus every
namespaced field flattened for use as {{ fetched.* }}.

Add a feed-inspection endpoint that discovers a feed's real fields and feeds
them into the editor's expression autocomplete. Allow {{ }} expressions in the
feed URL via a ResolvableUrl rule. Parsing moves to a dedicated FeedParser
service; the fetch keeps the SSRF guard and gains XXE-safe parsing.
2026-06-16 10:56:08 -03:00
Paulo Castellano
229862fcae chore: update project license from FSL to AGPL-3.0-only 2026-05-04 15:46:56 -03:00
Paulo Castellano
563f752866 chore: remove unused language files and update project configuration files 2026-05-04 14:04:03 -03:00
Paulo Castellano
20eeeaa493 feat: migrate custom API token implementation to Laravel Passport for authentication and token management 2026-05-03 18:38:17 -03:00
Paulo Castellano
8ba5877bdf feat: implement automated brand color extraction from web metadata, CSS, and logos with supporting database schema updates 2026-05-02 13:30:59 -03:00
Paulo Castellano
886fca3152 feat: use LLM to polish brand autofill when provider is configured
When config('trypost.ai.text_provider') points to a provider whose
API key is populated (services.gemini.api_key or
services.openai.api_key), the autofill action pipes the homepage
markdown through a BrandAnalyzer agent with structured output to
produce higher-quality values for:

- brand_description — 2-3 sentences summarizing the company based on
  the actual page content, not the raw meta description (which is
  often generic SEO boilerplate)
- brand_tone — classified into one of our seven enum values from the
  writing style on the page
- content_language — detected from the actual content (more reliable
  than <html lang> which is often wrong)
- brand_voice_notes — concrete writing guidelines inferred from the
  site's style, written in the detected language

When the LLM provider is NOT configured (open-source self-hosted
deploys without API keys), the action keeps the existing
deterministic meta-tag-only flow — no crash, no noise, no LLM cost.
When the LLM fails mid-request, we log a warning and fall back to
the meta-tag values so the user still gets something useful.

Stack additions:
- league/html-to-markdown ^5.1 converts the main body to clean
  markdown for the LLM input (truncated to 4000 chars).
- BrandAnalyzer agent (Agent + HasStructuredOutput) with schema
  enums matching our allowed tones and languages.
- resources/views/prompts/brand_analyzer.blade.php holds the
  instructions, including explicit enum lists and examples of good
  voice_notes.

Frontend: Brand.vue now also fills brand_tone and brand_voice_notes
from the response when present.

Tests (+3): LLM-configured happy path, no-credentials fallback
(asserts BrandAnalyzer is never prompted via preventStrayPrompts),
and LLM-exception fallback (meta tags win, brand_tone stays null).
2026-04-16 11:33:02 -03:00
Paulo Castellano
bde10059e3 feat: add brand autofill from website URL in onboarding
Users on the Brand step can type their website URL and click
'Preencher' (Portuguese) / 'Autofill' (English). The backend fetches
their homepage, parses standard meta tags, and returns:

- name        ← og:site_name | title (suffix stripped at ' | ', ' - ')
- description ← meta[name=description] | og:description
- language    ← <html lang> mapped to en / pt-BR / es
- logo        ← apple-touch-icon | largest link[rel*=icon] | og:image

The logo is downloaded, validated (mime whitelist, 2MB max), and
attached to the workspace's 'logo' media collection so the avatar
updates immediately.

Zero LLM calls, zero external APIs. Uses symfony/dom-crawler +
symfony/css-selector (newly required) for meta extraction. Everything
else (Http client, workspace media, Intervention) was already in the
project.

Security:
- SSRF guardrail: resolves the host, rejects private / loopback /
  link-local ranges, enforces http(s) scheme on both the initial
  page fetch and the logo download.
- Rate limited at 10 req/min per user via the throttle middleware
  alias on the route.
- Logo content-type must be one of the allowed image mimes; wrong
  types are silently dropped so users never see broken images.

UX:
- 'Autofill' button next to the website input, disabled until there
  is a URL; shows a spinner while running.
- If a logo was captured, a small preview appears below the input
  so users can see what was pulled before saving.
- Success and error paths both surface as vue-sonner toasts, with
  translations in en / pt-BR / es.
- Failures leave the form untouched — nothing is destructively
  overwritten if parsing gave us nothing.

Tests (16 new): action-level coverage for happy path, title-suffix
fallback, language code normalization across 6 locales, scheme
rejection, private-range SSRF rejection, implicit https prefixing,
empty sites, upstream errors, and wrong-mime logo rejection. Plus
two controller-level tests for the autofill endpoint.
2026-04-16 11:13:54 -03:00
Paulo Castellano
3d94c57748 chore: install laravel/ai SDK v0.5.1 2026-04-16 09:30:12 -03:00
Paulo Castellano
b2bf5c2059 feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.

Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 09:25:08 -03:00
Paulo Castellano
0f6ae9a4e6 feat: add PostCommentCreated broadcast event 2026-04-15 20:11:36 -03:00
Paulo Castellano
c867faa2ee fix: address code review issues for billing migration 2026-04-14 18:44:47 -03:00
Paulo Castellano
30bfb2a890 refactor: use data_get in MediaOptimizer for consistency 2026-03-31 21:25:37 -03:00
Paulo Castellano
9f3b8e547a fix: overhaul social publishing — validation, uploads, token refresh
- Fix UpdatePostRequest missing content_type, synced, meta fields
  (content_type was silently dropped, causing Instagram Reels to post as Feed)
- Create API FormRequests (StorePostRequest, UpdatePostRequest) replacing inline validation
- Fix syntax errors in all publishers ($media->isVideo() missing variable)
- Fix Instagram Feed with single video calling publishSingleImage instead of publishReel
- Fix TikTok hardcoded SELF_ONLY privacy — now queries creator_info API
- Refactor YouTubePublisher to use google/apiclient SDK with chunked resumable upload
- Fix all publishers using file_get_contents for large videos (memory overflow)
  — X, LinkedIn, LinkedInPage, Pinterest, Bluesky, Mastodon now use temp file + stream
- Fix Media::isVideo/isImage to use mime_type instead of extension
- Fix Threads not saving refresh_token (was null, now saves access_token)
- Add Instagram token refresh to publisher and ConnectionVerifier
- Fix PublishToSocialPlatform job: tries 3→1 (prevents duplicate uploads),
  timeout 60→600s, added failed() method for cleanup
- Increase Horizon worker timeout 60→630s, Redis retry_after 90→660s
- Increase upload limit 500MB→1GB
- Add mastodon to getDefaultContentType in Edit.vue
2026-03-31 19:25:19 -03:00
Paulo Castellano
176929aa12 chore: reverting domains 2026-03-31 11:35:49 -03:00
Paulo Castellano
843b3991ec chore: posthog, ui, features and more 2026-03-30 21:18:07 -03:00
Paulo Castellano
f3f72afecd refactor: auth split layout, subscribe redesign, onboarding, i18n, cookie locale
Auth pages:
- Create AuthSplitLayout with animated feature slides (6 slides, 3 languages)
- All auth pages use split layout (form left, visual right)
- Add show/hide password toggle with tooltip on Register
- Legal footer only shown on Register via showLegal prop

Subscribe page:
- Redesign to match auth card pattern (centered, clean)
- Platform icons, feature checklist, dynamic trial days (trialDays - 1)
- Add "Switch workspace" link
- Full i18n (en, es, pt-BR)

Onboarding:
- Rename URLs: step1 -> role, step2 -> connect
- Add enforceStep() to prevent skipping/going back steps
- Redirect /onboarding to /onboarding/role
- Redesign Step2 with AuthSplitLayout and compact platform list
- 21 tests covering all step enforcement scenarios

Workspaces page:
- Redesign with AuthSplitLayout (list with avatars, current badge)

Language system:
- Move locale from DB to cookie (forever, unencrypted, session.domain)
- Create SetLocale middleware (sets cookie if missing, validates against config)
- Rename lang/pt-br to lang/pt-BR
- Add dayjs es locale

Other:
- Copy utils.ts from sendkit (formatNumber, formatMoney, copyToClipboard)
- ConfirmDeleteModal with text confirmation (sendkit pattern)
- i18n for ConfirmDeleteModal internal strings (common.php)
- EmptyState component for posts index
- Exact match for "All" posts in sidebar
- Posts breadcrumbs show current status filter
- DialogFooter buttons aligned left
- API Keys page redesign with Table, DropdownMenu, EmptyState
- Extract CreateApiKeyDialog and InviteMemberDialog to components
- Remove API Keys from sidebar
- DropdownMenuItem destructive variant for Remove action
2026-03-30 11:53:42 -03:00
Paulo Castellano
a926033d06 refactor: organize middleware/requests into App/ subdirs, add Resources, fix auth routes
- Move middleware to App/ subdir (HandleInertiaRequests, HandleAppearance,
  EnsureSubscribed, EnsureUserSetupIsComplete) matching Sendkit pattern
- Move all Form Requests into organized subdirs (App/Post, App/Workspace,
  App/Media, App/Invite, App/Settings, App/Auth)
- Create AuthUserResource and AuthWorkspaceResource for HandleInertiaRequests
  shared data (role inside currentWorkspace, matching Sendkit pattern)
- Split auth.php into 3 route groups (no middleware, guest, auth) matching
  Sendkit pattern exactly
- Fix UserFactory to include all nullable attributes (current_workspace_id,
  stripe_id, pm_type, pm_last_four, trial_ends_at)
- Fix SocialAccountResource (display_name not name)
- Update frontend for new auth prop structure
- 702 tests passing (2 pre-existing Mastodon failures)
2026-03-29 21:13:30 -03:00
Paulo Castellano
fabce14aad feat: implement MCP server with tools, Post API tests, auth middleware
- Create TryPostServer MCP server with 17 tools:
  Post (List, Get, Create, Delete), Hashtag (List, Create, Update, Delete),
  Label (List, Create, Update, Delete), Workspace (Get),
  ApiKey (List, Create, Delete)
- Create AuthenticateMcpToken middleware (logs in workspace owner)
- Register mcp.auth middleware alias in bootstrap/app.php
- Create routes/ai.php with mcp.trypost.test subdomain
- Add PostApiTest with 6 tests (list, show, create, delete, isolation)
- Fix PostApiTest assertions for pagination/resource wrapping
- 704 tests passing, frontend build passing
2026-03-29 20:30:36 -03:00
Paulo Castellano
03c6cc8d1d
Merge branch 'main' into upgrade/laravel-13-inertia-3 2026-03-29 18:28:54 -03:00
Paulo Castellano
56ffe2da90 upgrade: Laravel 13, Inertia v3, remove pion/chunk-upload 2026-03-29 18:10:10 -03:00
Paulo Castellano
fafa220bfd refactor: move helpers.php to app/Helpers/upload.php 2026-03-29 17:40:02 -03:00
Paulo Castellano
8776a3233f chore: change email provider to sendkit 2026-03-16 13:14:22 -03:00
Paulo Castellano
3f5411e73c chore: add author to composer.json
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 12:15:21 -03:00
Paulo Castellano
366bf51d0a chore: update composer.json with proper project metadata
- Update name to trypost-it/trypost
- Add description and keywords for discoverability
- Add homepage and support links

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 12:14:41 -03:00
Paulo Castellano
1196551a26 feat: improvements on invites 2026-01-20 16:53:54 -03:00
Paulo Castellano
d080134c65 feat: add documentation for connecting social media platforms and getting started guide 2026-01-18 18:57:15 -03:00
Paulo Castellano
af32361e89 chore: Update and add various project dependencies. 2026-01-18 16:46:27 -03:00
Paulo Castellano
adfba2755e feat: Add Pinterest integration and user language preferences
Pinterest Integration:
- Add Pinterest OAuth controller and routes
- Add PinterestPublisher service with support for pins, video pins, and carousels
- Add PinterestPreview component with board selector and content type options
- Add Pinterest content types enum (Pin, VideoPin, Carousel)
- Add Pinterest to Platform enum with proper configuration
- Support sandbox mode via PINTEREST_SANDBOX env variable
- Pass platform-specific data (boards) through PlatformPreview

Language Feature:
- Add languages table with migration
- Add Language model and seeder (en-US, pt-BR)
- Add LanguageCombobox component for profile settings
- Set default language (en-US) on user registration
- Add language_id foreign key to users table

UI Improvements:
- Refactor PlatformPreview to support contentTypeOptions, meta, and platformData props
- Move content type and board selectors into platform-specific preview components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 13:10:01 -03:00
Paulo Castellano
7c00c3387e feat: Implement user onboarding, subscription management, and refactor social integrations with new UI components and mail templates. 2026-01-16 23:46:30 -03:00
Paulo Castellano
7867fcce02 feat: Implement real-time post status updates and configurable social platform enabling/disabling. 2026-01-16 12:36:52 -03:00
Paulo Castellano
7c80d717b1 chore: first commit 2026-01-15 14:24:39 -03:00
Paulo Castellano
6890147aa6 chore: first commit 2026-01-14 22:13:44 -03:00