336 lines
13 KiB
PHP
336 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\Api\NovaCards;
|
|
|
|
use App\Jobs\UpdateNovaCardStatsJob;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\NovaCard;
|
|
use App\Models\NovaCardChallenge;
|
|
use App\Models\NovaCardReaction;
|
|
use App\Models\NovaCardVersion;
|
|
use App\Services\NovaCards\NovaCardChallengeService;
|
|
use App\Services\NovaCards\NovaCardCollectionService;
|
|
use App\Services\NovaCards\NovaCardDraftService;
|
|
use App\Services\NovaCards\NovaCardLineageService;
|
|
use App\Services\NovaCards\NovaCardPresenter;
|
|
use App\Services\NovaCards\NovaCardReactionService;
|
|
use App\Services\NovaCards\NovaCardVersionService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class NovaCardInteractionController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly NovaCardReactionService $reactions,
|
|
private readonly NovaCardCollectionService $collections,
|
|
private readonly NovaCardDraftService $drafts,
|
|
private readonly NovaCardVersionService $versions,
|
|
private readonly NovaCardChallengeService $challenges,
|
|
private readonly NovaCardLineageService $lineage,
|
|
private readonly NovaCardPresenter $presenter,
|
|
) {
|
|
}
|
|
|
|
public function lineage(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = $this->visibleCard($request, $id);
|
|
|
|
return response()->json([
|
|
'data' => $this->lineage->resolve($card, $request->user()),
|
|
]);
|
|
}
|
|
|
|
public function like(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = $this->visibleCard($request, $id);
|
|
$state = $this->reactions->setReaction($request->user(), $card, NovaCardReaction::TYPE_LIKE, true);
|
|
|
|
return response()->json(['ok' => true, ...$state]);
|
|
}
|
|
|
|
public function unlike(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = $this->visibleCard($request, $id);
|
|
$state = $this->reactions->setReaction($request->user(), $card, NovaCardReaction::TYPE_LIKE, false);
|
|
|
|
return response()->json(['ok' => true, ...$state]);
|
|
}
|
|
|
|
public function favorite(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = $this->visibleCard($request, $id);
|
|
$state = $this->reactions->setReaction($request->user(), $card, NovaCardReaction::TYPE_FAVORITE, true);
|
|
|
|
return response()->json(['ok' => true, ...$state]);
|
|
}
|
|
|
|
public function unfavorite(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = $this->visibleCard($request, $id);
|
|
$state = $this->reactions->setReaction($request->user(), $card, NovaCardReaction::TYPE_FAVORITE, false);
|
|
|
|
return response()->json(['ok' => true, ...$state]);
|
|
}
|
|
|
|
public function collections(Request $request): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'data' => $this->collections->listCollections($request->user()),
|
|
]);
|
|
}
|
|
|
|
public function storeCollection(Request $request): JsonResponse
|
|
{
|
|
$payload = $request->validate([
|
|
'name' => ['required', 'string', 'min:2', 'max:120'],
|
|
'slug' => ['nullable', 'string', 'max:140'],
|
|
'description' => ['nullable', 'string', 'max:500'],
|
|
'visibility' => ['nullable', 'in:private,public'],
|
|
]);
|
|
|
|
$collection = $this->collections->createCollection($request->user(), $payload);
|
|
|
|
return response()->json([
|
|
'collection' => [
|
|
'id' => (int) $collection->id,
|
|
'slug' => (string) $collection->slug,
|
|
'name' => (string) $collection->name,
|
|
'description' => $collection->description,
|
|
'visibility' => (string) $collection->visibility,
|
|
'cards_count' => (int) $collection->cards_count,
|
|
],
|
|
], Response::HTTP_CREATED);
|
|
}
|
|
|
|
public function updateCollection(Request $request, int $id): JsonResponse
|
|
{
|
|
$collection = $this->ownedCollection($request, $id);
|
|
$payload = $request->validate([
|
|
'name' => ['required', 'string', 'min:2', 'max:120'],
|
|
'slug' => ['nullable', 'string', 'max:140'],
|
|
'description' => ['nullable', 'string', 'max:500'],
|
|
'visibility' => ['nullable', 'in:private,public'],
|
|
]);
|
|
|
|
$collection->update([
|
|
'name' => $payload['name'],
|
|
'slug' => $payload['slug'] ?: $collection->slug,
|
|
'description' => $payload['description'] ?? null,
|
|
'visibility' => $payload['visibility'] ?? $collection->visibility,
|
|
]);
|
|
|
|
return response()->json([
|
|
'collection' => $this->presenter->collection($collection->fresh(['user', 'items.card.user.profile', 'items.card.category', 'items.card.template', 'items.card.backgroundImage', 'items.card.tags']), $request->user(), true),
|
|
]);
|
|
}
|
|
|
|
public function storeCollectionItem(Request $request, int $id): JsonResponse
|
|
{
|
|
$collection = $this->ownedCollection($request, $id);
|
|
$payload = $request->validate([
|
|
'card_id' => ['required', 'integer'],
|
|
'note' => ['nullable', 'string', 'max:500'],
|
|
'sort_order' => ['nullable', 'integer', 'min:0'],
|
|
]);
|
|
|
|
$card = $this->visibleCard($request, (int) $payload['card_id']);
|
|
$this->collections->addCardToCollection($collection, $card, $payload['note'] ?? null, $payload['sort_order'] ?? null);
|
|
|
|
return response()->json([
|
|
'collection' => $this->presenter->collection($collection->fresh(['user', 'items.card.user.profile', 'items.card.category', 'items.card.template', 'items.card.backgroundImage', 'items.card.tags']), $request->user(), true),
|
|
], Response::HTTP_CREATED);
|
|
}
|
|
|
|
public function destroyCollectionItem(Request $request, int $id, int $cardId): JsonResponse
|
|
{
|
|
$collection = $this->ownedCollection($request, $id);
|
|
$card = NovaCard::query()->findOrFail($cardId);
|
|
$this->collections->removeCardFromCollection($collection, $card);
|
|
|
|
return response()->json([
|
|
'collection' => $this->presenter->collection($collection->fresh(['user', 'items.card.user.profile', 'items.card.category', 'items.card.template', 'items.card.backgroundImage', 'items.card.tags']), $request->user(), true),
|
|
]);
|
|
}
|
|
|
|
public function challenges(Request $request): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'data' => $this->presenter->options()['challenge_feed'] ?? [],
|
|
]);
|
|
}
|
|
|
|
public function assets(Request $request): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'data' => $this->presenter->options()['asset_packs'] ?? [],
|
|
]);
|
|
}
|
|
|
|
public function templates(Request $request): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'packs' => $this->presenter->options()['template_packs'] ?? [],
|
|
'templates' => $this->presenter->options()['templates'] ?? [],
|
|
]);
|
|
}
|
|
|
|
public function save(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = $this->visibleCard($request, $id);
|
|
$payload = $request->validate([
|
|
'collection_id' => ['nullable', 'integer'],
|
|
'note' => ['nullable', 'string', 'max:500'],
|
|
]);
|
|
$collection = $this->collections->saveCard($request->user(), $card, $payload['collection_id'] ?? null, $payload['note'] ?? null);
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'saves_count' => (int) $card->fresh()->saves_count,
|
|
'collection' => [
|
|
'id' => (int) $collection->id,
|
|
'name' => (string) $collection->name,
|
|
'slug' => (string) $collection->slug,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function unsave(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = $this->visibleCard($request, $id);
|
|
$payload = $request->validate([
|
|
'collection_id' => ['nullable', 'integer'],
|
|
]);
|
|
$this->collections->unsaveCard($request->user(), $card, $payload['collection_id'] ?? null);
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'saves_count' => (int) $card->fresh()->saves_count,
|
|
]);
|
|
}
|
|
|
|
public function remix(Request $request, int $id): JsonResponse
|
|
{
|
|
$source = $this->visibleCard($request, $id);
|
|
abort_unless($source->allow_remix, 422, 'This card does not allow remixes.');
|
|
|
|
$card = $this->drafts->createRemix($request->user(), $source);
|
|
$source->increment('remixes_count');
|
|
UpdateNovaCardStatsJob::dispatch($source->id);
|
|
|
|
return response()->json([
|
|
'data' => $this->presenter->card($card, true, $request->user()),
|
|
], Response::HTTP_CREATED);
|
|
}
|
|
|
|
public function duplicate(Request $request, int $id): JsonResponse
|
|
{
|
|
$source = $this->ownedCard($request, $id);
|
|
$card = $this->drafts->createDuplicate($request->user(), $source->loadMissing('tags'));
|
|
|
|
return response()->json([
|
|
'data' => $this->presenter->card($card, true, $request->user()),
|
|
], Response::HTTP_CREATED);
|
|
}
|
|
|
|
public function versions(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = $this->ownedCard($request, $id);
|
|
$versions = $card->versions()
|
|
->latest('version_number')
|
|
->get()
|
|
->map(fn (NovaCardVersion $version): array => [
|
|
'id' => (int) $version->id,
|
|
'version_number' => (int) $version->version_number,
|
|
'label' => $version->label,
|
|
'created_at' => $version->created_at?->toISOString(),
|
|
'snapshot_json' => is_array($version->snapshot_json) ? $version->snapshot_json : [],
|
|
])
|
|
->values()
|
|
->all();
|
|
|
|
return response()->json(['data' => $versions]);
|
|
}
|
|
|
|
public function restoreVersion(Request $request, int $id, int $versionId): JsonResponse
|
|
{
|
|
$card = $this->ownedCard($request, $id);
|
|
$version = $card->versions()->findOrFail($versionId);
|
|
$card = $this->versions->restore($card, $version, $request->user());
|
|
|
|
return response()->json([
|
|
'data' => $this->presenter->card($card, true, $request->user()),
|
|
]);
|
|
}
|
|
|
|
public function submitChallenge(Request $request, int $challengeId, int $id): JsonResponse
|
|
{
|
|
$payload = $request->validate([
|
|
'note' => ['nullable', 'string', 'max:500'],
|
|
]);
|
|
|
|
return $this->submitChallengeWithPayload($request, $challengeId, $id, $payload['note'] ?? null);
|
|
}
|
|
|
|
public function submitChallengeByChallenge(Request $request, int $id): JsonResponse
|
|
{
|
|
$payload = $request->validate([
|
|
'card_id' => ['required', 'integer'],
|
|
'note' => ['nullable', 'string', 'max:500'],
|
|
]);
|
|
|
|
return $this->submitChallengeWithPayload($request, $id, (int) $payload['card_id'], $payload['note'] ?? null);
|
|
}
|
|
|
|
private function submitChallengeWithPayload(Request $request, int $challengeId, int $cardId, ?string $note = null): JsonResponse
|
|
{
|
|
$card = $this->ownedCard($request, $cardId);
|
|
abort_unless($card->status === NovaCard::STATUS_PUBLISHED, 422, 'Publish the card before entering a challenge.');
|
|
|
|
$challenge = NovaCardChallenge::query()
|
|
->where('status', NovaCardChallenge::STATUS_ACTIVE)
|
|
->findOrFail($challengeId);
|
|
|
|
$entry = $this->challenges->submit($request->user(), $challenge, $card, $note);
|
|
UpdateNovaCardStatsJob::dispatch($card->id);
|
|
|
|
return response()->json([
|
|
'entry' => [
|
|
'id' => (int) $entry->id,
|
|
'challenge_id' => (int) $entry->challenge_id,
|
|
'card_id' => (int) $entry->card_id,
|
|
'status' => (string) $entry->status,
|
|
],
|
|
'challenge_entries_count' => (int) $card->fresh()->challenge_entries_count,
|
|
]);
|
|
}
|
|
|
|
private function visibleCard(Request $request, int $id): NovaCard
|
|
{
|
|
$card = NovaCard::query()
|
|
->with(['user.profile', 'category', 'template', 'backgroundImage', 'tags', 'originalCard', 'rootCard'])
|
|
->published()
|
|
->findOrFail($id);
|
|
|
|
abort_unless($card->canBeViewedBy($request->user()), 404);
|
|
|
|
return $card;
|
|
}
|
|
|
|
private function ownedCard(Request $request, int $id): NovaCard
|
|
{
|
|
return NovaCard::query()
|
|
->with(['user.profile', 'category', 'template', 'backgroundImage', 'tags', 'versions'])
|
|
->where('user_id', $request->user()->id)
|
|
->findOrFail($id);
|
|
}
|
|
|
|
private function ownedCollection(Request $request, int $id): \App\Models\NovaCardCollection
|
|
{
|
|
return \App\Models\NovaCardCollection::query()
|
|
->where('user_id', $request->user()->id)
|
|
->findOrFail($id);
|
|
}
|
|
} |