optimizations
This commit is contained in:
206
app/Http/Controllers/Api/DiscoveryNegativeSignalController.php
Normal file
206
app/Http/Controllers/Api/DiscoveryNegativeSignalController.php
Normal 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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user