Upload beautify
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class FeedPerformanceReportController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'from' => ['nullable', 'date_format:Y-m-d'],
|
||||
'to' => ['nullable', 'date_format:Y-m-d'],
|
||||
'limit' => ['nullable', 'integer', 'min:1', 'max:1000'],
|
||||
]);
|
||||
|
||||
$from = (string) ($validated['from'] ?? now()->subDays(29)->toDateString());
|
||||
$to = (string) ($validated['to'] ?? now()->toDateString());
|
||||
$limit = (int) ($validated['limit'] ?? 100);
|
||||
|
||||
if ($from > $to) {
|
||||
return response()->json([
|
||||
'message' => 'Invalid date range: from must be before or equal to to.',
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$rows = DB::table('feed_daily_metrics')
|
||||
->selectRaw('algo_version, source')
|
||||
->selectRaw('SUM(impressions) as impressions')
|
||||
->selectRaw('SUM(clicks) as clicks')
|
||||
->selectRaw('SUM(saves) as saves')
|
||||
->selectRaw('SUM(dwell_0_5) as dwell_0_5')
|
||||
->selectRaw('SUM(dwell_5_30) as dwell_5_30')
|
||||
->selectRaw('SUM(dwell_30_120) as dwell_30_120')
|
||||
->selectRaw('SUM(dwell_120_plus) as dwell_120_plus')
|
||||
->whereBetween('metric_date', [$from, $to])
|
||||
->groupBy('algo_version', 'source')
|
||||
->orderBy('algo_version')
|
||||
->orderBy('source')
|
||||
->get();
|
||||
|
||||
$byAlgoSource = $rows->map(static function ($row): array {
|
||||
$impressions = (int) ($row->impressions ?? 0);
|
||||
$clicks = (int) ($row->clicks ?? 0);
|
||||
$saves = (int) ($row->saves ?? 0);
|
||||
|
||||
return [
|
||||
'algo_version' => (string) $row->algo_version,
|
||||
'source' => (string) $row->source,
|
||||
'impressions' => $impressions,
|
||||
'clicks' => $clicks,
|
||||
'saves' => $saves,
|
||||
'ctr' => round($impressions > 0 ? $clicks / $impressions : 0.0, 6),
|
||||
'save_rate' => round($clicks > 0 ? $saves / $clicks : 0.0, 6),
|
||||
'dwell_buckets' => [
|
||||
'0_5' => (int) ($row->dwell_0_5 ?? 0),
|
||||
'5_30' => (int) ($row->dwell_5_30 ?? 0),
|
||||
'30_120' => (int) ($row->dwell_30_120 ?? 0),
|
||||
'120_plus' => (int) ($row->dwell_120_plus ?? 0),
|
||||
],
|
||||
];
|
||||
})->values();
|
||||
|
||||
$topClickedArtworks = DB::table('feed_events as e')
|
||||
->leftJoin('artworks as a', 'a.id', '=', 'e.artwork_id')
|
||||
->selectRaw('e.algo_version')
|
||||
->selectRaw('e.source')
|
||||
->selectRaw('e.artwork_id')
|
||||
->selectRaw('a.title as artwork_title')
|
||||
->selectRaw("SUM(CASE WHEN e.event_type = 'feed_impression' THEN 1 ELSE 0 END) AS impressions")
|
||||
->selectRaw("SUM(CASE WHEN e.event_type = 'feed_click' THEN 1 ELSE 0 END) AS clicks")
|
||||
->whereBetween('e.event_date', [$from, $to])
|
||||
->groupBy('e.algo_version', 'e.source', 'e.artwork_id', 'a.title')
|
||||
->get()
|
||||
->map(static function ($row): array {
|
||||
$impressions = (int) ($row->impressions ?? 0);
|
||||
$clicks = (int) ($row->clicks ?? 0);
|
||||
|
||||
return [
|
||||
'algo_version' => (string) $row->algo_version,
|
||||
'source' => (string) $row->source,
|
||||
'artwork_id' => (int) $row->artwork_id,
|
||||
'artwork_title' => (string) ($row->artwork_title ?? ''),
|
||||
'impressions' => $impressions,
|
||||
'clicks' => $clicks,
|
||||
'ctr' => round($impressions > 0 ? $clicks / $impressions : 0.0, 6),
|
||||
];
|
||||
})
|
||||
->sort(static function (array $a, array $b): int {
|
||||
$clickCompare = $b['clicks'] <=> $a['clicks'];
|
||||
if ($clickCompare !== 0) {
|
||||
return $clickCompare;
|
||||
}
|
||||
|
||||
return $b['ctr'] <=> $a['ctr'];
|
||||
})
|
||||
->take($limit)
|
||||
->values();
|
||||
|
||||
return response()->json([
|
||||
'meta' => [
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'generated_at' => now()->toISOString(),
|
||||
'limit' => $limit,
|
||||
],
|
||||
'by_algo_source' => $byAlgoSource,
|
||||
'top_clicked_artworks' => $topClickedArtworks,
|
||||
], Response::HTTP_OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class SimilarArtworkReportController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'from' => ['nullable', 'date_format:Y-m-d'],
|
||||
'to' => ['nullable', 'date_format:Y-m-d'],
|
||||
'limit' => ['nullable', 'integer', 'min:1', 'max:1000'],
|
||||
]);
|
||||
|
||||
$from = (string) ($validated['from'] ?? now()->subDays(29)->toDateString());
|
||||
$to = (string) ($validated['to'] ?? now()->toDateString());
|
||||
$limit = (int) ($validated['limit'] ?? 100);
|
||||
|
||||
if ($from > $to) {
|
||||
return response()->json([
|
||||
'message' => 'Invalid date range: from must be before or equal to to.',
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$byAlgoRows = DB::table('similar_artwork_events')
|
||||
->selectRaw('algo_version')
|
||||
->selectRaw("SUM(CASE WHEN event_type = 'impression' THEN 1 ELSE 0 END) AS impressions")
|
||||
->selectRaw("SUM(CASE WHEN event_type = 'click' THEN 1 ELSE 0 END) AS clicks")
|
||||
->whereBetween('event_date', [$from, $to])
|
||||
->groupBy('algo_version')
|
||||
->orderBy('algo_version')
|
||||
->get();
|
||||
|
||||
$byAlgo = $byAlgoRows->map(static function ($row): array {
|
||||
$impressions = (int) ($row->impressions ?? 0);
|
||||
$clicks = (int) ($row->clicks ?? 0);
|
||||
$ctr = $impressions > 0 ? $clicks / $impressions : 0.0;
|
||||
|
||||
return [
|
||||
'algo_version' => (string) $row->algo_version,
|
||||
'impressions' => $impressions,
|
||||
'clicks' => $clicks,
|
||||
'ctr' => round($ctr, 6),
|
||||
];
|
||||
})->values();
|
||||
|
||||
$pairRows = DB::table('similar_artwork_events as e')
|
||||
->leftJoin('artworks as source', 'source.id', '=', 'e.source_artwork_id')
|
||||
->leftJoin('artworks as similar', 'similar.id', '=', 'e.similar_artwork_id')
|
||||
->selectRaw('e.algo_version')
|
||||
->selectRaw('e.source_artwork_id')
|
||||
->selectRaw('e.similar_artwork_id')
|
||||
->selectRaw('source.title as source_title')
|
||||
->selectRaw('similar.title as similar_title')
|
||||
->selectRaw("SUM(CASE WHEN e.event_type = 'impression' THEN 1 ELSE 0 END) AS impressions")
|
||||
->selectRaw("SUM(CASE WHEN e.event_type = 'click' THEN 1 ELSE 0 END) AS clicks")
|
||||
->whereBetween('e.event_date', [$from, $to])
|
||||
->whereNotNull('e.similar_artwork_id')
|
||||
->groupBy('e.algo_version', 'e.source_artwork_id', 'e.similar_artwork_id', 'source.title', 'similar.title')
|
||||
->get();
|
||||
|
||||
$topSimilarities = $pairRows
|
||||
->map(static function ($row): array {
|
||||
$impressions = (int) ($row->impressions ?? 0);
|
||||
$clicks = (int) ($row->clicks ?? 0);
|
||||
$ctr = $impressions > 0 ? $clicks / $impressions : 0.0;
|
||||
|
||||
return [
|
||||
'algo_version' => (string) $row->algo_version,
|
||||
'source_artwork_id' => (int) $row->source_artwork_id,
|
||||
'source_title' => (string) ($row->source_title ?? ''),
|
||||
'similar_artwork_id' => (int) $row->similar_artwork_id,
|
||||
'similar_title' => (string) ($row->similar_title ?? ''),
|
||||
'impressions' => $impressions,
|
||||
'clicks' => $clicks,
|
||||
'ctr' => round($ctr, 6),
|
||||
];
|
||||
})
|
||||
->sort(function (array $a, array $b): int {
|
||||
$ctrCompare = $b['ctr'] <=> $a['ctr'];
|
||||
if ($ctrCompare !== 0) {
|
||||
return $ctrCompare;
|
||||
}
|
||||
|
||||
$clickCompare = $b['clicks'] <=> $a['clicks'];
|
||||
if ($clickCompare !== 0) {
|
||||
return $clickCompare;
|
||||
}
|
||||
|
||||
return $b['impressions'] <=> $a['impressions'];
|
||||
})
|
||||
->take($limit)
|
||||
->values();
|
||||
|
||||
return response()->json([
|
||||
'meta' => [
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'generated_at' => now()->toISOString(),
|
||||
'limit' => $limit,
|
||||
],
|
||||
'by_algo_version' => $byAlgo,
|
||||
'top_similarities' => $topSimilarities,
|
||||
], Response::HTTP_OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Upload;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class UploadModerationController extends Controller
|
||||
{
|
||||
public function pending(): JsonResponse
|
||||
{
|
||||
$uploads = Upload::query()
|
||||
->where('status', 'draft')
|
||||
->where('moderation_status', 'pending')
|
||||
->orderBy('created_at')
|
||||
->get([
|
||||
'id',
|
||||
'user_id',
|
||||
'type',
|
||||
'status',
|
||||
'processing_state',
|
||||
'title',
|
||||
'preview_path',
|
||||
'created_at',
|
||||
'moderation_status',
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'data' => $uploads,
|
||||
], Response::HTTP_OK);
|
||||
}
|
||||
|
||||
public function approve(string $id, Request $request): JsonResponse
|
||||
{
|
||||
$upload = Upload::query()->find($id);
|
||||
|
||||
if (! $upload) {
|
||||
return response()->json(['message' => 'Upload not found.'], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$upload->moderation_status = 'approved';
|
||||
$upload->moderated_at = now();
|
||||
$upload->moderated_by = (int) $request->user()->id;
|
||||
$upload->moderation_note = $request->input('note');
|
||||
$upload->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'id' => (string) $upload->id,
|
||||
'moderation_status' => (string) $upload->moderation_status,
|
||||
], Response::HTTP_OK);
|
||||
}
|
||||
|
||||
public function reject(string $id, Request $request): JsonResponse
|
||||
{
|
||||
$upload = Upload::query()->find($id);
|
||||
|
||||
if (! $upload) {
|
||||
return response()->json(['message' => 'Upload not found.'], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$upload->moderation_status = 'rejected';
|
||||
$upload->status = 'rejected';
|
||||
$upload->processing_state = 'rejected';
|
||||
$upload->moderated_at = now();
|
||||
$upload->moderated_by = (int) $request->user()->id;
|
||||
$upload->moderation_note = (string) $request->input('note', '');
|
||||
$upload->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'id' => (string) $upload->id,
|
||||
'status' => (string) $upload->status,
|
||||
'processing_state' => (string) $upload->processing_state,
|
||||
'moderation_status' => (string) $upload->moderation_status,
|
||||
], Response::HTTP_OK);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user