exam support valid multiple

This commit is contained in:
xiaomlove
2022-04-17 16:38:44 +08:00
parent bacfdd0df1
commit e0a515b66c
59 changed files with 950 additions and 79 deletions
@@ -0,0 +1,110 @@
<?php
namespace App\Http\Controllers;
use App\Http\Resources\HitAndRunResource;
use App\Models\HitAndRun;
use App\Repositories\HitAndRunRepository;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
class HitAndRunController extends Controller
{
private $repository;
public function __construct(HitAndRunRepository $repository)
{
$this->repository = $repository;
}
private function getRules(): array
{
return [
'family_id' => 'required|numeric',
'name' => 'required|string',
'peer_id' => 'required|string',
'agent' => 'required|string',
'comment' => 'required|string',
];
}
/**
* Display a listing of the resource.
*
* @return array
*/
public function index(Request $request)
{
$result = $this->repository->getList($request->all());
$resource = HitAndRunResource::collection($result);
return $this->success($resource);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate($this->getRules());
$result = $this->repository->store($request->all());
$resource = new HitAndRunResource($result);
return $this->success($resource);
}
/**
* Display the specified resource.
*
* @param int $id
* @return array
*/
public function show($id)
{
$result = $this->repository->getDetail($id);
$resource = new HitAndRunResource($result);
return $this->success($resource);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return array
*/
public function update(Request $request, $id)
{
$request->validate($this->getRules());
$result = $this->repository->update($request->all(), $id);
$resource = new HitAndRunResource($result);
return $this->success($resource);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return array
*/
public function destroy($id)
{
$result = $this->repository->delete($id);
return $this->success($result);
}
public function listStatus(): array
{
$result = $this->repository->listStatus();
return $this->success($result);
}
public function pardon($id): array
{
$result = $this->repository->pardon($id, Auth::user());
return $this->success($result);
}
}
+3 -2
View File
@@ -25,9 +25,10 @@ class Locale
*/
public function handle(Request $request, Closure $next)
{
$language = $request->user()->language;
$user = $request->user();
$language = $user->language;
$locale = self::$languageMaps[$language->site_lang_folder] ?? 'en';
do_log("set locale: " . $locale);
do_log("user: {$user->id}, language: {$language->id}, set locale: $locale");
App::setLocale($locale);
Carbon::setLocale($locale);
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class HitAndRunResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$out = [
'id' => $this->id,
'uid' => $this->uid,
'user' => new UserResource($this->whenLoaded('user')),
'torrent_id' => $this->torrent_id,
'torrent' => new TorrentResource($this->whenLoaded('torrent')),
'snatched_id' => $this->snatched_id,
'snatch' => new SnatchResource($this->whenLoaded('snatch')),
'status' => $this->status,
'status_text' => $this->status_text,
'comment' => $this->comment,
'created_at' => format_datetime($this->created_at),
'updated_at' => format_datetime($this->updated_at),
'seed_time_required' => $this->seedTimeRequired,
'inspect_time_left' => $this->inspectTimeLeft,
];
if (nexus()->isPlatformAdmin()) {
$out['comment'] = nl2br(trim($out['comment']));
}
return $out;
}
}
+5 -5
View File
@@ -4,7 +4,7 @@ namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class SnatchResource extends PeerResource
class SnatchResource extends JsonResource
{
/**
* Transform the resource into an array.
@@ -20,10 +20,10 @@ class SnatchResource extends PeerResource
'upload_text' => $this->upload_text,
'download_text' => $this->download_text,
'share_ratio' => $this->share_ratio,
'seed_time' => $this->seed_time,
'leech_time' => $this->leech_time,
'completed_at_human' => $this->completed_at_human,
'last_action_human' => $this->last_action_human,
'seed_time' => mkprettytime($this->seedtime),
'leech_time' => mkprettytime($this->leechtime),
'completed_at_human' => $this->completedat ? $this->completedat->diffForHumans() : '',
'last_action_human' => $this->last_action ? $this->last_action->diffForHumans() : '',
'user' => new UserResource($this->whenLoaded('user')),
];
}
+5
View File
@@ -6,6 +6,7 @@ use App\Models\Attachment;
use App\Models\Torrent;
use Carbon\CarbonInterface;
use Illuminate\Http\Resources\Json\JsonResource;
use Nexus\Nexus;
class TorrentResource extends JsonResource
{
@@ -69,6 +70,10 @@ class TorrentResource extends JsonResource
$out['peers_count'] = $this->peers_count;
$out['reward_logs_count'] = $this->reward_logs_count;
}
if (nexus()->isPlatformAdmin()) {
$out['details_url'] = sprintf('%s/details.php?id=%s', getSchemeAndHttpHost(), $this->id);
}
// $out['upload_peers_count'] = $this->upload_peers_count;
// $out['download_peers_count'] = $this->download_peers_count;
// $out['finish_peers_count'] = $this->finish_peers_count;
+5 -1
View File
@@ -14,7 +14,11 @@ class NexusFormatter
protected function formatter()
{
$format = "[%datetime%] [" . nexus()->getRequestId() . "] %channel%.%level_name%: %message% %context% %extra%\n";
$id = 'NO_REQUEST_ID';
if (nexus()) {
$id = nexus()->getRequestId();
}
$format = "[%datetime%] [" . $id . "] %channel%.%level_name%: %message% %context% %extra%\n";
return tap(new LineFormatter($format, 'Y-m-d H:i:s', true, true), function ($formatter) {
$formatter->includeStacktraces();
});
+16
View File
@@ -2,6 +2,8 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
class HitAndRun extends NexusModel
{
protected $table = 'hit_and_runs';
@@ -34,6 +36,20 @@ class HitAndRun extends NexusModel
const MINIMUM_IGNORE_USER_CLASS = User::CLASS_VIP;
protected function seedTimeRequired(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => $this->status == self::STATUS_INSPECTING ? mkprettytime(3600 * Setting::get('hr.seed_time_minimum') - $this->snatch->seedtime) : '---'
);
}
protected function inspectTimeLeft(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => $this->status == self::STATUS_INSPECTING ? mkprettytime(\Carbon\Carbon::now()->diffInSeconds($this->snatch->completedat->addHours(Setting::get('hr.inspect_time')))) : '---'
);
}
public function getStatusTextAttribute()
{
return nexus_trans('hr.status_' . $this->status);
+55
View File
@@ -4,6 +4,8 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use JetBrains\PhpStorm\Pure;
class Snatch extends NexusModel
{
@@ -33,6 +35,59 @@ class Snatch extends NexusModel
const FINISHED_NO = 'no';
protected function uploadText(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => sprintf('%s@%s', mksize($attributes['uploaded']), $this->getUploadSpeed())
);
}
protected function downloadText(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => sprintf('%s@%s', mksize($attributes['downloaded']), $this->getDownloadSpeed())
);
}
protected function shareRatio(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => $this->getShareRatio()
);
}
public function getUploadSpeed(): string
{
if ($this->seedtime <= 0) {
$speed = mksize(0);
} else {
$speed = mksize($this->uploaded / ($this->seedtime + $this->leechtime));
}
return "$speed/s";
}
public function getDownloadSpeed(): string
{
if ($this->leechtime <= 0) {
$speed = mksize(0);
} else {
$speed = mksize($this->downloaded / $this->leechtime);
}
return "$speed/s";
}
public function getShareRatio()
{
if ($this->downloaded) {
$ratio = floor(($this->uploaded / $this->downloaded) * 1000) / 1000;
} elseif ($this->uploaded) {
$ratio = nexus_trans('snatch.share_ratio_infinity');
} else {
$ratio = '---';
}
return $ratio;
}
public function scopeIsFinished(Builder $builder)
{
return $builder->where('finished', self::FINISHED_YES);
+1 -1
View File
@@ -13,7 +13,7 @@ class Torrent extends NexusModel
'category', 'source', 'medium', 'codec', 'standard', 'processing', 'team', 'audiocodec',
'size', 'added', 'type', 'numfiles', 'owner', 'nfo', 'sp_state', 'promotion_time_type',
'promotion_until', 'anonymous', 'url', 'pos_state', 'cache_stamp', 'picktype', 'picktime',
'last_reseed', 'pt_gen', 'technical_info'
'last_reseed', 'pt_gen', 'technical_info', 'leechers', 'seeders',
];
private static $globalPromotionState;
+67 -22
View File
@@ -3,7 +3,6 @@ namespace App\Repositories;
use App\Exceptions\NexusException;
use App\Models\Exam;
use App\Models\ExamIndexInitValue;
use App\Models\ExamProgress;
use App\Models\ExamUser;
use App\Models\Message;
@@ -14,6 +13,7 @@ use App\Models\User;
use App\Models\UserBanLog;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
@@ -33,11 +33,15 @@ class ExamRepository extends BaseRepository
$this->checkIndexes($params);
$this->checkBeginEnd($params);
$this->checkFilters($params);
$valid = $this->listValid(null, Exam::DISCOVERED_YES);
if ($valid->isNotEmpty() && $params['status'] == Exam::STATUS_ENABLED) {
throw new NexusException("Enabled and discovered exam already exists.");
}
$exam = Exam::query()->create($params);
/**
* does not limit this
* @since 1.7.4
*/
// $valid = $this->listValid(null, Exam::DISCOVERED_YES);
// if ($valid->isNotEmpty() && $params['status'] == Exam::STATUS_ENABLED) {
// throw new NexusException("Enabled and discovered exam already exists.");
// }
$exam = Exam::query()->create($this->formatParams($params));
return $exam;
}
@@ -46,15 +50,30 @@ class ExamRepository extends BaseRepository
$this->checkIndexes($params);
$this->checkBeginEnd($params);
$this->checkFilters($params);
$valid = $this->listValid($id, Exam::DISCOVERED_YES);
if ($valid->isNotEmpty() && $params['status'] == Exam::STATUS_ENABLED) {
throw new NexusException("Enabled and discovered exam already exists.");
}
/**
* does not limit this
* @since 1.7.4
*/
// $valid = $this->listValid($id, Exam::DISCOVERED_YES);
// if ($valid->isNotEmpty() && $params['status'] == Exam::STATUS_ENABLED) {
// throw new NexusException("Enabled and discovered exam already exists.");
// }
$exam = Exam::query()->findOrFail($id);
$exam->update($params);
$exam->update($this->formatParams($params));
return $exam;
}
private function formatParams(array $params): array
{
if (isset($params['begin']) && $params['begin'] == '') {
$params['begin'] = null;
}
if (isset($params['end']) && $params['end'] == '') {
$params['end'] = null;
}
return $params;
}
private function checkIndexes(array $params): bool
{
if (empty($params['indexes'])) {
@@ -187,14 +206,15 @@ class ExamRepository extends BaseRepository
$query = Exam::query()
->where('status', Exam::STATUS_ENABLED)
->whereRaw("if(begin is not null and end is not null, begin <= '$now' and end >= '$now', duration > 0)")
->orderBy('id', 'desc');
;
if (!is_null($excludeId)) {
$query->whereNotIn('id', Arr::wrap($excludeId));
}
if (!is_null($isDiscovered)) {
$query->where('is_discovered', $isDiscovered);
}
return $query->get();
return $query->orderBy('id', 'asc')->get();
}
/**
@@ -732,23 +752,40 @@ class ExamRepository extends BaseRepository
do_log("No valid and discovered exam.");
return false;
}
if ($exams->count() > 1) {
do_log("Valid and discovered exam more than 1.", "error");
return false;
/**
* valid exam can has multiple
*
* @since 1.7.4
*/
// if ($exams->count() > 1) {
// do_log("Valid and discovered exam more than 1.", "error");
// return false;
// }
$result = 0;
foreach ($exams as $exam) {
$start = microtime(true);
$count = $this->fetchUserAndDoAssign($exam);
do_log(sprintf(
'exam: %s assign to user count: %s -> %s, cost time: %s',
$exam->id, gettype($count), $count, number_format(microtime(true) - $start, 3)
));
$result += $count;
}
/** @var Exam $exam */
$exam = $exams->first();
return $result;
}
private function fetchUserAndDoAssign(Exam $exam): bool|int
{
$filters = $exam->filters;
do_log("exam: {$exam->id}, filters: " . nexus_json_encode($filters));
$userTable = (new User())->getTable();
$examUserTable = (new ExamUser())->getTable();
//Fetch user doesn't has this exam and doesn't has any other unfinished exam
$baseQuery = User::query()
->leftJoin($examUserTable, function (JoinClause $join) use ($examUserTable, $userTable) {
$join->on("$userTable.id", "=", "$examUserTable.uid");
})
->where("$userTable.enabled", User::ENABLED_YES)
->where("$userTable.status", User::STATUS_CONFIRMED)
->whereRaw("$examUserTable.id is null")
->selectRaw("$userTable.*")
->orderBy("$userTable.id", "asc");
@@ -782,6 +819,14 @@ class ExamRepository extends BaseRepository
$baseQuery->where("$userTable.added", ">=", Carbon::parse($range[0])->toDateTimeString())
->where("$userTable.added", '<=', Carbon::parse($range[1])->toDateTimeString());
}
//Does not has this exam
$baseQuery->whereDoesntHave('exams', function (Builder $query) use ($exam) {
$query->where('exam_id', $exam->id);
});
//Does not has any other normal exam
$baseQuery->whereDoesntHave('exams', function (Builder $query) use ($exam) {
$query->where('status',ExamUser::STATUS_NORMAL);
});
$size = 1000;
$minId = 0;
+68
View File
@@ -7,10 +7,57 @@ use App\Models\Setting;
use App\Models\User;
use App\Models\UserBanLog;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class HitAndRunRepository extends BaseRepository
{
public function getList(array $params)
{
$query = HitAndRun::query()->with(['user', 'torrent', 'snatch']);
if (!empty($params['status'])) {
$query->where('status', $params['status']);
}
if (!empty($params['uid'])) {
$query->where('uid', $params['uid']);
}
if (!empty($params['torrent_id'])) {
$query->where('torrent_id', $params['torrent_id']);
}
if (!empty($params['username'])) {
$query->whereHas('user', function (Builder $query) use ($params) {
return $query->where('username', $params['username']);
});
}
$query->orderBy('id', 'desc');
return $query->paginate();
}
public function store(array $params)
{
$model = HitAndRun::query()->create($params);
return $model;
}
public function update(array $params, $id)
{
$model = HitAndRun::query()->findOrFail($id);
$model->update($params);
return $model;
}
public function getDetail($id)
{
$model = HitAndRun::query()->with(['user', 'torrent', 'snatch'])->findOrFail($id);
return $model;
}
public function delete($id)
{
$model = HitAndRun::query()->findOrFail($id);
$result = $model->delete();
return $result;
}
public function cronjobUpdateStatus($uid = null, $torrentId = null, $ignoreTime = false): bool|int
{
@@ -277,4 +324,25 @@ class HitAndRunRepository extends BaseRepository
return $results;
}
public function listStatus()
{
$results = [];
foreach (HitAndRun::$status as $key => $value) {
$results[] = ['status' => $key, 'text' => nexus_trans('hr.status_' . $key)];
}
return $results;
}
public function pardon($id, User $user): bool
{
$model = HitAndRun::query()->findOrFail($id);
if (!in_array($model->status, [HitAndRun::STATUS_INSPECTING, HitAndRun::STATUS_UNREACHED])) {
throw new \LogicException("Can't be pardoned due to status is: " . $model->status_text . " !");
}
$model->status = HitAndRun::STATUS_PARDONED;
$model->comment = DB::raw(sprintf("concat_ws('\n', comment, '%s')", addslashes('Pardon by ' . $user->username)));
$model->save();
return true;
}
}
-9
View File
@@ -285,15 +285,6 @@ class TorrentRepository extends BaseRepository
->with(['user'])
->orderBy('completedat', 'desc')
->paginate();
foreach ($snatches as &$snatch) {
$snatch->upload_text = sprintf('%s@%s', mksize($snatch->uploaded), $this->getSnatchUploadSpeed($snatch));
$snatch->download_text = sprintf('%s@%s', mksize($snatch->downloaded), $this->getSnatchDownloadSpeed($snatch));
$snatch->share_ratio = $this->getShareRatio($snatch);
$snatch->seed_time = mkprettytime($snatch->seedtime);
$snatch->leech_time = mkprettytime($snatch->leechtime);
$snatch->completed_at_human = $snatch->completedat->diffForHumans();
$snatch->last_action_human = $snatch->last_action->diffForHumans();
}
return $snatches;
}
+1 -1
View File
@@ -50,7 +50,7 @@ class UserRepository extends BaseRepository
$baseInfo = $userResource->response()->getData(true)['data'];
$examRep = new ExamRepository();
$examProgress = $examRep->getUserExamProgress($id, null);
$examProgress = $examRep->getUserExamProgress($id, ExamUser::STATUS_NORMAL);
if ($examProgress) {
$examResource = new ExamUserResource($examProgress);
$examInfo = $examResource->response()->getData(true)['data'];