Commit graph

16 commits

Author SHA1 Message Date
Paulo Castellano
8d7dcdf6eb refactor(social): trim verbose comments + harden X chunked upload from review
Cold-review follow-ups on the PR:
- Trim the oversized docblocks/inline comments added across the API controller,
  MediaAttacher, Post, the publish job, and the X publisher to one line (keeping
  the @param/@return array-shape annotations).
- XPublisher::chunkedUpload now accepts ?string $mediaCategory and only sends
  media_category when present — getMediaCategory() can return null, so the strict
  string param was a latent TypeError (unreachable on X today, removed anyway).
- Fix MediaAttacher docblocks: the file imports Type as MediaType, so the
  @param array<Type> annotations didn't resolve — now array<MediaType>.
- Tests: cover the failed() job hook genericizing a raw error, and X failing
  cleanly (XPublishException) when media can't be downloaded.
2026-06-28 20:37:04 -03:00
Paulo Castellano
81d43c30f4 fix(api): download and host external media URLs on post create/update
The public REST API accepted inline post media as a free-form array and stored
it verbatim, so a client could create/update a post whose media was a bare
external URL we never hosted. Publishing then depended on that third-party URL
staying alive — when it 404'd (e.g. an image proxy), the post failed across
platforms (Facebook 'unsupported media type', X 'HTTP 404', Instagram 'could
not fetch media').

Inline media URLs on create/update now go through the same download + MIME-
validate + host path as the attach-from-url endpoint (MediaAttacher), so the
stored media always points at our own storage. Items already hosted (carrying a
path) pass through untouched. If any URL can't be fetched the request is
rejected with 422 and nothing is persisted, so a post is never created with
broken media. MCP and the web flow were already safe and are unchanged.

- MediaAttacher: extract fetchToWorkspace() + add resolveInlineMedia()
- Post::allowedMediaTypesFor() so the create flow can compute allowed types
  without a persisted post
- API Store/UpdatePostRequest: media.* item rules (mirroring the web; prevents
  validated() from stripping hosted-item keys)
- PostController store()/update(): host external media before persisting
2026-06-28 17:28:05 -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
335180d7a1 refactor: move post-state concerns out of MediaAttacher
MediaAttacher had grown into a 180-line class with four private
methods doing different things — download, validation, storage,
and post-state mutation. Two of those (lock-and-merge into the
JSON media column, intersection of allowed media types per
platform) are post concerns, not network concerns.

Move them onto the Post model:

  Post::allowedMediaTypes(): array<Type>
      The intersection of media types accepted by every enabled
      platform. Used to be inlined as MediaAttacher::allowedMediaTypesFor.

  Post::appendMedia(array $items): void
      Lock-then-merge into the post's media[] JSON column so
      concurrent writers don't overwrite each other. Used to be
      MediaAttacher::mergeIntoPostMedia.

MediaAttacher now reads cleanly:

  attachFromUrls   — orchestrate (loop URLs, batch the appendMedia)
  attachOne        — download → validate type/size → handoff
  download         — Http::sink + progress abort, returns DTO

Drops to ~120 lines with no nested concerns. The streaming progress
abort uses the same throw-RuntimeException pattern, but now
contained in download() instead of leaking into the orchestrator.
2026-05-04 15:17:48 -03:00
Paulo Castellano
dc75e2b381 refactor: standardize post platform data structure and simplify display logic in PostResource 2026-05-03 22:11:58 -03:00
Paulo Castellano
1e1519876d feat: replace legacy AI assistant with modular post content generation, review, and template management system 2026-05-03 09:36:50 -03:00
Paulo Castellano
dafdd5da43 feat: media gallery picker, custom emoji picker, preview tabs, real platform logos
- gallery: extract /assets tabs (uploads, Unsplash, Giphy) into shared
  GalleryBrowser used by both /assets and a new MediaPickerDialog inside the
  post editor; add JSON search endpoint for workspace assets with tests
- emoji: replace broken emoji-picker-element web component with a custom
  EmojiPicker (full Unicode set, search, categories, recently-used,
  light/dark, i18n)
- preview tab: platform selector pills, variant tabs (data-driven from
  content_types map) so the user can switch Feed/Reel/Story etc. and have
  it autosave through the same handler ScheduleTab uses
- platform logos: shared usePlatformLogo composable (logo + label + content
  types); replaces inline maps across 5 components, fixes
  instagram-facebook falling back to default.png
- tooltips: hover details (display_name · @username + platform label) on
  platform avatars across editor, posts list and calendar
- settings cards: show ` · @username` in the title bar so multiple accounts
  on the same network are distinguishable
- routes: drop the throttle:6,1 group middleware on social connect routes
  (was 429ing legitimate OAuth retries) and rely on the default limiter
2026-05-01 14:53:49 -03:00
Paulo Castellano
91e0930182 feat: add ai_messages table and model 2026-04-15 21:01:26 -03:00
Paulo Castellano
7f491b2c2c feat: add post_comments table, model, and factory 2026-04-15 20:08:16 -03:00
Paulo Castellano
56b8c92e72 refactor: settings redesign, Spanish translations, language system, strict_types
Settings pages:
- Redesign layout to match Sendkit (max-w-4xl, space-y-12, Separator sections)
- Merge Members page into Workspace settings with Table, invite Dialog, ConfirmDeleteModal
- Add workspace logo upload/delete routes and controller methods
- Translate all hardcoded strings in Workspace.vue modals

Language system:
- Drop languages table, replace language_id FK with locale string column on users
- Create config/languages.php for available languages and default locale
- Add Spanish (es) translations (13 files)
- Simplify HandleInertiaRequests, ProfileController, RegisteredUserController

Code quality:
- Add declare(strict_types=1) to all PHP files
- Fix MastodonPublisher using wrong attribute (filename -> original_filename)
- Fix HasMediaTest for new has_photo/photo_url accessors
- Fix PublishToSocialPlatformTest type error revealed by strict_types
- Remove orphaned Language model from AppServiceProvider morph map
- Update User TypeScript interface (has_photo, photo_url, locale)
- Eager load media relation on workspaces to prevent N+1
- Add 8 new tests for workspace logo upload/delete
- Update workspace settings test to assert members/invitations props

All 710 tests passing.
2026-03-30 00:20:43 -03:00
Paulo Castellano
8689e54e55 refactor: restructure to Actions, subdomain routes (app/api), API tokens
- Extract business logic from controllers into Action classes:
  Post/, Workspace/, Hashtag/, Label/, Invite/, ApiKey/
- Create subdomain routing: app.trypost.test (Inertia dashboard),
  api.trypost.test (REST API with token auth)
- Add ApiToken model with tp_ prefix, token_lookup/hash auth
- Add AuthenticateApiToken middleware for API authentication
- Create Api controllers with JSON Resources for all entities
- Create App controllers that use Actions + Inertia responses
- Organize Form Requests into Api/ and App/ directories
- Add api_tokens migration
- Update all route names with app. prefix
- Update all tests to use new route names (684 passing)
2026-03-29 19:24:28 -03:00
Paulo Castellano
c0886737c4 feat: add labels and hashtags to posts
- Add many-to-many relationship between posts and workspace labels
- Add label selector in post edit page with autosave
- Display labels in posts list
- Add hashtags modal with search for appending hashtags to post content
- Fix sidebar active state for posts edit page
- Fix AvatarImage null src warning in SocialAccountsGrid
- Fix new post state by using Inertia::location for full page reload
- Hide close button on CommandDialog by default
2026-01-26 15:18:35 -03:00
Paulo Castellano
7042bcb5ae feat: Implement soft deletes for workspace hashtags and labels, and integrate content_type into the initial post platforms table creation. 2026-01-17 14:44:37 -03:00
Paulo Castellano
d538537706 feat: Implement user onboarding, subscription management, and refactor social integrations with new UI components and mail templates. 2026-01-17 12:36:49 -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