trypost/app/Http/Controllers/App/MediaController.php
Paulo Castellano 74c6442728 refactor: code review fixes — policies, enums, data_get, tests
- Refactor WorkspacePolicy to use pivot role instead of workspace.user_id
- Add manageBilling policy (owner only) to BillingController
- Fix ApiKeyController authorization (view → manageTeam for store/destroy)
- Fix WorkspaceInviteController using workspace.user_id for owner checks
- Fix WorkspaceController settings is_owner using workspace.user_id
- Create PostAction enum for UpdatePost/PostController action strings
- Create ApiToken\Status enum
- Add User::SUBSCRIPTION_NAME constant, replace all hardcoded 'default'
- Convert wantsEmailFor to accept NotificationType enum
- Convert all $data[] to data_get() across publishers, controllers, jobs
- Fix SocialLoginController callback missing try/catch
- Fix SocialController::toggleActive missing workspace null check
- Fix UpdatePost NPE on meta merge when postPlatform not found
- Remove HTML5 required attributes from form inputs
- Convert function declarations to arrow functions in Vue components
- Replace hardcoded URLs with Wayfinder route helpers
- Replace new Date() with dayjs
- Add 16 new test files covering policies, authorization, publishing
2026-03-31 00:40:18 -03:00

190 lines
6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\App;
use App\Http\Requests\App\Media\StoreChunkedMediaRequest;
use App\Http\Requests\App\Media\StoreMediaRequest;
use App\Models\Media;
use App\Models\Post;
use App\Models\PostPlatform;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class MediaController extends Controller
{
public function store(StoreMediaRequest $request): JsonResponse
{
$model = $this->resolveModel($request->input('model'), $request->input('model_id'));
$this->authorizeModelOwnership($model, $request);
$collection = $request->input('collection', 'default');
$media = $model->addMedia(
$request->file('media'),
$collection
);
return response()->json([
'id' => $media->id,
'group_id' => $media->group_id,
'url' => $media->url,
'type' => $media->type->value,
'original_filename' => $media->original_filename,
]);
}
public function storeChunked(StoreChunkedMediaRequest $request): JsonResponse
{
$tempFile = $this->chunkTempPath($request->chunkIdentifier());
$this->appendChunk($tempFile, $request->getContent(), $request->isFirstChunk());
if (! $request->isLastChunk()) {
return response()->json([
'done' => false,
'progress' => $request->progress(),
]);
}
$model = $this->resolveModel($request->input('model'), $request->input('model_id'));
$this->authorizeModelOwnership($model, $request);
$media = $model->addMediaFromPath(
$tempFile,
$request->input('file_name'),
$request->input('collection'),
);
@unlink($tempFile);
return response()->json([
'done' => true,
'id' => $media->id,
'group_id' => $media->group_id,
'url' => $media->url,
'type' => $media->type->value,
'original_filename' => $media->original_filename,
]);
}
public function destroy(string $modelId, Media $media, Request $request): JsonResponse
{
if ($media->mediable_id !== $modelId) {
abort(403);
}
$model = $media->mediable;
$this->authorizeModelOwnership($model, $request);
$media->delete();
return response()->json(['success' => true]);
}
public function reorder(Request $request): JsonResponse
{
$request->validate([
'media' => 'required|array',
'media.*.id' => 'required|exists:medias,id',
'media.*.order' => 'required|integer|min:0',
]);
$workspace = $request->user()->currentWorkspace;
$mediaIds = collect($request->input('media'))->pluck('id');
// Verify all media belongs to the current workspace
$ownedCount = Media::whereIn('id', $mediaIds)
->whereHasMorph('mediable', [PostPlatform::class], fn ($query) => $query->whereHas('post', fn ($q) => $q->where('workspace_id', $workspace->id))
)->count();
if ($ownedCount !== $mediaIds->count()) {
abort(403);
}
foreach ($request->input('media') as $item) {
Media::where('id', data_get($item, 'id'))->update(['order' => data_get($item, 'order')]);
}
return response()->json(['success' => true]);
}
public function duplicate(Media $media, Request $request): JsonResponse
{
$sourceModel = $media->mediable;
$this->authorizeModelOwnership($sourceModel, $request);
$targets = $request->input('targets', []);
$duplicates = [];
foreach ($targets as $target) {
$model = $this->resolveModel(data_get($target, 'model'), data_get($target, 'model_id'));
$this->authorizeModelOwnership($model, $request);
$collection = data_get($target, 'collection', $media->collection);
$duplicate = $model->media()->create([
'group_id' => $media->group_id,
'collection' => $collection,
'type' => $media->type,
'path' => $media->path,
'original_filename' => $media->original_filename,
'mime_type' => $media->mime_type,
'size' => $media->size,
'order' => $media->order,
'meta' => $media->meta,
]);
$duplicates[] = [
'id' => $duplicate->id,
'group_id' => $duplicate->group_id,
'mediable_id' => $duplicate->mediable_id,
'mediable_type' => $duplicate->mediable_type,
'url' => $duplicate->url,
'type' => $duplicate->type->value,
'original_filename' => $duplicate->original_filename,
];
}
return response()->json($duplicates);
}
private function authorizeModelOwnership(Model $model, Request $request): void
{
$workspace = $request->user()->currentWorkspace;
if ($model instanceof PostPlatform) {
if ($model->post->workspace_id !== $workspace->id) {
abort(403);
}
} elseif ($model instanceof Post) {
if ($model->workspace_id !== $workspace->id) {
abort(403);
}
}
}
private function resolveModel(string $alias, string $id): Model
{
$modelClass = Relation::getMorphedModel($alias) ?? $alias;
return $modelClass::findOrFail($id);
}
private function chunkTempPath(string $identifier): string
{
return storage_path("app/private/chunks/{$identifier}");
}
private function appendChunk(string $path, string $content, bool $isFirst): void
{
$directory = dirname($path);
if (! is_dir($directory)) {
mkdir($directory, 0755, true);
}
file_put_contents($path, $content, $isFirst ? 0 : FILE_APPEND);
}
}