mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-23 11:27:24 +08:00
Merge branch '1.7' into php8
This commit is contained in:
+12
-2
@@ -15,8 +15,8 @@ DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
CACHE_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
@@ -63,3 +63,13 @@ GOOGLE_DRIVE_FOLDER_ID=
|
||||
|
||||
GEOIP2_DATABASE=
|
||||
EXAM_PROGRESS_UPDATE_PROBABILITY=20
|
||||
|
||||
TRACKER_API_LOCAL_HOST=
|
||||
|
||||
ELASTICSEARCH_HOST=
|
||||
ELASTICSEARCH_PORT=
|
||||
ELASTICSEARCH_SCHEME=
|
||||
ELASTICSEARCH_USER=
|
||||
ELASTICSEARCH_PASS=
|
||||
|
||||
ELASTICSEARCH_ENABLED=
|
||||
|
||||
@@ -7,10 +7,30 @@
|
||||
<el-input v-model="formData.name" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Color" prop="color">
|
||||
<el-form-item label="Background color" prop="color">
|
||||
<el-input v-model="formData.color" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Font color" prop="font_color">
|
||||
<el-input v-model="formData.font_color" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Font size" prop="font_size">
|
||||
<el-input v-model="formData.font_size" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Padding" prop="padding">
|
||||
<el-input v-model="formData.padding" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Margin" prop="margin">
|
||||
<el-input v-model="formData.margin" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Border radius" prop="border_radius">
|
||||
<el-input v-model="formData.border_radius" placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Priority" prop="priority">
|
||||
<el-input v-model="formData.priority" placeholder="The higher the value, the higher the ranking"></el-input>
|
||||
</el-form-item>
|
||||
@@ -47,7 +67,12 @@ export default {
|
||||
formData: {
|
||||
color: '',
|
||||
name: '',
|
||||
priority: ''
|
||||
priority: '',
|
||||
font_color: '#ffffff',
|
||||
font_size: '12px',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
border_radius: 0,
|
||||
},
|
||||
rules: {
|
||||
color: [
|
||||
@@ -64,6 +89,11 @@ export default {
|
||||
state.formData.name = res.data.name
|
||||
state.formData.color = res.data.color
|
||||
state.formData.priority = res.data.priority
|
||||
state.formData.font_color = res.data.font_color
|
||||
state.formData.font_size = res.data.font_size
|
||||
state.formData.margin = res.data.margin
|
||||
state.formData.padding = res.data.padding
|
||||
state.formData.border_radius = res.data.border_radius
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,7 +33,37 @@
|
||||
|
||||
<el-table-column
|
||||
prop="color"
|
||||
label="Color"
|
||||
label="Background color"
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="font_color"
|
||||
label="Font color"
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="font_size"
|
||||
label="Font size"
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="margin"
|
||||
label="Margin"
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="padding"
|
||||
label="Padding"
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="border_radius"
|
||||
label="Border radius"
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
@@ -48,12 +78,6 @@
|
||||
label="Updated at"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="created_at"
|
||||
label="Created at"
|
||||
></el-table-column>
|
||||
|
||||
|
||||
<el-table-column
|
||||
label="Action"
|
||||
width="120"
|
||||
|
||||
@@ -41,7 +41,7 @@ class AttendanceMigrate extends Command
|
||||
{
|
||||
$rep = new AttendanceRepository();
|
||||
$result = $rep->migrateAttendance();
|
||||
$log = sprintf('[%s], %s, result: %s, query: %s', REQUEST_ID, __METHOD__, var_export($result, true), last_query());
|
||||
$log = sprintf('[%s], %s, result: %s, query: %s', nexus() ? nexus()->getRequestId() : 'NO_REQUEST_ID', __METHOD__, var_export($result, true), last_query());
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
return 0;
|
||||
|
||||
@@ -42,7 +42,7 @@ class BackuAll extends Command
|
||||
$result = $rep->backupAll();
|
||||
$log = sprintf(
|
||||
'[%s], %s, result: %s',
|
||||
REQUEST_ID, __METHOD__, var_export($result, true)
|
||||
nexus()->getRequestId(), __METHOD__, var_export($result, true)
|
||||
);
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
|
||||
@@ -42,7 +42,7 @@ class BackupCronjob extends Command
|
||||
$result = $rep->cronjobBackup();
|
||||
$log = sprintf(
|
||||
'[%s], %s, result: %s',
|
||||
REQUEST_ID, __METHOD__, var_export($result, true)
|
||||
nexus()->getRequestId(), __METHOD__, var_export($result, true)
|
||||
);
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
|
||||
@@ -40,7 +40,7 @@ class BackupDatabase extends Command
|
||||
{
|
||||
$rep = new ToolRepository();
|
||||
$result = $rep->backupDatabase();
|
||||
$log = sprintf('[%s], %s, result: %s', REQUEST_ID, __METHOD__, var_export($result, true));
|
||||
$log = sprintf('[%s], %s, result: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true));
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class BackupWeb extends Command
|
||||
{
|
||||
$rep = new ToolRepository();
|
||||
$result = $rep->backupWeb();
|
||||
$log = sprintf('[%s], %s, result: %s', REQUEST_ID, __METHOD__, var_export($result, true));
|
||||
$log = sprintf('[%s], %s, result: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true));
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class DeleteExpiredToken extends Command
|
||||
|
||||
$query->where('last_used_at', '<', Carbon::now()->subDays($days));
|
||||
$result = $query->delete();
|
||||
$log = sprintf('[%s], %s, result: %s, query: %s', REQUEST_ID, __METHOD__, var_export($result, true), last_query());
|
||||
$log = sprintf('[%s], %s, result: %s, query: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true), last_query());
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
return 0;
|
||||
|
||||
@@ -45,7 +45,7 @@ class ExamAssign extends Command
|
||||
$end = $this->option('end');
|
||||
$this->info(sprintf('uid: %s, examId: %s, begin: %s, end: %s', $uid, $examId, $begin, $end));
|
||||
$result = $examRep->assignToUser($uid, $examId, $begin, $end);
|
||||
$log = sprintf('[%s], %s, result: %s', REQUEST_ID, __METHOD__, var_export($result, true));
|
||||
$log = sprintf('[%s], %s, result: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true));
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
return 0;
|
||||
|
||||
@@ -40,7 +40,7 @@ class ExamAssignCronjob extends Command
|
||||
{
|
||||
$examRep = new ExamRepository();
|
||||
$result = $examRep->cronjonAssign();
|
||||
$log = sprintf('[%s], %s, result: %s', REQUEST_ID, __METHOD__, var_export($result, true));
|
||||
$log = sprintf('[%s], %s, result: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true));
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
return 0;
|
||||
|
||||
@@ -42,7 +42,7 @@ class ExamCheckoutCronjob extends Command
|
||||
$ignoreTimeRange = $this->option('ignore-time-range');
|
||||
$this->info('ignore-time-range: ' . var_export($ignoreTimeRange, true));
|
||||
$result = $examRep->cronjobCheckout($ignoreTimeRange);
|
||||
$log = sprintf('[%s], %s, result: %s', REQUEST_ID, __METHOD__, var_export($result, true));
|
||||
$log = sprintf('[%s], %s, result: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true));
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
return 0;
|
||||
|
||||
@@ -42,7 +42,7 @@ class ExamUpdateProgress extends Command
|
||||
$uid = $this->argument('uid');
|
||||
$examRep = new ExamRepository();
|
||||
$result = $examRep->updateProgress($uid);
|
||||
$this->info(REQUEST_ID . ", result: " . var_export($result, true));
|
||||
$this->info(nexus()->getRequestId() . ", result: " . var_export($result, true));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ class HitAndRunUpdateStatus extends Command
|
||||
$rep = new HitAndRunRepository();
|
||||
$result = $rep->cronjobUpdateStatus($uid, $torrentId, $ignoreTime);
|
||||
$log = sprintf(
|
||||
'[%s], %s, uid: %s, torrentId: %s, ignoreTime: %s, result: %s',
|
||||
REQUEST_ID, __METHOD__, $uid, $torrentId, $ignoreTime, var_export($result, true)
|
||||
'[%s], %s, uid: %s, torrentId: %s, result: %s',
|
||||
nexus()->getRequestId(), __METHOD__, $uid, $torrentId, var_export($result, true)
|
||||
);
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
|
||||
@@ -43,7 +43,7 @@ class MigrateTorrentTag extends Command
|
||||
{
|
||||
$rep = new TagRepository();
|
||||
$result = $rep->migrateTorrentTag();
|
||||
$log = sprintf('[%s], %s, result: %s, query: %s', REQUEST_ID, __METHOD__, var_export($result, true), last_query());
|
||||
$log = sprintf('[%s], %s, result: %s, query: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true), last_query());
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
return 0;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\TorrentUpdated;
|
||||
use App\Http\Resources\TagResource;
|
||||
use App\Models\Attendance;
|
||||
use App\Models\Exam;
|
||||
@@ -9,15 +10,18 @@ use App\Models\ExamProgress;
|
||||
use App\Models\ExamUser;
|
||||
use App\Models\HitAndRun;
|
||||
use App\Models\Medal;
|
||||
use App\Models\Peer;
|
||||
use App\Models\SearchBox;
|
||||
use App\Models\Snatch;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use App\Repositories\AgentAllowRepository;
|
||||
use App\Repositories\AttendanceRepository;
|
||||
use App\Repositories\ExamRepository;
|
||||
use App\Repositories\HitAndRunRepository;
|
||||
use App\Repositories\SearchBoxRepository;
|
||||
use App\Repositories\SearchRepository;
|
||||
use App\Repositories\TagRepository;
|
||||
use App\Repositories\TorrentRepository;
|
||||
use App\Repositories\UserRepository;
|
||||
@@ -27,7 +31,10 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Encryption\Encrypter;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use JeroenG\Explorer\Domain\Syntax\Matching;
|
||||
use JeroenG\Explorer\Infrastructure\Scout\ElasticEngine;
|
||||
use Rhilip\Bencode\Bencode;
|
||||
|
||||
class Test extends Command
|
||||
@@ -63,9 +70,55 @@ class Test extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$rep = new AttendanceRepository();
|
||||
$r = $rep->migrateAttendance();
|
||||
$searchRep = new SearchRepository();
|
||||
$r = $searchRep->deleteIndex();
|
||||
$r = $searchRep->createIndex();
|
||||
$r = $searchRep->import();
|
||||
|
||||
$arr = [
|
||||
'cat' => 'category',
|
||||
'source' => 'source',
|
||||
'medium' => 'medium',
|
||||
'codec' => 'codec',
|
||||
'audiocodec' => 'audiocodec',
|
||||
'standard' => 'standard',
|
||||
'processing' => 'processing',
|
||||
'team' => 'team',
|
||||
];
|
||||
$queryString = 'cat401=1&cat404=1&source2=1&medium2=1&medium3=1&codec3=1&audiocodec3=1&standard2=1&standard3=1&processing2=1&team3=1&team4=1&incldead=1&spstate=0&inclbookmarked=0&search=&search_area=0&search_mode=0';
|
||||
$userSetting = '[cat401][cat404][sou1][med1][cod1][sta2][sta3][pro2][tea2][aud2][incldead=0][spstate=3][inclbookmarked=2]';
|
||||
// foreach ($arr as $queryField => $value) {
|
||||
//// $pattern = sprintf("/\[%s([\d]+)\]/", substr($queryField, 0, 3));
|
||||
// $pattern = "/{$queryField}([\d]+)=/";
|
||||
// if (preg_match_all($pattern, $queryString, $matches)) {
|
||||
// dump($matches);
|
||||
// echo '----------------------' . PHP_EOL;
|
||||
// }
|
||||
// }
|
||||
// $r = preg_match("/\[incldead=([\d]+)\]/", $userSetting, $matches);
|
||||
// dump($matches);
|
||||
|
||||
$params = [
|
||||
'tag_id' => 1,
|
||||
// 'incldead' => 0,
|
||||
// 'spstate' => 0,
|
||||
// 'inclbookmarked' => 0,
|
||||
// 'search' => '5034',
|
||||
// 'search_area' => 4,
|
||||
// 'search_mode' => 0,
|
||||
];
|
||||
$queryString = "cat401=1&cat404=1&cat405=1&cat402=1&cat403=1&cat406=1&cat407=1&cat409=1&cat408=1&incldead=0&spstate=0&inclbookmarked=0&search=5034838&search_area=4&search_mode=0";
|
||||
// $r = $searchRep->listTorrentFromEs($params, 1, '');
|
||||
|
||||
// $r = $searchRep->updateTorrent(1);
|
||||
// $r = $searchRep->updateUser(1);
|
||||
// $r = $searchRep->addTorrent(1);
|
||||
// $r = $searchRep->deleteBookmark(1);
|
||||
// $r = $searchRep->addBookmark(1);
|
||||
|
||||
dd($r);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class UserResetPassword extends Command
|
||||
|
||||
$rep = new UserRepository();
|
||||
$result = $rep->resetPassword($uid, $password, $passwordConfirmation);
|
||||
$log = sprintf('[%s], %s, result: %s', REQUEST_ID, __METHOD__, var_export($result, true));
|
||||
$log = sprintf('[%s], %s, result: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true));
|
||||
$this->info($log);
|
||||
do_log($log);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TorrentUpdated
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public int $torrentId;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(int $torrentId)
|
||||
{
|
||||
$this->torrentId = $torrentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return \Illuminate\Broadcasting\Channel|array
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return new PrivateChannel('channel-name');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class ClientNotAllowedException extends NexusException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -61,7 +61,8 @@ class Handler extends ExceptionHandler
|
||||
|
||||
$this->renderable(function (NotFoundHttpException $e) {
|
||||
if ($e->getPrevious() && $e->getPrevious() instanceof ModelNotFoundException) {
|
||||
return response()->json(fail('No query result.', request()->all()), 404);
|
||||
$exception = $e->getPrevious();
|
||||
return response()->json(fail($exception->getMessage(), request()->all()), 404);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -97,7 +98,12 @@ class Handler extends ExceptionHandler
|
||||
|
||||
protected function getHttpStatusCode(Throwable $e)
|
||||
{
|
||||
if ($e instanceof \InvalidArgumentException || $e instanceof NexusException) {
|
||||
if (
|
||||
$e instanceof NexusException
|
||||
|| $e instanceof \InvalidArgumentException
|
||||
|| $e instanceof \LogicException
|
||||
|| $e instanceof \RuntimeException
|
||||
) {
|
||||
return 200;
|
||||
}
|
||||
if ($this->isHttpException($e)) {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class TrackerException extends NexusException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -4,10 +4,20 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\CommentResource;
|
||||
use App\Models\Comment;
|
||||
use App\Repositories\CommentRepository;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CommentController extends Controller
|
||||
{
|
||||
private $repository;
|
||||
|
||||
public function __construct(CommentRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
@@ -20,7 +30,6 @@ class CommentController extends Controller
|
||||
$comments = Comment::query()
|
||||
->with($with)
|
||||
->where('torrent', $torrentId)
|
||||
->whereHas('create_user')
|
||||
->paginate();
|
||||
$resource = CommentResource::collection($comments);
|
||||
$resource->additional([
|
||||
@@ -30,6 +39,35 @@ class CommentController extends Controller
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
private function prepareData(Request $request)
|
||||
{
|
||||
$allTypes = array_keys(Comment::TYPE_MAPS);
|
||||
$request->validate([
|
||||
'type' => ['required', Rule::in($allTypes)],
|
||||
'torrent_id' => 'nullable|integer',
|
||||
'text' => 'required',
|
||||
'offer_id' => 'nullable|integer',
|
||||
'request_id' => 'nullable|integer',
|
||||
'anonymous' => 'nullable',
|
||||
]);
|
||||
$data = [
|
||||
'type' => $request->type,
|
||||
'torrent' => $request->torrent_id,
|
||||
'text' => $request->text,
|
||||
'ori_text' => $request->text,
|
||||
'offer' => $request->offer_id,
|
||||
'request' => $request->request_id,
|
||||
'anonymous' => $request->anonymous,
|
||||
];
|
||||
$data = array_filter($data);
|
||||
foreach ($allTypes as $type) {
|
||||
if ($data['type'] == $type && empty($data[$type])) {
|
||||
throw new \InvalidArgumentException("require {$type}_id");
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
@@ -38,7 +76,10 @@ class CommentController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
$user = Auth::user();
|
||||
$comment = $this->repository->store($this->prepareData($request), $user);
|
||||
$resource = new CommentResource($comment);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,11 +18,14 @@ class MessageController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$messages = Message::query()
|
||||
->where('receiver', $user->id)
|
||||
$query = $user->receive_messages()
|
||||
->with(['send_user'])
|
||||
->orderBy('id', 'desc')
|
||||
->paginate();
|
||||
->orderBy('id', 'desc');
|
||||
|
||||
if ($request->unread) {
|
||||
$query->where('unread', 'yes');
|
||||
}
|
||||
$messages = $query->paginate();
|
||||
$resource = MessageResource::collection($messages);
|
||||
return $this->success($resource);
|
||||
|
||||
@@ -48,7 +51,11 @@ class MessageController extends Controller
|
||||
public function show($id)
|
||||
{
|
||||
$message = Message::query()->with(['send_user'])->findOrFail($id);
|
||||
$message->update(['unread' => 'no']);
|
||||
$resource = new MessageResource($message);
|
||||
$resource->additional([
|
||||
'page_title' => nexus_trans('message.show.page_title'),
|
||||
]);
|
||||
|
||||
return $this->success($resource);
|
||||
}
|
||||
@@ -75,4 +82,27 @@ class MessageController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function listUnread(Request $request): array
|
||||
{
|
||||
$user = Auth::user();
|
||||
$query = $user->receive_messages()
|
||||
->with(['send_user'])
|
||||
->orderBy('id', 'desc')
|
||||
->where('unread', 'yes');
|
||||
|
||||
$messages = $query->paginate();
|
||||
$resource = MessageResource::collection($messages);
|
||||
$resource->additional([
|
||||
'site_info' => site_info(),
|
||||
]);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
public function countUnread()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$count = $user->receive_messages()->where('unread', 'yes')->count();
|
||||
return $this->success(['unread' => $count]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ namespace App\Http\Controllers;
|
||||
use App\Http\Resources\NewsResource;
|
||||
use App\Models\News;
|
||||
use App\Repositories\NewsRepository;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class NewsController extends Controller
|
||||
{
|
||||
@@ -93,4 +96,24 @@ class NewsController extends Controller
|
||||
$result = $this->repository->delete($id);
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function latest()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$result = News::query()->orderBy('id', 'desc')->first();
|
||||
$resource = new NewsResource($result);
|
||||
$resource->additional([
|
||||
'site_info' => site_info(),
|
||||
]);
|
||||
/**
|
||||
* Visiting the home page is the same as viewing the latest news
|
||||
* @see functions.php line 2590
|
||||
*/
|
||||
$user->update(['last_home' => Carbon::now()]);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\PollResource;
|
||||
use App\Models\Poll;
|
||||
use App\Repositories\PollRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PollController extends Controller
|
||||
{
|
||||
private $repository;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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 = PollResource::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 PollResource($result);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$result = Poll::query()->findOrFail($id);
|
||||
$resource = new PollResource($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 PollResource($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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo 弃权选项
|
||||
* @return array
|
||||
*/
|
||||
public function latest()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$poll = Poll::query()->orderBy('id', 'desc')->first();
|
||||
$selection = null;
|
||||
$answerStats = [];
|
||||
if ($poll) {
|
||||
$baseAnswerQuery = $poll->answers()->where('selection', '<=', Poll::MAX_OPTION_INDEX);
|
||||
$poll->answers_count = (clone $baseAnswerQuery)->count();
|
||||
$answer = $poll->answers()->where('userid', $user->id)->first();
|
||||
$options = [];
|
||||
for ($i = 0; $i <= Poll::MAX_OPTION_INDEX; $i++) {
|
||||
$field = "option{$i}";
|
||||
$value = $poll->{$field};
|
||||
if ($value !== '') {
|
||||
$options[$i] = $value;
|
||||
}
|
||||
}
|
||||
if ($answer) {
|
||||
$selection = $answer->selection;
|
||||
} else {
|
||||
$options["255"] = "弃权(我想偷看结果!)";
|
||||
}
|
||||
$poll->options = $options;
|
||||
|
||||
$answerStats = (clone $baseAnswerQuery)
|
||||
->selectRaw("selection, count(*) as count")->groupBy("selection")
|
||||
->get()->pluck('count', 'selection')->toArray();
|
||||
foreach ($answerStats as $index => &$value) {
|
||||
$value = number_format(($value / $poll->answers_count) * 100, 2) . '%';
|
||||
}
|
||||
}
|
||||
|
||||
$resource = new PollResource($poll);
|
||||
$resource->additional([
|
||||
'selection' => $selection,
|
||||
'answer_stats' => $answerStats,
|
||||
'site_info' => site_info(),
|
||||
]);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
public function vote(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'poll_id' => 'required',
|
||||
'selection' => 'required|integer|min:0|max:255',
|
||||
]);
|
||||
$pollId = $request->poll_id;
|
||||
$selection = $request->selection;
|
||||
$user = Auth::user();
|
||||
$poll = Poll::query()->findOrFail($pollId);
|
||||
$data = [
|
||||
'userid' => $user->id,
|
||||
'selection' => $selection,
|
||||
];
|
||||
$answer = $poll->answers()->create($data);
|
||||
return $this->success($answer->toArray());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\RewardResource;
|
||||
use App\Http\Resources\PeerResource;
|
||||
use App\Http\Resources\SnatchResource;
|
||||
use App\Models\Peer;
|
||||
use App\Models\Snatch;
|
||||
use App\Repositories\RewardRepository;
|
||||
use App\Repositories\TorrentRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class RewardController extends Controller
|
||||
{
|
||||
private $repository;
|
||||
|
||||
public function __construct(RewardRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'torrent_id' => 'required',
|
||||
]);
|
||||
$result = $this->repository->getList($request->all());
|
||||
$resource = RewardResource::collection($result);
|
||||
$resource->additional([
|
||||
'page_title' => nexus_trans('reward.index.page_title'),
|
||||
]);
|
||||
|
||||
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([
|
||||
'torrent_id' => 'required',
|
||||
'value' => 'required',
|
||||
]);
|
||||
$result = $this->repository->store($request->torrent_id, $request->value, Auth::user());
|
||||
$resource = new RewardResource($result);
|
||||
return $this->success($resource, '赠魔成功!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,7 @@ class TagController extends Controller
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate($this->getRules());
|
||||
$data = $request->all();
|
||||
if (isset($data['priority'])) {
|
||||
$data['priority'] = intval($data['priority']);
|
||||
}
|
||||
$data = array_filter($request->all());
|
||||
$result = $this->repository->store($data);
|
||||
$resource = new TagResource($result);
|
||||
return $this->success($resource);
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\ThankResource;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Thank;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ThankController extends Controller
|
||||
{
|
||||
@@ -37,7 +42,48 @@ class ThankController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
$user = Auth::user();
|
||||
$request->validate(['torrent_id' => 'required']);
|
||||
$torrentId = $request->torrent_id;
|
||||
$torrent = Torrent::query()->findOrFail($torrentId, Torrent::$commentFields);
|
||||
$torrent->checkIsNormal();
|
||||
$torrentOwner = User::query()->findOrFail($torrent->owner);
|
||||
if ($user->id == $torrentOwner->id) {
|
||||
throw new \LogicException("you can't thank to yourself");
|
||||
}
|
||||
$torrentOwner->checkIsNormal();
|
||||
if ($user->thank_torrent_logs()->where('torrentid', $torrentId)->exists()) {
|
||||
throw new \LogicException("you already thank this torrent");
|
||||
}
|
||||
|
||||
$result = DB::transaction(function () use ($user, $torrentOwner, $torrent) {
|
||||
$thank = $user->thank_torrent_logs()->create(['torrentid' => $torrent->id]);
|
||||
$sayThanksBonus = Setting::get('bonus.saythanks');
|
||||
$receiveThanksBonus = Setting::get('bonus.receivethanks');
|
||||
if ($sayThanksBonus > 0) {
|
||||
$affectedRows = User::query()
|
||||
->where('id', $user->id)
|
||||
->where('seedbonus', $user->seedbonus)
|
||||
->increment('seedbonus', $sayThanksBonus);
|
||||
if ($affectedRows != 1) {
|
||||
do_log("affectedRows: $affectedRows, query: " . last_query(), 'error');
|
||||
throw new \RuntimeException("increment user bonus fail.");
|
||||
}
|
||||
}
|
||||
if ($receiveThanksBonus > 0) {
|
||||
$affectedRows = User::query()
|
||||
->where('id', $torrentOwner->id)
|
||||
->where('seedbonus', $torrentOwner->seedbonus)
|
||||
->increment('seedbonus', $receiveThanksBonus);
|
||||
if ($affectedRows != 1) {
|
||||
do_log("affectedRows: $affectedRows, query: " . last_query(), 'error');
|
||||
throw new \RuntimeException("increment owner bonus fail.");
|
||||
}
|
||||
}
|
||||
return $thank;
|
||||
});
|
||||
$resource = new ThankResource($result);
|
||||
return $this->success($resource, '说谢谢成功!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Repositories\ToolRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ToolController extends Controller
|
||||
{
|
||||
@@ -14,4 +15,11 @@ class ToolController extends Controller
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function notifications(): array
|
||||
{
|
||||
$user = Auth::user();
|
||||
$result = $this->repository->getNotificationCount($user);
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\RewardResource;
|
||||
use App\Http\Resources\TorrentResource;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use App\Repositories\TorrentRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -51,16 +53,19 @@ class TorrentController extends Controller
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
|
||||
$result = $this->repository->getDetail($id, Auth::user());
|
||||
|
||||
$isBookmarked = Auth::user()->bookmarks()->where('torrentid', $id)->exists();
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
$user = Auth::user();
|
||||
$result = $this->repository->getDetail($id, $user);
|
||||
$isBookmarked = $user->bookmarks()->where('torrentid', $id)->exists();
|
||||
|
||||
$resource = new TorrentResource($result);
|
||||
$resource->additional([
|
||||
'page_title' => nexus_trans('torrent.show.page_title'),
|
||||
'field_labels' => Torrent::getFieldLabels(),
|
||||
'is_bookmarked' => (int)$isBookmarked,
|
||||
'bonus_reward_values' => Torrent::BONUS_REWARD_VALUES,
|
||||
]);
|
||||
|
||||
return $this->success($resource);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Repositories\TrackerRepository;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrackerController extends Controller
|
||||
{
|
||||
private TrackerRepository $repository;
|
||||
|
||||
public function __construct(TrackerRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function announce(Request $request): \Illuminate\Http\Response
|
||||
{
|
||||
return $this->repository->announce($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function scrape(Request $request): \Illuminate\Http\Response
|
||||
{
|
||||
return $this->repository->scrape($request);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ class Kernel extends HttpKernel
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
// \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\BootNexus::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Nexus\Nexus;
|
||||
|
||||
class BootNexus
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
Nexus::boot();
|
||||
do_log(sprintf(
|
||||
"Nexus booted. request.server: %s, request.header: %s, request.query: %s, request.input: %s",
|
||||
nexus_json_encode($request->server()), nexus_json_encode($request->header()), nexus_json_encode($request->query()), nexus_json_encode($request->input())
|
||||
));
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class Permission
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
if (!$user || (IS_PLATFORM_ADMIN && !$user->canAccessAdmin())) {
|
||||
if (!$user || (nexus()->isPlatformAdmin() && !$user->canAccessAdmin())) {
|
||||
do_log("denied!");
|
||||
throw new UnauthorizedException('Unauthorized!');
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ class Platform
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (empty(CURRENT_PLATFORM)) {
|
||||
if (empty(nexus()->getPlatform())) {
|
||||
throw new \InvalidArgumentException("Require platform header.");
|
||||
}
|
||||
if (!in_array(CURRENT_PLATFORM, PLATFORMS)) {
|
||||
if (!nexus()->isPlatformValid()) {
|
||||
throw new \InvalidArgumentException("Invalid platform: " . CURRENT_PLATFORM);
|
||||
}
|
||||
return $next($request);
|
||||
|
||||
@@ -15,5 +15,7 @@ class TrimStrings extends Middleware
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
'peer_id',
|
||||
'info_hash',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class MessageResource extends JsonResource
|
||||
'subject' => $this->subject,
|
||||
'msg' => strip_all_tags($this->msg),
|
||||
'added_human' => $this->added->diffForHumans(),
|
||||
'added' => format_datetime($this->added),
|
||||
'send_user' => new UserResource($this->whenLoaded('send_user')),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ class NewsResource extends JsonResource
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
$descriptionArr = format_description($this->body);
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'body' => $this->body,
|
||||
'added' => $this->added,
|
||||
'body' => $descriptionArr,
|
||||
'images' => get_image_from_description($descriptionArr),
|
||||
'added' => format_datetime($this->added, 'Y.m.d'),
|
||||
'user' => new UserResource($this->whenLoaded('user'))
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class PollResource extends JsonResource
|
||||
{
|
||||
public $preserveKeys = true;
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
$out = [
|
||||
'id' => $this->id,
|
||||
'added' => format_datetime($this->added),
|
||||
'question' => $this->question,
|
||||
'answers_count' => $this->answers_count,
|
||||
'options' => $this->options,
|
||||
];
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class RewardResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'user_id' => $this->userid,
|
||||
'torrent_id' => $this->torrentid,
|
||||
'value' => $this->value,
|
||||
'created_at' => format_datetime($this->created_at),
|
||||
'updated_at' => format_datetime($this->updated_at),
|
||||
'user' => new UserResource($this->whenLoaded('user'))
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,11 @@ class TagResource extends JsonResource
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'color' => $this->color,
|
||||
'font_color' => $this->font_color,
|
||||
'font_size' => $this->font_size,
|
||||
'padding' => $this->padding,
|
||||
'margin' => $this->margin,
|
||||
'border_radius' => $this->border_radius,
|
||||
'priority' => $this->priority,
|
||||
'created_at' => format_datetime($this->created_at),
|
||||
'updated_at' => format_datetime($this->updated_at),
|
||||
|
||||
@@ -16,6 +16,8 @@ class ThankResource extends JsonResource
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'torrent_id' => $this->torrentid,
|
||||
'user_id' => $this->userid,
|
||||
'user' => new UserResource($this->whenLoaded('user')),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -35,14 +35,18 @@ class TorrentResource extends JsonResource
|
||||
'numfiles' => $this->numfiles,
|
||||
'sp_state' => $this->sp_state,
|
||||
'sp_state_real' => $this->sp_state_real,
|
||||
'sp_state_real_text' => $this->spStateRealText,
|
||||
'promotion_info' => $this->promotionInfo,
|
||||
'hr' => $this->hr,
|
||||
'pick_type' => $this->picktype,
|
||||
'pick_time' => $this->picktime,
|
||||
'pick_info' => $this->pickInfo,
|
||||
'download_url' => $this->download_url,
|
||||
'user' => new UserResource($this->whenLoaded('user')),
|
||||
'anonymous' => $this->anonymous,
|
||||
'basic_category' => new CategoryResource($this->whenLoaded('basic_category')),
|
||||
'tags' => TagResource::collection($this->whenLoaded('tags')),
|
||||
'thanks' => ThankResource::collection($this->whenLoaded('thanks')),
|
||||
'reward_logs' => RewardResource::collection($this->whenLoaded('reward_logs')),
|
||||
];
|
||||
$descriptionArr = format_description($this->descr);
|
||||
$out['cover'] = get_image_from_description($descriptionArr, true);
|
||||
@@ -63,6 +67,7 @@ class TorrentResource extends JsonResource
|
||||
|
||||
$out['thank_users_count'] = $this->thank_users_count;
|
||||
$out['peers_count'] = $this->peers_count;
|
||||
$out['reward_logs_count'] = $this->reward_logs_count;
|
||||
}
|
||||
// $out['upload_peers_count'] = $this->upload_peers_count;
|
||||
// $out['download_peers_count'] = $this->download_peers_count;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Nexus\Nexus;
|
||||
|
||||
class ResetNexus
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param mixed $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event): void
|
||||
{
|
||||
Nexus::flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Repositories\SearchRepository;
|
||||
use App\Repositories\ToolRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SyncTorrentToEs implements ShouldQueue
|
||||
{
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
$id = $event->torrentId;
|
||||
$searchRep = new SearchRepository();
|
||||
$result = $searchRep->updateTorrent($id);
|
||||
do_log("result: " . var_export($result, true));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* handle failed
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function failed($event, \Throwable $exception)
|
||||
{
|
||||
$toolRep = new ToolRepository();
|
||||
$to = Setting::get('main.SITEEMAIL');
|
||||
$subject = sprintf('Event: %s listener: %s handle error', get_class($event), __CLASS__);
|
||||
$body = sprintf("%s\n%s", $exception->getMessage(), $exception->getTraceAsString());
|
||||
try {
|
||||
$result = $toolRep->sendMail($to, $subject, $body);
|
||||
if ($result === false) {
|
||||
do_log("$subject send mail fail", 'alert');
|
||||
}
|
||||
} catch (\Throwable $exception) {
|
||||
do_log("$subject send mail fail: " . $exception->getMessage() . $exception->getTraceAsString(), 'alert');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class NexusFormatter
|
||||
|
||||
protected function formatter()
|
||||
{
|
||||
$format = "[%datetime%] [" . REQUEST_ID . "] %channel%.%level_name%: %message% %context% %extra%\n";
|
||||
$format = "[%datetime%] [" . nexus()->getRequestId() . "] %channel%.%level_name%: %message% %context% %extra%\n";
|
||||
return tap(new LineFormatter($format, 'Y-m-d H:i:s', true, true), function ($formatter) {
|
||||
$formatter->includeStacktraces();
|
||||
});
|
||||
|
||||
@@ -8,4 +8,14 @@ class Bookmark extends NexusModel
|
||||
protected $table = 'bookmarks';
|
||||
|
||||
protected $fillable = ['userid', 'torrentid'];
|
||||
|
||||
public function torrent()
|
||||
{
|
||||
return $this->belongsTo(Torrent::class, 'torrentid');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(Torrent::class, 'userid');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class Cheater extends NexusModel
|
||||
{
|
||||
protected $fillable = [
|
||||
'added', 'userid', 'torrentid', 'uploaded', 'downloaded', 'anctime', 'seeders', 'leechers', 'hit',
|
||||
'dealtby', 'dealtwith', 'comment',
|
||||
];
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class Comment extends NexusModel
|
||||
{
|
||||
protected $casts = [
|
||||
@@ -10,6 +12,45 @@ class Comment extends NexusModel
|
||||
'editdate' => 'datetime',
|
||||
];
|
||||
|
||||
protected $fillable = ['user', 'torrent', 'added', 'text', 'ori_text', 'editedby', 'editdate', 'offer', 'request', 'anonymous'];
|
||||
|
||||
const TYPE_TORRENT = 'torrent';
|
||||
const TYPE_REQUEST = 'request';
|
||||
const TYPE_OFFER = 'offer';
|
||||
|
||||
const TYPE_MAPS = [
|
||||
self::TYPE_TORRENT => [
|
||||
'model' => Torrent::class,
|
||||
'foreign_key' => 'torrent',
|
||||
'target_name_field' => 'name',
|
||||
'target_script' => 'details.php?id=%s'
|
||||
],
|
||||
self::TYPE_REQUEST => [
|
||||
'model' => Request::class,
|
||||
'foreign_key' => 'request',
|
||||
'target_name_field' => 'request',
|
||||
'target_script' => 'viewrequests.php?id=%s&req_details=1'
|
||||
],
|
||||
self::TYPE_OFFER => [
|
||||
'model' => Offer::class,
|
||||
'foreign_key' => 'offer',
|
||||
'target_name_field' => 'name',
|
||||
'target_script' => 'offers.php?id=%s&off_details=1'
|
||||
],
|
||||
];
|
||||
|
||||
public function scopeType(Builder $query, string $type, int $typeValue)
|
||||
{
|
||||
foreach (self::TYPE_MAPS as $key => $value) {
|
||||
if ($type != $key) {
|
||||
$query->where($value['foreign_key'], 0);
|
||||
} else {
|
||||
$query->where($value['foreign_key'], $typeValue);
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function related_torrent()
|
||||
{
|
||||
return $this->belongsTo(Torrent::class, 'torrent');
|
||||
|
||||
@@ -21,4 +21,20 @@ class NexusModel extends Model
|
||||
return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is valid date string
|
||||
*
|
||||
* @see https://stackoverflow.com/questions/19271381/correctly-determine-if-date-string-is-a-valid-date-in-that-format
|
||||
* @param $name
|
||||
* @param string $format
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidDate($name, $format = 'Y-m-d H:i:s'): bool
|
||||
{
|
||||
$date = $this->getRawOriginal($name);
|
||||
$d = \DateTime::createFromFormat($format, $date);
|
||||
// The Y ( 4 digits year ) returns TRUE for any integer with any number of digits so changing the comparison from == to === fixes the issue.
|
||||
return $d && $d->format($format) === $date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class Offer extends NexusModel
|
||||
{
|
||||
protected $fillable = ['userid', 'name', 'descr', 'comments', 'added'];
|
||||
|
||||
protected $casts = [
|
||||
'added' => 'datetime'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userid');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,15 @@ namespace App\Models;
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Peer extends NexusModel
|
||||
{
|
||||
protected $fillable = [
|
||||
'torrent', 'peer_id', 'ip', 'port', 'uploaded', 'downloaded', 'to_go', 'seeder', 'started', 'last_action',
|
||||
'prev_action', 'connectable', 'userid', 'agent', 'finishedat', 'downloadoffset', 'uploadedoffset', 'passkey',
|
||||
];
|
||||
|
||||
const CONNECTABLE_YES = 'yes';
|
||||
|
||||
const CONNECTABLE_NO = 'no';
|
||||
@@ -71,4 +77,32 @@ class Peer extends NexusModel
|
||||
{
|
||||
return $this->belongsTo(Torrent::class, 'torrent');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function updateConnectableStateIfNeeded()
|
||||
{
|
||||
$tmp_ip = $this->ip;
|
||||
// IPv6 Check
|
||||
if (filter_var($tmp_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$tmp_ip = '['.$tmp_ip.']';
|
||||
}
|
||||
$cacheKey = 'peers:connectable:'.$tmp_ip.'-'.$this->port.'-'.$this->agent;
|
||||
$log = "cacheKey: $cacheKey";
|
||||
if (!Cache::has($cacheKey)) {
|
||||
$con = @fsockopen($tmp_ip, $this->port, $error_code, $error_message, 1);
|
||||
if (is_resource($con)) {
|
||||
$this->connectable = self::CONNECTABLE_YES;
|
||||
fclose($con);
|
||||
} else {
|
||||
$this->connectable = self::CONNECTABLE_NO;
|
||||
}
|
||||
Cache::put($cacheKey, $this->connectable, 600);
|
||||
$log .= ", do check, connectable: " . $this->connectable;
|
||||
} else {
|
||||
$log .= ", don't do check";
|
||||
}
|
||||
do_log($log);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class Poll extends NexusModel
|
||||
{
|
||||
protected $fillable = ['added', 'question', 'option0', 'option1', 'option2', 'option3', 'option4', 'option5'];
|
||||
|
||||
protected $casts = [
|
||||
'added' => 'datetime'
|
||||
];
|
||||
|
||||
const MAX_OPTION_INDEX = 19;
|
||||
|
||||
public function answers()
|
||||
{
|
||||
return $this->hasMany(PollAnswer::class, 'pollid');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class PollAnswer extends NexusModel
|
||||
{
|
||||
protected $table = 'pollanswers';
|
||||
|
||||
protected $fillable = ['pollid', 'userid', 'selection',];
|
||||
|
||||
public function poll()
|
||||
{
|
||||
return $this->belongsTo(Poll::class, 'pollid');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userid');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class Request extends NexusModel
|
||||
{
|
||||
protected $fillable = ['userid', 'request', 'descr', 'comments', 'hits', 'added'];
|
||||
|
||||
protected $casts = [
|
||||
'added' => 'datetime'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userid');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class Reward extends NexusModel
|
||||
{
|
||||
protected $table = 'magic';
|
||||
|
||||
protected $fillable = ['torrentid', 'userid', 'value', ];
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userid');
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class Setting extends NexusModel
|
||||
{
|
||||
@@ -10,9 +12,9 @@ class Setting extends NexusModel
|
||||
|
||||
public static function get($name = null)
|
||||
{
|
||||
static $settings;
|
||||
if (is_null($settings)) {
|
||||
$settings = NexusDB::remember("nexus_settings_in_laravel", 10, function () {
|
||||
$rows = self::query()->get(['name', 'value']);
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$value = $row->value;
|
||||
if (!is_null($value)) {
|
||||
@@ -21,9 +23,10 @@ class Setting extends NexusModel
|
||||
$value = $arr;
|
||||
}
|
||||
}
|
||||
Arr::set($settings, $row->name, $value);
|
||||
Arr::set($result, $row->name, $value);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
if (is_null($name)) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ class Snatch extends NexusModel
|
||||
{
|
||||
protected $table = 'snatched';
|
||||
|
||||
protected $fillable = [
|
||||
'torrentid', 'userid', 'ip', 'port', 'uploaded', 'downloaded', 'to_go', 'seedtime', 'leechtime',
|
||||
'last_action', 'startdat', 'completedat', 'finished'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'last_action' => 'datetime',
|
||||
'startdat' => 'datetime',
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ class Tag extends NexusModel
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = [
|
||||
'id', 'name', 'color', 'priority', 'created_at', 'updated_at'
|
||||
'id', 'name', 'color', 'priority', 'created_at', 'updated_at', 'font_size', 'font_color', 'padding', 'margin', 'border_radius'
|
||||
];
|
||||
|
||||
const DEFAULTS = [
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Models;
|
||||
|
||||
class Thank extends NexusModel
|
||||
{
|
||||
protected $fillable = ['torrentid', 'userid'];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userid');
|
||||
|
||||
+100
-9
@@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Repositories\TagRepository;
|
||||
use JeroenG\Explorer\Application\Explored;
|
||||
use Laravel\Scout\Searchable;
|
||||
|
||||
class Torrent extends NexusModel
|
||||
{
|
||||
@@ -26,6 +28,11 @@ class Torrent extends NexusModel
|
||||
'added' => 'datetime'
|
||||
];
|
||||
|
||||
public static $commentFields = [
|
||||
'id', 'name', 'added', 'visible', 'banned', 'owner', 'sp_state', 'pos_state', 'hr', 'picktype', 'picktime',
|
||||
'last_action', 'leechers', 'seeders', 'times_completed', 'views', 'size'
|
||||
];
|
||||
|
||||
public static $basicRelations = [
|
||||
'basic_category', 'basic_audio_codec', 'basic_codec', 'basic_media',
|
||||
'basic_source', 'basic_standard', 'basic_team',
|
||||
@@ -61,16 +68,79 @@ class Torrent extends NexusModel
|
||||
const PROMOTION_HALF_DOWN_TWO_TIMES_UP = 6;
|
||||
const PROMOTION_ONE_THIRD_DOWN = 7;
|
||||
|
||||
public static $promotionTypes = [
|
||||
self::PROMOTION_NORMAL => ['text' => 'Normal', 'up_multiplier' => 1, 'down_multiplier' => 1],
|
||||
self::PROMOTION_FREE => ['text' => 'Free', 'up_multiplier' => 1, 'down_multiplier' => 0],
|
||||
self::PROMOTION_TWO_TIMES_UP => ['text' => '2X', 'up_multiplier' => 2, 'down_multiplier' => 1],
|
||||
self::PROMOTION_FREE_TWO_TIMES_UP => ['text' => '2X Free', 'up_multiplier' => 2, 'down_multiplier' => 0],
|
||||
self::PROMOTION_HALF_DOWN => ['text' => '50%', 'up_multiplier' => 1, 'down_multiplier' => 0.5],
|
||||
self::PROMOTION_HALF_DOWN_TWO_TIMES_UP => ['text' => '2X 50%', 'up_multiplier' => 2, 'down_multiplier' => 0.5],
|
||||
self::PROMOTION_ONE_THIRD_DOWN => ['text' => '30%', 'up_multiplier' => 1, 'down_multiplier' => 0.3],
|
||||
public static array $promotionTypes = [
|
||||
self::PROMOTION_NORMAL => [
|
||||
'text' => 'Normal',
|
||||
'up_multiplier' => 1,
|
||||
'down_multiplier' => 1,
|
||||
'color' => ''
|
||||
],
|
||||
self::PROMOTION_FREE => [
|
||||
'text' => 'Free',
|
||||
'up_multiplier' => 1,
|
||||
'down_multiplier' => 0,
|
||||
'color' => 'linear-gradient(to right, rgba(0,52,206,0.5), rgba(0,52,206,1), rgba(0,52,206,0.5))'
|
||||
],
|
||||
self::PROMOTION_TWO_TIMES_UP => [
|
||||
'text' => '2X',
|
||||
'up_multiplier' => 2,
|
||||
'down_multiplier' => 1,
|
||||
'color' => 'linear-gradient(to right, rgba(0,153,0,0.5), rgba(0,153,0,1), rgba(0,153,0,0.5))'
|
||||
],
|
||||
self::PROMOTION_FREE_TWO_TIMES_UP => [
|
||||
'text' => '2X Free',
|
||||
'up_multiplier' => 2,
|
||||
'down_multiplier' => 0,
|
||||
'color' => 'linear-gradient(to right, rgba(0,153,0,1), rgba(0,52,206,1)'
|
||||
],
|
||||
self::PROMOTION_HALF_DOWN => [
|
||||
'text' => '50%',
|
||||
'up_multiplier' => 1,
|
||||
'down_multiplier' => 0.5,
|
||||
'color' => 'linear-gradient(to right, rgba(220,0,3,0.5), rgba(220,0,3,1), rgba(220,0,3,0.5))'
|
||||
],
|
||||
self::PROMOTION_HALF_DOWN_TWO_TIMES_UP => [
|
||||
'text' => '2X 50%',
|
||||
'up_multiplier' => 2,
|
||||
'down_multiplier' => 0.5,
|
||||
'color' => 'linear-gradient(to right, rgba(0,153,0,1), rgba(220,0,3,1)'
|
||||
],
|
||||
self::PROMOTION_ONE_THIRD_DOWN => [
|
||||
'text' => '30%',
|
||||
'up_multiplier' => 1,
|
||||
'down_multiplier' => 0.3,
|
||||
'color' => 'linear-gradient(to right, rgba(65,23,73,0.5), rgba(65,23,73,1), rgba(65,23,73,0.5))'
|
||||
],
|
||||
];
|
||||
|
||||
const PICK_NORMAL = 'normal';
|
||||
const PICK_HOT = 'hot';
|
||||
const PICK_CLASSIC = 'classic';
|
||||
const PICK_RECOMMENDED = 'recommended';
|
||||
|
||||
public static array $pickTypes = [
|
||||
self::PICK_NORMAL => ['text' => self::PICK_NORMAL, 'color' => ''],
|
||||
self::PICK_HOT => ['text' => self::PICK_HOT, 'color' => '#e78d0f'],
|
||||
self::PICK_CLASSIC => ['text' => self::PICK_CLASSIC, 'color' => '#77b300'],
|
||||
self::PICK_RECOMMENDED => ['text' => self::PICK_RECOMMENDED, 'color' => '#820084'],
|
||||
];
|
||||
|
||||
const BONUS_REWARD_VALUES = [50, 100, 200, 500, 1000];
|
||||
|
||||
public function getPickInfoAttribute()
|
||||
{
|
||||
$info = self::$pickTypes[$this->picktype] ?? null;
|
||||
if ($info) {
|
||||
$info['text'] = nexus_trans('torrent.pick_info.' . $this->picktype);
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPromotionInfoAttribute()
|
||||
{
|
||||
return self::$promotionTypes[$this->sp_state_real] ?? null;
|
||||
}
|
||||
|
||||
public function getSpStateRealTextAttribute()
|
||||
{
|
||||
$spStateReal = $this->sp_state_real;
|
||||
@@ -79,6 +149,9 @@ class Torrent extends NexusModel
|
||||
|
||||
public function getSpStateRealAttribute()
|
||||
{
|
||||
if ($this->getRawOriginal('sp_state') === null) {
|
||||
throw new \RuntimeException('no select sp_state field');
|
||||
}
|
||||
$spState = $this->sp_state;
|
||||
$global = self::getGlobalPromotionState();
|
||||
$log = sprintf('torrent: %s sp_state: %s, global sp state: %s', $this->id, $spState, $global);
|
||||
@@ -143,7 +216,10 @@ class Torrent extends NexusModel
|
||||
|
||||
public static function getFieldLabels(): array
|
||||
{
|
||||
$fields = ['comments', 'times_completed', 'peers_count', 'thank_users_count', 'numfiles', 'bookmark_yes', 'bookmark_no'];
|
||||
$fields = [
|
||||
'comments', 'times_completed', 'peers_count', 'thank_users_count', 'numfiles', 'bookmark_yes', 'bookmark_no',
|
||||
'reward_yes', 'reward_no', 'reward_logs', 'download', 'thanks_yes', 'thanks_no'
|
||||
];
|
||||
$result = [];
|
||||
foreach($fields as $field) {
|
||||
$result[$field] = nexus_trans("torrent.show.{$field}_label");
|
||||
@@ -163,6 +239,11 @@ class Torrent extends NexusModel
|
||||
return true;
|
||||
}
|
||||
|
||||
public function bookmarks(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Bookmark::class, 'torrentid');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'owner')->withDefault(User::getDefaultUserAttributes());
|
||||
@@ -263,9 +344,19 @@ class Torrent extends NexusModel
|
||||
$query->where('visible', $visible);
|
||||
}
|
||||
|
||||
public function torrent_tags(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(TorrentTag::class, 'torrent_id');
|
||||
}
|
||||
|
||||
public function tags(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Tag::class, 'torrent_tags', 'torrent_id', 'tag_id')
|
||||
->orderByRaw(sprintf("field(`tags`.`id`,%s)", TagRepository::getOrderByFieldIdString()));
|
||||
}
|
||||
|
||||
public function reward_logs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Reward::class, 'torrentid');
|
||||
}
|
||||
}
|
||||
|
||||
+52
-4
@@ -79,6 +79,32 @@ class User extends Authenticatable
|
||||
'invites' => '邀请',
|
||||
];
|
||||
|
||||
public function mappableAs(): array
|
||||
{
|
||||
return [
|
||||
'id' => 'long',
|
||||
'username' => [
|
||||
'type' => 'text',
|
||||
'analyzer' => 'ik_max_word',
|
||||
],
|
||||
'email' => [
|
||||
'type' => 'text',
|
||||
'analyzer' => 'ik_max_word',
|
||||
],
|
||||
'added' => 'date',
|
||||
];
|
||||
}
|
||||
|
||||
public function toSearchableArray()
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'username' => $this->username,
|
||||
'email' => $this->email,
|
||||
'added' => $this->added,
|
||||
];
|
||||
}
|
||||
|
||||
public function getClassTextAttribute(): string
|
||||
{
|
||||
return self::$classes[$this->class]['text'] ?? '';
|
||||
@@ -116,7 +142,7 @@ class User extends Authenticatable
|
||||
*/
|
||||
protected $fillable = [
|
||||
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'modcomment', 'enabled', 'status',
|
||||
'leechwarn', 'leechwarnuntil', 'page', 'class'
|
||||
'leechwarn', 'leechwarnuntil', 'page', 'class', 'uploaded', 'downloaded', 'clientselect', 'showclienterror',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -320,8 +346,10 @@ class User extends Authenticatable
|
||||
public function medals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Medal::class, 'user_medals', 'uid', 'medal_id')
|
||||
->withPivot(['id', 'expire_at'])
|
||||
->withTimestamps();
|
||||
->withPivot(['id', 'expire_at', 'status'])
|
||||
->withTimestamps()
|
||||
->orderByPivot('id', 'desc')
|
||||
;
|
||||
}
|
||||
|
||||
public function valid_medals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
@@ -331,6 +359,26 @@ class User extends Authenticatable
|
||||
});
|
||||
}
|
||||
|
||||
public function wearing_medals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
{
|
||||
return $this->valid_medals()->where('user_medals.status', UserMedal::STATUS_WEARING);
|
||||
}
|
||||
|
||||
public function reward_torrent_logs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Reward::class, 'userid');
|
||||
}
|
||||
|
||||
public function thank_torrent_logs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Thank::class, 'userid');
|
||||
}
|
||||
|
||||
public function poll_answers(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(PollAnswer::class, 'userid');
|
||||
}
|
||||
|
||||
public function getAvatarAttribute($value)
|
||||
{
|
||||
if ($value) {
|
||||
@@ -348,7 +396,7 @@ class User extends Authenticatable
|
||||
public function updateWithModComment(array $update, $modComment)
|
||||
{
|
||||
if (!$this->exists) {
|
||||
throw new \RuntimeException('This mehtod only works when user exists!');
|
||||
throw new \RuntimeException('This method only works when user exists!');
|
||||
}
|
||||
//@todo how to do prepare bindings here ?
|
||||
$modComment = addslashes($modComment);
|
||||
|
||||
@@ -4,5 +4,10 @@ namespace App\Models;
|
||||
|
||||
class UserMedal extends NexusModel
|
||||
{
|
||||
protected $fillable = ['uid', 'medal_id', 'expire_at'];
|
||||
protected $fillable = ['uid', 'medal_id', 'expire_at', 'status'];
|
||||
|
||||
const STATUS_NOT_WEARING = 0;
|
||||
const STATUS_WEARING = 1;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Nexus\Nexus;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -28,5 +29,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
// JsonResource::withoutWrapping();
|
||||
DB::connection(config('database.default'))->enableQueryLog();
|
||||
// Nexus::boot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Events\TorrentUpdated;
|
||||
use App\Listeners\SyncTorrentToEs;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
@@ -18,6 +20,9 @@ class EventServiceProvider extends ServiceProvider
|
||||
Registered::class => [
|
||||
SendEmailVerificationNotification::class,
|
||||
],
|
||||
TorrentUpdated::class => [
|
||||
SyncTorrentToEs::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,10 @@ class RouteServiceProvider extends ServiceProvider
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web.php'));
|
||||
|
||||
Route::prefix('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/tracker.php'));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Exceptions\NexusException;
|
||||
use App\Exceptions\ClientNotAllowedException;
|
||||
use App\Models\AgentAllow;
|
||||
use App\Models\AgentDeny;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class AgentAllowRepository extends BaseRepository
|
||||
{
|
||||
@@ -52,31 +53,40 @@ class AgentAllowRepository extends BaseRepository
|
||||
public function getPatternMatches($pattern, $start, $matchNum)
|
||||
{
|
||||
if (!preg_match($pattern, $start, $matches)) {
|
||||
throw new NexusException(sprintf('pattern: %s can not match start: %s', $pattern, $start));
|
||||
throw new ClientNotAllowedException(sprintf('pattern: %s can not match start: %s', $pattern, $start));
|
||||
}
|
||||
$matchCount = count($matches) - 1;
|
||||
//due to old data may be matchNum > matchCount
|
||||
if ($matchNum > $matchCount && !IN_NEXUS) {
|
||||
throw new NexusException("pattern: $pattern match start: $start got matches count: $matchCount, but require $matchNum.");
|
||||
}
|
||||
// if ($matchNum > $matchCount && !IN_NEXUS) {
|
||||
// throw new ClientNotAllowedException("pattern: $pattern match start: $start got matches count: $matchCount, but require $matchNum.");
|
||||
// }
|
||||
return array_slice($matches, 1, $matchNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $peerId
|
||||
* @param $agent
|
||||
* @param false $debug
|
||||
* @return \App\Models\NexusModel|mixed
|
||||
* @throws ClientNotAllowedException
|
||||
*/
|
||||
public function checkClient($peerId, $agent, $debug = false)
|
||||
{
|
||||
//check from high version to low version, if high version allow, stop!
|
||||
$allows = AgentAllow::query()
|
||||
->orderBy('peer_id_start', 'desc')
|
||||
->orderBy('agent_start', 'desc')
|
||||
->get();
|
||||
$allows = NexusDB::remember("all_agent_allows", 600, function () {
|
||||
return AgentAllow::query()
|
||||
->orderBy('peer_id_start', 'desc')
|
||||
->orderBy('agent_start', 'desc')
|
||||
->get();
|
||||
});
|
||||
$agentAllowPassed = null;
|
||||
$versionTooLowStr = '';
|
||||
foreach ($allows as $agentAllow) {
|
||||
$agentAllowId = $agentAllow->id;
|
||||
$logPrefix = "[ID: $agentAllowId]";
|
||||
$isPeerIdAllowed = $isAgentAllowed = $isPeerIdTooLow = $isAgentTooLow = false;
|
||||
//check peer_id
|
||||
if ($agentAllow->peer_id_pattern == '') {
|
||||
//check peer_id, when handle scrape request, no peer_id, so let it pass
|
||||
if ($agentAllow->peer_id_pattern == '' || $peerId === null) {
|
||||
$isPeerIdAllowed = true;
|
||||
} else {
|
||||
$pattern = $agentAllow->peer_id_pattern;
|
||||
@@ -93,7 +103,7 @@ class AgentAllowRepository extends BaseRepository
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
do_log("$logPrefix, check peer_id error: " . $exception->getMessage(), 'error');
|
||||
throw new NexusException("regular expression err for peer_id: " . $start . ", please ask sysop to fix this");
|
||||
throw new ClientNotAllowedException("regular expression err for peer_id: " . $start . ", please ask sysop to fix this");
|
||||
}
|
||||
if ($peerIdResult == 1) {
|
||||
$isPeerIdAllowed = true;
|
||||
@@ -121,7 +131,7 @@ class AgentAllowRepository extends BaseRepository
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
do_log("$logPrefix, check agent error: " . $exception->getMessage(), 'error');
|
||||
throw new NexusException("regular expression err for agent: " . $start . ", please ask sysop to fix this");
|
||||
throw new ClientNotAllowedException("regular expression err for agent: " . $start . ", please ask sysop to fix this");
|
||||
}
|
||||
if ($agentResult == 1) {
|
||||
$isAgentAllowed = true;
|
||||
@@ -142,11 +152,11 @@ class AgentAllowRepository extends BaseRepository
|
||||
}
|
||||
|
||||
if ($versionTooLowStr) {
|
||||
throw new NexusException($versionTooLowStr);
|
||||
throw new ClientNotAllowedException($versionTooLowStr);
|
||||
}
|
||||
|
||||
if (!$agentAllowPassed) {
|
||||
throw new NexusException("Banned Client, Please goto " . getSchemeAndHttpHost() . "/faq.php#id29 for a list of acceptable clients");
|
||||
throw new ClientNotAllowedException("Banned Client, Please goto " . getSchemeAndHttpHost() . "/faq.php#id29 for a list of acceptable clients");
|
||||
}
|
||||
|
||||
if ($debug) {
|
||||
@@ -160,14 +170,14 @@ class AgentAllowRepository extends BaseRepository
|
||||
if ($debug) {
|
||||
do_log("agentDeny: " . $agentDeny->toJson());
|
||||
}
|
||||
throw new NexusException(sprintf(
|
||||
throw new ClientNotAllowedException(sprintf(
|
||||
"[%s-%s]Client: %s is banned due to: %s",
|
||||
$agentAllowPassed->id, $agentDeny->id, $agentDeny->name, $agentDeny->comment
|
||||
));
|
||||
}
|
||||
}
|
||||
if (isHttps() && $agentAllowPassed->allowhttps != 'yes') {
|
||||
throw new NexusException(sprintf(
|
||||
throw new ClientNotAllowedException(sprintf(
|
||||
"[%s]This client does not support https well, Please goto %s/faq.php#id29 for a list of proper clients",
|
||||
$agentAllowPassed->id, getSchemeAndHttpHost()
|
||||
));
|
||||
@@ -202,7 +212,7 @@ class AgentAllowRepository extends BaseRepository
|
||||
* @param bool $debug
|
||||
* @param string $logPrefix
|
||||
* @return int
|
||||
* @throws NexusException
|
||||
* @throws ClientNotAllowedException
|
||||
*/
|
||||
private function isAllowed($pattern, $start, $matchNum, $matchType, $value, $debug = false, $logPrefix = ''): int
|
||||
{
|
||||
@@ -234,7 +244,7 @@ class AgentAllowRepository extends BaseRepository
|
||||
$matchBench[$i] = hexdec($matchBench[$i]);
|
||||
$matchTarget[$i] = hexdec($matchTarget[$i]);
|
||||
} else {
|
||||
throw new NexusException(sprintf("Invalid match type: %s", $matchType));
|
||||
throw new ClientNotAllowedException(sprintf("Invalid match type: %s", $matchType));
|
||||
}
|
||||
if ($matchTarget[$i] > $matchBench[$i]) {
|
||||
//higher, pass directly
|
||||
|
||||
@@ -75,8 +75,8 @@ class AttendanceRepository extends BaseRepository
|
||||
->where('uid', $uid)
|
||||
->orderBy('id', 'desc');
|
||||
if (!empty($date)) {
|
||||
$query->where('added', '>=', Carbon::today())
|
||||
->where('added', '<', Carbon::tomorrow());
|
||||
$query->where('added', '>=', Carbon::parse($date)->startOfDay())
|
||||
->where('added', '<=', Carbon::parse($date)->endOfDay());
|
||||
}
|
||||
return $query->first();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class AuthenticateRepository extends BaseRepository
|
||||
if (!$user || md5($user->secret . $password . $user->secret) != $user->passhash) {
|
||||
throw new \InvalidArgumentException('Username or password invalid.');
|
||||
}
|
||||
if (IS_PLATFORM_ADMIN && !$user->canAccessAdmin()) {
|
||||
if (nexus()->isPlatformAdmin() && !$user->canAccessAdmin()) {
|
||||
throw new UnauthorizedException('Unauthorized!');
|
||||
}
|
||||
$user->checkIsNormal();
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\HitAndRun;
|
||||
use App\Models\Medal;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMedal;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Nexus\Database\NexusDB;
|
||||
@@ -66,7 +67,7 @@ class BonusRepository extends BaseRepository
|
||||
if ($medal->duration > 0) {
|
||||
$expireAt = Carbon::now()->addDays($medal->duration)->toDateTimeString();
|
||||
}
|
||||
$user->medals()->attach([$medal->id => ['expire_at' => $expireAt]]);
|
||||
$user->medals()->attach([$medal->id => ['expire_at' => $expireAt, 'status' => UserMedal::STATUS_NOT_WEARING]]);
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Comment;
|
||||
use App\Models\Message;
|
||||
use App\Models\NexusModel;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Hamcrest\Core\Set;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class CommentRepository extends BaseRepository
|
||||
{
|
||||
public function getList(array $params)
|
||||
{
|
||||
$query = Comment::query()->with(['create_user', 'update_user']);
|
||||
if (!empty($params['torrent_id'])) {
|
||||
$query->where('torrent', $params['torrent_id']);
|
||||
}
|
||||
if (!empty($params['offer_id'])) {
|
||||
$query->where('offer', $params['offer_id']);
|
||||
}
|
||||
if (!empty($params['request_id'])) {
|
||||
$query->where('request', $params['request_id']);
|
||||
}
|
||||
list($sortField, $sortType) = $this->getSortFieldAndType($params);
|
||||
$query->orderBy($sortField, $sortType);
|
||||
return $query->paginate();
|
||||
}
|
||||
|
||||
public function store(array $params, User $user)
|
||||
{
|
||||
$type = $params['type'];
|
||||
$modelName = Comment::TYPE_MAPS[$params['type']]['model'];
|
||||
/**
|
||||
* @var NexusModel $model
|
||||
*/
|
||||
$model = new $modelName;
|
||||
$target = $model->newQuery()->with('user')->find($params[$type]);
|
||||
return DB::transaction(function () use ($params, $user, $target) {
|
||||
$params['added'] = Carbon::now();
|
||||
$comment = $user->comments()->create($params);
|
||||
$commentCount = Comment::query()->type($params['type'], $params[$params['type']])->count();
|
||||
$target->comments = $commentCount;
|
||||
$target->save();
|
||||
|
||||
$userUpdate = [
|
||||
'seedbonus' => DB::raw('seedbonus + ' . Setting::get('bonus.addcomment')),
|
||||
'last_comment' => Carbon::now(),
|
||||
];
|
||||
$user->update($userUpdate);
|
||||
|
||||
//message
|
||||
if ($target->user->commentpm == 'yes' && $user->id != $target->user->id) {
|
||||
$messageInfo = $this->getNoticeMessage($target, $params['type']);
|
||||
$insert = [
|
||||
'sender' => 0,
|
||||
'receiver' => $target->user->id,
|
||||
'subject' => $messageInfo['subject'],
|
||||
'msg' => $messageInfo['body'],
|
||||
'added' => $params['added'],
|
||||
];
|
||||
Message::query()->insert($insert);
|
||||
NexusDB::cache_del('user_'.$target->user->id.'_unread_message_count');
|
||||
NexusDB::cache_del('user_'.$target->user->id.'_inbox_count');
|
||||
}
|
||||
|
||||
return $comment;
|
||||
});
|
||||
}
|
||||
|
||||
public function update(array $params, $id)
|
||||
{
|
||||
$model = Comment::query()->findOrFail($id);
|
||||
$model->update($params);
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function getDetail($id)
|
||||
{
|
||||
$model = Comment::query()->findOrFail($id);
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$model = Comment::query()->findOrFail($id);
|
||||
$result = $model->delete();
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getNoticeMessage($target, $type): array
|
||||
{
|
||||
$allTrans = require_once base_path('lang/_target/lang_comment.php');
|
||||
$lang = $target->user->language->site_lang_folder ?? 'en';
|
||||
$trans = $allTrans[$lang];
|
||||
$subject = $trans['msg_new_comment'];
|
||||
$targetScript = Comment::TYPE_MAPS[$type]['target_script'];
|
||||
$targetNameField = Comment::TYPE_MAPS[$type]['target_name_field'];
|
||||
$body = sprintf(
|
||||
'%s [url=%s]%s[/url]',
|
||||
$trans['msg_torrent_receive_comment'],
|
||||
sprintf($targetScript, $target->id),
|
||||
$target->{$targetNameField}
|
||||
);
|
||||
return compact('subject', 'body');
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ class DashboardRepository extends BaseRepository
|
||||
$result[$name] = [
|
||||
'name' => $name,
|
||||
'text' => nexus_trans("dashboard.system_info.$name"),
|
||||
'value' => $_SERVER['SERVER_SOFTWARE'],
|
||||
'value' => $_SERVER['SERVER_SOFTWARE'] ?? '',
|
||||
];
|
||||
$name = 'load_average';
|
||||
$result[$name] = [
|
||||
|
||||
@@ -66,7 +66,23 @@ class MedalRepository extends BaseRepository
|
||||
if ($duration > 0) {
|
||||
$expireAt = Carbon::now()->addDays($duration)->toDateTimeString();
|
||||
}
|
||||
return $user->medals()->attach([$medal->id => ['expire_at' => $expireAt]]);
|
||||
return $user->medals()->attach([$medal->id => ['expire_at' => $expireAt, 'status' => UserMedal::STATUS_NOT_WEARING]]);
|
||||
}
|
||||
|
||||
function toggleUserMedalStatus($id, $userId)
|
||||
{
|
||||
$userMedal = UserMedal::query()->findOrFail($id);
|
||||
if ($userMedal->uid != $userId) {
|
||||
throw new \LogicException("no privilege");
|
||||
}
|
||||
$current = $userMedal->status;
|
||||
if ($current == UserMedal::STATUS_NOT_WEARING) {
|
||||
$userMedal->status = UserMedal::STATUS_WEARING;
|
||||
} elseif ($current == UserMedal::STATUS_WEARING) {
|
||||
$userMedal->status = UserMedal::STATUS_NOT_WEARING;
|
||||
}
|
||||
$userMedal->save();
|
||||
return $userMedal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Poll;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PollRepository extends BaseRepository
|
||||
{
|
||||
public function getList(array $params)
|
||||
{
|
||||
$query = Poll::query();
|
||||
list($sortField, $sortType) = $this->getSortFieldAndType($params);
|
||||
$query->orderBy($sortField, $sortType);
|
||||
return $query->paginate();
|
||||
}
|
||||
|
||||
public function store($torrentId, $value, User $user)
|
||||
{
|
||||
if ($user->seedbonus < $value) {
|
||||
throw new \LogicException("user bonus not enough.");
|
||||
}
|
||||
if ($user->reward_torrent_logs()->where('torrentid', $torrentId)->exists()) {
|
||||
throw new \LogicException("user already reward this torrent.");
|
||||
}
|
||||
$torrent = Torrent::query()->findOrFail($torrentId, ['owner']);
|
||||
$torrentOwner = User::query()->findOrFail($torrent->owner, ['id', 'seedbonus']);
|
||||
return DB::transaction(function () use ($torrentId, $value, $user, $torrentOwner) {
|
||||
$model = $user->reward_torrent_logs()->create([
|
||||
'torrentid' => $torrentId,
|
||||
'value' => $value,
|
||||
]);
|
||||
$affectedRows = $user->where('seedbonus', $user->seedbonus)->decrement('seedbonus', $value);
|
||||
if ($affectedRows != 1) {
|
||||
do_log("affectedRows: $affectedRows, query: " . last_query(), 'error');
|
||||
throw new \RuntimeException("decrement user bonus fail.");
|
||||
}
|
||||
$affectedRows = $torrentOwner->where('seedbonus', $torrentOwner->seedbonus)->increment('seedbonus', $value);
|
||||
if ($affectedRows != 1) {
|
||||
do_log("affectedRows: $affectedRows, query: " . last_query(), 'error');
|
||||
throw new \RuntimeException("increment owner bonus fail.");
|
||||
}
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
|
||||
public function update(array $params, $id)
|
||||
{
|
||||
$model = Poll::query()->findOrFail($id);
|
||||
$model->update($params);
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function getDetail($id)
|
||||
{
|
||||
$model = Poll::query()->findOrFail($id);
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$model = Poll::query()->findOrFail($id);
|
||||
$result = $model->delete();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function vote($selection, User $user)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Reward;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use Google\Service\ToolResults\StepLabelsEntry;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RewardRepository extends BaseRepository
|
||||
{
|
||||
public function getList(array $params)
|
||||
{
|
||||
$query = Reward::query()->with(['user']);
|
||||
if (!empty($params['torrent_id'])) {
|
||||
$query->where('torrentid', $params['torrent_id']);
|
||||
}
|
||||
list($sortField, $sortType) = $this->getSortFieldAndType($params);
|
||||
$query->orderBy($sortField, $sortType);
|
||||
return $query->paginate();
|
||||
}
|
||||
|
||||
public function store($torrentId, $value, User $user)
|
||||
{
|
||||
if ($user->seedbonus < $value) {
|
||||
throw new \LogicException("your bonus not enough.");
|
||||
}
|
||||
if ($user->reward_torrent_logs()->where('torrentid', $torrentId)->exists()) {
|
||||
throw new \LogicException("you already reward this torrent.");
|
||||
}
|
||||
$torrent = Torrent::query()->findOrFail($torrentId, Torrent::$commentFields);
|
||||
$torrent->checkIsNormal();
|
||||
$torrentOwner = User::query()->findOrFail($torrent->owner);
|
||||
if ($user->id == $torrentOwner->id) {
|
||||
throw new \LogicException("you can't reward to yourself.");
|
||||
}
|
||||
$torrentOwner->checkIsNormal();
|
||||
return DB::transaction(function () use ($torrentId, $value, $user, $torrentOwner) {
|
||||
$model = $user->reward_torrent_logs()->create([
|
||||
'torrentid' => $torrentId,
|
||||
'value' => $value,
|
||||
]);
|
||||
$affectedRows = User::query()
|
||||
->where('id', $user->id)
|
||||
->where('seedbonus', $user->seedbonus)
|
||||
->decrement('seedbonus', $value);
|
||||
if ($affectedRows != 1) {
|
||||
do_log("affectedRows: $affectedRows, query: " . last_query(), 'error');
|
||||
throw new \RuntimeException("decrement user bonus fail.");
|
||||
}
|
||||
$affectedRows = User::query()
|
||||
->where('id', $torrentOwner->id)
|
||||
->where('seedbonus', $torrentOwner->seedbonus)
|
||||
->increment('seedbonus', $value);
|
||||
if ($affectedRows != 1) {
|
||||
do_log("affectedRows: $affectedRows, query: " . last_query(), 'error');
|
||||
throw new \RuntimeException("increment owner bonus fail.");
|
||||
}
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
|
||||
public function update(array $params, $id)
|
||||
{
|
||||
$model = Reward::query()->findOrFail($id);
|
||||
$model->update($params);
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function getDetail($id)
|
||||
{
|
||||
$model = Reward::query()->findOrFail($id);
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$model = Reward::query()->findOrFail($id);
|
||||
$result = $model->delete();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,879 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Bookmark;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\TorrentTag;
|
||||
use App\Models\User;
|
||||
use Elasticsearch\Client;
|
||||
use Elasticsearch\ClientBuilder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class SearchRepository extends BaseRepository
|
||||
{
|
||||
private Client $es;
|
||||
|
||||
private bool $enabled = false;
|
||||
|
||||
const INDEX_NAME = 'nexus_torrents';
|
||||
|
||||
const DOC_TYPE_TORRENT = 'torrent';
|
||||
const DOC_TYPE_TAG = 'tag';
|
||||
const DOC_TYPE_BOOKMARK = 'bookmark';
|
||||
const DOC_TYPE_USER = 'user';
|
||||
|
||||
const SEARCH_MODE_AND = '0';
|
||||
const SEARCH_MODE_OR = '1';
|
||||
const SEARCH_MODE_EXACT = '2';
|
||||
|
||||
const SEARCH_MODES = [
|
||||
self::SEARCH_MODE_AND => ['text' => 'and'],
|
||||
self::SEARCH_MODE_OR => ['text' => 'or'],
|
||||
self::SEARCH_MODE_EXACT => ['text' => 'exact'],
|
||||
];
|
||||
|
||||
const SEARCH_AREA_TITLE = '0';
|
||||
const SEARCH_AREA_DESC = '1';
|
||||
const SEARCH_AREA_OWNER = '3';
|
||||
const SEARCH_AREA_IMDB = '4';
|
||||
|
||||
const SEARCH_AREAS = [
|
||||
self::SEARCH_AREA_TITLE => ['text' => 'title'],
|
||||
self::SEARCH_AREA_DESC => ['text' => 'desc'],
|
||||
self::SEARCH_AREA_OWNER => ['text' => 'owner'],
|
||||
self::SEARCH_AREA_IMDB => ['text' => 'imdb'],
|
||||
];
|
||||
|
||||
|
||||
|
||||
private array $indexSetting = [
|
||||
'index' => self::INDEX_NAME,
|
||||
'body' => [
|
||||
'settings' => [
|
||||
'number_of_shards' => 1,
|
||||
'number_of_replicas' => 0,
|
||||
],
|
||||
'mappings' => [
|
||||
'properties' => [
|
||||
'_doc_type' => ['type' => 'keyword'],
|
||||
|
||||
//torrent
|
||||
'torrent_id' => ['type' => 'long', ],
|
||||
|
||||
//user
|
||||
'username' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
|
||||
|
||||
//bookmark + user + tag
|
||||
'user_id' => ['type' => 'long', ],
|
||||
|
||||
//tag
|
||||
'tag_id' => ['type' => 'long', ],
|
||||
|
||||
//relations
|
||||
'torrent_relations' => [
|
||||
'type' => 'join',
|
||||
'eager_global_ordinals' => true,
|
||||
'relations' => [
|
||||
'user' => ['torrent'],
|
||||
'torrent' => ['bookmark', 'tag'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
//cat401=1&source1=1&medium1=1&codec1=1&audiocodec1=1&standard1=1&processing1=1&team1=1&incldead=1&spstate=2&inclbookmarked=1&search=tr&search_area=1&search_mode=1
|
||||
private static array $queryFieldToTorrentFieldMaps = [
|
||||
'cat' => 'category',
|
||||
'source' => 'source',
|
||||
'medium' => 'medium',
|
||||
'codec' => 'codec',
|
||||
'audiocodec' => 'audiocodec',
|
||||
'standard' => 'standard',
|
||||
'processing' => 'processing',
|
||||
'team' => 'team',
|
||||
];
|
||||
|
||||
private static array $sortFieldMaps = [
|
||||
'1' => 'name',
|
||||
'2' => 'numfiles',
|
||||
'3' => 'comments',
|
||||
'4' => 'added',
|
||||
'5' => 'size',
|
||||
'6' => 'times_completed',
|
||||
'7' => 'seeders',
|
||||
'8' => 'leechers',
|
||||
'9' => 'owner',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$elasticsearchEnabled = nexus_env('ELASTICSEARCH_ENABLED');
|
||||
if ($elasticsearchEnabled) {
|
||||
$this->enabled = true;
|
||||
$this->es = $this->getEs();
|
||||
} else {
|
||||
$this->enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function getEs(): Client
|
||||
{
|
||||
$config = nexus_config('nexus.elasticsearch');
|
||||
$es = ClientBuilder::create()->setHosts($config['hosts']);
|
||||
if (!empty($config['ssl_verification'])) {
|
||||
$es->setSSLVerification($config['ssl_verification']);
|
||||
}
|
||||
return $es->build();
|
||||
}
|
||||
|
||||
private function getTorrentRawMappingFields(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
|
||||
'descr' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
|
||||
'small_descr' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
|
||||
'category' => ['type' => 'long', ],
|
||||
'source' => ['type' => 'long', ],
|
||||
'medium' => ['type' => 'long', ],
|
||||
'codec' => ['type' => 'long', ],
|
||||
'standard' => ['type' => 'long', ],
|
||||
'processing' => ['type' => 'long', ],
|
||||
'team' => ['type' => 'long', ],
|
||||
'audiocodec' => ['type' => 'long', ],
|
||||
'size' => ['type' => 'long', ],
|
||||
'added' => ['type' => 'date', 'format' => 'yyyy-MM-dd HH:mm:ss'],
|
||||
'numfiles' => ['type' => 'long', ],
|
||||
'comments' => ['type' => 'long', ],
|
||||
'views' => ['type' => 'long', ],
|
||||
'hits' => ['type' => 'long', ],
|
||||
'times_completed' => ['type' => 'long', ],
|
||||
'leechers' => ['type' => 'long', ],
|
||||
'seeders' => ['type' => 'long', ],
|
||||
'last_action' => ['type' => 'date', 'format' => 'yyyy-MM-dd HH:mm:ss'],
|
||||
'visible' => ['type' => 'keyword', ],
|
||||
'banned' => ['type' => 'keyword', ],
|
||||
'owner' => ['type' => 'long', ],
|
||||
'sp_state' => ['type' => 'long', ],
|
||||
'url' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
|
||||
'pos_state' => ['type' => 'keyword', ],
|
||||
'picktype' => ['type' => 'keyword', ],
|
||||
'hr' => ['type' => 'long', ],
|
||||
];
|
||||
}
|
||||
|
||||
public function getEsInfo(): callable|array
|
||||
{
|
||||
return $this->es->info();
|
||||
}
|
||||
|
||||
public function createIndex()
|
||||
{
|
||||
$params = $this->indexSetting;
|
||||
$properties = $params['body']['mappings']['properties'];
|
||||
$properties = array_merge($properties, $this->getTorrentRawMappingFields());
|
||||
$params['body']['mappings']['properties'] = $properties;
|
||||
return $this->es->indices()->create($params);
|
||||
}
|
||||
|
||||
public function deleteIndex()
|
||||
{
|
||||
$params = ['index' => self::INDEX_NAME];
|
||||
return $this->es->indices()->delete($params);
|
||||
}
|
||||
|
||||
public function import($torrentId = null)
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
$page = 1;
|
||||
$size = 1000;
|
||||
$fields = $this->getTorrentBaseFields();
|
||||
array_unshift($fields, 'id');
|
||||
$query = Torrent::query()
|
||||
->with(['user', 'torrent_tags', 'bookmarks'])
|
||||
->select($fields);
|
||||
if (!is_null($torrentId)) {
|
||||
$idArr = preg_split('/[,\s]+/', $torrentId);
|
||||
$query->whereIn('id', $idArr);
|
||||
}
|
||||
while (true) {
|
||||
$log = "page: $page, size: $size";
|
||||
$torrentResults = (clone $query)->forPage($page, $size)->get();
|
||||
if ($torrentResults->isEmpty()) {
|
||||
do_log("$log, no more data...", 'info', true);
|
||||
break;
|
||||
}
|
||||
do_log("$log, get counts: " . $torrentResults->count(), 'info', true);
|
||||
|
||||
$torrentBodyBulk = $userBodyBulk = $tagBodyBulk = $bookmarkBodyBulk = ['body' => []];
|
||||
foreach ($torrentResults as $torrent) {
|
||||
$body = $this->buildUserBody($torrent->user, true);
|
||||
$userBodyBulk['body'][] = ['index' => $body['index']];
|
||||
$userBodyBulk['body'][] = $body['body'];
|
||||
|
||||
$body = $this->buildTorrentBody($torrent, true);
|
||||
$torrentBodyBulk['body'][] = ['index' => $body['index']];
|
||||
$torrentBodyBulk['body'][] = $body['body'];
|
||||
|
||||
foreach ($torrent->torrent_tags as $torrentTag) {
|
||||
$body = $this->buildTorrentTagBody($torrent, $torrentTag, true);
|
||||
$tagBodyBulk['body'][] = ['index' => $body['index']];
|
||||
$tagBodyBulk['body'][] = $body['body'];
|
||||
}
|
||||
|
||||
foreach ($torrent->bookmarks as $bookmark) {
|
||||
$body = $this->buildBookmarkBody($torrent, $bookmark, true);
|
||||
$bookmarkBodyBulk['body'][] = ['index' => $body['index']];
|
||||
$bookmarkBodyBulk['body'][] = $body['body'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//index user
|
||||
$result = $this->es->bulk($userBodyBulk);
|
||||
$this->logEsResponse("$log, bulk index user done!", $result);
|
||||
|
||||
//index torrent
|
||||
$result = $this->es->bulk($torrentBodyBulk);
|
||||
$this->logEsResponse("$log, bulk index torrent done!", $result);
|
||||
|
||||
//index tag
|
||||
$result = $this->es->bulk($tagBodyBulk);
|
||||
$this->logEsResponse("$log, bulk index tag done!", $result);
|
||||
|
||||
//index bookmark
|
||||
$result = $this->es->bulk($bookmarkBodyBulk);
|
||||
$this->logEsResponse("$log, bulk index bookmark done!", $result);
|
||||
|
||||
$page++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private function buildUserBody(User $user, bool $underlinePrefix = false)
|
||||
{
|
||||
$docType = self::DOC_TYPE_USER;
|
||||
$indexName = 'index';
|
||||
$idName = 'id';
|
||||
if ($underlinePrefix) {
|
||||
$indexName = "_$indexName";
|
||||
$idName = "_$idName";
|
||||
}
|
||||
$index = [
|
||||
$indexName => self::INDEX_NAME,
|
||||
$idName => $this->getUserId($user->id),
|
||||
'routing' => $user->id,
|
||||
];
|
||||
$body = [
|
||||
'_doc_type' => $docType,
|
||||
'user_id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'torrent_relations' => [
|
||||
'name' => $docType,
|
||||
],
|
||||
];
|
||||
return compact('index', 'body');
|
||||
}
|
||||
|
||||
|
||||
private function buildTorrentBody($torrent, bool $underlinePrefix = false): array
|
||||
{
|
||||
$baseFields = $this->getTorrentBaseFields();
|
||||
if (!$torrent instanceof Torrent) {
|
||||
$torrent = Torrent::query()->findOrFail((int)$torrent, array_merge(['id'], $baseFields));
|
||||
}
|
||||
$docType = self::DOC_TYPE_TORRENT;
|
||||
$indexName = 'index';
|
||||
$idName = 'id';
|
||||
if ($underlinePrefix) {
|
||||
$indexName = "_$indexName";
|
||||
$idName = "_$idName";
|
||||
}
|
||||
$index = [
|
||||
$indexName => self::INDEX_NAME,
|
||||
$idName => $this->getTorrentId($torrent->id),
|
||||
'routing' => $torrent->owner,
|
||||
];
|
||||
$data = Arr::only($torrent->toArray(), $baseFields);
|
||||
$body = array_merge($data, [
|
||||
'_doc_type' => $docType,
|
||||
'torrent_id' => $torrent->id,
|
||||
'torrent_relations' => [
|
||||
'name' => $docType,
|
||||
'parent' => 'user_' . $torrent->owner,
|
||||
],
|
||||
]);
|
||||
return compact('index', 'body');
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function buildTorrentTagBody(Torrent $torrent, TorrentTag $torrentTag, bool $underlinePrefix = false)
|
||||
{
|
||||
$docType = self::DOC_TYPE_TAG;
|
||||
$indexName = 'index';
|
||||
$idName = 'id';
|
||||
if ($underlinePrefix) {
|
||||
$indexName = "_$indexName";
|
||||
$idName = "_$idName";
|
||||
}
|
||||
$index = [
|
||||
$indexName => self::INDEX_NAME,
|
||||
$idName => $this->getTorrentTagId($torrentTag->id),
|
||||
'routing' => $torrent->owner,
|
||||
];
|
||||
$body = [
|
||||
'_doc_type' => $docType,
|
||||
'torrent_id' => $torrentTag->torrent_id,
|
||||
'tag_id' => $torrentTag->tag_id,
|
||||
'torrent_relations' => [
|
||||
'name' => $docType,
|
||||
'parent' => 'torrent_' . $torrent->id,
|
||||
],
|
||||
];
|
||||
return compact('index', 'body');
|
||||
}
|
||||
|
||||
private function buildBookmarkBody(Torrent $torrent, Bookmark $bookmark, bool $underlinePrefix = false)
|
||||
{
|
||||
$docType = self::DOC_TYPE_BOOKMARK;
|
||||
$indexName = 'index';
|
||||
$idName = 'id';
|
||||
if ($underlinePrefix) {
|
||||
$indexName = "_$indexName";
|
||||
$idName = "_$idName";
|
||||
}
|
||||
$index = [
|
||||
$indexName => self::INDEX_NAME,
|
||||
$idName => $this->getBookmarkId($bookmark->id),
|
||||
'routing' => $torrent->owner,
|
||||
];
|
||||
$body = [
|
||||
'_doc_type' => $docType,
|
||||
'torrent_id' => $bookmark->torrentid,
|
||||
'user_id' => $bookmark->userid,
|
||||
'torrent_relations' => [
|
||||
'name' => $docType,
|
||||
'parent' => 'torrent_' . $torrent->id,
|
||||
],
|
||||
];
|
||||
return compact('index', 'body');
|
||||
}
|
||||
|
||||
|
||||
private function logEsResponse($msg, $response)
|
||||
{
|
||||
if (isset($response['errors']) && $response['errors'] == true) {
|
||||
$msg .= var_export($response, true);
|
||||
}
|
||||
do_log($msg, 'info', app()->runningInConsole());
|
||||
}
|
||||
|
||||
private function getTorrentId($id): string
|
||||
{
|
||||
return "torrent_" . intval($id);
|
||||
}
|
||||
|
||||
private function getTorrentTagId($id): string
|
||||
{
|
||||
return "torrent_tag_" . intval($id);
|
||||
}
|
||||
|
||||
private function getUserId($id): string
|
||||
{
|
||||
return "user_" . intval($id);
|
||||
}
|
||||
|
||||
private function getBookmarkId($id): string
|
||||
{
|
||||
return "bookmark_" . intval($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* detect elastic response has error or not
|
||||
*
|
||||
* @param $esResponse
|
||||
* @return bool
|
||||
*/
|
||||
private function isEsResponseError($esResponse)
|
||||
{
|
||||
if (isset($esResponse['error'])) {
|
||||
return true;
|
||||
}
|
||||
//bulk insert
|
||||
if (isset($esResponse['errors']) && $esResponse['errors']) {
|
||||
return true;
|
||||
}
|
||||
//update by query
|
||||
if (!empty($esResponse['failures'])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* build es query
|
||||
*
|
||||
* @param array $params
|
||||
* @param $user
|
||||
* @param string $queryString cat401=1&cat404=1&source2=1&medium2=1&medium3=1&codec3=1&audiocodec3=1&standard2=1&standard3=1&processing2=1&team3=1&team4=1&incldead=1&spstate=0&inclbookmarked=0&search=&search_area=0&search_mode=0
|
||||
* @return array
|
||||
*/
|
||||
public function buildQuery(array $params, $user, string $queryString)
|
||||
{
|
||||
if (!($user instanceof User) || !$user->torrentsperpage || !$user->notifs) {
|
||||
$user = User::query()->findOrFail(intval($user));
|
||||
}
|
||||
//[cat401][cat404][sou1][med1][cod1][sta2][sta3][pro2][tea2][aud2][incldead=0][spstate=3][inclbookmarked=2]
|
||||
$userSetting = $user->notifs;
|
||||
$must = $must_not = [];
|
||||
$mustBoolShould = [];
|
||||
$must[] = ['match' => ['_doc_type' => self::DOC_TYPE_TORRENT]];
|
||||
|
||||
foreach (self::$queryFieldToTorrentFieldMaps as $queryField => $torrentField) {
|
||||
if (isset($params[$queryField]) && $params[$queryField] !== '') {
|
||||
$mustBoolShould[$torrentField][] = ['match' => [$torrentField => $params[$queryField]]];
|
||||
do_log("get mustBoolShould for $torrentField from params through $queryField: {$params[$queryField]}");
|
||||
} elseif (preg_match_all("/{$queryField}([\d]+)=/", $queryString, $matches)) {
|
||||
if (count($matches) == 2 && !empty($matches[1])) {
|
||||
foreach ($matches[1] as $match) {
|
||||
$mustBoolShould[$torrentField][] = ['match' => [$torrentField => $match]];
|
||||
do_log("get mustBoolShould for $torrentField from params through $queryField: $match");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//get user setting
|
||||
$pattern = sprintf("/\[%s([\d]+)\]/", substr($queryField, 0, 3));
|
||||
if (preg_match($pattern, $userSetting, $matches)) {
|
||||
if (count($matches) == 2 && !empty($matches[1])) {
|
||||
foreach ($matches[1] as $match) {
|
||||
$mustBoolShould[$torrentField][] = ['match' => [$torrentField => $match]];
|
||||
do_log("get mustBoolShould for $torrentField from user setting through $queryField: $match");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$includeDead = 1;
|
||||
if (isset($params['incldead'])) {
|
||||
$includeDead = (int)$params['incldead'];
|
||||
do_log("maybe get must for visible from params");
|
||||
} elseif (preg_match("/\[incldead=([\d]+)\]/", $userSetting, $matches)) {
|
||||
$includeDead = $matches[1];
|
||||
do_log("maybe get must for visible from user setting");
|
||||
}
|
||||
if ($includeDead == 1) {
|
||||
//active torrent
|
||||
$must[] = ['match' => ['visible' => 'yes']];
|
||||
do_log("get must for visible = yes through incldead: $includeDead");
|
||||
} elseif ($includeDead == 2) {
|
||||
//dead torrent
|
||||
$must[] = ['match' => ['visible' => 'no']];
|
||||
do_log("get must for visible = no through incldead: $includeDead");
|
||||
}
|
||||
|
||||
|
||||
$includeBookmarked = 0;
|
||||
if (isset($params['inclbookmarked'])) {
|
||||
$includeBookmarked = (int)$params['inclbookmarked'];
|
||||
do_log("maybe get must or must_not for has_child.bookmark from params");
|
||||
} elseif (preg_match("/\[inclbookmarked=([\d]+)\]/", $userSetting, $matches)) {
|
||||
$includeBookmarked = $matches[1];
|
||||
do_log("maybe get must or must_not for has_child.bookmark from user setting");
|
||||
}
|
||||
if ($includeBookmarked == 1) {
|
||||
//only bookmark
|
||||
$must[] = ['has_child' => ['type' => 'bookmark', 'query' => ['match' => ['user_id' => $user->id]]]];
|
||||
do_log("get must for has_child.bookmark through inclbookmarked: $includeBookmarked");
|
||||
} elseif ($includeBookmarked == 2) {
|
||||
//only not bookmark
|
||||
$must_not[] = ['has_child' => ['type' => 'bookmark', 'query' => ['match' => ['user_id' => $user->id]]]];
|
||||
do_log("get must_not for has_child.bookmark through inclbookmarked: $includeBookmarked");
|
||||
}
|
||||
|
||||
|
||||
$spState = 0;
|
||||
if (isset($params['spstate'])) {
|
||||
$spState = (int)$params['spstate'];
|
||||
do_log("maybe get must for spstate from params");
|
||||
} elseif (preg_match("/\[spstate=([\d]+)\]/", $userSetting, $matches)) {
|
||||
$spState = $matches[1];
|
||||
do_log("maybe get must for spstate from user setting");
|
||||
}
|
||||
if ($spState > 0) {
|
||||
$must[] = ['match' => ['sp_state' => $spState]];
|
||||
do_log("get must for sp_state = $spState through spstate: $spState");
|
||||
}
|
||||
|
||||
if (!empty($params['tag_id'])) {
|
||||
$must[] = ['has_child' => ['type' => 'tag', 'query' => ['match' => ['tag_id' => $params['tag_id']]]]];
|
||||
do_log("get must for has_child.tag through params.tag_id: {$params['tag_id']}");
|
||||
}
|
||||
|
||||
|
||||
if (!empty($params['search'])) {
|
||||
$searchMode = isset($params['search_mode']) && isset(self::SEARCH_MODES[$params['search_mode']]) ? $params['search_mode'] : self::SEARCH_MODE_AND;
|
||||
if (in_array($searchMode, [self::SEARCH_MODE_AND, self::SEARCH_MODE_OR])) {
|
||||
//and, or
|
||||
$keywordsArr = preg_split("/[\.\s]+/", trim($params['search']));
|
||||
} else {
|
||||
$keywordsArr = [trim($params['search'])];
|
||||
}
|
||||
$keywordsArr = array_slice($keywordsArr, 0, 10);
|
||||
$searchArea = isset($params['search_area']) && isset(self::SEARCH_AREAS[$params['search_area']]) ? $params['search_area'] : self::SEARCH_AREA_TITLE;
|
||||
if ($searchMode == self::SEARCH_MODE_AND || $searchMode == self::SEARCH_MODE_EXACT) {
|
||||
$keywordFlag = $searchMode == self::SEARCH_MODE_EXACT ? ".keyword" : "";
|
||||
if ($searchArea == self::SEARCH_AREA_TITLE) {
|
||||
foreach ($keywordsArr as $keyword) {
|
||||
$tmpMustBoolShould = [];
|
||||
$tmpMustBoolShould[] = ['match' => ["name{$keywordFlag}" => $keyword]];
|
||||
$tmpMustBoolShould[] = ['match' => ["small_descr{$keywordFlag}" => $keyword]];
|
||||
$must[]['bool']['should'] = $tmpMustBoolShould;
|
||||
do_log("get must bool should [SEARCH_MODE_AND + SEARCH_MODE_EXACT] for name+small_descr match '$keyword' through search");
|
||||
}
|
||||
} elseif ($searchArea == self::SEARCH_AREA_DESC) {
|
||||
foreach ($keywordsArr as $keyword) {
|
||||
$must[] = ['match' => ["descr{$keywordFlag}" => $keyword]];
|
||||
do_log("get must [SEARCH_MODE_AND + SEARCH_MODE_EXACT] for descr match '$keyword' through search");
|
||||
}
|
||||
} elseif ($searchArea == self::SEARCH_AREA_IMDB) {
|
||||
foreach ($keywordsArr as $keyword) {
|
||||
$must[] = ['match' => ["url{$keywordFlag}" => $keyword]];
|
||||
do_log("get must [SEARCH_MODE_AND + SEARCH_MODE_EXACT] for url match '$keyword' through search");
|
||||
}
|
||||
} elseif ($searchArea == self::SEARCH_AREA_OWNER) {
|
||||
foreach ($keywordsArr as $keyword) {
|
||||
$must[] = ['has_parent' => ['parent_type' => 'user', 'query' => ['match' => ["username{$keywordFlag}" => $keyword]]]];
|
||||
do_log("get must [SEARCH_MODE_AND + SEARCH_MODE_EXACT] has_parent.user match '$keyword' through search");
|
||||
}
|
||||
}
|
||||
} elseif ($searchMode == self::SEARCH_MODE_OR) {
|
||||
if ($searchArea == self::SEARCH_AREA_TITLE) {
|
||||
$tmpMustBoolShould = [];
|
||||
foreach ($keywordsArr as $keyword) {
|
||||
$tmpMustBoolShould[] = ['match' => ['name' => $keyword]];
|
||||
$tmpMustBoolShould[] = ['match' => ['small_descr' => $keyword]];
|
||||
do_log("get must bool should [SEARCH_MODE_OR] for name+small_descr match '$keyword' through search");
|
||||
}
|
||||
$must[]['bool']['should'] = $tmpMustBoolShould;
|
||||
} elseif ($searchArea == self::SEARCH_AREA_DESC) {
|
||||
$tmpMustBoolShould = [];
|
||||
foreach ($keywordsArr as $keyword) {
|
||||
$tmpMustBoolShould[] = ['match' => ['descr' => $keyword]];
|
||||
do_log("get must bool should [SEARCH_MODE_OR] for descr match '$keyword' through search");
|
||||
}
|
||||
$must[]['bool']['should'] = $tmpMustBoolShould;
|
||||
} elseif ($searchArea == self::SEARCH_AREA_IMDB) {
|
||||
$tmpMustBoolShould = [];
|
||||
foreach ($keywordsArr as $keyword) {
|
||||
$tmpMustBoolShould[] = ['match' => ['url' => $keyword]];
|
||||
do_log("get must bool should [SEARCH_MODE_OR] for url match '$keyword' through search");
|
||||
}
|
||||
$must[]['bool']['should'] = $tmpMustBoolShould;
|
||||
} elseif ($searchArea == self::SEARCH_AREA_OWNER) {
|
||||
$tmpMustBoolShould = [];
|
||||
foreach ($keywordsArr as $keyword) {
|
||||
$tmpMustBoolShould[] = ['has_parent' => ['parent_type' => 'user', 'query' => ['match' => ['username' => $keyword]]]];
|
||||
do_log("get must bool should [SEARCH_MODE_OR] has_parent.user match '$keyword' through search");
|
||||
}
|
||||
$must[]['bool']['should'] = $tmpMustBoolShould;
|
||||
}
|
||||
}
|
||||
}
|
||||
$query = [
|
||||
'bool' => [
|
||||
'must' => $must
|
||||
]
|
||||
];
|
||||
foreach ($mustBoolShould as $torrentField => $boolShoulds) {
|
||||
$query['bool']['must'][]['bool']['should'] = $boolShoulds;
|
||||
}
|
||||
if (!empty($must_not)) {
|
||||
$query['bool']['must_not'] = $must_not;
|
||||
}
|
||||
|
||||
|
||||
$sort = [];
|
||||
$sort[] = ['pos_state' => ['order' => 'desc']];
|
||||
$hasAddSetSortField = false;
|
||||
if (!empty($params['sort'])) {
|
||||
$direction = isset($params['type']) && in_array($params['type'], ['asc', 'desc']) ? $params['type'] : 'desc';
|
||||
foreach (self::$sortFieldMaps as $key => $value) {
|
||||
if ($key == $params['sort']) {
|
||||
$hasAddSetSortField = true;
|
||||
$sort[] = [$value => ['order' => $direction]];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$hasAddSetSortField) {
|
||||
$sort[] = ['torrent_id' => ['order' => 'desc']];
|
||||
}
|
||||
|
||||
$page = isset($params['page']) && is_numeric($params['page']) ? $params['page'] : 0;
|
||||
if ($user->torrentsperpage) {
|
||||
$size = $user->torrentsperpage;
|
||||
} elseif (($sizeFromConfig = Setting::get('main.torrentsperpage')) > 0) {
|
||||
$size = $sizeFromConfig;
|
||||
} else {
|
||||
$size = 50;
|
||||
}
|
||||
$size = min($size, 200);
|
||||
$offset = $page * $size;
|
||||
|
||||
$result = [
|
||||
'query' => $query,
|
||||
'sort' => $sort,
|
||||
'from' => $offset,
|
||||
'size' => $size,
|
||||
'_source' => ['torrent_id', 'name', 'small_descr', 'owner']
|
||||
];
|
||||
do_log(sprintf(
|
||||
"params: %s, user: %s, queryString: %s, result: %s",
|
||||
nexus_json_encode($params), $user->id, $queryString, nexus_json_encode($result)
|
||||
));
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
public function listTorrentFromEs(array $params, $user, string $queryString)
|
||||
{
|
||||
$query = $this->buildQuery($params, $user, $queryString);
|
||||
$esParams = [
|
||||
'index' => self::INDEX_NAME,
|
||||
'body' => $query,
|
||||
];
|
||||
$response = $this->es->search($esParams);
|
||||
$result = [
|
||||
'total' => 0,
|
||||
'data' => [],
|
||||
];
|
||||
if ($this->isEsResponseError($response)) {
|
||||
do_log("error response: " . nexus_json_encode($response), 'error');
|
||||
return $result;
|
||||
}
|
||||
if (empty($response['hits'])) {
|
||||
do_log("empty response hits: " . nexus_json_encode($response), 'error');
|
||||
return $result;
|
||||
}
|
||||
if ($response['hits']['total']['value'] == 0) {
|
||||
do_log("total = 0, " . nexus_json_encode($response));
|
||||
return $result;
|
||||
}
|
||||
$result['total'] = $response['hits']['total']['value'];
|
||||
$torrentIdArr = [];
|
||||
foreach ($response['hits']['hits'] as $value) {
|
||||
$torrentIdArr[] = $value['_source']['torrent_id'];
|
||||
}
|
||||
$fieldStr = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, pt_gen, hr';
|
||||
$idStr = implode(',', $torrentIdArr);
|
||||
$result['data'] = Torrent::query()
|
||||
->selectRaw($fieldStr)
|
||||
->whereIn('id', $torrentIdArr)
|
||||
->orderByRaw("field(id,$idStr)")
|
||||
->get()
|
||||
->toArray()
|
||||
;
|
||||
|
||||
return $result;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function getTorrentBaseFields()
|
||||
{
|
||||
return array_keys($this->getTorrentRawMappingFields());
|
||||
}
|
||||
|
||||
public function updateTorrent(int $id): bool
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
$log = "[UPDATE_TORRENT]: $id";
|
||||
$baseFields = $this->getTorrentBaseFields();
|
||||
$torrent = Torrent::query()->findOrFail($id, array_merge(['id'], $baseFields));
|
||||
$data = $this->buildTorrentBody($torrent);
|
||||
$params = $data['index'];
|
||||
$params['body']['doc'] = $data['body'];
|
||||
$result = $this->es->update($params);
|
||||
if ($this->isEsResponseError($result)) {
|
||||
do_log("$log, fail: " . nexus_json_encode($result), 'error');
|
||||
return false;
|
||||
}
|
||||
do_log("$log, success: " . nexus_json_encode($result));
|
||||
|
||||
return $this->syncTorrentTags($torrent);
|
||||
}
|
||||
|
||||
public function addTorrent(int $id): bool
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
$log = "[ADD_TORRENT]: $id";
|
||||
$baseFields = $this->getTorrentBaseFields();
|
||||
$torrent = Torrent::query()->findOrFail($id, array_merge(['id'], $baseFields));
|
||||
$data = $this->buildTorrentBody($torrent, true);
|
||||
$params = ['body' => []];
|
||||
$params['body'][] = ['index' => $data['index']];
|
||||
$params['body'][] = $data['body'];
|
||||
$result = $this->es->bulk($params);
|
||||
if ($this->isEsResponseError($result)) {
|
||||
do_log("$log, fail: " . nexus_json_encode($result), 'error');
|
||||
return false;
|
||||
}
|
||||
do_log("$log, success: " . nexus_json_encode($result));
|
||||
|
||||
return $this->syncTorrentTags($torrent);
|
||||
}
|
||||
|
||||
public function deleteTorrent(int $id): bool
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
$log = "[DELETE_TORRENT]: $id";
|
||||
$params = [
|
||||
'index' => self::INDEX_NAME,
|
||||
'id' => $this->getTorrentId($id),
|
||||
];
|
||||
$result = $this->es->delete($params);
|
||||
if ($this->isEsResponseError($result)) {
|
||||
do_log("$log, fail: " . nexus_json_encode($result), 'error');
|
||||
return false;
|
||||
}
|
||||
do_log("$log, success: " . nexus_json_encode($result));
|
||||
|
||||
return $this->syncTorrentTags($id, true);
|
||||
}
|
||||
|
||||
public function syncTorrentTags($torrent, $onlyDelete = false): bool
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
if (!$torrent instanceof Torrent) {
|
||||
$torrent = Torrent::query()->findOrFail((int)$torrent, ['id']);
|
||||
}
|
||||
$log = "sync torrent tags, torrent: " . $torrent->id;
|
||||
//remove first
|
||||
$params = [
|
||||
'index' => self::INDEX_NAME,
|
||||
'body' => [
|
||||
'query' => [
|
||||
'bool' => [
|
||||
'must' => [
|
||||
['match' => ['_doc_type' => self::DOC_TYPE_TAG]],
|
||||
['has_parent' => ['parent_type' => 'torrent', 'query' => ['match' => ['torrent_id' => $torrent->id]]]]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
$result = $this->es->deleteByQuery($params);
|
||||
if ($this->isEsResponseError($result)) {
|
||||
do_log("$log, delete torrent tag fail: " . nexus_json_encode($result), 'error');
|
||||
return false;
|
||||
}
|
||||
do_log("$log, delete torrent tag success: " . nexus_json_encode($result));
|
||||
if ($onlyDelete) {
|
||||
do_log("$log, only delete, return true");
|
||||
return true;
|
||||
}
|
||||
|
||||
//then insert new
|
||||
$bulk = ['body' => []];
|
||||
foreach ($torrent->torrent_tags as $torrentTag) {
|
||||
$body = $this->buildTorrentTagBody($torrent, $torrentTag, true);
|
||||
$bulk['body'][] = ['index' => $body['index']];
|
||||
$bulk['body'][] = $body['body'];
|
||||
}
|
||||
if (empty($bulk['body'])) {
|
||||
do_log("$log, no tags, return true");
|
||||
return true;
|
||||
}
|
||||
$result = $this->es->bulk($bulk);
|
||||
if ($this->isEsResponseError($result)) {
|
||||
do_log("$log, insert torrent tag fail: " . nexus_json_encode($result), 'error');
|
||||
return false;
|
||||
}
|
||||
do_log("$log, insert torrent tag success: " . nexus_json_encode($result));
|
||||
return true;
|
||||
}
|
||||
|
||||
public function updateUser($user): bool
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
if (!$user instanceof User) {
|
||||
$user = User::query()->findOrFail((int)$user, ['id', 'username']);
|
||||
}
|
||||
$log = "[UPDATE_USER]: " . $user->id;
|
||||
$data = $this->buildUserBody($user);
|
||||
$params = $data['index'];
|
||||
$params['body']['doc'] = $data['body'];
|
||||
$result = $this->es->update($params);
|
||||
if ($this->isEsResponseError($result)) {
|
||||
do_log("$log, fail: " . nexus_json_encode($result), 'error');
|
||||
return false;
|
||||
}
|
||||
do_log("$log, success: " . nexus_json_encode($result));
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addBookmark($bookmark): bool
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
if (!$bookmark instanceof Bookmark) {
|
||||
$bookmark = Bookmark::query()->with([
|
||||
'torrent' => function ($query) {$query->select(['id', 'owner']);}
|
||||
])->findOrFail((int)$bookmark);
|
||||
}
|
||||
$log = "[ADD_BOOKMARK]: " . $bookmark->toJson();
|
||||
$bulk = ['body' => []];
|
||||
$body = $this->buildBookmarkBody($bookmark->torrent, $bookmark, true);
|
||||
$bulk['body'][] = ['index' => $body['index']];
|
||||
$bulk['body'][] = $body['body'];
|
||||
$result = $this->es->bulk($bulk);
|
||||
if ($this->isEsResponseError($result)) {
|
||||
do_log("$log, fail: " . nexus_json_encode($result), 'error');
|
||||
return false;
|
||||
}
|
||||
do_log("$log, success: " . nexus_json_encode($result));
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteBookmark(int $id): bool
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
$log = "[DELETE_BOOKMARK]: $id";
|
||||
$params = [
|
||||
'index' => self::INDEX_NAME,
|
||||
'id' => $this->getBookmarkId($id),
|
||||
];
|
||||
$result = $this->es->delete($params);
|
||||
if ($this->isEsResponseError($result)) {
|
||||
do_log("$log, fail: " . nexus_json_encode($result), 'error');
|
||||
return false;
|
||||
}
|
||||
do_log("$log, success: " . nexus_json_encode($result));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -67,7 +67,10 @@ class TagRepository extends BaseRepository
|
||||
foreach ($renderIdArr as $tagId) {
|
||||
$value = $tagKeyById->get($tagId);
|
||||
if ($value) {
|
||||
$item = "<span style=\"background-color:{$value->color};color:white;padding: 1px 2px\">{$value->name}</span> ";
|
||||
$item = sprintf(
|
||||
"<span style=\"background-color:%s;color:%s;border-radius:%s;font-size:%s;margin:%s;padding:%s\">%s</span>",
|
||||
$value->color, $value->font_color, $value->border_radius, $value->font_size, $value->margin, $value->padding, $value->name
|
||||
);
|
||||
if ($withFilterLink) {
|
||||
$html .= sprintf('<a href="torrents.php?tag_id=%s">%s</a>', $tagId, $item);
|
||||
} else {
|
||||
@@ -129,7 +132,7 @@ class TagRepository extends BaseRepository
|
||||
return count($values);
|
||||
}
|
||||
|
||||
public static function getOrderByFieldIdString()
|
||||
public static function getOrderByFieldIdString(): string
|
||||
{
|
||||
if (is_null(self::$orderByFieldIdString)) {
|
||||
$results = self::createBasicQuery()->get(['id']);
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Message;
|
||||
use App\Models\News;
|
||||
use App\Models\Poll;
|
||||
use App\Models\PollAnswer;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Encryption\Encrypter;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -148,4 +153,72 @@ class ToolRepository extends BaseRepository
|
||||
{
|
||||
return new Encrypter($key, 'AES-256-CBC');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $to
|
||||
* @param $subject
|
||||
* @param $body
|
||||
* @return bool
|
||||
*/
|
||||
public function sendMail($to, $subject, $body): bool
|
||||
{
|
||||
do_log("to: $to, subject: $subject, body: $body");
|
||||
$smtp = Setting::get('smtp');
|
||||
// Create the Transport
|
||||
$encryption = null;
|
||||
if (isset($smtp['encryption']) && in_array($smtp['encryption'], ['ssl', 'tls'])) {
|
||||
$encryption = $smtp['encryption'];
|
||||
}
|
||||
$transport = (new \Swift_SmtpTransport($smtp['smtpaddress'], $smtp['smtpport'], $encryption))
|
||||
->setUsername($smtp['accountname'])
|
||||
->setPassword($smtp['accountpassword'])
|
||||
;
|
||||
|
||||
// Create the Mailer using your created Transport
|
||||
$mailer = new \Swift_Mailer($transport);
|
||||
|
||||
// Create a message
|
||||
$message = (new \Swift_Message($subject))
|
||||
->setFrom($smtp['accountname'], Setting::get('basic.SITENAME'))
|
||||
->setTo([$to])
|
||||
->setBody($body, 'text/html')
|
||||
;
|
||||
|
||||
// Send the message
|
||||
try {
|
||||
$result = $mailer->send($message);
|
||||
if ($result == 0) {
|
||||
do_log("send mail fail, unknown error", 'error');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
do_log("send email fail: " . $e->getMessage() . "\n" . $e->getTraceAsString(), 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getNotificationCount(User $user): array
|
||||
{
|
||||
$result = [];
|
||||
//attend or not
|
||||
$attendRep = new AttendanceRepository();
|
||||
$attendance = $attendRep->getAttendance($user->id, date('Ymd'));
|
||||
$result['attendance'] = $attendance ? 0 : 1;
|
||||
|
||||
//unread news
|
||||
$count = News::query()->where('added', '>', $user->last_home)->count();
|
||||
$result['news'] = $count;
|
||||
|
||||
//unread messages
|
||||
$count = Message::query()->where('receiver', $user->id)->where('unread', 'yes')->count();
|
||||
$result['message'] = $count;
|
||||
|
||||
//un-vote poll
|
||||
$total = Poll::query()->count();
|
||||
$userVoteCount = PollAnswer::query()->where('userid', $user->id)->selectRaw('count(distinct(pollid)) as counts')->first()->counts;
|
||||
$result['poll'] = $total - $userVoteCount;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use Carbon\Carbon;
|
||||
use Hashids\Hashids;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class TorrentRepository extends BaseRepository
|
||||
{
|
||||
@@ -93,8 +94,12 @@ class TorrentRepository extends BaseRepository
|
||||
|
||||
public function getDetail($id, User $user)
|
||||
{
|
||||
$with = ['user', 'basic_audio_codec', 'basic_category', 'basic_codec', 'basic_media', 'basic_source', 'basic_standard', 'basic_team'];
|
||||
$result = Torrent::query()->with($with)->withCount(['peers', 'thank_users'])->visible()->findOrFail($id);
|
||||
$with = [
|
||||
'user', 'basic_audio_codec', 'basic_category', 'basic_codec', 'basic_media', 'basic_source', 'basic_standard', 'basic_team',
|
||||
'thanks' => function ($query) use ($user) {$query->where('userid', $user->id);},
|
||||
'reward_logs' => function ($query) use ($user) {$query->where('userid', $user->id);},
|
||||
];
|
||||
$result = Torrent::query()->with($with)->withCount(['peers', 'thank_users', 'reward_logs'])->visible()->findOrFail($id);
|
||||
$result->download_url = $this->getDownloadUrl($id, $user->toArray());
|
||||
return $result;
|
||||
}
|
||||
@@ -368,12 +373,14 @@ class TorrentRepository extends BaseRepository
|
||||
|
||||
private function getTrackerReportAuthKeySecret($id, $uid, $initializeIfNotExists = false)
|
||||
{
|
||||
$secret = TorrentSecret::query()
|
||||
->where('uid', $uid)
|
||||
->whereIn('torrent_id', [0, $id])
|
||||
->orderBy('torrent_id', 'desc')
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
$secret = NexusDB::remember("tracker_report_authkey_secret:$id:$uid", 3600*24, function () use ($id, $uid) {
|
||||
return TorrentSecret::query()
|
||||
->where('uid', $uid)
|
||||
->whereIn('torrent_id', [0, $id])
|
||||
->orderBy('torrent_id', 'desc')
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
});
|
||||
if ($secret) {
|
||||
return $secret->secret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,873 @@
|
||||
<?php
|
||||
/**
|
||||
* Handle announce and scrape
|
||||
*
|
||||
* @link https://github.com/HDInnovations/UNIT3D-Community-Edition/blob/master/app/Http/Controllers/AnnounceController.php
|
||||
* @link https://github.com/Rhilip/RidPT/blob/master/application/Controllers/Tracker/AnnounceController.php
|
||||
*/
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Exceptions\ClientNotAllowedException;
|
||||
use App\Models\Cheater;
|
||||
use App\Models\Peer;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Snatch;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Exceptions\TrackerException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Rhilip\Bencode\Bencode;
|
||||
|
||||
class TrackerRepository extends BaseRepository
|
||||
{
|
||||
const MIN_ANNOUNCE_WAIT_SECOND = 300;
|
||||
|
||||
const MAX_PEER_NUM_WANT = 50;
|
||||
|
||||
const MUST_BE_CHEATER_SPEED = 1024 * 1024 * 1024; //1024 MB/s
|
||||
const MAY_BE_CHEATER_SPEED = 1024 * 1024 * 100; //100 MB/s
|
||||
|
||||
// Port Blacklist
|
||||
protected const BLACK_PORTS = [
|
||||
22, // SSH Port
|
||||
53, // DNS queries
|
||||
80, 81, 8080, 8081, // Hyper Text Transfer Protocol (HTTP) - port used for web traffic
|
||||
411, 412, 413, // Direct Connect Hub (unofficial)
|
||||
443, // HTTPS / SSL - encrypted web traffic, also used for VPN tunnels over HTTPS.
|
||||
1214, // Kazaa - peer-to-peer file sharing, some known vulnerabilities, and at least one worm (Benjamin) targeting it.
|
||||
3389, // IANA registered for Microsoft WBT Server, used for Windows Remote Desktop and Remote Assistance connections
|
||||
4662, // eDonkey 2000 P2P file sharing service. http://www.edonkey2000.com/
|
||||
6346, 6347, // Gnutella (FrostWire, Limewire, Shareaza, etc.), BearShare file sharing app
|
||||
6699, // Port used by p2p software, such as WinMX, Napster.
|
||||
];
|
||||
|
||||
private array $userUpdates = [];
|
||||
|
||||
public function announce(Request $request): \Illuminate\Http\Response
|
||||
{
|
||||
do_log("queryString: " . $request->getQueryString());
|
||||
try {
|
||||
$withPeers = false;
|
||||
$queries = $this->checkAnnounceFields($request);
|
||||
$user = $this->checkUser($request);
|
||||
$clientAllow = $this->checkClient($request);
|
||||
$torrent = $this->checkTorrent($queries, $user);
|
||||
if ($this->isReAnnounce($queries) === false) {
|
||||
$withPeers = true;
|
||||
$peerSelf = $this->checkMinInterval($torrent, $queries, $user);
|
||||
if (!$peerSelf) {
|
||||
$this->checkPeer($torrent, $queries, $user);
|
||||
$this->checkPermission($torrent, $queries, $user);
|
||||
$peerSelf = new Peer([
|
||||
'torrent' => $torrent->id,
|
||||
'peer_id' => $queries['peer_id'],
|
||||
'userid' => $user->id,
|
||||
'passkey' => $user->passkey,
|
||||
]);
|
||||
} else {
|
||||
$this->checkCheater($torrent, $queries, $user, $peerSelf);
|
||||
}
|
||||
/**
|
||||
* Note: Must get before update peer!
|
||||
*/
|
||||
$dataTraffic = $this->getDataTraffic($torrent, $queries, $user, $peerSelf);
|
||||
|
||||
$this->updatePeer($peerSelf, $queries);
|
||||
$this->updateSnatch($peerSelf, $queries, $dataTraffic);
|
||||
$this->updateTorrent($torrent, $queries);
|
||||
|
||||
if ($dataTraffic['uploaded_increment_for_user'] > 0) {
|
||||
$this->userUpdates['uploaded'] = DB::raw('uploaded + ' . $dataTraffic['uploaded_increment_for_user']);
|
||||
}
|
||||
if ($dataTraffic['downloaded_increment_for_user'] > 0) {
|
||||
$this->userUpdates['downloaded'] = DB::raw('downloaded + ' . $dataTraffic['downloaded_increment_for_user']);
|
||||
}
|
||||
if ($user->clientselect != $clientAllow->id) {
|
||||
$this->userUpdates['clientselect'] = $clientAllow->id;
|
||||
}
|
||||
if ($user->showclienterror == 'yes') {
|
||||
$this->userUpdates['showclienterror'] = 'no';
|
||||
}
|
||||
}
|
||||
$repDict = $this->generateSuccessAnnounceResponse($torrent, $queries, $user, $withPeers);
|
||||
} catch (ClientNotAllowedException $exception) {
|
||||
do_log("[ClientNotAllowedException] " . $exception->getMessage());
|
||||
if (isset($user) && $user->showclienterror == 'no') {
|
||||
$this->userUpdates['showclienterror'] = 'yes';
|
||||
}
|
||||
$repDict = $this->generateFailedAnnounceResponse($exception->getMessage());
|
||||
} catch (TrackerException $exception) {
|
||||
$repDict = $this->generateFailedAnnounceResponse($exception->getMessage());
|
||||
} catch (\Throwable $exception) {
|
||||
//other system exception
|
||||
do_log("[" . get_class($exception) . "] " . $exception->getMessage() . "\n" . $exception->getTraceAsString(), 'error');
|
||||
$repDict = $this->generateFailedAnnounceResponse("system error, report to sysop please, hint: " . nexus()->getRequestId());
|
||||
} finally {
|
||||
if (isset($user) && count($this->userUpdates)) {
|
||||
$user->update($this->userUpdates);
|
||||
do_log(last_query(), 'debug');
|
||||
}
|
||||
return $this->sendFinalAnnounceResponse($repDict);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @throws ClientNotAllowedException
|
||||
* @throws TrackerException
|
||||
* @refs
|
||||
*/
|
||||
protected function checkClient(Request $request)
|
||||
{
|
||||
// Miss Header User-Agent is not allowed.
|
||||
if (! $request->header('User-Agent')) {
|
||||
throw new TrackerException('Invalid user-agent !');
|
||||
}
|
||||
|
||||
// Block Other Browser, Crawler (May Cheater or Faker Client) by check Requests headers
|
||||
if ($request->header('accept-language') || $request->header('referer')
|
||||
|| $request->header('accept-charset')
|
||||
|
||||
/**
|
||||
* This header check may block Non-bittorrent client `Aria2` to access tracker,
|
||||
* Because they always add this header which other clients don't have.
|
||||
*
|
||||
* @see https://blog.rhilip.info/archives/1010/ ( in Chinese )
|
||||
*/
|
||||
|| $request->header('want-digest')
|
||||
) {
|
||||
throw new TrackerException('Abnormal access blocked !');
|
||||
}
|
||||
|
||||
$userAgent = $request->header('User-Agent');
|
||||
|
||||
// Should also block User-Agent strings that are to long. (For Database reasons)
|
||||
if (\strlen((string) $userAgent) > 64) {
|
||||
throw new TrackerException('The User-Agent of this client is too long!');
|
||||
}
|
||||
|
||||
// Block Browser by checking it's User-Agent
|
||||
if (\preg_match('/(Mozilla|Browser|Chrome|Safari|AppleWebKit|Opera|Links|Lynx|Bot|Unknown)/i', (string) $userAgent)) {
|
||||
throw new TrackerException('Browser, Crawler or Cheater is not Allowed.');
|
||||
}
|
||||
|
||||
$agentAllowRep = new AgentAllowRepository();
|
||||
|
||||
return $agentAllowRep->checkClient($request->peer_id, $userAgent, config('app.debug'));
|
||||
|
||||
}
|
||||
|
||||
protected function checkPasskey($passkey)
|
||||
{
|
||||
// If Passkey Lenght Is Wrong
|
||||
if (\strlen((string) $passkey) !== 32) {
|
||||
throw new TrackerException('Invalid passkey ! the length of passkey must be 32');
|
||||
}
|
||||
|
||||
// If Passkey Format Is Wrong
|
||||
if (\strspn(\strtolower($passkey), 'abcdef0123456789') !== 32) { // MD5 char limit
|
||||
throw new TrackerException("Invalid passkey ! The format of passkey is not correct");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function checkAuthkey($authkey)
|
||||
{
|
||||
$arr = explode('|', $authkey);
|
||||
if (count($arr) != 3) {
|
||||
throw new TrackerException('Invalid authkey');
|
||||
}
|
||||
$torrentId = $arr[0];
|
||||
$uid = $arr[1];
|
||||
$torrentRep = new TorrentRepository();
|
||||
try {
|
||||
$decrypted = $torrentRep->checkTrackerReportAuthKey($authkey);
|
||||
} catch (\Exception $exception) {
|
||||
throw new TrackerException($exception->getMessage());
|
||||
}
|
||||
if (empty($decrypted)) {
|
||||
throw new TrackerException('Invalid authkey');
|
||||
}
|
||||
return compact('torrentId', 'uid');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return array
|
||||
* @throws TrackerException
|
||||
*/
|
||||
protected function checkAnnounceFields(Request $request): array
|
||||
{
|
||||
$queries = [];
|
||||
|
||||
// Part.1 check Announce **Need** Fields
|
||||
foreach (['info_hash', 'peer_id', 'port', 'uploaded', 'downloaded', 'left'] as $item) {
|
||||
$itemData = $request->query->get($item);
|
||||
if (! \is_null($itemData)) {
|
||||
$queries[$item] = $itemData;
|
||||
} else {
|
||||
throw new TrackerException("key: $item is Missing !");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['info_hash', 'peer_id'] as $item) {
|
||||
if (($length = \strlen((string) $queries[$item])) !== 20) {
|
||||
throw new TrackerException("Invalid $item ! $item is not 20 bytes long($length)");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['uploaded', 'downloaded', 'left'] as $item) {
|
||||
$itemData = $queries[$item];
|
||||
if (! \is_numeric($itemData) || $itemData < 0) {
|
||||
throw new TrackerException("Invalid $item ! $item Must be a number greater than or equal to 0");
|
||||
}
|
||||
}
|
||||
|
||||
// Part.2 check Announce **Option** Fields
|
||||
foreach (['event' => '', 'no_peer_id' => 1, 'compact' => 0, 'numwant' => 50, 'corrupt' => 0, 'key' => ''] as $item => $value) {
|
||||
$queries[$item] = $request->query->get($item, $value);
|
||||
if ($queries[$item] && $item == 'event') {
|
||||
$queries[$item] = strtolower($queries[$item]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['numwant', 'corrupt', 'no_peer_id', 'compact'] as $item) {
|
||||
if (! \is_numeric($queries[$item]) || $queries[$item] < 0) {
|
||||
throw new TrackerException("Invalid $item ! $item Must be a number greater than or equal to 0");
|
||||
}
|
||||
}
|
||||
|
||||
if (! \in_array(\strtolower($queries['event']), ['started', 'completed', 'stopped', 'paused', ''])) {
|
||||
throw new TrackerException("Unsupported Event type {$queries['event']} .");
|
||||
}
|
||||
|
||||
// Part.3 check Port is Valid and Allowed
|
||||
/**
|
||||
* Normally , the port must in 1 - 65535 , that is ( $port > 0 && $port < 0xffff )
|
||||
* However, in some case , When `&event=stopped` the port may set to 0.
|
||||
*/
|
||||
if ($queries['port'] === 0 && \strtolower($queries['event']) !== 'stopped') {
|
||||
throw new TrackerException("Illegal port 0 under Event type {$queries['event']} .");
|
||||
}
|
||||
|
||||
if (! \is_numeric($queries['port']) || $queries['port'] < 0 || $queries['port'] > 0xFFFF || \in_array($queries['port'], self::BLACK_PORTS,
|
||||
true)) {
|
||||
throw new TrackerException("Illegal port {$queries['port']} . Port should between 6881-64999");
|
||||
}
|
||||
|
||||
// Part.4 Get User Ip Address
|
||||
$queries['ip'] = $request->getClientIp();
|
||||
|
||||
// Part.5 Get Users Agent
|
||||
$queries['user_agent'] = $request->headers->get('user-agent');
|
||||
|
||||
// Part.6 info_hash, binary
|
||||
$queries['info_hash'] = $queries['info_hash'];
|
||||
|
||||
// Part.7
|
||||
$queries['peer_id'] = $queries['peer_id'];
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
protected function checkUser(Request $request)
|
||||
{
|
||||
if ($authkey = $request->query->get('authkey')) {
|
||||
$checkResult = $this->checkAuthkey($authkey);
|
||||
$field = 'id';
|
||||
$value = $checkResult['uid'];
|
||||
} elseif ($passkey = $request->query->get('passkey')) {
|
||||
$this->checkPasskey($passkey);
|
||||
$field = 'passkey';
|
||||
$value = $passkey;
|
||||
} else {
|
||||
throw new TrackerException("Require authkey or passkey.");
|
||||
}
|
||||
/**
|
||||
* @var $user User
|
||||
*/
|
||||
$user = Cache::remember("user:$field:$value:" . __METHOD__, 60, function () use ($field, $value) {
|
||||
return User::query()->where($field, $value)->first();
|
||||
});
|
||||
if (!$user) {
|
||||
throw new TrackerException("Invalid $field: $value.");
|
||||
}
|
||||
$user->checkIsNormal();
|
||||
|
||||
if ($user->parked == 'yes') {
|
||||
throw new TrackerException("Your account is parked! (Read the FAQ)");
|
||||
}
|
||||
if ($user->downloadpos == 'no') {
|
||||
throw new TrackerException("Your downloading privilege have been disabled! (Read the rules)");
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
protected function checkTorrent($queries, User $user)
|
||||
{
|
||||
// Check Info Hash Against Torrents Table
|
||||
$torrent = $this->getTorrentByInfoHash($queries['info_hash']);
|
||||
|
||||
// If Torrent Doesnt Exists Return Error to Client
|
||||
if ($torrent === null) {
|
||||
throw new TrackerException('Torrent not registered with this tracker.');
|
||||
}
|
||||
|
||||
if ($torrent->banned == 'yes' && $user->class < Setting::get('authority.seebanned')) {
|
||||
throw new TrackerException("torrent banned");
|
||||
}
|
||||
|
||||
return $torrent;
|
||||
}
|
||||
|
||||
protected function checkPeer(Torrent $torrent, array $queries, User $user): void
|
||||
{
|
||||
if ($queries['event'] === 'completed') {
|
||||
throw new TrackerException("Torrent being announced as complete but no record found.");
|
||||
}
|
||||
|
||||
$counts = Peer::query()
|
||||
->where('torrent', '=', $torrent->id)
|
||||
->where('userid', $user->id)
|
||||
->count();
|
||||
if ($queries['left'] == 0 && $counts >= 3) {
|
||||
throw new TrackerException("You cannot seed the same torrent from more than 3 locations.");
|
||||
}
|
||||
if ($queries['left'] > 0 && $counts >= 1) {
|
||||
throw new TrackerException("You already are downloading the same torrent. You may only leech from one location at a time.");
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkPermission(Torrent $torrent, $queries, User $user)
|
||||
{
|
||||
if ($user->class >= User::CLASS_VIP) {
|
||||
return;
|
||||
}
|
||||
$gigs = $user->downloaded / (1024*1024*1024);
|
||||
if ($gigs < 10) {
|
||||
return;
|
||||
}
|
||||
$ratio = ($user->downloaded > 0) ? ($user->uploaded / $user->downloaded) : 1;
|
||||
$settingsMain = Setting::get('main');
|
||||
if ($settingsMain['waitsystem'] == 'yes') {
|
||||
$elapsed = Carbon::now()->diffInHours($torrent->added);
|
||||
if ($ratio < 0.4) $wait = 24;
|
||||
elseif ($ratio < 0.5) $wait = 12;
|
||||
elseif ($ratio < 0.6) $wait = 6;
|
||||
elseif ($ratio < 0.8) $wait = 3;
|
||||
else $wait = 0;
|
||||
|
||||
if ($elapsed < $wait) {
|
||||
$msg = "Your ratio is too low! You need to wait " . mkprettytime($wait * 3600 - $elapsed) . " to start";
|
||||
throw new TrackerException($msg);
|
||||
}
|
||||
}
|
||||
|
||||
if ($settingsMain['maxdlsystem'] == 'yes') {
|
||||
if ($ratio < 0.5) $max = 1;
|
||||
elseif ($ratio < 0.65) $max = 2;
|
||||
elseif ($ratio < 0.8) $max = 3;
|
||||
elseif ($ratio < 0.95) $max = 4;
|
||||
else $max = 0;
|
||||
|
||||
if ($max > 0) {
|
||||
$counts = Peer::query()->where('userid', $user->id)->where('seeder', 'no')->count();
|
||||
if ($counts > $max) {
|
||||
$msg = "Your slot limit is reached! You may at most download $max torrents at the same time";
|
||||
throw new TrackerException($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Torrent $torrent
|
||||
* @param $queries
|
||||
* @param User $user
|
||||
* @return \Illuminate\Database\Eloquent\Builder|Model|object|null
|
||||
* @throws TrackerException
|
||||
*/
|
||||
protected function checkMinInterval(Torrent $torrent, $queries, User $user)
|
||||
{
|
||||
$peer = Peer::query()
|
||||
->where('torrent', $torrent->id)
|
||||
->where('peer_id', $queries['peer_id'])
|
||||
->first();
|
||||
|
||||
if (
|
||||
$peer
|
||||
&& $queries['event'] == ''
|
||||
&& $peer->isValidDate('prev_action')
|
||||
&& Carbon::now()->diffInSeconds($peer->prev_action) < self::MIN_ANNOUNCE_WAIT_SECOND
|
||||
) {
|
||||
throw new TrackerException('There is a minimum announce time of ' . self::MIN_ANNOUNCE_WAIT_SECOND . ' seconds');
|
||||
}
|
||||
return $peer;
|
||||
}
|
||||
|
||||
protected function checkCheater(Torrent $torrent, $queries, User $user, Peer $peer)
|
||||
{
|
||||
$settingSecurity = Setting::get('security');
|
||||
$level = $settingSecurity['cheaterdet'];
|
||||
if ($level == 0) {
|
||||
//don't do check
|
||||
return;
|
||||
}
|
||||
if ($user->class >= $settingSecurity['nodetect']) {
|
||||
//forever trust
|
||||
return;
|
||||
}
|
||||
if (!$peer->isValidDate('last_action')) {
|
||||
//no last action
|
||||
return;
|
||||
}
|
||||
$duration = Carbon::now()->diffInSeconds($peer->last_action);
|
||||
$upSpeed = $queries['uploaded'] > 0 ? ($queries['uploaded'] / $duration) : 0;
|
||||
$oneGB = 1024 * 1024 * 1024;
|
||||
$tenMB = 1024 * 1024 * 10;
|
||||
$nowStr = Carbon::now()->toDateTimeString();
|
||||
$cheaterBaseData = [
|
||||
'added' => $nowStr,
|
||||
'userid' => $user->id,
|
||||
'torrentid' => $torrent->id,
|
||||
'uploaded' => $queries['uploaded'],
|
||||
'downloaded' => $queries['downloaded'],
|
||||
'anctime' => $duration,
|
||||
'seeders' => $torrent->seeders,
|
||||
'leechers' => $torrent->leechers,
|
||||
];
|
||||
|
||||
if ($queries['uploaded'] > $oneGB && ($upSpeed > self::MUST_BE_CHEATER_SPEED / $level)) {
|
||||
//Uploaded more than 1 GB with uploading rate higher than 1024 MByte/S (For Consertive level). This is no doubt cheating.
|
||||
$comment = "User account was automatically disabled by system";
|
||||
$data = array_merge($cheaterBaseData, ['comment' => $comment]);
|
||||
Cheater::query()->insert($data);
|
||||
$modComment = "We believe you're trying to cheat. And your account is disabled.";
|
||||
$user->updateWithModComment(['enabled' => User::ENABLED_NO], $modComment);
|
||||
throw new TrackerException($modComment);
|
||||
}
|
||||
|
||||
if ($queries['uploaded'] > $oneGB && ($upSpeed > self::MAY_BE_CHEATER_SPEED / $level)) {
|
||||
//Uploaded more than 1 GB with uploading rate higher than 100 MByte/S (For Consertive level). This is likely cheating.
|
||||
$comment = "Abnormally high uploading rate";
|
||||
$data = array_merge($cheaterBaseData, ['comment' => $comment]);
|
||||
$this->createOrUpdateCheater($torrent, $user, $data);
|
||||
}
|
||||
|
||||
if ($level > 1) {
|
||||
if ($queries['uploaded'] > $oneGB && ($upSpeed > 1024 * 1024) && ($queries['leechers'] < 2 * $level)) {
|
||||
//Uploaded more than 1 GB with uploading rate higher than 1 MByte/S when there is less than 8 leechers (For Consertive level). This is likely cheating.
|
||||
$comment = "User is uploading fast when there is few leechers";
|
||||
$data = array_merge($cheaterBaseData, ['comment' => $comment]);
|
||||
$this->createOrUpdateCheater($torrent, $user, $data);
|
||||
}
|
||||
|
||||
if ($queries['uploaded'] > $tenMB && ($upSpeed > 1024 * 100) && ($queries['leechers'] == 0)) {
|
||||
///Uploaded more than 10 MB with uploading speed faster than 100 KByte/S when there is no leecher. This is likely cheating.
|
||||
$comment = "User is uploading when there is no leecher";
|
||||
$data = array_merge($cheaterBaseData, ['comment' => $comment]);
|
||||
$this->createOrUpdateCheater($torrent, $user, $data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function createOrUpdateCheater(Torrent $torrent, User $user, array $createData)
|
||||
{
|
||||
$existsCheater = Cheater::query()
|
||||
->where('torrentid', $torrent->id)
|
||||
->where('userid', $user->id)
|
||||
->where('added', '>', Carbon::now()->subHours(24))
|
||||
->first();
|
||||
if ($existsCheater) {
|
||||
$existsCheater->increment('hit');
|
||||
} else {
|
||||
$createData['hit'] = 1;
|
||||
Cheater::query()->insert($createData);
|
||||
}
|
||||
}
|
||||
|
||||
protected function isReAnnounce(array $queries): bool
|
||||
{
|
||||
unset($queries['key']);
|
||||
$lockKey = md5(http_build_query($queries));
|
||||
$redis = Redis::connection()->client();
|
||||
if (!$redis->set($lockKey, nexus()->getStartTimestamp(), ['nx', 'ex' => 5])) {
|
||||
do_log('[RE_ANNOUNCE]');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function generateSuccessAnnounceResponse($torrent, $queries, $user, $withPeers = true): array
|
||||
{
|
||||
// Build Response For Bittorrent Client
|
||||
$minInterval = self::MIN_ANNOUNCE_WAIT_SECOND;
|
||||
$interval = max($this->getRealAnnounceInterval($torrent), $minInterval);
|
||||
$repDict = [
|
||||
'interval' => $interval + random_int(10, 100),
|
||||
'min interval' => $minInterval + random_int(1, 10),
|
||||
'complete' => (int) $torrent->seeders,
|
||||
'incomplete' => (int) $torrent->leechers,
|
||||
'peers' => [],
|
||||
'peers6' => [],
|
||||
];
|
||||
do_log("[REP_DICT_BASE] " . json_encode($repDict));
|
||||
|
||||
/**
|
||||
* For non `stopped` event only
|
||||
* We query peers from database and send peer list, otherwise just quick return.
|
||||
*/
|
||||
if (\strtolower($queries['event']) !== 'stopped' && $withPeers) {
|
||||
$limit = ($queries['numwant'] <= self::MAX_PEER_NUM_WANT ? $queries['numwant'] : self::MAX_PEER_NUM_WANT);
|
||||
$baseQuery = Peer::query()
|
||||
->select(['peer_id', 'ip', 'port'])
|
||||
->where('torrent', $torrent->id)
|
||||
->where('userid', '!=', $user->id)
|
||||
->limit($limit)
|
||||
->orderByRaw('rand()')
|
||||
;
|
||||
|
||||
// Get Torrents Peers
|
||||
if ($queries['left'] == 0) {
|
||||
// Only include leechers in a seeder's peerlist
|
||||
$peers = $baseQuery->where('seeder', 'no')->get()->toArray();
|
||||
} else {
|
||||
$peers = $baseQuery->get()->toArray();
|
||||
}
|
||||
do_log("[REP_DICT_PEER_QUERY] " . last_query());
|
||||
$repDict['peers'] = $this->givePeers($peers, $queries['compact'], $queries['no_peer_id']);
|
||||
$repDict['peers6'] = $this->givePeers($peers, $queries['compact'], $queries['no_peer_id'], FILTER_FLAG_IPV6);
|
||||
}
|
||||
|
||||
return $repDict;
|
||||
}
|
||||
|
||||
private function getRealAnnounceInterval(Torrent $torrent)
|
||||
{
|
||||
$settingMain = Setting::get('main');
|
||||
$announce_wait = self::MIN_ANNOUNCE_WAIT_SECOND;
|
||||
$real_annnounce_interval = $settingMain['announce_interval'];
|
||||
$torrentSurvivalDays = Carbon::now()->diffInDays($torrent->added);
|
||||
if (
|
||||
$settingMain['anninterthreeage']
|
||||
&& ($settingMain['anninterthree'] > $announce_wait)
|
||||
&& ($torrentSurvivalDays >= $settingMain['anninterthreeage'])
|
||||
) {
|
||||
$real_annnounce_interval = $settingMain['anninterthree'];
|
||||
} elseif (
|
||||
$settingMain['annintertwoage']
|
||||
&& ($settingMain['annintertwo'] > $announce_wait)
|
||||
&& ($torrentSurvivalDays >= $settingMain['annintertwoage'])
|
||||
) {
|
||||
$real_annnounce_interval = $settingMain['annintertwo'];
|
||||
}
|
||||
do_log(sprintf(
|
||||
'torrent: %s, survival days: %s, real_announce_interval: %s',
|
||||
$torrent->id, $torrentSurvivalDays, $real_annnounce_interval
|
||||
), 'debug');
|
||||
|
||||
return $real_annnounce_interval;
|
||||
}
|
||||
|
||||
private function getDataTraffic(Torrent $torrent, $queries, User $user, Peer $peer): array
|
||||
{
|
||||
$log = sprintf(
|
||||
"torrent: %s, user: %s, peer: %s, queriesUploaded: %s, queriesDownloaded: %s",
|
||||
$torrent->id, $user->id, $peer->id, $queries['uploaded'], $queries['downloaded']
|
||||
);
|
||||
if ($peer->exists) {
|
||||
$realUploaded = max($queries['uploaded'] - $peer->uploaded, 0);
|
||||
$realDownloaded = max($queries['downloaded'] - $peer->downloaded, 0);
|
||||
$log .= ", [PEER_EXISTS], realUploaded: $realUploaded, realDownloaded: $realDownloaded";
|
||||
} else {
|
||||
$realUploaded = $queries['uploaded'];
|
||||
$realDownloaded = $queries['downloaded'];
|
||||
$log .= ", [PEER_NOT_EXISTS],, realUploaded: $realUploaded, realDownloaded: $realDownloaded";
|
||||
}
|
||||
$spStateReal = $torrent->spStateReal;
|
||||
$uploaderRatio = Setting::get('torrent.uploaderdouble');
|
||||
$log .= ", spStateReal: $spStateReal, uploaderRatio: $uploaderRatio";
|
||||
if ($torrent->owner == $user->id) {
|
||||
//uploader, use the bigger one
|
||||
$upRatio = max($uploaderRatio, Torrent::$promotionTypes[$spStateReal]['up_multiplier']);
|
||||
$log .= ", [IS_UPLOADER], upRatio: $upRatio";
|
||||
} else {
|
||||
$upRatio = Torrent::$promotionTypes[$spStateReal]['up_multiplier'];
|
||||
$log .= ", [IS_NOT_UPLOADER], upRatio: $upRatio";
|
||||
}
|
||||
$downRatio = Torrent::$promotionTypes[$spStateReal]['down_multiplier'];
|
||||
$log .= ", downRatio: $downRatio";
|
||||
$result = [
|
||||
'uploaded_increment' => $realUploaded,
|
||||
'uploaded_increment_for_user' => $realUploaded * $upRatio,
|
||||
'downloaded_increment' => $realDownloaded,
|
||||
'downloaded_increment_for_user' => $realDownloaded * $downRatio,
|
||||
];
|
||||
do_log("$log, result: " . json_encode($result));
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function givePeers($peers, $compact, $noPeerId, int $filterFlag = FILTER_FLAG_IPV4): string|array
|
||||
{
|
||||
if ($compact) {
|
||||
$pcomp = '';
|
||||
foreach ($peers as $p) {
|
||||
if (isset($p['ip'], $p['port']) && \filter_var($p['ip'], FILTER_VALIDATE_IP, $filterFlag)) {
|
||||
$pcomp .= \inet_pton($p['ip']);
|
||||
$pcomp .= \pack('n', (int) $p['port']);
|
||||
}
|
||||
}
|
||||
|
||||
return $pcomp;
|
||||
}
|
||||
|
||||
if ($noPeerId) {
|
||||
foreach ($peers as &$p) {
|
||||
unset($p['peer_id']);
|
||||
}
|
||||
|
||||
return $peers;
|
||||
}
|
||||
|
||||
return $peers;
|
||||
}
|
||||
|
||||
protected function generateFailedAnnounceResponse($reason): array
|
||||
{
|
||||
return [
|
||||
'failure reason' => $reason,
|
||||
'min interval' => self::MIN_ANNOUNCE_WAIT_SECOND,
|
||||
//'retry in' => self::MIN_ANNOUNCE_WAIT_SECOND
|
||||
];
|
||||
}
|
||||
|
||||
protected function sendFinalAnnounceResponse($repDict): \Illuminate\Http\Response
|
||||
{
|
||||
do_log("[repDict] " . nexus_json_encode($repDict));
|
||||
return \response(Bencode::encode($repDict))
|
||||
->withHeaders(['Content-Type' => 'text/plain; charset=utf-8'])
|
||||
->withHeaders(['Connection' => 'close'])
|
||||
->withHeaders(['Pragma' => 'no-cache']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Torrent $torrent
|
||||
* @param $queries
|
||||
*/
|
||||
private function updateTorrent(Torrent $torrent, $queries)
|
||||
{
|
||||
if (empty($queries['event'])) {
|
||||
do_log("no event, return", 'debug');
|
||||
return;
|
||||
}
|
||||
$torrent->seeders = Peer::query()
|
||||
->where('torrent', $torrent->id)
|
||||
->where('to_go', '=',0)
|
||||
->count();
|
||||
|
||||
$torrent->leechers = Peer::query()
|
||||
->where('torrent', $torrent->id)
|
||||
->where('to_go', '>', 0)
|
||||
->count();
|
||||
|
||||
$torrent->visible = Torrent::VISIBLE_YES;
|
||||
$torrent->last_action = Carbon::now();
|
||||
|
||||
if ($queries['event'] == 'completed') {
|
||||
$torrent->times_completed = DB::raw("times_completed + 1");
|
||||
}
|
||||
|
||||
$torrent->save();
|
||||
do_log(last_query(), 'debug');
|
||||
}
|
||||
|
||||
private function updatePeer(Peer $peer, $queries)
|
||||
{
|
||||
if ($queries['event'] == 'stopped') {
|
||||
$peer->delete();
|
||||
do_log(last_query(), 'debug');
|
||||
return;
|
||||
}
|
||||
|
||||
$nowStr = Carbon::now()->toDateTimeString();
|
||||
//torrent, userid, peer_id, ip, port, connectable, uploaded, downloaded, to_go, started, last_action, seeder, agent, downloadoffset, uploadoffset, passkey
|
||||
$peer->ip = $queries['ip'];
|
||||
$peer->port = $queries['port'];
|
||||
$peer->agent = $queries['user_agent'];
|
||||
$peer->updateConnectableStateIfNeeded();
|
||||
|
||||
$peer->to_go = $queries['left'];
|
||||
$peer->seeder = $queries['left'] == 0 ? 'yes' : 'no';
|
||||
$peer->last_action = $nowStr;
|
||||
$peer->uploaded = $queries['uploaded'];
|
||||
$peer->downloaded = $queries['downloaded'];
|
||||
|
||||
if ($peer->exists) {
|
||||
$peer->prev_action = $peer->last_action;
|
||||
}
|
||||
|
||||
if ($queries['event'] == 'started') {
|
||||
$peer->started = $nowStr;
|
||||
$peer->uploadoffset = $queries['uploaded'];
|
||||
$peer->downloadoffset = $queries['downloaded'];
|
||||
} elseif ($queries['event'] == 'completed') {
|
||||
$peer->finishedat = time();
|
||||
}
|
||||
|
||||
$peer->save();
|
||||
do_log(last_query(), 'debug');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update snatch, uploaded & downloaded, use the increment value to do increment
|
||||
*
|
||||
* @param Peer $peer
|
||||
* @param $queries
|
||||
* @param $dataTraffic
|
||||
*/
|
||||
private function updateSnatch(Peer $peer, $queries, $dataTraffic)
|
||||
{
|
||||
$nowStr = Carbon::now()->toDateTimeString();
|
||||
|
||||
$snatch = Snatch::query()
|
||||
->where('torrentid', $peer->torrent)
|
||||
->where('userid', $peer->userid)
|
||||
->first();
|
||||
|
||||
//torrentid, userid, ip, port, uploaded, downloaded, to_go, ,seedtime, leechtime, last_action, startdat, completedat, finished
|
||||
if (!$snatch) {
|
||||
$snatch = new Snatch();
|
||||
//initial
|
||||
$snatch->torrentid = $peer->torrent;
|
||||
$snatch->userid = $peer->userid;
|
||||
$snatch->uploaded = $dataTraffic['uploaded_increment'];
|
||||
$snatch->downloaded = $dataTraffic['downloaded_increment'];
|
||||
$snatch->startdat = $nowStr;
|
||||
} else {
|
||||
//increase, use the increment value
|
||||
$snatch->uploaded = DB::raw("uploaded + " . $dataTraffic['uploaded_increment']);
|
||||
$snatch->downloaded = DB::raw("downloaded + " . $dataTraffic['downloaded_increment']);
|
||||
$timeIncrease = Carbon::now()->diffInSeconds($peer->last_action);
|
||||
if ($queries['left'] == 0) {
|
||||
//seeder
|
||||
$timeField = 'seedtime';
|
||||
} else {
|
||||
$timeField = 'leechtime';
|
||||
}
|
||||
$snatch->{$timeField} = DB::raw("$timeField + $timeIncrease");
|
||||
}
|
||||
|
||||
//always update
|
||||
$snatch->ip = $queries['ip'];
|
||||
$snatch->port = $queries['port'];
|
||||
$snatch->to_go = $queries['left'];
|
||||
$snatch->last_action = $nowStr;
|
||||
if ($queries['event'] == 'completed') {
|
||||
$snatch->completedat = $nowStr;
|
||||
$snatch->finished = 'yes';
|
||||
}
|
||||
|
||||
$snatch->save();
|
||||
do_log(last_query(), 'debug');
|
||||
}
|
||||
|
||||
public function scrape(Request $request): \Illuminate\Http\Response
|
||||
{
|
||||
do_log("queryString: " . $request->getQueryString());
|
||||
try {
|
||||
$infoHashArr = $this->checkScrapeFields($request);
|
||||
$user = $this->checkUser($request);
|
||||
$clientAllow = $this->checkClient($request);
|
||||
|
||||
if ($user->clientselect != $clientAllow->id) {
|
||||
$this->userUpdates['clientselect'] = $clientAllow->id;
|
||||
}
|
||||
if ($user->showclienterror == 'yes') {
|
||||
$this->userUpdates['showclienterror'] = 'no';
|
||||
}
|
||||
$repDict = $this->generateScrapeResponse($infoHashArr);
|
||||
} catch (ClientNotAllowedException $exception) {
|
||||
do_log("[ClientNotAllowedException] " . $exception->getMessage());
|
||||
if (isset($user) && $user->showclienterror == 'no') {
|
||||
$this->userUpdates['showclienterror'] = 'yes';
|
||||
}
|
||||
$repDict = $this->generateFailedAnnounceResponse($exception->getMessage());
|
||||
} catch (TrackerException $exception) {
|
||||
$repDict = $this->generateFailedAnnounceResponse($exception->getMessage());
|
||||
} catch (\Throwable $exception) {
|
||||
//other system exception
|
||||
do_log("[" . get_class($exception) . "] " . $exception->getMessage() . "\n" . $exception->getTraceAsString(), 'error');
|
||||
$repDict = $this->generateFailedAnnounceResponse("system error, report to sysop please, hint: " . nexus()->getRequestId());
|
||||
} finally {
|
||||
do_log("userUpdates: " . nexus_json_encode($this->userUpdates));
|
||||
if (isset($user) && count($this->userUpdates)) {
|
||||
$user->update($this->userUpdates);
|
||||
do_log(last_query(), 'debug');
|
||||
}
|
||||
return $this->sendFinalAnnounceResponse($repDict);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkScrapeFields(Request $request): array
|
||||
{
|
||||
preg_match_all('/info_hash=([^&]*)/i', urldecode($request->getQueryString()), $info_hash_match);
|
||||
|
||||
$info_hash_array = $info_hash_match[1];
|
||||
if (count($info_hash_array) < 1) {
|
||||
throw new TrackerException("key: info_hash is Missing !");
|
||||
} else {
|
||||
foreach ($info_hash_array as $item) {
|
||||
if (strlen($item) != 20) {
|
||||
throw new TrackerException("Invalid info_hash ! info_hash is not 20 bytes long");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $info_hash_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $info_hash_array
|
||||
* @return array[]
|
||||
* @see http://www.bittorrent.org/beps/bep_0048.html
|
||||
*/
|
||||
private function generateScrapeResponse($info_hash_array)
|
||||
{
|
||||
$torrent_details = [];
|
||||
foreach ($info_hash_array as $item) {
|
||||
$torrent = $this->getTorrentByInfoHash($item);
|
||||
if ($torrent) {
|
||||
$torrent_details[$item] = [
|
||||
'complete' => (int)$torrent->seeders,
|
||||
'downloaded' => (int)$torrent->times_completed,
|
||||
'incomplete' => (int)$torrent->leechers,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return ['files' => $torrent_details];
|
||||
}
|
||||
|
||||
private function getTorrentByInfoHash($infoHash)
|
||||
{
|
||||
$cacheKey = bin2hex($infoHash) . __METHOD__;
|
||||
return Cache::remember($cacheKey, 60, function () use ($infoHash) {
|
||||
$fieldRaw = 'id, owner, sp_state, seeders, leechers, added, banned, hr, visible, last_action, times_completed';
|
||||
$torrent = Torrent::query()->where('info_hash', $infoHash)->selectRaw($fieldRaw)->first();
|
||||
do_log("[getTorrentByInfoHash] cache miss, from database: " . last_query() . ", and get: " . $torrent->id);
|
||||
return $torrent;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,6 @@ define('LARAVEL_START', microtime(true));
|
||||
| loading of any our classes "manually". Feels great to relax.
|
||||
|
|
||||
*/
|
||||
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
+6
-2
@@ -1,8 +1,12 @@
|
||||
<?php
|
||||
|
||||
defined('NEXUS_START') || define('NEXUS_START', LARAVEL_START);
|
||||
defined('LARAVEL_START') || define('LARAVEL_START', microtime(true));
|
||||
defined('IN_NEXUS') || define('IN_NEXUS', false);
|
||||
require dirname(__DIR__) . '/include/constants.php';
|
||||
require dirname(__DIR__) . '/include/globalfunctions.php';
|
||||
require dirname(__DIR__) . '/include/functions.php';
|
||||
if (!RUNNING_IN_OCTANE) {
|
||||
\Nexus\Nexus::boot();
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
+6
-4
@@ -17,19 +17,18 @@
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
},
|
||||
"files": [
|
||||
"include/globalfunctions.php",
|
||||
"include/functions.php"
|
||||
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"ext-bcmath": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-bcmath": "*",
|
||||
"ext-redis": "*",
|
||||
"ext-xml": "*",
|
||||
"doctrine/dbal": "^3.1",
|
||||
"fideloper/proxy": "^4.4",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
@@ -37,9 +36,12 @@
|
||||
"guzzlehttp/guzzle": "~6.0",
|
||||
"hashids/hashids": "^4.1",
|
||||
"imdbphp/imdbphp": "^7.0",
|
||||
"jeroen-g/explorer": "^2.5",
|
||||
"laravel-lang/lang": "~7.0",
|
||||
"laravel/framework": "^8.12",
|
||||
"laravel/octane": "^1.2",
|
||||
"laravel/sanctum": "^2.10",
|
||||
"laravel/scout": "^9.4",
|
||||
"laravel/tinker": "^2.5",
|
||||
"nao-pon/flysystem-google-drive": "^1.1",
|
||||
"orangehill/iseed": "^3.0",
|
||||
|
||||
Generated
+775
-4
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4893668aed1fe063d932fc9d9e5ea4d5",
|
||||
"content-hash": "6bbf6c91d5313119bf3eec1087dab109",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
@@ -916,6 +916,201 @@
|
||||
],
|
||||
"time": "2020-12-29T14:50:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "elasticsearch/elasticsearch",
|
||||
"version": "v7.17.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/elastic/elasticsearch-php.git",
|
||||
"reference": "1890f9d7fde076b5a3ddcf579a802af05b2e781b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/1890f9d7fde076b5a3ddcf579a802af05b2e781b",
|
||||
"reference": "1890f9d7fde076b5a3ddcf579a802af05b2e781b",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"ext-json": ">=1.3.7",
|
||||
"ezimuel/ringphp": "^1.1.2",
|
||||
"php": "^7.3 || ^8.0",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-yaml": "*",
|
||||
"ext-zip": "*",
|
||||
"mockery/mockery": "^1.2",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"squizlabs/php_codesniffer": "^3.4",
|
||||
"symfony/finder": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "*",
|
||||
"monolog/monolog": "Allows for client-level logging and tracing"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/autoload.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Elasticsearch\\": "src/Elasticsearch/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0",
|
||||
"LGPL-2.1-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Zachary Tong"
|
||||
},
|
||||
{
|
||||
"name": "Enrico Zimuel"
|
||||
}
|
||||
],
|
||||
"description": "PHP Client for Elasticsearch",
|
||||
"keywords": [
|
||||
"client",
|
||||
"elasticsearch",
|
||||
"search"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/elastic/elasticsearch-php/issues",
|
||||
"source": "https://github.com/elastic/elasticsearch-php/tree/v7.17.0"
|
||||
},
|
||||
"time": "2022-02-03T13:40:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ezimuel/guzzlestreams",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ezimuel/guzzlestreams.git",
|
||||
"reference": "abe3791d231167f14eb80d413420d1eab91163a8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/abe3791d231167f14eb80d413420d1eab91163a8",
|
||||
"reference": "abe3791d231167f14eb80d413420d1eab91163a8",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Stream\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php",
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"keywords": [
|
||||
"Guzzle",
|
||||
"stream"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/ezimuel/guzzlestreams/tree/3.0.1"
|
||||
},
|
||||
"time": "2020-02-14T23:11:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ezimuel/ringphp",
|
||||
"version": "1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ezimuel/ringphp.git",
|
||||
"reference": "92b8161404ab1ad84059ebed41d9f757e897ce74"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ezimuel/ringphp/zipball/92b8161404ab1ad84059ebed41d9f757e897ce74",
|
||||
"reference": "92b8161404ab1ad84059ebed41d9f757e897ce74",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"ezimuel/guzzlestreams": "^3.0.1",
|
||||
"php": ">=5.4.0",
|
||||
"react/promise": "~2.0"
|
||||
},
|
||||
"replace": {
|
||||
"guzzlehttp/ringphp": "self.version"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "~9.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Guzzle will use specific adapters if cURL is present"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Ring\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php",
|
||||
"support": {
|
||||
"source": "https://github.com/ezimuel/ringphp/tree/1.2.0"
|
||||
},
|
||||
"time": "2021-11-16T11:51:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fideloper/proxy",
|
||||
"version": "4.4.1",
|
||||
@@ -1791,6 +1986,185 @@
|
||||
},
|
||||
"time": "2021-07-15T12:15:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jeroen-g/explorer",
|
||||
"version": "2.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jeroen-G/Explorer.git",
|
||||
"reference": "5ffd1a813875c44508cb3fb4916fa31ecd8098d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Jeroen-G/Explorer/zipball/5ffd1a813875c44508cb3fb4916fa31ecd8098d9",
|
||||
"reference": "5ffd1a813875c44508cb3fb4916fa31ecd8098d9",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"elasticsearch/elasticsearch": "^7.9",
|
||||
"illuminate/support": "~7|~8",
|
||||
"laravel/scout": "~8|~9",
|
||||
"php": "^7.4|^8.0",
|
||||
"webmozart/assert": "^1.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"infection/infection": "^0.20",
|
||||
"mockery/mockery": "^1.4",
|
||||
"orchestra/testbench": "~6",
|
||||
"phpunit/phpunit": "~9.0",
|
||||
"symplify/easy-coding-standard": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"JeroenG\\Explorer\\ExplorerServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JeroenG\\Explorer\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"EUPL-1.2"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jeroen",
|
||||
"email": "jeroengjeroeng@gmail.com",
|
||||
"homepage": "https://jeroeng.dev"
|
||||
}
|
||||
],
|
||||
"description": "Next-gen Elasticsearch driver for Laravel Scout.",
|
||||
"homepage": "https://jeroen-g.github.io/Explorer/",
|
||||
"keywords": [
|
||||
"elastic",
|
||||
"elasticsearch",
|
||||
"explorer",
|
||||
"laravel",
|
||||
"scout",
|
||||
"search"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Jeroen-G/Explorer/issues",
|
||||
"source": "https://github.com/Jeroen-G/Explorer/tree/2.5.1"
|
||||
},
|
||||
"time": "2022-02-09T18:56:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-diactoros",
|
||||
"version": "2.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laminas/laminas-diactoros.git",
|
||||
"reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199",
|
||||
"reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.3 || ~8.0.0 || ~8.1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpspec/prophecy": "<1.9.0",
|
||||
"zendframework/zend-diactoros": "*"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-factory-implementation": "1.0",
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-libxml": "*",
|
||||
"http-interop/http-factory-tests": "^0.8.0",
|
||||
"laminas/laminas-coding-standard": "~1.0.0",
|
||||
"php-http/psr7-integration-tests": "^1.1",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.1",
|
||||
"psalm/plugin-phpunit": "^0.14.0",
|
||||
"vimeo/psalm": "^4.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laminas": {
|
||||
"config-provider": "Laminas\\Diactoros\\ConfigProvider",
|
||||
"module": "Laminas\\Diactoros"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions/create_uploaded_file.php",
|
||||
"src/functions/marshal_headers_from_sapi.php",
|
||||
"src/functions/marshal_method_from_sapi.php",
|
||||
"src/functions/marshal_protocol_version_from_sapi.php",
|
||||
"src/functions/marshal_uri_from_sapi.php",
|
||||
"src/functions/normalize_server.php",
|
||||
"src/functions/normalize_uploaded_files.php",
|
||||
"src/functions/parse_cookie_header.php",
|
||||
"src/functions/create_uploaded_file.legacy.php",
|
||||
"src/functions/marshal_headers_from_sapi.legacy.php",
|
||||
"src/functions/marshal_method_from_sapi.legacy.php",
|
||||
"src/functions/marshal_protocol_version_from_sapi.legacy.php",
|
||||
"src/functions/marshal_uri_from_sapi.legacy.php",
|
||||
"src/functions/normalize_server.legacy.php",
|
||||
"src/functions/normalize_uploaded_files.legacy.php",
|
||||
"src/functions/parse_cookie_header.legacy.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Laminas\\Diactoros\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "PSR HTTP Message implementations",
|
||||
"homepage": "https://laminas.dev",
|
||||
"keywords": [
|
||||
"http",
|
||||
"laminas",
|
||||
"psr",
|
||||
"psr-17",
|
||||
"psr-7"
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://laminas.dev/chat",
|
||||
"docs": "https://docs.laminas.dev/laminas-diactoros/",
|
||||
"forum": "https://discourse.laminas.dev",
|
||||
"issues": "https://github.com/laminas/laminas-diactoros/issues",
|
||||
"rss": "https://github.com/laminas/laminas-diactoros/releases.atom",
|
||||
"source": "https://github.com/laminas/laminas-diactoros"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||
"type": "community_bridge"
|
||||
}
|
||||
],
|
||||
"time": "2021-09-22T03:54:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel-lang/lang",
|
||||
"version": "7.0.9",
|
||||
@@ -2009,6 +2383,87 @@
|
||||
},
|
||||
"time": "2022-01-18T15:51:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/octane",
|
||||
"version": "v1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/octane.git",
|
||||
"reference": "5862dec90e3c064bd7d4c31f3f3a20467baa4027"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/octane/zipball/5862dec90e3c064bd7d4c31f3f3a20467baa4027",
|
||||
"reference": "5862dec90e3c064bd7d4c31f3f3a20467baa4027",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"laminas/laminas-diactoros": "^2.5",
|
||||
"laravel/framework": "^8.77|^9.0",
|
||||
"laravel/serializable-closure": "^1.0",
|
||||
"php": "^8.0",
|
||||
"symfony/psr-http-message-bridge": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"mockery/mockery": "^1.4",
|
||||
"nunomaduro/collision": "^5.10|^6.0",
|
||||
"orchestra/testbench": "^6.16|^7.0",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"spiral/roadrunner": "^2.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/roadrunner-worker",
|
||||
"bin/swoole-server"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Octane\\OctaneServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Octane": "Laravel\\Octane\\Facades\\Octane"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Octane\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Supercharge your Laravel application's performance.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"octane",
|
||||
"roadrunner",
|
||||
"swoole"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/octane/issues",
|
||||
"source": "https://github.com/laravel/octane"
|
||||
},
|
||||
"time": "2022-01-12T15:35:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sanctum",
|
||||
"version": "v2.14.0",
|
||||
@@ -2073,6 +2528,84 @@
|
||||
},
|
||||
"time": "2022-01-12T15:07:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/scout",
|
||||
"version": "v9.4.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/scout.git",
|
||||
"reference": "ac962d2e052e90ca271e9bb1b30229ed870c4963"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/scout/zipball/ac962d2e052e90ca271e9bb1b30229ed870c4963",
|
||||
"reference": "ac962d2e052e90ca271e9bb1b30229ed870c4963",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"illuminate/bus": "^8.0|^9.0",
|
||||
"illuminate/contracts": "^8.0|^9.0",
|
||||
"illuminate/database": "^8.0|^9.0",
|
||||
"illuminate/http": "^8.0|^9.0",
|
||||
"illuminate/pagination": "^8.0|^9.0",
|
||||
"illuminate/queue": "^8.0|^9.0",
|
||||
"illuminate/support": "^8.0|^9.0",
|
||||
"php": "^7.3|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"meilisearch/meilisearch-php": "^0.19",
|
||||
"mockery/mockery": "^1.0",
|
||||
"orchestra/testbench": "^6.17|^7.0",
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"suggest": {
|
||||
"algolia/algoliasearch-client-php": "Required to use the Algolia engine (^2.2).",
|
||||
"meilisearch/meilisearch-php": "Required to use the MeiliSearch engine (^0.17)."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Scout\\ScoutServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Scout\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Scout provides a driver based solution to searching your Eloquent models.",
|
||||
"keywords": [
|
||||
"algolia",
|
||||
"laravel",
|
||||
"search"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/scout/issues",
|
||||
"source": "https://github.com/laravel/scout"
|
||||
},
|
||||
"time": "2022-02-22T16:20:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v1.0.5",
|
||||
@@ -3819,6 +4352,67 @@
|
||||
},
|
||||
"time": "2019-01-08T18:20:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-factory.git",
|
||||
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
||||
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"psr/http-message": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interfaces for PSR-7 HTTP message factories",
|
||||
"keywords": [
|
||||
"factory",
|
||||
"http",
|
||||
"message",
|
||||
"psr",
|
||||
"psr-17",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-factory/tree/master"
|
||||
},
|
||||
"time": "2019-04-30T12:38:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "1.0.1",
|
||||
@@ -4269,6 +4863,88 @@
|
||||
],
|
||||
"time": "2021-09-25T23:10:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/promise",
|
||||
"version": "v2.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/promise.git",
|
||||
"reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/234f8fd1023c9158e2314fa9d7d0e6a83db42910",
|
||||
"reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"React\\Promise\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"email": "jsorgalla@gmail.com",
|
||||
"homepage": "https://sorgalla.com/"
|
||||
},
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering",
|
||||
"homepage": "https://clue.engineering/"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"email": "reactphp@ceesjankiewiet.nl",
|
||||
"homepage": "https://wyrihaximus.net/"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"email": "cboden@gmail.com",
|
||||
"homepage": "https://cboden.dev/"
|
||||
}
|
||||
],
|
||||
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
|
||||
"keywords": [
|
||||
"promise",
|
||||
"promises"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/reactphp/promise/issues",
|
||||
"source": "https://github.com/reactphp/promise/tree/v2.9.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/WyriHaximus",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/clue",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-02-11T10:27:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "rhilip/bencode",
|
||||
"version": "v1.1.1",
|
||||
@@ -6070,6 +6746,100 @@
|
||||
],
|
||||
"time": "2021-12-27T21:01:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/psr-http-message-bridge",
|
||||
"version": "v2.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/psr-http-message-bridge.git",
|
||||
"reference": "22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34",
|
||||
"reference": "22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"psr/http-message": "^1.0",
|
||||
"symfony/http-foundation": "^4.4 || ^5.0 || ^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nyholm/psr7": "^1.1",
|
||||
"psr/log": "^1.1 || ^2 || ^3",
|
||||
"symfony/browser-kit": "^4.4 || ^5.0 || ^6.0",
|
||||
"symfony/config": "^4.4 || ^5.0 || ^6.0",
|
||||
"symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0",
|
||||
"symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0",
|
||||
"symfony/http-kernel": "^4.4 || ^5.0 || ^6.0",
|
||||
"symfony/phpunit-bridge": "^5.4@dev || ^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
|
||||
},
|
||||
"type": "symfony-bridge",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Bridge\\PsrHttpMessage\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "http://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "PSR HTTP message bridge",
|
||||
"homepage": "http://symfony.com",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-message",
|
||||
"psr-17",
|
||||
"psr-7"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/symfony/psr-http-message-bridge/issues",
|
||||
"source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-11-05T13:13:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v5.4.0",
|
||||
@@ -9438,13 +10208,14 @@
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^7.3|^8.0",
|
||||
"php": "^8.0",
|
||||
"ext-bcmath": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-bcmath": "*"
|
||||
"ext-redis": "*",
|
||||
"ext-xml": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.2.0"
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_DRIVER', 'file'),
|
||||
'default' => 'redis',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -75,7 +75,7 @@ return [
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'cache',
|
||||
'connection' => 'default',
|
||||
'lock_connection' => 'default',
|
||||
],
|
||||
|
||||
@@ -101,6 +101,6 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
|
||||
'prefix' => '',
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'hosts' => [
|
||||
[
|
||||
'host' => env('ELASTICSEARCH_HOST','localhost'),
|
||||
'port' => env('ELASTICSEARCH_PORT','9200'),
|
||||
'scheme' => env('ELASTICSEARCH_SCHEME','https'),
|
||||
'user' => env('ELASTICSEARCH_USER','elastic'),
|
||||
'pass' => env('ELASTICSEARCH_PASS',''),
|
||||
]
|
||||
],
|
||||
|
||||
'ssl_verification' => env('ELASTICSEARCH_SSL_VERIFICATION', ''),
|
||||
];
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
/*
|
||||
* There are different options for the connection. Since Explorer uses the Elasticsearch PHP SDK
|
||||
* under the hood, all the host configuration options of the SDK are applicable here. See
|
||||
* https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/configuration.html
|
||||
*/
|
||||
'connection' => [
|
||||
'host' => env('ELASTICSEARCH_HOST','localhost'),
|
||||
'port' => env('ELASTICSEARCH_PORT','9200'),
|
||||
'scheme' => env('ELASTICSEARCH_SCHEME','https'),
|
||||
'user' => env('ELASTICSEARCH_USER','elastic'),
|
||||
'pass' => env('ELASTICSEARCH_PASS',''),
|
||||
|
||||
],
|
||||
|
||||
/**
|
||||
* An index may be defined on an Eloquent model or inline below. A more in depth explanation
|
||||
* of the mapping possibilities can be found in the documentation of Explorer's repository.
|
||||
*/
|
||||
'indexes' => [
|
||||
\App\Models\Torrent::class,
|
||||
\App\Models\User::class,
|
||||
],
|
||||
|
||||
/**
|
||||
* You may opt to keep the old indices after the alias is pointed to a new index.
|
||||
* A model is only using index aliases if it implements the Aliased interface.
|
||||
*/
|
||||
'prune_old_aliases' => true,
|
||||
];
|
||||
@@ -24,4 +24,18 @@ return [
|
||||
'database' => nexus_env('REDIS_DB', 0),
|
||||
],
|
||||
|
||||
'elasticsearch' => [
|
||||
'hosts' => [
|
||||
[
|
||||
'host' => nexus_env('ELASTICSEARCH_HOST','localhost'),
|
||||
'port' => nexus_env('ELASTICSEARCH_PORT','9200'),
|
||||
'scheme' => nexus_env('ELASTICSEARCH_SCHEME','https'),
|
||||
'user' => nexus_env('ELASTICSEARCH_USER','elastic'),
|
||||
'pass' => nexus_env('ELASTICSEARCH_PASS',''),
|
||||
]
|
||||
],
|
||||
|
||||
'ssl_verification' => nexus_env('ELASTICSEARCH_SSL_VERIFICATION', ''),
|
||||
]
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
use Laravel\Octane\Contracts\OperationTerminated;
|
||||
use Laravel\Octane\Events\RequestHandled;
|
||||
use Laravel\Octane\Events\RequestReceived;
|
||||
use Laravel\Octane\Events\RequestTerminated;
|
||||
use Laravel\Octane\Events\TaskReceived;
|
||||
use Laravel\Octane\Events\TaskTerminated;
|
||||
use Laravel\Octane\Events\TickReceived;
|
||||
use Laravel\Octane\Events\TickTerminated;
|
||||
use Laravel\Octane\Events\WorkerErrorOccurred;
|
||||
use Laravel\Octane\Events\WorkerStarting;
|
||||
use Laravel\Octane\Events\WorkerStopping;
|
||||
use Laravel\Octane\Listeners\CollectGarbage;
|
||||
use Laravel\Octane\Listeners\DisconnectFromDatabases;
|
||||
use Laravel\Octane\Listeners\EnsureUploadedFilesAreValid;
|
||||
use Laravel\Octane\Listeners\EnsureUploadedFilesCanBeMoved;
|
||||
use Laravel\Octane\Listeners\FlushTemporaryContainerInstances;
|
||||
use Laravel\Octane\Listeners\ReportException;
|
||||
use Laravel\Octane\Listeners\StopWorkerIfNecessary;
|
||||
use Laravel\Octane\Octane;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Octane Server
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the default "server" that will be used by Octane
|
||||
| when starting, restarting, or stopping your server via the CLI. You
|
||||
| are free to change this to the supported server of your choosing.
|
||||
|
|
||||
| Supported: "roadrunner", "swoole"
|
||||
|
|
||||
*/
|
||||
|
||||
'server' => env('OCTANE_SERVER', 'swoole'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Force HTTPS
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When this configuration value is set to "true", Octane will inform the
|
||||
| framework that all absolute links must be generated using the HTTPS
|
||||
| protocol. Otherwise your links may be generated using plain HTTP.
|
||||
|
|
||||
*/
|
||||
|
||||
'https' => env('OCTANE_HTTPS', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Octane Listeners
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All of the event listeners for Octane's events are defined below. These
|
||||
| listeners are responsible for resetting your application's state for
|
||||
| the next request. You may even add your own listeners to the list.
|
||||
|
|
||||
*/
|
||||
|
||||
'listeners' => [
|
||||
WorkerStarting::class => [
|
||||
EnsureUploadedFilesAreValid::class,
|
||||
EnsureUploadedFilesCanBeMoved::class,
|
||||
],
|
||||
|
||||
RequestReceived::class => [
|
||||
...Octane::prepareApplicationForNextOperation(),
|
||||
...Octane::prepareApplicationForNextRequest(),
|
||||
\App\Listeners\ResetNexus::class,
|
||||
],
|
||||
|
||||
RequestHandled::class => [
|
||||
//
|
||||
],
|
||||
|
||||
RequestTerminated::class => [
|
||||
//
|
||||
],
|
||||
|
||||
TaskReceived::class => [
|
||||
...Octane::prepareApplicationForNextOperation(),
|
||||
//
|
||||
],
|
||||
|
||||
TaskTerminated::class => [
|
||||
//
|
||||
],
|
||||
|
||||
TickReceived::class => [
|
||||
...Octane::prepareApplicationForNextOperation(),
|
||||
//
|
||||
],
|
||||
|
||||
TickTerminated::class => [
|
||||
//
|
||||
],
|
||||
|
||||
OperationTerminated::class => [
|
||||
FlushTemporaryContainerInstances::class,
|
||||
// DisconnectFromDatabases::class,
|
||||
// CollectGarbage::class,
|
||||
],
|
||||
|
||||
WorkerErrorOccurred::class => [
|
||||
ReportException::class,
|
||||
StopWorkerIfNecessary::class,
|
||||
],
|
||||
|
||||
WorkerStopping::class => [
|
||||
//
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Warm / Flush Bindings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The bindings listed below will either be pre-warmed when a worker boots
|
||||
| or they will be flushed before every new request. Flushing a binding
|
||||
| will force the container to resolve that binding again when asked.
|
||||
|
|
||||
*/
|
||||
|
||||
'warm' => [
|
||||
...Octane::defaultServicesToWarm(),
|
||||
],
|
||||
|
||||
'flush' => [
|
||||
//
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Octane Cache Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| While using Swoole, you may leverage the Octane cache, which is powered
|
||||
| by a Swoole table. You may set the maximum number of rows as well as
|
||||
| the number of bytes per row using the configuration options below.
|
||||
|
|
||||
*/
|
||||
|
||||
'cache' => [
|
||||
'rows' => 1000,
|
||||
'bytes' => 10000,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Octane Swoole Tables
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| While using Swoole, you may define additional tables as required by the
|
||||
| application. These tables can be used to store data that needs to be
|
||||
| quickly accessed by other workers on the particular Swoole server.
|
||||
|
|
||||
*/
|
||||
|
||||
'tables' => [
|
||||
'example:1000' => [
|
||||
'name' => 'string:1000',
|
||||
'votes' => 'int',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| File Watching
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following list of files and directories will be watched when using
|
||||
| the --watch option offered by Octane. If any of the directories and
|
||||
| files are changed, Octane will automatically reload your workers.
|
||||
|
|
||||
*/
|
||||
|
||||
'watch' => [
|
||||
'app',
|
||||
'bootstrap',
|
||||
'config',
|
||||
'database',
|
||||
'public/**/*.php',
|
||||
'resources/**/*.php',
|
||||
'routes',
|
||||
'composer.lock',
|
||||
'.env',
|
||||
'include/**/*.php',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Garbage Collection Threshold
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When executing long-lived PHP scripts such as Octane, memory can build
|
||||
| up before being cleared by PHP. You can force Octane to run garbage
|
||||
| collection if your application consumes this amount of megabytes.
|
||||
|
|
||||
*/
|
||||
|
||||
'garbage' => 50,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maximum Execution Time
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following setting configures the maximum execution time for requests
|
||||
| being handled by Octane. You may set this value to 0 to indicate that
|
||||
| there isn't a specific time limit on Octane request execution time.
|
||||
|
|
||||
*/
|
||||
|
||||
'max_execution_time' => 30,
|
||||
|
||||
];
|
||||
+2
-2
@@ -13,7 +13,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'sync'),
|
||||
'default' => env('QUEUE_CONNECTION', 'redis'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -68,7 +68,7 @@ return [
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => 90,
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
'after_commit' => true,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Search Engine
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default search connection that gets used while
|
||||
| using Laravel Scout. This connection is used when syncing all models
|
||||
| to the search service. You should adjust this based on your needs.
|
||||
|
|
||||
| Supported: "algolia", "meilisearch", "database", "collection", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SCOUT_DRIVER', 'elastic'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Index Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify a prefix that will be applied to all search index
|
||||
| names used by Scout. This prefix may be useful if you have multiple
|
||||
| "tenants" or applications sharing the same search infrastructure.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('SCOUT_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Data Syncing
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to control if the operations that sync your data
|
||||
| with your search engines are queued. When this is set to "true" then
|
||||
| all automatic data syncing will get queued for better performance.
|
||||
|
|
||||
*/
|
||||
|
||||
'queue' => env('SCOUT_QUEUE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Transactions
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This configuration option determines if your data will only be synced
|
||||
| with your search indexes after every open database transaction has
|
||||
| been committed, thus preventing any discarded data from syncing.
|
||||
|
|
||||
*/
|
||||
|
||||
'after_commit' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Chunk Sizes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options allow you to control the maximum chunk size when you are
|
||||
| mass importing data into the search engine. This allows you to fine
|
||||
| tune each of these chunk sizes based on the power of the servers.
|
||||
|
|
||||
*/
|
||||
|
||||
'chunk' => [
|
||||
'searchable' => 500,
|
||||
'unsearchable' => 500,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Soft Deletes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows to control whether to keep soft deleted records in
|
||||
| the search indexes. Maintaining soft deleted records can be useful
|
||||
| if your application still needs to search for the records later.
|
||||
|
|
||||
*/
|
||||
|
||||
'soft_delete' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Identify User
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to control whether to notify the search engine
|
||||
| of the user performing the search. This is sometimes useful if the
|
||||
| engine supports any analytics based on this application's users.
|
||||
|
|
||||
| Supported engines: "algolia"
|
||||
|
|
||||
*/
|
||||
|
||||
'identify' => env('SCOUT_IDENTIFY', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Algolia Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure your Algolia settings. Algolia is a cloud hosted
|
||||
| search engine which works great with Scout out of the box. Just plug
|
||||
| in your application ID and admin API key to get started searching.
|
||||
|
|
||||
*/
|
||||
|
||||
'algolia' => [
|
||||
'id' => env('ALGOLIA_APP_ID', ''),
|
||||
'secret' => env('ALGOLIA_SECRET', ''),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| MeiliSearch Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure your MeiliSearch settings. MeiliSearch is an open
|
||||
| source search engine with minimal configuration. Below, you can state
|
||||
| the host and key information for your own MeiliSearch installation.
|
||||
|
|
||||
| See: https://docs.meilisearch.com/guides/advanced_guides/configuration.html
|
||||
|
|
||||
*/
|
||||
|
||||
'meilisearch' => [
|
||||
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
|
||||
'key' => env('MEILISEARCH_KEY', null),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -19,9 +19,8 @@ class CreateThanksTable extends Migration
|
||||
Schema::create('thanks', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('torrentid')->default(0);
|
||||
$table->unsignedMediumInteger('userid')->default(0);
|
||||
$table->unique(['torrentid', 'id'], 'torrentid_id');
|
||||
$table->index(['torrentid', 'userid'], 'torrentid_userid');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index();
|
||||
$table->unique(['torrentid', 'userid']);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIndexToCheatersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('cheaters', function (Blueprint $table) {
|
||||
$table->index('torrentid');
|
||||
$table->index('userid');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('cheaters', function (Blueprint $table) {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddStatusToUserMedalsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('user_medals', function (Blueprint $table) {
|
||||
$table->integer('status')->default(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('user_medals', function (Blueprint $table) {
|
||||
$table->dropColumn('status');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddMarginPaddingToTagsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tags', function (Blueprint $table) {
|
||||
$table->string('padding')->default(0);
|
||||
$table->string('margin')->default('0 4px 0 0');
|
||||
$table->string('border_radius')->default(0);
|
||||
$table->string('font_size')->default('12px');
|
||||
$table->string('font_color')->default('#ffffff');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tags', function (Blueprint $table) {
|
||||
$table->dropColumn(['padding', 'margin', 'font_size', 'font_color', 'border_radius']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
define('NEXUS_START', microtime(true));
|
||||
define('IN_NEXUS', true);
|
||||
$rootpath = dirname(__DIR__) . '/';
|
||||
set_include_path(get_include_path() . PATH_SEPARATOR . $rootpath);
|
||||
require $rootpath . 'include/globalfunctions.php';
|
||||
require $rootpath . 'include/functions.php';
|
||||
require $rootpath . 'include/core.php';
|
||||
require $rootpath . 'classes/class_advertisement.php';
|
||||
require $rootpath . 'classes/class_attendance.php';
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<?php
|
||||
define('NEXUS_START', microtime(true));
|
||||
define('IN_NEXUS', true);
|
||||
defined('IN_NEXUS') || define('IN_NEXUS', true);
|
||||
defined('IN_TRACKER') || define('IN_TRACKER', true);
|
||||
$rootpath= dirname(__DIR__) . '/';
|
||||
require $rootpath . 'include/core.php';
|
||||
require $rootpath . 'include/functions_announce.php';
|
||||
require_once $rootpath . 'include/constants.php';
|
||||
require_once $rootpath . 'include/globalfunctions.php';
|
||||
require_once $rootpath . 'include/functions_announce.php';
|
||||
require_once $rootpath . 'vendor/autoload.php';
|
||||
\Nexus\Nexus::boot();
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ die('Hacking attempt!');
|
||||
|
||||
function printProgress($msg) {
|
||||
$br = php_sapi_name() == 'cli' ? "\n" : "<br />";
|
||||
echo sprintf("[%s] [%s] %s ... done!%s", date('Y-m-d H:i:s'), REQUEST_ID, $msg, $br);
|
||||
echo sprintf("[%s] [%s] %s ... done!%s", date('Y-m-d H:i:s'), nexus()->getRequestId(), $msg, $br);
|
||||
}
|
||||
|
||||
function torrent_promotion_expire($days, $type = 2, $targettype = 1){
|
||||
|
||||
+31
-24
@@ -7,31 +7,38 @@ defined('NEXUSPHPURL') || define("NEXUSPHPURL","https://nexusphp.org");
|
||||
defined('NEXUSWIKIURL') || define("NEXUSWIKIURL","https://doc.nexusphp.org");
|
||||
defined('VERSION') || define("VERSION","Powered by <a href=\"aboutnexus.php\">".PROJECTNAME."</a>");
|
||||
defined('THISTRACKER') || define("THISTRACKER","General");
|
||||
$showversion = " - Powered by ".PROJECTNAME;
|
||||
defined('ROOT_PATH') || define('ROOT_PATH', dirname(__DIR__) . '/');
|
||||
defined('CURRENT_SCRIPT') || define('CURRENT_SCRIPT', strstr(basename($_SERVER['SCRIPT_FILENAME']), '.', true));
|
||||
defined('IS_ANNOUNCE') || define('IS_ANNOUNCE', CURRENT_SCRIPT == 'announce');
|
||||
|
||||
defined('PLATFORM_ADMIN') || define('PLATFORM_ADMIN', 'admin');
|
||||
defined('PLATFORM_USER') || define('PLATFORM_USER', 'user');
|
||||
defined('PLATFORMS') || define('PLATFORMS', [PLATFORM_ADMIN, PLATFORM_USER]);
|
||||
defined('CURRENT_PLATFORM') || define('CURRENT_PLATFORM', $_SERVER['HTTP_PLATFORM'] ?? '');
|
||||
defined('IS_PLATFORM_ADMIN') || define('IS_PLATFORM_ADMIN', CURRENT_PLATFORM == PLATFORM_ADMIN);
|
||||
defined('IS_PLATFORM_USER') || define('IS_PLATFORM_USER', CURRENT_PLATFORM == PLATFORM_USER);
|
||||
|
||||
|
||||
//define the REQUEST_ID
|
||||
if (!defined('REQUEST_ID')) {
|
||||
if (!empty($_SERVER['HTTP_X_REQUEST_ID'])) {
|
||||
$requestId = $_SERVER['HTTP_X_REQUEST_ID'];
|
||||
} elseif (!empty($_SERVER['REQUEST_ID'])) {
|
||||
$requestId = $_SERVER['REQUEST_ID'];
|
||||
if (!defined('RUNNING_IN_OCTANE')) {
|
||||
if (!empty($_SERVER['PWD']) && str_contains($_SERVER['PWD'], 'vendor/laravel/octane/bin')) {
|
||||
define('RUNNING_IN_OCTANE', true);
|
||||
} else {
|
||||
$prefix = ($_SERVER['SCRIPT_FILENAME'] ?? '') . implode('', $_SERVER['argv'] ?? []);
|
||||
$prefix = substr(md5($prefix), 0, 4);
|
||||
// 4 + 23 = 27 characters, after replace '.', 26
|
||||
$requestId = str_replace('.', '', uniqid($prefix, true));
|
||||
$requestId .= bin2hex(random_bytes(3));
|
||||
define('RUNNING_IN_OCTANE', false);
|
||||
}
|
||||
define('REQUEST_ID', $requestId);
|
||||
}
|
||||
|
||||
//defined('CURRENT_SCRIPT') || define('CURRENT_SCRIPT', strstr(basename($_SERVER['SCRIPT_FILENAME']), '.', true));
|
||||
//defined('IS_ANNOUNCE') || define('IS_ANNOUNCE', CURRENT_SCRIPT == 'announce');
|
||||
//
|
||||
//defined('PLATFORM_ADMIN') || define('PLATFORM_ADMIN', 'admin');
|
||||
//defined('PLATFORM_USER') || define('PLATFORM_USER', 'user');
|
||||
//defined('PLATFORMS') || define('PLATFORMS', [PLATFORM_ADMIN, PLATFORM_USER]);
|
||||
//defined('CURRENT_PLATFORM') || define('CURRENT_PLATFORM', $_SERVER['HTTP_PLATFORM'] ?? '');
|
||||
//defined('IS_PLATFORM_ADMIN') || define('IS_PLATFORM_ADMIN', CURRENT_PLATFORM == PLATFORM_ADMIN);
|
||||
//defined('IS_PLATFORM_USER') || define('IS_PLATFORM_USER', CURRENT_PLATFORM == PLATFORM_USER);
|
||||
//
|
||||
//
|
||||
////define the REQUEST_ID
|
||||
//if (!defined('REQUEST_ID')) {
|
||||
// if (!empty($_SERVER['HTTP_X_REQUEST_ID'])) {
|
||||
// $requestId = $_SERVER['HTTP_X_REQUEST_ID'];
|
||||
// } elseif (!empty($_SERVER['REQUEST_ID'])) {
|
||||
// $requestId = $_SERVER['REQUEST_ID'];
|
||||
// } else {
|
||||
// $prefix = ($_SERVER['SCRIPT_FILENAME'] ?? '') . implode('', $_SERVER['argv'] ?? []);
|
||||
// $prefix = substr(md5($prefix), 0, 4);
|
||||
// // 4 + 23 = 27 characters, after replace '.', 26
|
||||
// $requestId = str_replace('.', '', uniqid($prefix, true));
|
||||
// $requestId .= bin2hex(random_bytes(3));
|
||||
// }
|
||||
// define('REQUEST_ID', $requestId);
|
||||
//}
|
||||
|
||||
+8
-7
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
require __DIR__ . '/constants.php';
|
||||
require $rootpath . 'vendor/autoload.php';
|
||||
require_once __DIR__ . '/constants.php';
|
||||
require_once $rootpath . 'vendor/autoload.php';
|
||||
\Nexus\Nexus::boot();
|
||||
if (!file_exists($rootpath . '.env')) {
|
||||
$installScriptRelativePath = 'install/install.php';
|
||||
$installScriptFile = $rootpath . "public/$installScriptRelativePath";
|
||||
@@ -11,18 +12,18 @@ if (!file_exists($rootpath . '.env')) {
|
||||
require $rootpath . 'nexus/Database/helpers.php';
|
||||
require $rootpath . 'classes/class_cache_redis.php';
|
||||
require $rootpath . 'include/eloquent.php';
|
||||
require $rootpath . 'include/config.php';
|
||||
|
||||
ini_set('date.timezone', nexus_config('nexus.timezone'));
|
||||
ini_set('error_reporting', E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
if (!isRunningInConsole() && !in_array(CURRENT_SCRIPT, ['announce', 'scrape', 'torrentrss', 'download'])) {
|
||||
$Cache = new class_cache_redis(); //Load the caching class
|
||||
$Cache->setLanguageFolderArray(get_langfolder_list());
|
||||
require $rootpath . 'include/config.php';
|
||||
if (!in_array(nexus()->getScript(), ['announce', 'scrape', 'torrentrss', 'download'])) {
|
||||
require $rootpath . get_langfile_path("functions.php");
|
||||
checkGuestVisit();
|
||||
}
|
||||
$Cache = new class_cache_redis(); //Load the caching class
|
||||
$Cache->setLanguageFolderArray(get_langfolder_list());
|
||||
|
||||
define('TIMENOW', time());
|
||||
$USERUPDATESET = array();
|
||||
$query_name=array();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user