2026-01-15 01:13:44 +00:00
|
|
|
<?php
|
|
|
|
|
|
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 03:20:43 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
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-30 00:13:30 +00:00
|
|
|
namespace App\Http\Requests\App\Post;
|
2026-01-15 01:13:44 +00:00
|
|
|
|
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 22:25:19 +00:00
|
|
|
use App\Enums\Post\Status;
|
|
|
|
|
use App\Enums\PostPlatform\ContentType;
|
2026-01-15 01:13:44 +00:00
|
|
|
use Illuminate\Foundation\Http\FormRequest;
|
2026-03-30 17:58:25 +00:00
|
|
|
use Illuminate\Validation\Rule;
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
|
|
|
class UpdatePostRequest extends FormRequest
|
|
|
|
|
{
|
|
|
|
|
public function authorize(): bool
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function rules(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
2026-04-01 15:57:43 +00:00
|
|
|
'status' => ['required', 'string', Rule::in([Status::Draft->value, Status::Scheduled->value, Status::Publishing->value])],
|
2026-04-15 23:11:36 +00:00
|
|
|
'content' => ['nullable', 'string', 'max:63206'],
|
|
|
|
|
'media' => ['sometimes', 'array'],
|
|
|
|
|
'media.*.id' => ['required', 'string'],
|
|
|
|
|
'media.*.path' => ['required', 'string', 'max:500'],
|
|
|
|
|
'media.*.url' => ['required', 'string', 'max:2048'],
|
feat: @mentions in comments, AI Action layer + MCP tools, settings tabs
Mentions in post comments
- @mention autocomplete (workspace members, current user excluded) with
marker syntax @[uuid] persisted, display names rendered via CommentBody
chips; live edit replaces markers with names and converts back on save.
- NotifyMentions action with workspace-scoped membership check, dedupes
same user, only newly-added mentions on update.
- Email + in-app via SendNotification job, respecting per-user
notification_preferences.mentioned_in_comment.
- Heartbeat-based presence (Cache, 60s TTL, 30s ping) so online recipients
get only the in-app notification — no email noise.
- Real-time bell on workspace.{id}.user.{id} private channel
(NotificationCreated event), scoped channel name avoids client-side
filtering and lays out a convention for future workspace channels.
- Mailable localized via lang/{en,es,pt-BR}/mail.php; Maizzle source
template for the email is committed and built into resources/views/mail.
AI generation refactor (Action layer + MCP)
- Extracted Actions/Ai/Generate{Image,Video} with QuotaExhaustedException
so agent tools and MCP tools share a single domain entry point.
- Mcp/Tools/Ai/Generate{Image,Video}Tool registered in TryPostServer; both
return MediaResource payloads.
- Orientation::imageApiSize maps non-OpenAI ratios to 1:1/2:3/3:2.
- config/ai.php is now the single source of truth driven by env, removing
the trypost.ai shim. Default text/image providers flipped to OpenAI.
Settings/UX
- /settings/workspace split into shadcn Tabs (Workspace / Brand / Users)
with three components.
- /assets and the in-editor MediaPicker open the ImagePreviewDialog
lightbox on image click while preserving action button behaviour.
- Comments tab landed via ?tab=comments&comment=<id> from notification
click (scroll-to + temporary highlight).
- Mention autocomplete popover flips above when near the viewport bottom.
- Real social platform PNGs replace Tabler brand glyphs in schedule
pills and post list, with hover tooltip carrying display_name + handle.
Bug fixes
- AcceptInvite: controller now passes workspace + role payload that the
Vue page expects; login/register CTAs preselect the invite email.
- WorkspaceInvite mailable: stopped referencing nonexistent
$invite->workspace and $invite->role; column added to the migration,
Invite model casts role to WorkspaceRole, CreateInvite persists it.
- PostCommentCreated: added broadcastAs so .PostCommentCreated actually
matches the Echo listener; payload now includes mentioned_users so
receivers render the chip correctly without a refetch.
- Preview components for X/Pinterest/Threads/Bluesky/LinkedIn/Mastodon/
TikTok/YouTube switched from item.type === 'image' to
!isVideoMedia(item) so media without a persisted type still renders.
- UpdatePostRequest now accepts media.*.{type,mime_type,size,...} so the
posts.media JSON keeps the metadata that the previews need.
- Removed throttle:6,1 from social connect routes (was 429ing legitimate
OAuth retries).
- Used MediaType enum cases instead of literal 'image'/'video' strings
when creating media rows.
Tests
- MentionParser unit tests, NotifyMentions feature tests including
online/offline channel selection and preference gating, MCP AI tool
happy paths, MentionedInComment mailable rendering, AcceptInvite +
search-members + index mentioned_users path. 1229 passing.
2026-05-01 23:59:03 +00:00
|
|
|
'media.*.type' => ['sometimes', 'nullable', 'string', 'max:32'],
|
|
|
|
|
'media.*.mime_type' => ['sometimes', 'nullable', 'string', 'max:255'],
|
|
|
|
|
'media.*.original_filename' => ['sometimes', 'nullable', 'string', 'max:500'],
|
|
|
|
|
'media.*.size' => ['sometimes', 'nullable', 'integer'],
|
|
|
|
|
'media.*.meta' => ['sometimes', 'nullable', 'array'],
|
2026-04-01 15:19:24 +00:00
|
|
|
'scheduled_at' => [
|
|
|
|
|
'sometimes',
|
|
|
|
|
'nullable',
|
|
|
|
|
'date',
|
|
|
|
|
Rule::when(
|
2026-04-01 17:29:03 +00:00
|
|
|
$this->input('status') === Status::Scheduled->value,
|
2026-04-01 15:19:24 +00:00
|
|
|
['after:now']
|
|
|
|
|
),
|
|
|
|
|
],
|
2026-04-15 23:11:36 +00:00
|
|
|
'platforms' => ['sometimes', 'array'],
|
2026-03-30 17:58:25 +00:00
|
|
|
'platforms.*.id' => ['required', 'uuid', Rule::exists('post_platforms', 'id')->where('post_id', $this->route('post')->id ?? $this->route('post'))],
|
2026-04-15 23:11:36 +00:00
|
|
|
'platforms.*.content_type' => ['sometimes', 'string', Rule::in(array_column(ContentType::cases(), 'value'))],
|
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 22:25:19 +00:00
|
|
|
'platforms.*.meta' => ['nullable', 'array'],
|
2026-04-23 16:23:24 +00:00
|
|
|
'platforms.*.meta.privacy_level' => ['sometimes', 'nullable', 'string', Rule::in(['PUBLIC_TO_EVERYONE', 'MUTUAL_FOLLOW_FRIENDS', 'FOLLOWER_OF_CREATOR', 'SELF_ONLY'])],
|
2026-04-02 20:57:06 +00:00
|
|
|
'platforms.*.meta.auto_add_music' => ['sometimes', 'boolean'],
|
|
|
|
|
'platforms.*.meta.allow_comments' => ['sometimes', 'boolean'],
|
|
|
|
|
'platforms.*.meta.allow_duet' => ['sometimes', 'boolean'],
|
|
|
|
|
'platforms.*.meta.allow_stitch' => ['sometimes', 'boolean'],
|
|
|
|
|
'platforms.*.meta.is_aigc' => ['sometimes', 'boolean'],
|
2026-04-23 16:23:24 +00:00
|
|
|
'platforms.*.meta.disclose' => ['sometimes', 'boolean'],
|
2026-04-02 20:57:06 +00:00
|
|
|
'platforms.*.meta.brand_content_toggle' => ['sometimes', 'boolean'],
|
|
|
|
|
'platforms.*.meta.brand_organic_toggle' => ['sometimes', 'boolean'],
|
2026-01-26 18:18:35 +00:00
|
|
|
'label_ids' => ['sometimes', 'array'],
|
2026-03-30 17:58:25 +00:00
|
|
|
'label_ids.*' => ['uuid', Rule::exists('workspace_labels', 'id')->where('workspace_id', $this->user()->currentWorkspace->id)],
|
2026-01-15 01:13:44 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function messages(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
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 22:25:19 +00:00
|
|
|
'status.required' => 'The post status is required.',
|
|
|
|
|
'status.in' => 'Invalid post status.',
|
|
|
|
|
'platforms.*.content_type.in' => 'Invalid content type.',
|
2026-01-15 17:24:39 +00:00
|
|
|
'scheduled_at.after' => 'The scheduled date must be in the future.',
|
2026-01-15 01:13:44 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
}
|