feat: ship creator journey v2 and profile updates

This commit is contained in:
2026-04-12 21:42:07 +02:00
parent a2457f4e49
commit d5cff21ea2
335 changed files with 20147 additions and 1545 deletions

View File

@@ -17,6 +17,7 @@ use App\Models\Artwork;
use App\Models\Collection;
use App\Models\Group;
use App\Models\User;
use App\Services\Maturity\ArtworkMaturityService;
use App\Support\AvatarUrl;
use App\Services\ThumbnailPresenter;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
@@ -33,6 +34,7 @@ class CollectionService
private readonly SmartCollectionService $smartCollections,
private readonly CollectionCollaborationService $collaborators,
private readonly GroupMembershipService $groupMembers,
private readonly ArtworkMaturityService $maturity,
) {
}
@@ -492,12 +494,14 @@ class CollectionService
return $query->get();
}
public function getCollectionDetailArtworks(Collection $collection, bool $ownerView, int $perPage = 24): LengthAwarePaginator
public function getCollectionDetailArtworks(Collection $collection, bool $ownerView, int $perPage = 24, ?User $viewer = null): LengthAwarePaginator
{
if ($collection->isSmart()) {
return $this->smartCollections->resolveArtworks($collection, $ownerView, $perPage);
}
$viewer ??= $ownerView ? null : request()->user();
$query = $collection->artworks()
->with([
'user:id,name,username',
@@ -515,12 +519,21 @@ class CollectionService
->where('artworks.is_approved', true)
->whereNotNull('artworks.published_at')
->where('artworks.published_at', '<=', now());
if ($this->viewerShouldHideMature($viewer)) {
$query->whereRaw('COALESCE(artworks.is_mature, 0) = 0')
->whereRaw("COALESCE(artworks.maturity_status, 'clear') != ?", [ArtworkMaturityService::STATUS_SUSPECTED]);
}
}
$query = match ($collection->sort_mode) {
Collection::SORT_NEWEST => $query->orderByDesc('artworks.published_at'),
Collection::SORT_OLDEST => $query->orderBy('artworks.published_at'),
Collection::SORT_POPULAR => $query->orderByDesc('artworks.view_count')->orderByPivot('order_num'),
Collection::SORT_POPULAR => $query
->leftJoin('artwork_stats as artwork_stats_sort', 'artwork_stats_sort.artwork_id', '=', 'artworks.id')
->reorder()
->orderByRaw('COALESCE(artwork_stats_sort.views, 0) DESC')
->orderBy('collection_artwork.order_num'),
default => $query->orderByPivot('order_num'),
};
@@ -843,15 +856,18 @@ class CollectionService
public function mapCollectionCardPayloads(iterable $collections, bool $ownerView = false, ?User $viewer = null): array
{
$viewer ??= $ownerView ? null : request()->user();
$collectionList = $collections instanceof EloquentCollection
? $collections
: new EloquentCollection(is_array($collections) ? $collections : iterator_to_array($collections));
$collectionIds = $collectionList->pluck('id')->map(static fn ($id) => (int) $id)->all();
$hideMatureCovers = ! $ownerView && $this->viewerShouldHideMature($viewer);
$firstArtworkMap = $this->firstArtworkMapForCollections(
$collectionIds,
! $ownerView
! $ownerView,
$hideMatureCovers,
);
$savedCollectionIds = $viewer && ! $ownerView && $collectionIds !== []
@@ -866,9 +882,11 @@ class CollectionService
return $collectionList->map(function (Collection $collection) use ($ownerView, $viewer, $firstArtworkMap, $savedCollectionIds) {
$resolvedCover = $collection->isSmart()
? $this->smartCollections->firstArtwork($collection, $ownerView)
: $collection->resolvedCoverArtwork(! $ownerView);
: $collection->resolvedCoverArtwork(! $ownerView, ! $ownerView && $this->viewerShouldHideMature($viewer));
$fallbackCover = $firstArtworkMap->get((int) $collection->id);
$cover = $resolvedCover ?? $fallbackCover;
$cover = $this->eligibleCoverArtwork($resolvedCover, ! $ownerView, ! $ownerView && $this->viewerShouldHideMature($viewer))
? $resolvedCover
: $fallbackCover;
$summary = $collection->summary ?? $collection->description;
$isSaved = in_array((int) $collection->id, $savedCollectionIds, true);
$canSave = ! $ownerView && $viewer && $collection->canBeSavedBy($viewer);
@@ -958,6 +976,7 @@ class CollectionService
'last_recommendation_refresh_at' => optional($collection->last_recommendation_refresh_at)?->toISOString(),
'smart_summary' => $collection->isSmart() ? $this->smartCollections->smartSummary($collection->smart_rules_json) : null,
'cover_image' => $cover ? $this->mapArtworkThumb($cover) : null,
'cover_image_maturity' => ! $ownerView && $cover ? $this->maturity->presentation($cover, $viewer) : null,
'cover_artwork_id' => $cover?->id,
'saved' => $isSaved,
'save_url' => $canSave ? route('collections.save', ['collection' => $collection->id]) : null,
@@ -976,11 +995,18 @@ class CollectionService
})->all();
}
public function mapCollectionDetailPayload(Collection $collection, bool $ownerView = false): array
public function mapCollectionDetailPayload(Collection $collection, bool $ownerView = false, ?User $viewer = null): array
{
$viewer ??= $ownerView ? null : request()->user();
$hideMatureCovers = ! $ownerView && $this->viewerShouldHideMature($viewer);
$cover = $collection->isSmart()
? $this->smartCollections->firstArtwork($collection, $ownerView)
: $collection->resolvedCoverArtwork(! $ownerView);
: $collection->resolvedCoverArtwork(! $ownerView, $hideMatureCovers);
if (! $this->eligibleCoverArtwork($cover, ! $ownerView, $hideMatureCovers)) {
$cover = $this->firstArtworkMapForCollections([(int) $collection->id], ! $ownerView, $hideMatureCovers)
->get((int) $collection->id);
}
return [
'id' => $collection->id,
@@ -1074,7 +1100,8 @@ class CollectionService
'expired_at' => optional($collection->expired_at)?->toISOString(),
'history_count' => (int) $collection->history_count,
'cover_image' => $cover ? $this->mapArtworkThumb($cover) : null,
'cover_artwork_id' => $collection->cover_artwork_id,
'cover_image_maturity' => ! $ownerView && $cover ? $this->maturity->presentation($cover, $viewer) : null,
'cover_artwork_id' => $cover?->id,
'smart_rules_json' => $collection->smart_rules_json,
'layout_modules' => $this->normalizeLayoutModules($collection->layout_modules_json, $collection->type, (bool) $collection->allow_comments, (bool) $collection->allow_submissions),
'smart_summary' => $collection->isSmart() ? $this->smartCollections->smartSummary($collection->smart_rules_json) : null,
@@ -1194,7 +1221,7 @@ class CollectionService
* @param array<int, int> $collectionIds
* @return SupportCollection<int, Artwork>
*/
private function firstArtworkMapForCollections(array $collectionIds, bool $publicOnly): SupportCollection
private function firstArtworkMapForCollections(array $collectionIds, bool $publicOnly, bool $hideMature = false): SupportCollection
{
if ($collectionIds === []) {
return collect();
@@ -1210,6 +1237,10 @@ class CollectionService
->whereNotNull('a.published_at')
->where('a.published_at', '<=', now());
})
->when($hideMature, function ($query): void {
$query->whereRaw('COALESCE(a.is_mature, 0) = 0')
->whereRaw("COALESCE(a.maturity_status, 'clear') != ?", ['suspected']);
})
->orderBy('ca.collection_id')
->orderBy('ca.order_num')
->select(['ca.collection_id', 'a.id'])
@@ -1237,7 +1268,7 @@ class CollectionService
$contentType = $category?->contentType;
$stats = $artwork->stats;
return array_merge([
return $this->maturity->decoratePayload(array_merge([
'id' => $artwork->id,
'title' => $artwork->title,
'slug' => $artwork->slug,
@@ -1261,7 +1292,7 @@ class CollectionService
'username' => $artwork->user->username,
'profile_url' => route('profile.show', ['username' => strtolower((string) $artwork->user->username)]),
] : null,
], $extra);
], $extra), $artwork, request()->user());
}
private function normalizeLayoutModules(?array $modules, string $type, bool $allowComments, bool $allowSubmissions, bool $includePresentation = true): array
@@ -1458,6 +1489,29 @@ class CollectionService
return $presented['url'] ?? $artwork->thumbUrl('md');
}
private function eligibleCoverArtwork(?Artwork $artwork, bool $publicOnly, bool $hideMature): bool
{
if (! $artwork) {
return false;
}
if ($publicOnly && (! (bool) $artwork->is_public || ! (bool) $artwork->is_approved || $artwork->published_at === null || $artwork->published_at->gt(now()))) {
return false;
}
if (! $hideMature) {
return true;
}
return ! (bool) $artwork->is_mature
&& (string) ($artwork->maturity_status ?? ArtworkMaturityService::STATUS_CLEAR) !== ArtworkMaturityService::STATUS_SUSPECTED;
}
private function viewerShouldHideMature(?User $viewer): bool
{
return $this->maturity->viewerPreferences($viewer)['visibility'] === ArtworkMaturityService::VIEW_HIDE;
}
private function slugExistsForUser(User $user, string $slug, ?int $ignoreCollectionId = null, ?Group $group = null): bool
{
return Collection::query()