optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

View File

@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Tag;
use App\Models\UserNegativeSignal;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
final class DiscoveryNegativeSignalController extends Controller
{
public function hideArtwork(Request $request): JsonResponse
{
$payload = $request->validate([
'artwork_id' => ['required', 'integer', 'exists:artworks,id'],
'algo_version' => ['nullable', 'string', 'max:64'],
'source' => ['nullable', 'string', 'max:64'],
'meta' => ['nullable', 'array'],
]);
$signal = UserNegativeSignal::query()->updateOrCreate(
[
'user_id' => (int) $request->user()->id,
'signal_type' => 'hide_artwork',
'artwork_id' => (int) $payload['artwork_id'],
],
[
'tag_id' => null,
'algo_version' => $payload['algo_version'] ?? null,
'source' => $payload['source'] ?? 'api',
'meta' => (array) ($payload['meta'] ?? []),
]
);
$this->recordFeedbackEvent(
userId: (int) $request->user()->id,
artworkId: (int) $payload['artwork_id'],
eventType: 'hide_artwork',
algoVersion: isset($payload['algo_version']) ? (string) $payload['algo_version'] : null,
meta: (array) ($payload['meta'] ?? [])
);
return response()->json([
'stored' => true,
'signal_id' => (int) $signal->id,
'signal_type' => 'hide_artwork',
], Response::HTTP_ACCEPTED);
}
public function dislikeTag(Request $request): JsonResponse
{
$payload = $request->validate([
'tag_id' => ['nullable', 'integer', 'exists:tags,id'],
'tag_slug' => ['nullable', 'string', 'max:191'],
'artwork_id' => ['nullable', 'integer', 'exists:artworks,id'],
'algo_version' => ['nullable', 'string', 'max:64'],
'source' => ['nullable', 'string', 'max:64'],
'meta' => ['nullable', 'array'],
]);
$tagId = isset($payload['tag_id']) ? (int) $payload['tag_id'] : null;
if ($tagId === null && ! empty($payload['tag_slug'])) {
$tagId = Tag::query()->where('slug', (string) $payload['tag_slug'])->value('id');
}
abort_if($tagId === null || $tagId <= 0, Response::HTTP_UNPROCESSABLE_ENTITY, 'A valid tag is required.');
$signal = UserNegativeSignal::query()->updateOrCreate(
[
'user_id' => (int) $request->user()->id,
'signal_type' => 'dislike_tag',
'tag_id' => $tagId,
],
[
'artwork_id' => null,
'algo_version' => $payload['algo_version'] ?? null,
'source' => $payload['source'] ?? 'api',
'meta' => (array) ($payload['meta'] ?? []),
]
);
$this->recordFeedbackEvent(
userId: (int) $request->user()->id,
artworkId: isset($payload['artwork_id']) ? (int) $payload['artwork_id'] : (int) (($payload['meta']['artwork_id'] ?? 0)),
eventType: 'dislike_tag',
algoVersion: isset($payload['algo_version']) ? (string) $payload['algo_version'] : null,
meta: array_merge((array) ($payload['meta'] ?? []), ['tag_id' => $tagId])
);
return response()->json([
'stored' => true,
'signal_id' => (int) $signal->id,
'signal_type' => 'dislike_tag',
'tag_id' => $tagId,
], Response::HTTP_ACCEPTED);
}
public function unhideArtwork(Request $request): JsonResponse
{
$payload = $request->validate([
'artwork_id' => ['required', 'integer', 'exists:artworks,id'],
'algo_version' => ['nullable', 'string', 'max:64'],
'meta' => ['nullable', 'array'],
]);
$deleted = UserNegativeSignal::query()
->where('user_id', (int) $request->user()->id)
->where('signal_type', 'hide_artwork')
->where('artwork_id', (int) $payload['artwork_id'])
->delete();
if ($deleted > 0) {
$this->recordFeedbackEvent(
userId: (int) $request->user()->id,
artworkId: (int) $payload['artwork_id'],
eventType: 'unhide_artwork',
algoVersion: isset($payload['algo_version']) ? (string) $payload['algo_version'] : null,
meta: (array) ($payload['meta'] ?? [])
);
}
return response()->json([
'revoked' => $deleted > 0,
'signal_type' => 'hide_artwork',
'artwork_id' => (int) $payload['artwork_id'],
], Response::HTTP_OK);
}
public function undislikeTag(Request $request): JsonResponse
{
$payload = $request->validate([
'tag_id' => ['nullable', 'integer', 'exists:tags,id'],
'tag_slug' => ['nullable', 'string', 'max:191'],
'artwork_id' => ['nullable', 'integer', 'exists:artworks,id'],
'algo_version' => ['nullable', 'string', 'max:64'],
'meta' => ['nullable', 'array'],
]);
$tagId = isset($payload['tag_id']) ? (int) $payload['tag_id'] : null;
if ($tagId === null && ! empty($payload['tag_slug'])) {
$tagId = Tag::query()->where('slug', (string) $payload['tag_slug'])->value('id');
}
abort_if($tagId === null || $tagId <= 0, Response::HTTP_UNPROCESSABLE_ENTITY, 'A valid tag is required.');
$deleted = UserNegativeSignal::query()
->where('user_id', (int) $request->user()->id)
->where('signal_type', 'dislike_tag')
->where('tag_id', $tagId)
->delete();
if ($deleted > 0) {
$this->recordFeedbackEvent(
userId: (int) $request->user()->id,
artworkId: isset($payload['artwork_id']) ? (int) $payload['artwork_id'] : (int) (($payload['meta']['artwork_id'] ?? 0)),
eventType: 'undo_dislike_tag',
algoVersion: isset($payload['algo_version']) ? (string) $payload['algo_version'] : null,
meta: array_merge((array) ($payload['meta'] ?? []), ['tag_id' => $tagId])
);
}
return response()->json([
'revoked' => $deleted > 0,
'signal_type' => 'dislike_tag',
'tag_id' => $tagId,
], Response::HTTP_OK);
}
/**
* @param array<string, mixed> $meta
*/
private function recordFeedbackEvent(int $userId, int $artworkId, string $eventType, ?string $algoVersion = null, array $meta = []): void
{
if ($artworkId <= 0 || ! Schema::hasTable('user_discovery_events')) {
return;
}
$categoryId = DB::table('artwork_category')
->where('artwork_id', $artworkId)
->orderBy('category_id')
->value('category_id');
DB::table('user_discovery_events')->insert([
'event_id' => (string) Str::uuid(),
'user_id' => $userId,
'artwork_id' => $artworkId,
'category_id' => $categoryId !== null ? (int) $categoryId : null,
'event_type' => $eventType,
'event_version' => (string) config('discovery.event_version', 'event-v1'),
'algo_version' => (string) ($algoVersion ?: config('discovery.v2.algo_version', config('discovery.algo_version', 'clip-cosine-v1'))),
'weight' => 0.0,
'event_date' => now()->toDateString(),
'occurred_at' => now()->toDateTimeString(),
'meta' => json_encode($meta, JSON_THROW_ON_ERROR),
'created_at' => now(),
'updated_at' => now(),
]);
}
}