Forum: - TipTap WYSIWYG editor with full toolbar - @emoji-mart/react emoji picker (consistent with tweets) - @mention autocomplete with user search API - Fix PHP 8.4 parse errors in Blade templates - Fix thread data display (paginator items) - Align forum page widths to max-w-5xl Discover: - Extract shared _nav.blade.php partial - Add missing nav links to for-you page - Add Following link for authenticated users Feed/Posts: - Post model, controllers, policies, migrations - Feed page components (PostComposer, FeedCard, etc) - Post reactions, comments, saves, reports, sharing - Scheduled publishing support - Link preview controller Profile: - Profile page components (ProfileHero, ProfileTabs) - Profile API controller Uploads: - Upload wizard enhancements - Scheduled publish picker - Studio status bar and readiness checklist
116 lines
3.3 KiB
PHP
116 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Posts;
|
|
|
|
use App\Models\Post;
|
|
use App\Models\PostTarget;
|
|
use App\Models\User;
|
|
use App\Services\ContentSanitizer;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class PostService
|
|
{
|
|
public function __construct(
|
|
private PostCountersService $counters,
|
|
private PostHashtagService $hashtags,
|
|
) {}
|
|
|
|
/**
|
|
* Create a new text post.
|
|
*
|
|
* @param User $user
|
|
* @param string $visibility
|
|
* @param string|null $body
|
|
* @param array $targets [['type'=>'artwork','id'=>123], ...]
|
|
* @return Post
|
|
*/
|
|
public function createPost(
|
|
User $user,
|
|
string $type,
|
|
string $visibility,
|
|
?string $body,
|
|
array $targets = [],
|
|
?array $linkPreview = null,
|
|
?array $taggedUsers = null,
|
|
?Carbon $publishAt = null,
|
|
): Post {
|
|
$sanitizedBody = $body ? ContentSanitizer::render($body) : null;
|
|
|
|
$status = ($publishAt && $publishAt->isFuture())
|
|
? Post::STATUS_SCHEDULED
|
|
: Post::STATUS_PUBLISHED;
|
|
|
|
$meta = [];
|
|
if ($linkPreview && ! empty($linkPreview['url'])) {
|
|
$meta['link_preview'] = array_intersect_key($linkPreview, array_flip(['url', 'title', 'description', 'image', 'site_name']));
|
|
}
|
|
if ($taggedUsers && count($taggedUsers) > 0) {
|
|
$meta['tagged_users'] = array_map(
|
|
fn ($u) => ['id' => (int) $u['id'], 'username' => (string) $u['username'], 'name' => (string) ($u['name'] ?? $u['username'])],
|
|
array_slice($taggedUsers, 0, 10),
|
|
);
|
|
}
|
|
|
|
return DB::transaction(function () use ($user, $type, $visibility, $sanitizedBody, $targets, $meta, $status, $publishAt) {
|
|
$post = Post::create([
|
|
'user_id' => $user->id,
|
|
'type' => $type,
|
|
'visibility' => $visibility,
|
|
'body' => $sanitizedBody,
|
|
'meta' => $meta ?: null,
|
|
'status' => $status,
|
|
'publish_at' => $publishAt,
|
|
]);
|
|
|
|
foreach ($targets as $target) {
|
|
PostTarget::create([
|
|
'post_id' => $post->id,
|
|
'target_type' => $target['type'],
|
|
'target_id' => $target['id'],
|
|
]);
|
|
}
|
|
|
|
// Sync hashtags extracted from the body
|
|
if ($sanitizedBody) {
|
|
$this->hashtags->sync($post, $sanitizedBody);
|
|
}
|
|
|
|
return $post;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update body/visibility of an existing post.
|
|
*/
|
|
public function updatePost(Post $post, ?string $body, ?string $visibility): Post
|
|
{
|
|
$updates = [];
|
|
|
|
if ($body !== null) {
|
|
$updates['body'] = ContentSanitizer::render($body);
|
|
}
|
|
|
|
if ($visibility !== null) {
|
|
$updates['visibility'] = $visibility;
|
|
}
|
|
|
|
$post->update($updates);
|
|
|
|
// Re-sync hashtags whenever body changes
|
|
if (isset($updates['body'])) {
|
|
$this->hashtags->sync($post, $updates['body']);
|
|
}
|
|
|
|
return $post->fresh();
|
|
}
|
|
|
|
/**
|
|
* Soft-delete a post (cascades to targets/reactions/comments via DB).
|
|
*/
|
|
public function deletePost(Post $post): void
|
|
{
|
|
$post->delete();
|
|
}
|
|
}
|