API: torrents upload/list

This commit is contained in:
xiaomlove
2025-04-17 01:39:40 +07:00
parent 0d3a46231d
commit 2b029eba10
72 changed files with 2332 additions and 507 deletions

View File

@@ -3,16 +3,65 @@
namespace App\Auth;
use App\Enums\Permission\PermissionEnum;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class Permission
{
public static function canUploadToSpecialSection(): bool
{
return user_can(PermissionEnum::UPLOAD_TO_SPECIAL_SECTION->value);
return self::canUploadToNormalSection() && user_can(PermissionEnum::UPLOAD_TO_SPECIAL_SECTION->value);
}
public static function canUploadToNormalSection(): bool
{
$user = Auth::user();
return $user->uploadpos == "yes" && user_can(PermissionEnum::UPLOAD->value);
}
public static function canViewSpecialSection(): bool
{
return user_can(PermissionEnum::TORRENT_VIEW_SPECIAL->value);
}
public static function canBeAnonymous(): bool
{
return user_can(PermissionEnum::BE_ANONYMOUS->value);
}
public static function canSetTorrentHitAndRun(): bool
{
return user_can(PermissionEnum::TORRENT_SET_HR->value);
}
public static function canSetTorrentPrice(): bool
{
return user_can(PermissionEnum::TORRENT_SET_PRICE->value);
}
public static function canSetTorrentPosState(): bool
{
return user_can(PermissionEnum::TORRENT_SET_STICKY->value);
}
public static function canTorrentApprovalAllowAutomatic(): bool
{
return user_can(PermissionEnum::TORRENT_APPROVAL_ALLOW_AUTOMATIC->value);
}
public static function canManageTorrent(): bool
{
return user_can(PermissionEnum::TORRENT_MANAGE->value);
}
public static function canPickTorrent(): bool
{
$user = Auth::user();
return $user->picker == "yes" && self::canManageTorrent() || $user->class >= User::CLASS_SYSOP;
}
public static function canSetTorrentSpecialTag(): bool
{
return user_can(PermissionEnum::TORRENT_SET_SPECIAL_TAG->value);
}
}

View File

@@ -2,18 +2,7 @@
namespace App\Console\Commands;
use App\Events\NewsCreated;
use App\Events\TorrentCreated;
use App\Events\TorrentDeleted;
use App\Events\TorrentUpdated;
use App\Events\UserCreated;
use App\Events\UserDestroyed;
use App\Events\UserDisabled;
use App\Events\UserEnabled;
use App\Events\UserUpdated;
use App\Models\News;
use App\Models\Torrent;
use App\Models\User;
use App\Enums\ModelEventEnum;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Nexus\Database\NexusDB;
@@ -35,20 +24,6 @@ class FireEvent extends Command
*/
protected $description = 'Fire an event, options: --name, --idKey --idKeyOld';
protected array $eventMaps = [
"torrent_created" => ['event' => TorrentCreated::class, 'model' => Torrent::class],
"torrent_updated" => ['event' => TorrentUpdated::class, 'model' => Torrent::class],
"torrent_deleted" => ['event' => TorrentDeleted::class, 'model' => Torrent::class],
"user_created" => ['event' => UserCreated::class, 'model' => User::class],
"user_destroyed" => ['event' => UserDestroyed::class, 'model' => User::class],
"user_disabled" => ['event' => UserDisabled::class, 'model' => User::class],
"user_enabled" => ['event' => UserEnabled::class, 'model' => User::class],
"user_updated" => ['event' => UserUpdated::class, 'model' => User::class],
"news_created" => ['event' => NewsCreated::class, 'model' => News::class],
];
/**
* Execute the console command.
*
@@ -60,8 +35,8 @@ class FireEvent extends Command
$idKey = $this->option('idKey');
$idKeyOld = $this->option('idKeyOld');
$log = "FireEvent, name: $name, idKey: $idKey, idKeyOld: $idKeyOld";
if (isset($this->eventMaps[$name])) {
$eventName = $this->eventMaps[$name]['event'];
if (isset(ModelEventEnum::$eventMaps[$name])) {
$eventName = ModelEventEnum::$eventMaps[$name]['event'];
$model = unserialize(NexusDB::cache_get($idKey));
if ($model instanceof Model) {
$params = [$model];

View File

@@ -3,6 +3,9 @@
namespace App\Console\Commands;
use App\Models\PersonalAccessToken;
use App\Models\Torrent;
use App\Models\User;
use App\Repositories\UploadRepository;
use Illuminate\Console\Command;
use NexusPlugin\Menu\Filament\MenuItemResource\Pages\ManageMenuItems;
use NexusPlugin\Menu\MenuRepository;
@@ -15,6 +18,7 @@ use NexusPlugin\StickyPromotion\Models\StickyPromotionParticipator;
use NexusPlugin\Tracker\TrackerRepository;
use NexusPlugin\Work\Models\RoleWork;
use NexusPlugin\Work\WorkRepository;
use Stichoza\GoogleTranslate\GoogleTranslate;
class Test extends Command
{
@@ -49,8 +53,12 @@ class Test extends Command
*/
public function handle()
{
$result = getLogFile();
dd($result);
$a = ['acb' => 2];
if ($a = isset($a['ab'])) {
$this->info("isset ab = true");
}
dd($a);
}
}

View File

@@ -0,0 +1,214 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Stichoza\GoogleTranslate\GoogleTranslate;
class TranslateLang extends Command
{
protected $signature = 'lang:translate {source} {target}
{filename? : Optional file to translate (php file or json)}
{--dry-run : Only print translations without writing files}
{--ignore= : Comma-separated keys to ignore (e.g. key1,key2)}
{--json : Also translate JSON language files}';
protected $description = 'Translate Laravel language files (PHP or JSON) using Google Translate';
protected $tr;
protected $ignoreKeys = [];
protected $cache = [];
protected $cachePath;
public function handle()
{
$source = $this->argument('source');
$target = $this->argument('target');
$filename = $this->argument('filename');
$this->ignoreKeys = array_filter(explode(',', $this->option('ignore')));
$this->cachePath = storage_path("framework/lang-translate-cache.{$source}.{$target}.json");
$this->loadCache();
$this->tr = new GoogleTranslate();
$this->tr->setSource($source);
$this->tr->setTarget($target);
$langPath = resource_path('lang');
//谷歌使用的是 - 本地使用 _
$source = str_replace("-", "_", $source);
$target = str_replace("-", "_", $target);
$dir = "{$langPath}/{$source}";
if ($filename) {
// 👇 指定具体文件翻译
$this->translateSpecificFile($filename, $source, $target);
} else {
// 👇 未指定时,用户确认是否翻译所有文件
$answer = $this->ask("你没有指定文件名,是否翻译目录 $dir 下所有语言文件?请输入 yes 确认");
if (strtolower($answer) === 'yes') {
foreach (File::files("{$langPath}/{$source}") as $file) {
if ($file->getExtension() === 'php') {
$this->translatePhpFile($file->getPathname(), $source, $target);
}
}
if ($this->option('json')) {
$jsonFile = "{$langPath}/{$source}.json";
if (file_exists($jsonFile)) {
$this->translateJsonFile($jsonFile, $source, $target);
}
}
} else {
// 👇 用户的输入被当作 filename 处理
$this->translateSpecificFile($answer, $source, $target);
}
}
$this->saveCache();
$this->info("🎉 $source => $target Done !");
return 0;
}
protected function translatePhpFile($sourceFile, $sourceLang, $targetLang)
{
$relativePath = basename($sourceFile);
$targetFile = resource_path("lang/{$targetLang}/{$relativePath}");
$data = require $sourceFile;
$translated = $this->translateArray($data);
$export = var_export($translated, true);
if ($this->option('dry-run')) {
$this->line("🔍 Would write to: $targetFile\n$export\n");
} else {
if (!file_exists(dirname($targetFile))) {
mkdir(dirname($targetFile), 0755, true);
}
file_put_contents($targetFile, "<?php\n\nreturn $export;\n");
$this->info("✅ Wrote translated file: $targetFile");
}
}
protected function translateJsonFile($jsonFile, $sourceLang, $targetLang)
{
$targetFile = resource_path("lang/{$targetLang}.json");
$content = json_decode(file_get_contents($jsonFile), true);
$translated = [];
foreach ($content as $key => $value) {
if (in_array($key, $this->ignoreKeys)) {
$translated[$key] = $value;
continue;
}
$translated[$key] = $this->translateText($value);
}
$pretty = $this->json_encode_pretty($translated);
if ($this->option('dry-run')) {
$this->line("🔍 Would write to: $targetFile\n$pretty\n");
} else {
file_put_contents($targetFile, $pretty);
$this->info("✅ Wrote translated JSON: $targetFile");
}
}
protected function translateArray(array $data)
{
$result = [];
foreach ($data as $key => $value) {
if (in_array($key, $this->ignoreKeys)) {
$result[$key] = $value;
continue;
}
if (is_array($value)) {
$result[$key] = $this->translateArray($value);
} else {
$result[$key] = $this->translateText($value);
}
}
return $result;
}
protected function translateText(string $text): string
{
if (isset($this->cache[$text])) {
$this->line("⚡️ Cached: $text => {$this->cache[$text]}");
return $this->cache[$text];
}
try {
$translated = $this->tr->translate($text);
$this->cache[$text] = $translated;
$this->line("🌍 $text => $translated");
return $translated;
} catch (\Exception $e) {
$this->warn("❌ Failed to translate: $text");
return $text;
}
}
protected function translateSpecificFile($filename, $source, $target)
{
$langPath = resource_path("lang");
if (str_ends_with($filename, '.json')) {
$jsonPath = "{$langPath}/{$filename}";
if (!file_exists($jsonPath)) {
$jsonPath = "{$langPath}/{$source}.json";
}
if (file_exists($jsonPath)) {
$this->translateJsonFile($jsonPath, $source, $target);
} else {
$this->error("❌ JSON 文件未找到:$filename");
}
} else {
$phpPath = "{$langPath}/{$source}/$filename";
if (!str_ends_with($filename, '.php')) {
$phpPath .= '.php';
}
if (file_exists($phpPath)) {
$this->translatePhpFile($phpPath, $source, $target);
} else {
$this->error("❌ PHP 语言文件未找到:$filename");
}
}
}
protected function loadCache()
{
if (file_exists($this->cachePath)) {
$this->cache = json_decode(file_get_contents($this->cachePath), true);
}
}
protected function saveCache()
{
if (!$this->option('dry-run')) {
file_put_contents($this->cachePath, json_encode($this->cache, JSON_UNESCAPED_UNICODE));
}
}
protected function json_encode_pretty($data, int $indentSize = 4): string
{
// 默认格式化PHP 默认是 2 空格)
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// 自定义缩进
$indentChar = str_repeat(' ', $indentSize);
// 将默认的 2 空格缩进替换为自定义缩进
$formatted = preg_replace_callback('/^( +)/m', function ($matches) use ($indentChar) {
$level = strlen($matches[1]) / 2;
return str_repeat($indentChar, (int)$level);
}, $json);
return $formatted;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Enums;
use App\Events\NewsCreated;
use App\Events\TorrentCreated;
use App\Events\TorrentDeleted;
use App\Events\TorrentUpdated;
use App\Events\UserCreated;
use App\Events\UserDeleted;
use App\Events\UserDisabled;
use App\Events\UserEnabled;
use App\Events\UserUpdated;
use App\Models\News;
use App\Models\Torrent;
use App\Models\User;
final class ModelEventEnum {
const TORRENT_CREATED = 'torrent_created';
const TORRENT_UPDATED = 'torrent_updated';
const TORRENT_DELETED = 'torrent_deleted';
const USER_CREATED = 'user_created';
const USER_UPDATED = 'user_updated';
const USER_DELETED = 'user_deleted';
const USER_ENABLED = 'user_enabled';
const USER_DISABLED = 'user_disabled';
const NEWS_CREATED = 'news_created';
public static array $eventMaps = [
self::TORRENT_CREATED => ['event' => TorrentCreated::class, 'model' => Torrent::class],
self::TORRENT_UPDATED => ['event' => TorrentUpdated::class, 'model' => Torrent::class],
self::TORRENT_DELETED => ['event' => TorrentDeleted::class, 'model' => Torrent::class],
self::USER_CREATED => ['event' => UserCreated::class, 'model' => User::class],
self::USER_UPDATED => ['event' => UserUpdated::class, 'model' => User::class],
self::USER_DELETED => ['event' => UserDeleted::class, 'model' => User::class],
self::USER_ENABLED => ['event' => UserEnabled::class, 'model' => User::class],
self::USER_DISABLED => ['event' => UserDisabled::class, 'model' => User::class],
self::NEWS_CREATED => ['event' => NewsCreated::class, 'model' => News::class],
];
}

View File

@@ -7,5 +7,15 @@ enum PermissionEnum: string {
case BE_ANONYMOUS = 'beanonymous';
case TORRENT_LIST = 'torrent:list';
case TORRENT_VIEW = 'torrent:view';
case TORRENT_VIEW_SPECIAL = 'view_special_torrent';
case TORRENT_SET_HR = 'torrent_hr';
case TORRENT_SET_PRICE = 'torrent-set-price';
case TORRENT_SET_STICKY = 'torrentsticky';
case TORRENT_MANAGE = 'torrentmanage';
case TORRENT_APPROVAL_ALLOW_AUTOMATIC = 'torrent-approval-allow-automatic';
case TORRENT_SET_SPECIAL_TAG = 'torrent-set-special-tag';
case UPLOAD = 'upload';
case USER_VIEW = "user:view";
}

View File

@@ -11,7 +11,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserDestroyed
class UserDeleted
{
use Dispatchable, InteractsWithSockets, SerializesModels;

View File

@@ -101,6 +101,9 @@ class Handler extends ExceptionHandler
if (config('app.debug')) {
$data['trace'] = $trace;
}
if ($e instanceof \Error || $e instanceof \ErrorException) {
do_log(sprintf(get_class($e) . ": %s, trace: %s", $msg, $e->getTraceAsString()), "error");
}
return new JsonResponse(
fail($msg, $data),
$httpStatusCode,

View File

@@ -15,6 +15,7 @@ use Filament\Tables\Table;
use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Validation\Rule;
class SectionResource extends Resource
{
@@ -59,8 +60,13 @@ class SectionResource extends Resource
->schema([
Forms\Components\TextInput::make('name')
->label(__('label.search_box.name'))
->rules('alpha_dash')
->required()
->rules(function ($record) {
return [
'required',
'alpha_dash:ascii',
Rule::unique('searchbox', 'name')->ignore($record?->id)
];
})
,
Forms\Components\TextInput::make('catsperrow')
->label(__('label.search_box.catsperrow'))

View File

@@ -4,25 +4,34 @@ namespace App\Http\Controllers;
use App\Exceptions\InsufficientPermissionException;
use App\Models\Setting;
use App\Utils\ApiQueryBuilder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\MissingValue;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
/**
* @OA\Info(
* title="NexusPHP API",
* version="1.0"
* )
*/
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function success($data, $msg = null)
protected ?array $extraFields = null;
protected ?array $extraSettingNames = null;
/**
* 返回成功信息,这里是旧方法,大部分情况下 $data 已经是 JsonResource
* 但很多地方有使用,历史原因保留不动,尽量使用 successJsonResource
*
* @param $data
* @param $msg
* @return array
*/
public function success($data, $msg = null): array
{
if (is_null($msg)) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
@@ -32,6 +41,34 @@ class Controller extends BaseController
return success($msg, $data);
}
/**
* 返回成功信息,对于不是 JsonResource 的数据,进行包装。返回的数据在 data.data 中
*
* @param $data
* @param $msg
* @return array
*/
public function successJsonResource($data, $msg = null): array
{
if (is_null($msg)) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$caller = $backtrace[1];
$msg = $this->getReturnMsg($caller);
}
if ($data instanceof JsonResource) {
return $this->success($data, $msg);
}
$resource = new JsonResource($data);
return $this->success($resource, $msg);
}
/**
* 返回失败信息,目前对于失败信息不需要包装
*
* @param $data
* @param $msg
* @return array
*/
public function fail($data, $msg = null)
{
if (is_null($msg)) {
@@ -78,6 +115,31 @@ class Controller extends BaseController
return [$perPage, ['*'], 'page', $page];
}
protected function hasExtraField($field): bool
{
if ($this->extraFields === null) {
$extraFieldsStr = request()->input("extra_fields", '');
$this->extraFields = explode(',', $extraFieldsStr);
}
do_log(sprintf("field: %s, extraFields: %s", $field, json_encode($this->extraFields)));
return in_array($field, $this->extraFields);
}
protected function appendExtraSettings(array &$additional, array $names): void
{
if ($this->extraSettingNames === null) {
$extraSettingStr = request()->input("extra_settings", '');
$this->extraSettingNames = explode(',', $extraSettingStr);
}
$results = [];
foreach ($names as $name) {
if (in_array($name, $this->extraSettingNames)) {
$results[$name] = get_setting($name);
}
}
if (!empty($results)) {
$additional['extra_settings'] = $results;
}
}
}

View File

@@ -27,14 +27,12 @@ class TokenController extends Controller
$user = Auth::user();
$count = $user->tokens()->count();
if ($count >= 5) {
throw new NexusException("Token limit exceeded");
throw new NexusException(nexus_trans("token.maximum_allow_number_reached"));
}
$newAccessToken = $user->createToken($request->name, $request->permissions);
PersonalAccessTokenPlain::query()->create([
'access_token_id' => $newAccessToken->accessToken->getKey(),
'plain_text_token' => $newAccessToken->plainTextToken,
]);
return $this->success(true);
$tokenText = $newAccessToken->plainTextToken;
$msg = nexus_trans("token.create_success_tip", ['token' => $tokenText]);
return $this->successJsonResource(['token' => $tokenText], $msg);
} catch (\Exception $exception) {
return $this->fail(false, $exception->getMessage());
}
@@ -47,11 +45,7 @@ class TokenController extends Controller
'id' => 'required|integer',
]);
$user = Auth::user();
$token = $user->tokens()->where("id", $request->id)->first();
if ($token) {
PersonalAccessTokenPlain::query()->where("access_token_id", $token->id)->delete();
$token->delete();
}
$user->tokens()->where("id", $request->id)->delete();
return $this->success(true);
} catch (\Exception $exception) {
return $this->fail(false, $exception->getMessage());

View File

@@ -6,6 +6,7 @@ use App\Models\PluginStore;
use App\Repositories\ToolRepository;
use App\Repositories\UploadRepository;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\Process\Process;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -31,9 +32,9 @@ class ToolController extends Controller
public function test(Request $request)
{
$rep = new UploadRepository();
$result = $rep->listSections();
return $result;
$result = ['id' => 1];
$resource = new JsonResource($result);
return $this->success($resource);
}
}

View File

@@ -11,7 +11,10 @@ use App\Models\TorrentDenyReason;
use App\Models\TorrentOperationLog;
use App\Models\User;
use App\Repositories\TorrentRepository;
use App\Repositories\UploadRepository;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
class TorrentController extends Controller
@@ -23,17 +26,10 @@ class TorrentController extends Controller
$this->repository = $repository;
}
public function index(Request $request)
public function index(Request $request, string $section = null)
{
$params = $request->all();
$params['visible'] = Torrent::VISIBLE_YES;
$params['category_mode'] = Setting::get('main.browsecat');
$result = $this->repository->getList($params, Auth::user());
$result = $this->repository->getList($request, Auth::user(), $section);
$resource = TorrentResource::collection($result);
// $resource->additional([
// 'page_title' => nexus_trans('torrent.index.page_title'),
// ]);
return $this->success($resource);
}
@@ -41,18 +37,21 @@ class TorrentController extends Controller
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return array
*/
public function store(Request $request)
{
//
$uploadRep = new UploadRepository();
$newTorrent = $uploadRep->upload($request);
$resource = new JsonResource(["id" => $newTorrent->id]);
return $this->success($resource);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
* @return array
*/
public function show($id)
{
@@ -60,17 +59,15 @@ class TorrentController extends Controller
* @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,
]);
$torrent = $this->repository->getDetail($id, $user);
$resource = new TorrentResource($torrent);
$additional = [];
if ($this->hasExtraField('bonus_reward_values')) {
$additional['bonus_reward_values'] = Torrent::BONUS_REWARD_VALUES;
}
$extraSettingsNames = ['torrent.claim_torrent_user_counts_up_limit'];
$this->appendExtraSettings($additional, $extraSettingsNames);
$resource->additional($additional);
return $this->success($resource);
}

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Http\Resources\SearchBoxResource;
use App\Http\Resources\TorrentResource;
use App\Repositories\SearchBoxRepository;
use App\Repositories\UploadRepository;
use Illuminate\Http\Request;
@@ -26,10 +27,4 @@ class UploadController extends Controller
return $this->success($resource);
}
public function upload(Request $request)
{
$user = $request->user();
return $this->success("OK");
}
}

View File

@@ -15,6 +15,7 @@ use App\Repositories\UserRepository;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
use League\OAuth2\Server\Grant\AuthCodeGrant;
class UserController extends Controller
{
@@ -64,10 +65,15 @@ class UserController extends Controller
* @param int $id
* @return array
*/
public function show($id)
public function show($id = null)
{
$result = $this->repository->getDetail($id);
return $this->success($result);
$currentUser = Auth::user();
if ($id === null) {
$id = $currentUser->id;
}
$result = $this->repository->getDetail($id, $currentUser);
$resource = new UserResource($result);
return $this->success($resource);
}
/**

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
abstract class BaseResource extends JsonResource
{
protected abstract function getResourceName(): string;
protected function whenIncludeField($field): bool
{
return $this->whenInclude($field, "include_fields");
}
private function whenInclude($field, $prefix): bool
{
$fields = request()->input("$prefix." . $this->getResourceName());
if (!$fields) {
return false;
}
$fieldsArr = explode(',', $fields);
return in_array($field, $fieldsArr);
}
}

View File

@@ -22,10 +22,15 @@ class MedalResource extends JsonResource
'image_large' => $this->image_large,
'image_small' => $this->image_small,
'price' => $this->price,
'price_human' => number_format($this->price),
'duration' => $this->duration,
'description' => $this->description,
'expire_at' => $this->whenPivotLoaded('user_medals', function () {return $this->pivot->expire_at;}),
'user_medal_id' => $this->whenPivotLoaded('user_medals', function () {return $this->pivot->id;}),
'wearing_status' => $this->whenPivotLoaded('user_medals', function () {return $this->pivot->status;}),
'wearing_status_text' => $this->whenPivotLoaded('user_medals', function () {
return nexus_trans("medal.wearing_status_text." . $this->pivot->status);
}),
];
}
}

View File

@@ -19,23 +19,20 @@ class SearchBoxResource extends JsonResource
$searchBox = $this->resource;
$out = [
'id' => $this->id,
'name' => $this->displaySectionName,
'name' => $this->name,
'display_name' => $this->displaySectionName,
'categories' => CategoryResource::collection($this->whenLoaded('categories')),
'tags' => TagResource::collection($this->whenLoaded('tags')),
];
if ($searchBox->showsubcat) {
$subCategories = [];
$lang = get_langfolder_cookie();
$fields = array_keys(SearchBox::$taxonomies);
if (!empty($searchBox->extra['taxonomy_labels'])) {
$fields = array_column($searchBox->extra['taxonomy_labels'], 'torrent_field');
}
foreach ($fields as $field) {
$relationName = "taxonomy_$field";
if ($searchBox->relationLoaded($relationName)) {
$subCategories[] = [
'field' => $field,
'label' => $item['display_text'][$lang] ?? (nexus_trans("searchbox.sub_category_{$field}_label") ?: ucfirst($field)),
'label' => $searchBox->getTaxonomyLabel($field),
'data' => MediaResource::collection($searchBox->{$relationName}),
];
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class TorrentExtraResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'descr' => $this->descr,
'media_info' => $this->media_info,
'media_info_summary' => $this->media_info_summary,
'nfo' => $this->nfo,
];
}
}

View File

@@ -3,14 +3,21 @@
namespace App\Http\Resources;
use App\Models\Attachment;
use App\Models\SearchBox;
use App\Models\Torrent;
use App\Repositories\TorrentRepository;
use Carbon\CarbonInterface;
use Elasticsearch\Endpoints\Search;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
use Nexus\Nexus;
use Illuminate\Http\Request;
class TorrentResource extends JsonResource
class TorrentResource extends BaseResource
{
protected $imageTypes = ['image', 'attachment'];
const NAME = 'torrent';
protected static TorrentRepository $torrentRep;
/**
* Transform the resource into an array.
@@ -24,72 +31,82 @@ class TorrentResource extends JsonResource
'id' => $this->id,
'name' => $this->name,
'filename' => $this->filename,
'hash' => preg_replace_callback('/./s', [$this, "hex_esc"], $this->info_hash),
'cover' => $this->cover,
'small_descr' => $this->small_descr,
'comments' => $this->comments,
'category' => $this->category,
'category_info' => new CategoryResource($this->whenLoaded('basic_category')),
'size' => $this->size,
'size_human' => mksize($this->size),
'added' => $this->added->toDateTimeString(),
'added_human' => $this->added->format('Y-m-d H:i'),
'ttl' => $this->added->diffForHumans(['syntax' => CarbonInterface::DIFF_ABSOLUTE]),
'leechers' => $this->leechers,
'seeders' => $this->seeders,
'times_completed' => $this->times_completed,
'numfiles' => $this->numfiles,
'added' => format_datetime($this->added),
'added_human' => gettime($this->added),
'numfiles' => $this->numfiles ?: 0,
'leechers' => $this->leechers ?: 0,
'seeders' => $this->seeders ?: 0,
'times_completed' => $this->times_completed ?: 0,
'views' => $this->views ?: 0,
'hits' => $this->hits ?: 0,
'comments' => $this->comments ?: 0,
'pos_state' => $this->pos_state,
'pos_state_until' => format_datetime($this->pos_state_until),
'pos_state_until_human' => gettime($this->pos_state_until),
'sp_state' => $this->sp_state,
'sp_state_real' => $this->sp_state_real,
'promotion_info' => $this->promotionInfo,
'hr' => $this->hr,
'hr' => $this->hr ?: 0,
'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')),
'last_action' => format_datetime($this->last_action),
'last_action_human' => gettime($this->last_action),
'thank_users_count' => $this->whenCounted('thank_users'),
'reward_logs_count' => $this->whenCounted('reward_logs'),
'claims_count' => $this->whenCounted('claims'),
'has_bookmarked' => $this->whenHas('has_bookmarked'),
'has_claimed' => $this->whenHas('has_claimed'),
'has_thanked' => $this->whenHas('has_thanked'),
'has_rewarded' => $this->whenHas('has_rewarded'),
'description' => $this->whenHas('description'),
'images' => $this->whenHas('images'),
'download_url' => $this->whenHas('download_url'),
'user' => new UserResource($this->whenLoaded('user')),
'extra' => new TorrentExtraResource($this->whenLoaded('extra')),
'tags' => TagResource::collection($this->whenLoaded('tags')),
'thanks' => ThankResource::collection($this->whenLoaded('thanks')),
'reward_logs' => RewardResource::collection($this->whenLoaded('reward_logs')),
];
if ($this->cover) {
$cover = $this->cover;
} else {
$descriptionArr = format_description($this->descr);
$cover = get_image_from_description($descriptionArr, true);
}
$out['cover'] = resize_image($cover, 100, 100);
if ($request->routeIs('torrents.show')) {
if (!isset($descriptionArr)) {
$descriptionArr = format_description($this->descr);
$subCategories = [];
foreach (SearchBox::$taxonomies as $field => $info) {
$relation = "basic_$field";
if ($this->resource->{$field} > 0 && $this->resource->relationLoaded($relation)) {
$subCategories[$field] = [
'label' => $this->resource->getSubCategoryLabel($field),
'value' => $this->resource->{$relation}->name ?? '',
];
}
$baseInfo = [
['label' => nexus_trans('torrent.show.size'), 'value' => mksize($this->size)],
];
foreach (Torrent::getBasicInfo() as $relation => $text) {
if ($info = $this->whenLoaded($relation)) {
$baseInfo[] = ['label' => $text, 'value' => $info->name];
}
}
$out['base_info'] = $baseInfo;
$out['description'] = $descriptionArr;
$out['images'] = get_image_from_description($descriptionArr);
$out['thank_users_count'] = $this->thank_users_count;
$out['peers_count'] = $this->peers_count;
$out['reward_logs_count'] = $this->reward_logs_count;
}
if (nexus()->isPlatformAdmin()) {
$out['details_url'] = sprintf('%s/details.php?id=%s', getSchemeAndHttpHost(), $this->id);
}
// $out['upload_peers_count'] = $this->upload_peers_count;
// $out['download_peers_count'] = $this->download_peers_count;
// $out['finish_peers_count'] = $this->finish_peers_count;
$out['sub_categories'] = empty($subCategories) ? null : $subCategories;
return $out;
}
protected function getResourceName(): string
{
return self::NAME;
}
private function getTorrentRep(): TorrentRepository
{
if (!isset(self::$torrentRep)) {
self::$torrentRep = new TorrentRepository();
}
return self::$torrentRep;
}
protected function hex_esc($matches) {
return sprintf("%02x", ord($matches[0]));
}
}

View File

@@ -6,6 +6,7 @@ use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
const NAME = 'user';
/**
* Transform the resource into an array.
*
@@ -21,7 +22,9 @@ class UserResource extends JsonResource
'status' => $this->status,
'enabled' => $this->enabled,
'added' => format_datetime($this->added),
'added_human' => gettime($this->added),
'last_access' => format_datetime($this->last_access),
'last_access_human' => gettime($this->last_access),
'class' => $this->class,
'class_text' => $this->class_text,
'avatar' => $this->avatar,
@@ -31,8 +34,10 @@ class UserResource extends JsonResource
'uploaded_text' => mksize($this->uploaded),
'downloaded' => $this->downloaded,
'downloaded_text' => mksize($this->downloaded),
'bonus' => number_format($this->seedbonus, 1),
'seed_points' => number_format($this->seed_points, 1),
'bonus' => floatval($this->seedbonus),
'bonus_human' => number_format($this->seedbonus, 1),
'seed_points' => floatval($this->seed_points),
'seed_points_human' => number_format($this->seed_points, 1),
'seedtime' => $this->seedtime,
'seedtime_text' => mkprettytime($this->seedtime),
'leechtime' => $this->leechtime,
@@ -40,27 +45,27 @@ class UserResource extends JsonResource
'inviter' => new UserResource($this->whenLoaded('inviter')),
'valid_medals' => MedalResource::collection($this->whenLoaded('valid_medals')),
];
if ($request->routeIs('user.me')) {
$out['downloaded_human'] = mksize($this->downloaded);
$out['uploaded_human'] = mksize($this->uploaded);
$out['seed_time'] = mkprettytime($this->seedtime);
$out['leech_time'] = mkprettytime($this->leechtime);
$out['share_ratio'] = get_share_ratio($this->uploaded, $this->downloaded);
$out['comments_count'] = $this->comments_count;
$out['posts_count'] = $this->posts_count;
$out['torrents_count'] = $this->torrents_count;
$out['seeding_torrents_count'] = $this->seeding_torrents_count;
$out['leeching_torrents_count'] = $this->leeching_torrents_count;
$out['completed_torrents_count'] = $this->completed_torrents_count;
$out['incomplete_torrents_count'] = $this->incomplete_torrents_count;
}
if ($request->routeIs("oauth.user_info")) {
$out['name'] = $this->username;
}
if (nexus()->isPlatformAdmin() && $request->routeIs('users.show')) {
$out['two_step_secret'] = $this->two_step_secret;
}
// if ($request->routeIs('user.me')) {
// $out['downloaded_human'] = mksize($this->downloaded);
// $out['uploaded_human'] = mksize($this->uploaded);
// $out['seed_time'] = mkprettytime($this->seedtime);
// $out['leech_time'] = mkprettytime($this->leechtime);
// $out['share_ratio'] = get_share_ratio($this->uploaded, $this->downloaded);
// $out['comments_count'] = $this->comments_count;
// $out['posts_count'] = $this->posts_count;
// $out['torrents_count'] = $this->torrents_count;
// $out['seeding_torrents_count'] = $this->seeding_torrents_count;
// $out['leeching_torrents_count'] = $this->leeching_torrents_count;
// $out['completed_torrents_count'] = $this->completed_torrents_count;
// $out['incomplete_torrents_count'] = $this->incomplete_torrents_count;
// }
// if ($request->routeIs("oauth.user_info")) {
// $out['name'] = $this->username;
// }
//
// if (nexus()->isPlatformAdmin() && $request->routeIs('users.show')) {
// $out['two_step_secret'] = $this->two_step_secret;
// }
return $out;
}

View File

@@ -5,7 +5,7 @@ namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class DeductUserBonusWhenTorrentDeleted
class DeductUserBonusWhenTorrentDeleted implements ShouldQueue
{
/**
* Create the event listener.

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Listeners;
use App\Events\TorrentCreated;
use App\Repositories\UploadRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendEmailNotificationWhenTorrentCreated implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(TorrentCreated $event): void
{
$torrent = $event->model;
$uploadRepo = new UploadRepository();
$result = $uploadRepo->sendEmailNotification($torrent);
do_log("torrent: $torrent->id, sendEmailNotification result: " . var_export($result, true));
}
}

View File

@@ -8,7 +8,7 @@ use App\Repositories\ToolRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SyncTorrentToEs implements ShouldQueue
class SyncTorrentToElasticsearch implements ShouldQueue
{
public $tries = 3;
@@ -32,6 +32,10 @@ class SyncTorrentToEs implements ShouldQueue
public function handle($event)
{
$id = $event->model?->id ?? 0;
if ($id == 0) {
do_log("event: " . get_class($event) . " no model id", 'error');
return;
}
$searchRep = new SearchRepository();
$result = $searchRep->updateTorrent($id);
do_log(sprintf("updateTorrent: %s result: %s", $id, var_export($result, true)));

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Listeners;
use App\Events\TorrentCreated;
use App\Repositories\MeiliSearchRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SyncTorrentToMeilisearch implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle($event): void
{
$id = $event->model->id ?? 0;
if ($id == 0) {
do_log("event: " . get_class($event) . " no model id", 'error');
return;
}
$meiliSearch = new MeiliSearchRepository();
$result = $meiliSearch->doImportFromDatabase($id);
do_log(sprintf("doImportFromDatabase: %s result: %s", $id, var_export($result, true)));
}
}

View File

@@ -45,6 +45,7 @@ class BonusLogs extends NexusModel
const BUSINESS_TYPE_TORRENT_BE_DOWNLOADED = 1001;
const BUSINESS_TYPE_RECEIVE_REWARD = 1002;
const BUSINESS_TYPE_RECEIVE_GIFT = 1003;
const BUSINESS_TYPE_UPLOAD_TORRENT = 1004;
public static array $businessTypes = [
self::BUSINESS_TYPE_CANCEL_HIT_AND_RUN => ['text' => 'Cancel H&R'],
@@ -73,6 +74,7 @@ class BonusLogs extends NexusModel
self::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => ['text' => 'Torrent be downloaded'],
self::BUSINESS_TYPE_RECEIVE_REWARD => ['text' => 'Receive reward'],
self::BUSINESS_TYPE_RECEIVE_GIFT => ['text' => 'Receive gift'],
self::BUSINESS_TYPE_UPLOAD_TORRENT => ['text' => 'Upload torrent'],
];
public function getBusinessTypeTextAttribute()

View File

@@ -1,11 +0,0 @@
<?php
namespace App\Models;
class PersonalAccessTokenPlain extends NexusModel
{
protected $fillable = ['access_token_id', 'plain_text_token'];
public $timestamps = true;
}

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use App\Auth\Permission;
use App\Http\Middleware\Locale;
use App\Repositories\TagRepository;
use Illuminate\Database\Eloquent\Casts\Attribute;
@@ -106,7 +107,9 @@ class SearchBox extends NexusModel
$lang = get_langfolder_cookie();
foreach ($this->extra[self::EXTRA_TAXONOMY_LABELS] ?? [] as $item) {
if ($item['torrent_field'] == $torrentField) {
return $item['display_text'][$lang] ?? 'Unknown';
if (!empty($item['display_text'][$lang])) {
return $item['display_text'][$lang];
}
}
}
return nexus_trans("searchbox.sub_category_{$torrentField}_label") ?: ucfirst($torrentField);
@@ -214,7 +217,7 @@ class SearchBox extends NexusModel
public static function isSpecialEnabled(): bool
{
return Setting::get('main.spsct') == 'yes';
return Setting::getIsSpecialSectionEnabled();
}
public static function getBrowseMode()
@@ -303,7 +306,12 @@ class SearchBox extends NexusModel
public function loadTags(): void
{
$this->setRelation("tags", TagRepository::listAll($this->getKey()));
$allTags = TagRepository::listAll($this->getKey());
if (!Permission::canSetTorrentSpecialTag()) {
$specialTagIdList = Tag::listSpecial();
$allTags = $allTags->filter(fn ($item) => !in_array($item->id, $specialTagIdList));
}
$this->setRelation("tags", $allTags);
}
public static function getDefaultSearchMode()
@@ -349,5 +357,4 @@ class SearchBox extends NexusModel
return $results;
}
}

View File

@@ -100,7 +100,7 @@ class Setting extends NexusModel
return $value;
}
public static function getDefaultLang()
public static function getDefaultLang(): string
{
return self::get("main.defaultlang");
}
@@ -110,4 +110,112 @@ class Setting extends NexusModel
return self::get("security.use_challenge_response_authentication") == "yes";
}
public static function getUploadTorrentMaxSize(): int
{
return intval(self::get("main.max_torrent_size"));
}
public static function getUploadTorrentMaxPrice(): int
{
return intval(self::get("torrent.max_price"));
}
public static function getIsPaidTorrentEnabled(): bool
{
return self::get("torrent.paid_torrent_enabled") == "yes";
}
public static function getUploadDenyApprovalDenyCount(): int
{
return intval(self::get("main.upload_deny_approval_deny_count"));
}
public static function getOfferSkipApprovedCount(): int
{
return intval(self::get("main.offer_skip_approved_count"));
}
public static function getLargeTorrentSize(): int
{
return intval(self::get("torrent.largesize"));
}
public static function getLargeTorrentSpState(): int
{
return intval(self::get("torrent.largepro"));
}
public static function getUploadTorrentHalfDownProbability(): int
{
return intval(self::get("torrent.randomhalfleech"));
}
public static function getUploadTorrentFreeProbability(): int
{
return intval(self::get("torrent.randomfree"));
}
public static function getUploadTorrentTwoTimesUpProbability(): int
{
return intval(self::get("torrent.randomtwoup"));
}
public static function getUploadTorrentFreeTwoTimesUpProbability(): int
{
return intval(self::get("torrent.randomtwoupfree"));
}
public static function getUploadTorrentHalfDownTwoTimesUpProbability(): int
{
return intval(self::get("torrent.randomtwouphalfdown"));
}
public static function getUploadTorrentOneThirdDownProbability(): int
{
return intval(self::get("torrent.randomthirtypercentdown"));
}
public static function getUploadTorrentRewardBonus(): int
{
return intval(self::get("bonus.uploadtorrent"));
}
public static function getIsUploadOpenAtWeekend(): bool
{
return self::get("main.sptime") == "yes";
}
public static function getIsSpecialSectionEnabled(): bool
{
return self::get('main.spsct') == 'yes';
}
public static function getIsAllowUserReceiveEmailNotification(): bool
{
return self::get('smtp.emailnotify') == 'yes';
}
public static function getBaseUrl(): string
{
$result = self::get('basic.BASEURL', $_SERVER['HTTP_HOST'] ?? '');
return rtrim($result, '/');
}
public static function getSiteName(): string
{
return self::get("basic.SITENAME");
}
public static function getTorrentSaveDir(): string
{
return self::get("main.torrent_dir");
}
public static function getSmtpType(): string
{
return self::get("smtp.smtptype");
}
}

12
app/Models/SiteLog.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
namespace App\Models;
class SiteLog extends NexusModel
{
protected $table = 'sitelog';
protected $fillable = ['added', 'txt', 'security_level', 'uid'];
}

View File

@@ -13,8 +13,9 @@ class Torrent extends NexusModel
'category', 'source', 'medium', 'codec', 'standard', 'processing', 'team', 'audiocodec',
'size', 'added', 'type', 'numfiles', 'owner', 'nfo', 'sp_state', 'promotion_time_type',
'promotion_until', 'anonymous', 'url', 'pos_state', 'cache_stamp', 'picktype', 'picktime',
'last_reseed', 'leechers', 'seeders', 'cover', 'last_action',
'last_reseed', 'leechers', 'seeders', 'cover', 'last_action', 'info_hash', 'pieces_hash',
'times_completed', 'approval_status', 'banned', 'visible', 'pos_state_until', 'price',
'hr',
];
const VISIBLE_YES = 'yes';
@@ -27,6 +28,7 @@ class Torrent extends NexusModel
'added' => 'datetime',
'promotion_until' => 'datetime',
'pos_state_until' => 'datetime',
'last_action' => 'datetime',
];
public static $commentFields = [
@@ -334,15 +336,6 @@ class Torrent extends NexusModel
return implode('', $html);
}
public static function getBasicInfo(): array
{
$result = [];
foreach (self::$basicRelations as $relation) {
$result[$relation] = nexus_trans("torrent.show.$relation");
}
return $result;
}
public static function listPosStates($onlyKeyValue = false, $valueField = 'text'): array
{
$result = self::$posStates;
@@ -382,6 +375,11 @@ class Torrent extends NexusModel
return true;
}
public function getSubCategoryLabel($field): string
{
return $this->basic_category->search_box->getTaxonomyLabel($field);
}
public function bookmarks(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Bookmark::class, 'torrentid');
@@ -452,7 +450,7 @@ class Torrent extends NexusModel
return $this->belongsTo(Source::class, 'source');
}
public function basic_media()
public function basic_medium()
{
return $this->belongsTo(Media::class, 'medium');
}
@@ -477,11 +475,21 @@ class Torrent extends NexusModel
return $this->belongsTo(Team::class, 'team');
}
public function basic_audio_codec()
public function basic_audiocodec()
{
return $this->belongsTo(AudioCodec::class, 'audiocodec');
}
public function claim_users(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(User::class, 'claims', 'torrent_id');
}
public function claims()
{
return $this->hasMany(Claim::class, 'torrent_id');
}
public function scopeVisible($query, $visible = self::VISIBLE_YES)
{
$query->where('visible', $visible);

View File

@@ -3,16 +3,25 @@
namespace App\Models;
use Nexus\Database\NexusDB;
use Nexus\Torrent\TechnicalInformation;
class TorrentExtra extends NexusModel
{
public $timestamps = true;
protected $fillable = ['torrent_id', 'descr', 'ori_descr', 'media_info'];
protected $fillable = ['torrent_id', 'descr', 'ori_descr', 'media_info', 'nfo'];
public function torrent()
{
return $this->belongsTo(Torrent::class, 'torrent_id');
}
protected $appends = ['media_info_summary'];
public function getMediaInfoSummaryAttribute(): array
{
$technicalInfo = new TechnicalInformation($this->media_info ?? '');
return $technicalInfo->getSummaryInfo();
}
}

View File

@@ -540,6 +540,11 @@ class User extends Authenticatable implements FilamentUser, HasName
return $this->hasMany(UserModifyLog::class, "user_id");
}
public function claims(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Claim::class, 'uid');
}
public function getAvatarAttribute($value)
{
if ($value) {

View File

@@ -6,13 +6,15 @@ use App\Events\SeedBoxRecordUpdated;
use App\Events\TorrentCreated;
use App\Events\TorrentDeleted;
use App\Events\TorrentUpdated;
use App\Events\UserDestroyed;
use App\Events\UserDeleted;
use App\Events\UserDisabled;
use App\Listeners\DeductUserBonusWhenTorrentDeleted;
use App\Listeners\FetchTorrentImdb;
use App\Listeners\RemoveOauthTokens;
use App\Listeners\RemoveSeedBoxRecordCache;
use App\Listeners\SyncTorrentToEs;
use App\Listeners\SendEmailNotificationWhenTorrentCreated;
use App\Listeners\SyncTorrentToElasticsearch;
use App\Listeners\SyncTorrentToMeilisearch;
use App\Listeners\TestTorrentUpdated;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
@@ -34,11 +36,14 @@ class EventServiceProvider extends ServiceProvider
RemoveSeedBoxRecordCache::class,
],
TorrentUpdated::class => [
SyncTorrentToEs::class,
TestTorrentUpdated::class,
SyncTorrentToElasticsearch::class,
SyncTorrentToMeilisearch::class,
],
TorrentCreated::class => [
FetchTorrentImdb::class,
SyncTorrentToElasticsearch::class,
SyncTorrentToMeilisearch::class,
SendEmailNotificationWhenTorrentCreated::class,
],
TorrentDeleted::class => [
DeductUserBonusWhenTorrentDeleted::class,

View File

@@ -6,6 +6,7 @@ use App\Models\Setting;
use App\Models\Torrent;
use App\Models\User;
use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class BaseRepository
@@ -20,6 +21,11 @@ class BaseRepository
return [$field, $type];
}
protected function getPerPageFromRequest(Request $request)
{
return $request->get('per_page');
}
protected function handleAnonymous($username, $user, User $authenticator, Torrent $torrent = null)
{
if (!$user) {

View File

@@ -7,7 +7,9 @@ class TokenRepository extends BaseRepository
{
private static array $userTokenPermissions = [
PermissionEnum::TORRENT_LIST,
PermissionEnum::TORRENT_VIEW,
PermissionEnum::UPLOAD,
PermissionEnum::USER_VIEW,
];
public function listUserTokenPermissions(): array

View File

@@ -346,7 +346,8 @@ class ToolRepository extends BaseRepository
->from(new Address(Setting::get('main.SITEEMAIL'), Setting::get('basic.SITENAME')))
->to($to)
->subject($subject)
->html($body)
->text($body)
->html(nl2br($body))
;
// Send the message

View File

@@ -2,8 +2,10 @@
namespace App\Repositories;
use App\Auth\Permission;
use App\Exceptions\InsufficientPermissionException;
use App\Exceptions\NexusException;
use App\Http\Resources\TorrentResource;
use App\Models\AudioCodec;
use App\Models\Category;
use App\Models\Claim;
@@ -26,9 +28,12 @@ use App\Models\TorrentOperationLog;
use App\Models\TorrentSecret;
use App\Models\TorrentTag;
use App\Models\User;
use App\Utils\ApiQueryBuilder;
use Carbon\Carbon;
use Hashids\Hashids;
use Elasticsearch\Endpoints\Search;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
@@ -55,81 +60,134 @@ class TorrentRepository extends BaseRepository
/**
* fetch torrent list
*
* @param array $params
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function getList(array $params, User $user)
public function getList(Request $request, Authenticatable $user, string $sectionName = null)
{
$query = Torrent::query();
if (!empty($params['category'])) {
$query->where('category', $params['category']);
if (empty($sectionName)) {
$sectionId = SearchBox::getBrowseMode();
$searchBox = SearchBox::query()->find($sectionId);
} else {
$searchBox = SearchBox::query()->where('name', $sectionName)->first();
}
if (!empty($params['source'])) {
$query->where('source', $params['source']);
if (empty($searchBox)) {
throw new NexusException(nexus_trans("upload.invalid_section"));
}
if (!empty($params['medium'])) {
$query->where('medium', $params['medium']);
if ($searchBox->isSectionSpecial() && !Permission::canViewSpecialSection()) {
throw new InsufficientPermissionException();
}
if (!empty($params['codec'])) {
$query->where('codec', $params['codec']);
$categoryIdList = $searchBox->categories()->pluck('id')->toArray();
//query this info default
$query = Torrent::query()->with([
'basic_category', 'basic_category.search_box',
'basic_audiocodec', 'basic_codec', 'basic_medium',
'basic_source', 'basic_processing', 'basic_standard', 'basic_team',
])
->whereIn('category', $categoryIdList)
->orderBy("pos_state", "DESC");
$allowIncludes = ['user', 'extra', 'tags'];
$allowIncludeCounts = ['thank_users', 'reward_logs', 'claims'];
$allowIncludeFields = [
'has_bookmarked', 'has_claimed', 'has_thanked', 'has_rewarded',
'description', 'download_url'
];
$allowFilters = [
'title', 'category', 'source', 'medium', 'codec', 'audiocodec', 'standard', 'processing', 'team',
'owner', 'visible', 'added', 'size', 'sp_state', 'leechers', 'seeders', 'times_completed'
];
$allowSorts = ['id', 'comments', 'size', 'seeders', 'leechers', 'times_completed'];
$apiQueryBuilder = ApiQueryBuilder::for(TorrentResource::NAME, $query, $request)
->allowIncludes($allowIncludes)
->allowIncludeCounts($allowIncludeCounts)
->allowIncludeFields($allowIncludeFields)
->allowFilters($allowFilters)
->allowSorts($allowSorts)
->registerCustomFilter('title', function (Builder $query, Request $request) {
$title = $request->input(ApiQueryBuilder::PARAM_NAME_FILTER.".title");
if ($title) {
$query->where(function (Builder $query) use ($title) {
$query->where('name', 'like', '%' . $title . '%')
->orWhere('small_descr', 'like', '%' . $title . '%');
});
}
})
;
$query = $apiQueryBuilder->build();
if (!$apiQueryBuilder->hasSort()) {
$query->orderBy("id", "DESC");
}
if (!empty($params['audio_codec'])) {
$query->where('audiocodec', $params['audio_codec']);
}
if (!empty($params['standard'])) {
$query->where('standard', $params['standard']);
}
if (!empty($params['processing'])) {
$query->where('processing', $params['processing']);
}
if (!empty($params['team'])) {
$query->where('team', $params['team']);
}
if (!empty($params['owner'])) {
$query->where('owner', $params['owner']);
}
if (!empty($params['visible'])) {
$query->where('visible', $params['visible']);
}
if (!empty($params['query'])) {
$query->where(function (Builder $query) use ($params) {
$query->where('name', 'like', "%{$params['query']}%")
->orWhere('small_descr', 'like', "%{$params['query']}%");
});
}
if (!empty($params['category_mode'])) {
$query->whereHas('basic_category', function (Builder $query) use ($params) {
$query->where('mode', $params['category_mode']);
});
}
$query = $this->handleGetListSort($query, $params);
$with = ['user', 'tags'];
$torrents = $query->with($with)->paginate();
foreach ($torrents as &$item) {
$item->download_url = $this->getDownloadUrl($item->id, $user);
}
return $torrents;
$torrents = $query->paginate($this->getPerPageFromRequest($request));
return $this->appendIncludeFields($apiQueryBuilder, $user, $torrents);
}
public function getDetail($id, User $user)
public function getDetail($id, Authenticatable $user)
{
$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);
},
//query this info default
$query = Torrent::query()->with([
'basic_category', 'basic_category.search_box',
'basic_audiocodec', 'basic_codec', 'basic_medium',
'basic_source', 'basic_processing', 'basic_standard', 'basic_team',
]);
$allowIncludes = ['user', 'extra', 'tags'];
$allowIncludeCounts = ['thank_users', 'reward_logs', 'claims'];
$allowIncludeFields = [
'has_bookmarked', 'has_claimed', 'has_thanked', 'has_rewarded',
'description', 'download_url'
];
$result = Torrent::query()->with($with)->withCount(['peers', 'thank_users', 'reward_logs'])->visible()->findOrFail($id);
$result->download_url = $this->getDownloadUrl($id, $user);
return $result;
$apiQueryBuilder = ApiQueryBuilder::for(TorrentResource::NAME, $query)
->allowIncludes($allowIncludes)
->allowIncludeCounts($allowIncludeCounts)
->allowIncludeFields($allowIncludeFields)
;
$torrent = $apiQueryBuilder->build()->findOrFail($id);
$torrentList = $this->appendIncludeFields($apiQueryBuilder, $user, [$torrent]);
return $torrentList[0];
}
private function appendIncludeFields(ApiQueryBuilder $apiQueryBuilder, Authenticatable $user, $torrentList)
{
$torrentIdArr = $bookmarkData = $claimData = $thankData = $rewardData =[];
foreach ($torrentList as $torrent) {
$torrentIdArr[] = $torrent->id;
}
unset($torrent);
if ($hasFieldHasBookmarked = $apiQueryBuilder->hasIncludeField('has_bookmarked')) {
$bookmarkData = $user->bookmarks()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
}
if ($hasFieldHasClaimed = $apiQueryBuilder->hasIncludeField('has_claimed')) {
$claimData = $user->claims()->whereIn('torrent_id', $torrentIdArr)->get()->keyBy('torrent_id');
}
if ($hasFieldHasThanked = $apiQueryBuilder->hasIncludeField('has_thanked')) {
$thankData = $user->thank_torrent_logs()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
}
if ($hasFieldHasRewarded = $apiQueryBuilder->hasIncludeField('has_rewarded')) {
$rewardData = $user->reward_torrent_logs()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
}
foreach ($torrentList as $torrent) {
$id = $torrent->id;
if ($hasFieldHasBookmarked) {
$torrent->has_bookmarked = $bookmarkData->has($id);
}
if ($hasFieldHasClaimed) {
$torrent->has_claimed = $claimData->has($id);
}
if ($hasFieldHasThanked) {
$torrent->has_thanked = $thankData->has($id);
}
if ($hasFieldHasRewarded) {
$torrent->has_rewarded = $rewardData->has($id);
}
if ($apiQueryBuilder->hasIncludeField('description') && $apiQueryBuilder->hasInclude('extra')) {
$descriptionArr = format_description($torrent->extra->descr ?? '');
$torrent->description = $descriptionArr;
$torrent->images = get_image_from_description($descriptionArr);
}
if ($apiQueryBuilder->hasIncludeField("download_url")) {
$torrent->download_url = $this->getDownloadUrl($id, $user);
}
}
return $torrentList;
}
public function getDownloadUrl($id, array|User $user): string

View File

@@ -1,52 +1,195 @@
<?php
namespace App\Repositories;
use App\Auth\Permission;
use App\Enums\ModelEventEnum;
use App\Exceptions\NexusException;
use App\Http\Resources\SearchBoxResource;
use App\Models\BonusLogs;
use App\Models\Category;
use App\Models\File;
use App\Models\SearchBox;
use App\Models\Setting;
use App\Models\Torrent;
use App\Models\TorrentExtra;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Rhilip\Bencode\Bencode;
use Rhilip\Bencode\ParseException;
class UploadRepository extends BaseRepository
{
/**
* @throws NexusException
*/
public function upload(Request $request)
{
$user = $request->user();
if ($user->uploadpos != 'yes') {
throw new NexusException("user upload permission is disabled");
if (empty($request->name)) {
throw new NexusException(nexus_trans("upload.require_name"));
}
$rules = [
'descr' => 'required',
'type' => 'required',
'name' => 'required',
];
$request->validate($rules);
$category = Category::query()->findOrFail($request->type);
$mode = $category->mode;
$searchBox = SearchBox::query()->findOrFail($mode);
$searchBox->loadSubCategories();
$searchBox->loadTags();
if (empty($request->descr)) {
throw new NexusException(nexus_trans("upload.blank_description"));
}
if (empty($request->type)) {
throw new NexusException(nexus_trans("upload.category_unselected"));
}
$category = Category::query()->find($request->type);
if (!$category) {
throw new NexusException(nexus_trans("upload.invalid_category"));
}
$torrentFile = $this->getTorrentFile($request);
$filepath = $torrentFile->getRealPath();
try {
$dict = Bencode::load($filepath);
} catch (ParseException $e) {
do_log("Bencode load error:" . $e->getMessage(), 'error');
throw new NexusException("upload.not_bencoded_file");
}
$info = $this->checkTorrentDict($dict, 'info');
if (isset($dict['piece layers']) || isset($info['files tree']) || (isset($info['meta version']) && $info['meta version'] == 2)) {
throw new NexusException("Torrent files created with Bittorrent Protocol v2, or hybrid torrents are not supported.");
}
$this->checkTorrentDict($info, 'piece length', 'integer'); // Only Check without use
$dname = $this->checkTorrentDict($info, 'name', 'string');
$pieces = $this->checkTorrentDict($info, 'pieces', 'string');
if (strlen($pieces) % 20 != 0) {
throw new NexusException(nexus_trans("upload.invalid_pieces"));
}
$dict['info']['private'] = 1;
$dict['info']['source'] = sprintf("[%s] %s", Setting::getBaseUrl(), Setting::getSiteName());
unset ($dict['announce-list']); // remove multi-tracker capability
unset ($dict['nodes']); // remove cached peers (Bitcomet & Azareus)
$infoHash = pack("H*", sha1(Bencode::encode($dict['info'])));
$exists = Torrent::query()->where('info_hash', $infoHash)->first(['id']);
if ($exists) {
throw new NexusException(nexus_trans('upload.torrent_existed', ['id' => $exists->id]));
}
$subCategoriesAngTags = $this->getSubCategoriesAndTags($request, $category);
$fileListInfo = $this->getFileListInfo($info, $dname);
$posStateInfo = $this->getPosStateInfo($request);
$pickInfo = $this->getPickInfo($request);
$anonymous = "no";
$uploaderUsername = $user->username;
if ($request->uplver == 'yes' && Permission::canBeAnonymous()) {
if ($request->uplver == 'yes') {
if (!Permission::canBeAnonymous()) {
throw new NexusException(nexus_trans('upload.no_permission_to_be_anonymous'));
}
$anonymous = "yes";
$uploaderUsername = "Anonymous";
}
$torrentSavePath = $this->getTorrentSavePath();
$nowStr = Carbon::now()->toDateTimeString();
$torrentInsert = [
'filename' => $torrentFile->getClientOriginalName(),
'owner' => $user->id,
'visible' => 'yes',
'anonymous' => $anonymous,
'name' => $request->name,
'size' => $fileListInfo['totalLength'],
'numfiles' => count($fileListInfo['fileList']),
'type' => $fileListInfo['type'],
'url' => parse_imdb_id($request->url ?? ''),
'small_descr' => $request->small_descr ?? '',
'category' => $category->id,
'source' => $subCategoriesAngTags['subCategories']['source'],
'medium' => $subCategoriesAngTags['subCategories']['medium'],
'codec' => $subCategoriesAngTags['subCategories']['codec'],
'audiocodec' => $subCategoriesAngTags['subCategories']['audiocodec'],
'standard' => $subCategoriesAngTags['subCategories']['standard'],
'processing' => $subCategoriesAngTags['subCategories']['processing'],
'team' => $subCategoriesAngTags['subCategories']['team'],
'save_as' => $dname,
'sp_state' => $this->getSpState($fileListInfo['totalLength']),
'added' => $nowStr,
'last_action' => $nowStr,
'info_hash' => $infoHash,
'cover' => $this->getCover($request),
'pieces_hash' => sha1($info['pieces']),
'cache_stamp' => time(),
'hr' => $this->getHitAndRun($request),
'pos_state' => $posStateInfo['posState'],
'pos_state_until' => $posStateInfo['posStateUntil'],
'picktype' => $pickInfo['pickType'],
'picktime' => $pickInfo['pickTime'],
'approval_status' => $this->getApprovalStatus($request),
'price' => $this->getPrice($request),
];
$extraInsert = [
'descr' => $request->descr ?? '',
'media_info' => $request->technical_info ?? '',
'nfo' => $this->getNfoContent($request),
'created_at' => $nowStr,
];
$newTorrent = DB::transaction(function () use ($torrentInsert, $extraInsert, $fileListInfo, $subCategoriesAngTags, $dict, $torrentSavePath) {
$newTorrent = Torrent::query()->create($torrentInsert);
$id = $newTorrent->id;
$torrentFilePath = "$torrentSavePath/$id.torrent";
$saveResult = Bencode::dump($torrentFilePath, $dict);
if ($saveResult === false) {
do_log("save torrent failed: $torrentFilePath", 'error');
throw new NexusException(nexus_trans('upload.save_torrent_file_failed'));
}
$extraInsert['torrent_id'] = $id;
TorrentExtra::query()->insert($extraInsert);
$fileInsert = [];
foreach ($fileListInfo['fileList'] as $fileItem) {
$fileInsert[] = [
'torrent' => $id,
'filename' => $fileItem[0],
'size' => $fileItem[1],
];
}
File::query()->insert($fileInsert);
if (!empty($subCategoriesAngTags['tags'])) {
insert_torrent_tags($id, $subCategoriesAngTags['tags']);
}
$this->sendReward($id);
return $newTorrent;
});
$id = $newTorrent->id;
$torrentRep = new TorrentRepository();
$torrentRep->addPiecesHashCache($id, $torrentInsert['pieces_hash']);
write_log("Torrent $id ($newTorrent->name) was uploaded by $uploaderUsername");
fire_event(ModelEventEnum::TORRENT_CREATED, $newTorrent);
return $newTorrent;
}
private function getTorrentFile(Request $request): UploadedFile
{
$file = $request->file('file');
if (empty($file)) {
throw new NexusException("torrent file not found");
throw new NexusException(nexus_trans('upload.missing_torrent_file'));
}
if (!$file->isValid()) {
throw new NexusException("upload torrent file error");
}
$size = $file->getSize();
$maxAllowSize = Setting::getUploadTorrentMaxSize();
if ($size > $maxAllowSize) {
$msg = sprintf("%s%s%s",
nexus_trans("upload.torrent_file_too_big"),
number_format($maxAllowSize),
nexus_trans("upload.remake_torrent_note")
);
throw new NexusException($msg);
}
if ($size == 0) {
throw new NexusException("upload.empty_file");
}
$filename = $file->getClientOriginalName();
if (!validfilename($filename)) {
throw new NexusException("upload.invalid_filename");
}
if (!preg_match('/^(.+)\.torrent$/si', $filename, $matches)) {
throw new NexusException("upload.filename_not_torrent");
}
return $file;
}
@@ -61,16 +204,387 @@ class UploadRepository extends BaseRepository
return '';
}
if (!$file->isValid()) {
throw new NexusException("upload nfo file error");
throw new NexusException(nexus_trans("upload.nfo_upload_failed"));
}
$size = $file->getSize();
if ($size == 0) {
throw new NexusException("upload nfo file size is zero");
throw new NexusException(nexus_trans("upload.zero_byte_nfo"));
}
if ($size > 65535) {
throw new NexusException("upload nfo file size is too large");
throw new NexusException(nexus_trans("upload.nfo_too_big"));
}
return str_replace("\x0d\x0d\x0a", "\x0d\x0a", $file->getContent());
}
private function getApprovalStatus(Request $request): int
{
if (Permission::canTorrentApprovalAllowAutomatic()) {
return Torrent::APPROVAL_STATUS_ALLOW;
}
return Torrent::APPROVAL_STATUS_NONE;
}
private function getPrice(Request $request): int
{
$price = $request->price ?: 0;
if (!is_numeric($price)) {
throw new NexusException(nexus_trans('upload.invalid_price', ['price' => $price]));
}
if ($price > 0) {
if (!Permission::canSetTorrentPrice()) {
throw new NexusException(nexus_trans("upload.no_permission_to_set_torrent_price"));
}
$paidTorrentEnabled = Setting::getIsPaidTorrentEnabled();
if (!$paidTorrentEnabled) {
throw new NexusException(nexus_trans("upload.paid_torrent_not_enabled"));
}
$maxPrice = Setting::getUploadTorrentMaxPrice();
if ($maxPrice > 0 && $price > $maxPrice) {
throw new NexusException(nexus_trans('upload.price_too_much'));
}
}
return intval($price);
}
private function getHitAndRun(Request $request): int
{
$hr = $request->hr ?? 0;
if ($hr > 0 && !Permission::canSetTorrentHitAndRun()) {
throw new NexusException(nexus_trans("upload.no_permission_to_set_torrent_hr"));
}
if (!in_array($hr, [0, 1])) {
throw new NexusException(nexus_trans('upload.invalid_hr'));
}
return intval($hr);
}
private function getPosStateInfo(Request $request): array
{
$posState = $request->pos_state ?: Torrent::POS_STATE_STICKY_NONE;
$posStateUntil = $request->pos_state_until ?: null;
if ($posState !== Torrent::POS_STATE_STICKY_NONE) {
if (!Permission::canSetTorrentPosState()) {
throw new NexusException("upload.no_permission_to_set_torrent_pos_state");
}
if (!isset(Torrent::$posStates[$posState])) {
throw new NexusException(nexus_trans('upload.invalid_pos_state', ['pos_state' => $posState]));
}
}
if ($posState == Torrent::POS_STATE_STICKY_NONE) {
$posStateUntil = null;
}
if ($posStateUntil && Carbon::parse($posStateUntil)->lt(Carbon::now())) {
throw new NexusException(nexus_trans('upload.invalid_pos_state_until'));
}
return compact('posState', 'posStateUntil');
}
private function getPickInfo(Request $request): array
{
$pickType = $request->pick_type ?: Torrent::PICK_NORMAL;
$pickTime = null;
if ($pickType != Torrent::PICK_NORMAL) {
if (!isset(Torrent::$pickTypes[$pickType])) {
throw new NexusException(nexus_trans('upload.invalid_pick_type', ['pick_type' => $pickType]));
}
if (!Permission::canPickTorrent()) {
throw new NexusException("upload.no_permission_to_pick_torrent");
}
$pickTime = Carbon::now();
}
return compact('pickType', 'pickTime');
}
private function checkTorrentDict($dict, $key, $type = null)
{
if (!is_array($dict)) {
throw new NexusException(nexus_trans("upload.not_a_dictionary"));
}
if (!isset($dict[$key])) {
throw new NexusException(nexus_trans("upload.dictionary_is_missing_key"));
}
$value = $dict[$key];
if (!is_null($type)) {
$isFunction = 'is_' . $type;
if (function_exists($isFunction) && !$isFunction($value)) {
throw new NexusException(nexus_trans("upload.invalid_entry_in_dictionary"));
}
}
return $value;
}
/**
* @throws NexusException
*/
private function getFileListInfo(array $info, string $dname): array
{
$filelist = array();
$totallen = 0;
if (isset($info['length'])) {
$totallen = $info['length'];
$filelist[] = array($dname, $totallen);
$type = "single";
} else {
$flist = $this->checkTorrentDict($info, 'files', 'array');
if (!count($flist)) {
throw new NexusException(nexus_trans("upload.empty_file"));
}
foreach ($flist as $fn) {
$ll = $this->checkTorrentDict($fn, 'length', 'integer');
$path_key = isset($fn['path.utf-8']) ? 'path.utf-8' : 'path';
$ff = $this->checkTorrentDict($fn, $path_key, 'list');
$totallen += $ll;
$ffa = array();
foreach ($ff as $ffe) {
if (!is_string($ffe)) {
throw new NexusException(nexus_trans("upload.filename_errors"));
}
$ffa[] = $ffe;
}
if (!count($ffa)) {
throw new NexusException(nexus_trans("upload.filename_errors"));
}
$ffe = implode("/", $ffa);
$filelist[] = array($ffe, $ll);
}
$type = "multi";
}
return [
'type' => $type,
'totalLength' => $totallen,
'fileList' => $filelist,
];
}
private function canUploadToSection(SearchBox $section): bool
{
$user = Auth::user();
$uploadDenyApprovalDenyCount = Setting::getUploadDenyApprovalDenyCount();
$approvalDenyCount = Torrent::query()->where('owner', $user->id)
->where('approval_status', Torrent::APPROVAL_STATUS_DENY)
->count()
;
if ($uploadDenyApprovalDenyCount > 0 && $approvalDenyCount >= $uploadDenyApprovalDenyCount) {
throw new NexusException(nexus_trans("upload.approval_deny_reach_upper_limit"));
}
if ($section->isSectionBrowse()) {
$offerSkipApprovedCount = Setting::getOfferSkipApprovedCount();
if ($user->offer_allowed_count >= $offerSkipApprovedCount) {
return true;
}
if (get_if_restricted_is_open()) {
return true;
}
if (!Permission::canUploadToNormalSection()) {
throw new NexusException(nexus_trans('upload.unauthorized_upload_freely'));
}
return true;
} elseif ($section->isSectionSpecial()) {
if (!Setting::getIsSpecialSectionEnabled()) {
throw new NexusException(nexus_trans('upload.special_section_not_enabled'));
}
if (!Permission::canUploadToSpecialSection()) {
throw new NexusException(nexus_trans('upload.unauthorized_upload_freely'));
}
return true;
}
throw new NexusException(nexus_trans('upload.invalid_section'));
}
private function getSpState($torrentSize): int
{
$largeTorrentSize = Setting::getLargeTorrentSize();
if ($largeTorrentSize > 0 && $torrentSize > $largeTorrentSize * 1073741824) {
$largeTorrentSpState = Setting::getLargeTorrentSpState();
if (isset(Torrent::$promotionTypes[$largeTorrentSpState])) {
do_log("large torrent, sp state from config: $largeTorrentSpState");
return $largeTorrentSpState;
}
do_log("invalid large torrent sp state: $largeTorrentSpState", 'error');
return Torrent::PROMOTION_NORMAL;
} else {
$probabilities = [
Torrent::PROMOTION_FREE => Setting::getUploadTorrentFreeProbability(),
Torrent::PROMOTION_TWO_TIMES_UP => Setting::getUploadTorrentTwoTimesUpProbability(),
Torrent::PROMOTION_FREE_TWO_TIMES_UP => Setting::getUploadTorrentFreeTwoTimesUpProbability(),
Torrent::PROMOTION_HALF_DOWN => Setting::getUploadTorrentHalfDownProbability(),
Torrent::PROMOTION_HALF_DOWN_TWO_TIMES_UP => Setting::getUploadTorrentHalfDownTwoTimesUpProbability(),
Torrent::PROMOTION_ONE_THIRD_DOWN => Setting::getUploadTorrentOneThirdDownProbability(),
];
$sum = array_sum($probabilities);
if ($sum == 0) {
do_log("no random sp state", 'warning');
return Torrent::PROMOTION_NORMAL;
}
$random = mt_rand(1, $sum);
$currentProbability = 0;
foreach ($probabilities as $k => $v) {
$currentProbability += $v;
if ($random <= $currentProbability) {
do_log(sprintf("random sp state, probabilities: %s, get result: %s by probability: %s", json_encode($probabilities), $k, $v));
return $k;
}
}
throw new \RuntimeException();
}
}
/**
* @throws NexusException
*/
private function getSubCategoriesAndTags(Request $request, Category $category): array
{
$searchBoxRep = new SearchBoxRepository();
$sections = $searchBoxRep->listSections()->keyBy('id');
if (!$sections->has($category->mode)) {
throw new NexusException(nexus_trans('upload.invalid_section'));
}
/**
* @var $section SearchBox
*/
$section = $sections->get($category->mode);
$this->canUploadToSection($section);
$sectionResource = new SearchBoxResource($section);
$sectionData = $sectionResource->response()->getData(true);
$sectionInfo = $sectionData['data'];
$categories = array_column($sectionInfo['categories'], 'id');
if (!in_array($category->id, $categories)) {
throw new NexusException(nexus_trans('upload.invalid_category'));
}
$subCategoryInfo = array_column($sectionInfo['sub_categories'], null, 'field');
$subCategories = [];
foreach (SearchBox::$taxonomies as $name => $info) {
$value = $request->get($name, 0);
if ($value > 0) {
if (!isset($subCategoryInfo[$name])) {
throw new NexusException(nexus_trans('upload.not_supported_sub_category_field', ['field' => $name]));
}
$subCategoryValues = array_column($subCategoryInfo[$name]['data'], 'name', 'id');
if (!isset($subCategoryValues[$value])) {
throw new NexusException(nexus_trans(
'upload.invalid_sub_category_value',
['field' => $name, 'label' => $subCategoryInfo[$name]['label'], 'value' => $value]
));
}
}
$subCategories[$name] = $value;
}
$tags = $request->tags ?: [];
if (!is_array($tags)) {
$tags = explode(',', $tags);
}
$allTags = array_column($sectionInfo['tags'], 'name', 'id');
foreach ($tags as $tag) {
if (!isset($allTags[$tag])) {
throw new NexusException(nexus_trans('upload.invalid_tag', ['tag' => $tag]));
}
}
return compact('subCategories', 'tags');
}
private function getCover(Request $request):string
{
$descr = $request->descr ?? '';
if (empty($descr)) {
return '';
}
$descriptionArr = format_description($descr);
return get_image_from_description($descriptionArr, true, false);
}
private function getTorrentSavePath(): string
{
$torrentSavePath = getFullDirectory(Setting::getTorrentSaveDir());
if (!is_dir($torrentSavePath)) {
do_log(sprintf("torrentSavePath: %s not exists", $torrentSavePath), 'error');
throw new NexusException(nexus_trans('upload.torrent_save_dir_not_exists'));
}
if (!is_writable($torrentSavePath)) {
do_log(sprintf("torrentSavePath: %s not writable", $torrentSavePath), 'error');
throw new NexusException(nexus_trans('upload.torrent_save_dir_not_writable'));
}
return $torrentSavePath;
}
private function sendReward($torrentId): void
{
$user = Auth::user();
$old = $user->seedbonus;
$delta = Setting::getUploadTorrentRewardBonus();
if ($delta > 0) {
$new = $old + $delta;
$user->increment('seedbonus', $delta);
BonusLogs::add($user->id, $old, $delta, $new, "Upload torrent: $torrentId", BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT);
do_log("upload torrent: $torrentId, success send reward: $delta");
} else {
do_log("upload torrent: $torrentId, no reward");
}
}
public function sendEmailNotification(Torrent $torrent, $userId = 0): int
{
$logMsg = sprintf("torrent: %s, category: %s", $torrent->id, $torrent->category);
if (!Setting::getIsAllowUserReceiveEmailNotification() || Setting::getSmtpType() == 'none') {
do_log("$logMsg, not allow user receive email notification or smtp type is none");
return 0;
}
$page = 1;
$size = 1000;
$query = User::query()
->where("notifs", "like", "%[cat$torrent->category]%")
->where("notifs", "like","%[email]%")
->normal()
;
if ($userId > 0) {
$query->where("id", $userId);
}
$total = (clone $query)->count();
if ($total == 0) {
do_log(sprintf("%s, no user receive email notification", $logMsg));
return 0;
}
$toolRep = new ToolRepository();
$categoryName = $torrent->basic_category->name;
$torrentUploader = $torrent->user;
$successCount = 0;
while (true) {
$logPage = "$logMsg, page: $page";
$users = (clone $query)->with(['language'])->forPage($page, $size)->get(['id', 'email', 'lang']);
if ($users->isEmpty()) {
do_log(sprintf("%s, no more user", $logPage));
break;
}
foreach ($users as $user) {
$locale = $user->locale;
$logUser = "$logPage, user $user->id, locale: $locale";
$subject = nexus_trans("upload.email_notification_subject", [
'site_name' => Setting::getSiteName()
], $locale);
$body = nexus_trans("upload.email_notification_body", [
'site_name' => Setting::getSiteName(),
'name' => $torrent->name,
'size' => mksize($torrent->size),
'category' => $categoryName,
'upload_by' => $this->handleAnonymous($torrentUploader->username, $torrentUploader, $user, $torrent),
'description' => Str::limit(strip_tags(format_comment($torrent->extra->descr)), 500),
'torrent_url' => sprintf("%s/details.php?id=%s&hit=1", getBaseUrl(), $torrent->id),
], $locale);
$sendResult = $toolRep->sendMail($user->email, $subject, $body);
do_log(sprintf("%s, send result: %s", $logUser, $sendResult));
if ($sendResult) {
$successCount++;
}
}
$page++;
}
do_log("$logMsg, receive email notification user total: $total, successCount: $successCount, done!");
return $successCount;
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Repositories;
use App\Exceptions\InsufficientPermissionException;
use App\Exceptions\NexusException;
use App\Http\Resources\ExamUserResource;
use App\Http\Resources\TorrentResource;
use App\Http\Resources\UserResource;
use App\Models\ExamUser;
use App\Models\Invite;
@@ -11,12 +12,15 @@ use App\Models\LoginLog;
use App\Models\Message;
use App\Models\Setting;
use App\Models\Snatch;
use App\Models\Torrent;
use App\Models\User;
use App\Models\UserBanLog;
use App\Models\UserMeta;
use App\Models\UserModifyLog;
use App\Models\UsernameChangeLog;
use App\Utils\ApiQueryBuilder;
use Carbon\Carbon;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
@@ -53,28 +57,30 @@ class UserRepository extends BaseRepository
return $user;
}
public function getDetail($id)
public function getDetail($id, Authenticatable $currentUser)
{
$with = [
'inviter' => function ($query) {return $query->select(User::$commonFields);},
'valid_medals'
];
$user = User::query()->with($with)->findOrFail($id);
$userResource = new UserResource($user);
$baseInfo = $userResource->response()->getData(true)['data'];
//query this info default
$query = User::query()->with([]);
$allowIncludes = ['inviter', 'valid_medals'];
$allowIncludeCounts = [];
$allowIncludeFields = [];
$apiQueryBuilder = ApiQueryBuilder::for(UserResource::NAME, $query)
->allowIncludes($allowIncludes)
->allowIncludeCounts($allowIncludeCounts)
->allowIncludeFields($allowIncludeFields)
;
$user = $apiQueryBuilder->build()->findOrFail($id);
return $this->appendIncludeFields($apiQueryBuilder, $currentUser, $user);
}
$examRep = new ExamRepository();
$examProgress = $examRep->getUserExamProgress($id, ExamUser::STATUS_NORMAL);
if ($examProgress) {
$examResource = new ExamUserResource($examProgress);
$examInfo = $examResource->response()->getData(true)['data'];
} else {
$examInfo = null;
}
return [
'base_info' => $baseInfo,
'exam_info' => $examInfo,
];
private function appendIncludeFields(ApiQueryBuilder $apiQueryBuilder, Authenticatable $currentUser, User $user): User
{
// $id = $torrent->id;
// if ($apiQueryBuilder->hasIncludeField('has_bookmarked')) {
// $torrent->has_bookmarked = (int)$user->bookmarks()->where('torrentid', $id)->exists();;
// }
return $user;
}
/**

View File

@@ -0,0 +1,235 @@
<?php
namespace App\Utils;
use App\Http\Resources\TorrentResource;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Builder;
/**
* GET /api/v1/torrents
* ?includes=author,comments.user
* &include_counts=thanks_user,reward_logs
* &extra_fields=id,title
* &include_fields[torrent]=thanks_user,reward_logs
* &filter[status]=published
* &filter[size][gt]=1024
* filter_any[source][gt]=1
* filter_any[codec]=1
* &sorts=-created_at
* &page=1
* &per_page=10
*/
class ApiQueryBuilder
{
protected array $allowedIncludes = [],
$requestIncludes = [],
$allowedFilters = [],
$allowedSorts = [],
$requestSorts = [],
$allowedIncludeCounts = [],
$allowedIncludeFields = [],
$requestIncludeFields = []
;
protected array $customFilterCallbacks = [];
const PARAM_NAME_INCLUDES = "includes";
const PARAM_NAME_INCLUDE_COUNTS = "include_counts";
const PARAM_NAME_INCLUDE_FIELDS = "include_fields";
const PARAM_NAME_FILTER = "filter";
const PARAM_NAME_FILTER_ANY = "filter_any";
const PARAM_NAME_SORTS = "sorts";
public function __construct(protected Request $request, protected Builder $query, protected string $resourceName) {}
public static function for(string $resourceName, Builder $query, Request $request = null): self
{
return new self($request ?? request(), $query, $resourceName);
}
public function allowIncludes(array $includes): self
{
$this->allowedIncludes = $includes;
$requestIncludesStr = $this->request->input(self::PARAM_NAME_INCLUDES);
$this->requestIncludes = explode(',', $requestIncludesStr);
return $this;
}
public function allowFilters(array $filters): self { $this->allowedFilters = $filters; return $this; }
public function allowSorts(array $sorts): self
{
$this->allowedSorts = $sorts;
$requestSortsStr = $this->request->input(self::PARAM_NAME_SORTS, "");
foreach (explode(',', $requestSortsStr) as $sort) {
$direction = str_starts_with($sort, '-') ? 'desc' : 'asc';
$field = ltrim($sort, '-');
$this->requestSorts[$field] = $direction;
}
return $this;
}
public function allowIncludeCounts(array $counts): self { $this->allowedIncludeCounts = $counts; return $this; }
public function allowIncludeFields(array $fields): self
{
$this->allowedIncludeFields = $fields;
$requestIncludeFieldsStr = $this->request->input(sprintf("%s.%s", self::PARAM_NAME_INCLUDE_FIELDS, $this->resourceName), "");
$this->requestIncludeFields = explode(',', $requestIncludeFieldsStr);
return $this;
}
public function build(): Builder
{
$this->applyIncludes();
$this->applyFilters();
$this->applyOrFilters();
$this->applyCustomFilters();
$this->applySorts();
$this->applyIncludeCounts();
return $this->query;
}
protected function applyIncludes(): void
{
$includes = explode(',', $this->request->query(self::PARAM_NAME_INCLUDES, ''));
$valid = array_intersect($this->allowedIncludes, $includes);
$this->query->with($valid);
}
protected function applyIncludeCounts(): void
{
$includeCounts = explode(',', $this->request->query(self::PARAM_NAME_INCLUDE_COUNTS, ''));
$valid = array_intersect($this->allowedIncludeCounts, $includeCounts);
do_log(sprintf(
"includeCounts: %s, allow: %s, valid: %s",
json_encode($includeCounts), json_encode($this->allowedIncludeCounts), json_encode($valid)
));
$this->query->withCount($valid);
}
protected function applyFilters(): void
{
$filters = $this->request->input(self::PARAM_NAME_FILTER, []);
// dd($filters);
foreach ($filters as $field => $value) {
if (!in_array($field, $this->allowedFilters)) continue;
if (isset($this->customFilterCallbacks[$field])) continue;
// 如果是复杂条件
if (is_array($value)) {
foreach ($value as $operator => $val) {
$this->applyFilterOperator($field, $operator, $val);
}
} else {
// 简单形式,默认等于
$this->query->where($field, '=', $value);
}
}
}
protected function applyFilterOperator(string $field, string $operator, mixed $value): void
{
match ($operator) {
'eq' => $this->query->where($field, '=', $value),
'gt' => $this->query->where($field, '>', $value),
'lt' => $this->query->where($field, '<', $value),
'gte' => $this->query->where($field, '>=', $value),
'lte' => $this->query->where($field, '<=', $value),
'like' => $this->query->where($field, 'like', $value),
'in' => $this->query->whereIn($field, is_array($value) ? $value : explode(',', $value)),
default => null
};
}
protected function applyOrFilters(): void
{
$filters = $this->request->input(self::PARAM_NAME_FILTER_ANY, []);
if (!empty($filters)) {
$this->query->where(function ($q) use ($filters) {
foreach ($filters as $field => $value) {
if (!in_array($field, $this->allowedFilters)) continue;
if (isset($this->customFilterCallbacks[$field])) continue;
if (is_array($value)) {
foreach ($value as $operator => $val) {
$this->applyFilterAnyOperator($q, $field, $operator, $val);
}
} else {
$q->orWhere($field, '=', $value);
}
}
});
}
}
protected function applyFilterAnyOperator(Builder $query, string $field, string $operator, mixed $value): void
{
match ($operator) {
'eq' => $query->orWhere($field, '=', $value),
'gt' => $query->orWhere($field, '>', $value),
'lt' => $query->orWhere($field, '<', $value),
'gte' => $query->orWhere($field, '>=', $value),
'lte' => $query->orWhere($field, '<=', $value),
'like' => $query->orWhere($field, 'like', $value),
'in' => $query->orWhereIn($field, is_array($value) ? $value : explode(',', $value)),
default => null
};
}
public function registerCustomFilter(string $field, callable $callback): self
{
$this->customFilterCallbacks[$field] = $callback;
return $this;
}
protected function applyCustomFilters(): void
{
foreach ($this->customFilterCallbacks as $field => $callback) {
call_user_func($callback, $this->query, $this->request);
}
}
protected function applySorts(): void
{
$sorts = explode(',', $this->request->query(self::PARAM_NAME_SORTS, ''));
foreach ($sorts as $sort) {
$direction = str_starts_with($sort, '-') ? 'desc' : 'asc';
$field = ltrim($sort, '-');
if (in_array($field, $this->allowedSorts)) {
$this->query->orderBy($field, $direction);
}
}
}
public function hasIncludeField(string $field = null): bool
{
return $this->hasBoth($this->allowedIncludeFields, $this->requestIncludeFields, $field);
}
public function hasInclude(string $name = null): bool
{
return $this->hasBoth($this->allowedIncludes, $this->requestIncludes, $name);
}
public function hasSort(string $field = ""):bool
{
return $this->hasBoth($this->allowedSorts, array_keys($this->requestSorts), $field);
}
private function hasBoth(array $dataA, array $dataB, mixed $oneKey = null): bool
{
$result = array_intersect($dataA, $dataB);
if (empty($oneKey)) {
return !empty($result);
}
return in_array($oneKey, $result);
}
}

View File

@@ -29,11 +29,11 @@
"ext-mbstring": "*",
"ext-mysqli": "*",
"ext-pcntl": "*",
"ext-posix": "*",
"ext-redis": "*",
"ext-xml": "*",
"ext-zend-opcache": "*",
"ext-zip": "*",
"ext-posix": "*",
"calebporzio/sushi": "^2.5",
"elasticsearch/elasticsearch": "^7.16",
"filament/filament": "^3.2",
@@ -51,7 +51,8 @@
"orangehill/iseed": "^3.0",
"phpgangsta/googleauthenticator": "dev-master",
"rhilip/bencode": "^2.0",
"rlanvin/php-ip": "^3.0"
"rlanvin/php-ip": "^3.0",
"stichoza/google-translate-php": "^5.2"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

86
composer.lock generated
View File

@@ -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": "4391f5fb2633f1fa33c4099a1b176b78",
"content-hash": "e1de7765d56f7550f8cae764df3f7753",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@@ -6965,6 +6965,87 @@
],
"time": "2024-12-30T13:13:39+00:00"
},
{
"name": "stichoza/google-translate-php",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/Stichoza/google-translate-php.git",
"reference": "9429773d991c98f68a25bec40d20f590ea3312a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Stichoza/google-translate-php/zipball/9429773d991c98f68a25bec40d20f590ea3312a0",
"reference": "9429773d991c98f68a25bec40d20f590ea3312a0",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-json": "*",
"ext-mbstring": "*",
"guzzlehttp/guzzle": "^7.0",
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5.10"
},
"type": "library",
"autoload": {
"psr-4": {
"Stichoza\\GoogleTranslate\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Levan Velijanashvili",
"email": "me@stichoza.com"
}
],
"description": "Free Google Translate API PHP Package",
"homepage": "https://github.com/Stichoza/google-translate-php",
"keywords": [
"google",
"php",
"translate",
"translating",
"translator"
],
"support": {
"issues": "https://github.com/Stichoza/google-translate-php/issues",
"source": "https://github.com/Stichoza/google-translate-php/tree/v5.2.0"
},
"funding": [
{
"url": "https://btc.com/bc1qc25j4x7yahghm8nnn6lypnw59nptylsw32nkfl",
"type": "custom"
},
{
"url": "https://www.paypal.me/stichoza",
"type": "custom"
},
{
"url": "https://ko-fi.com/stichoza",
"type": "ko_fi"
},
{
"url": "https://liberapay.com/stichoza",
"type": "liberapay"
},
{
"url": "https://opencollective.com/stichoza",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/stichoza",
"type": "patreon"
}
],
"time": "2024-08-05T19:11:36+00:00"
},
{
"name": "symfony/clock",
"version": "v7.2.0",
@@ -13044,11 +13125,12 @@
"ext-mbstring": "*",
"ext-mysqli": "*",
"ext-pcntl": "*",
"ext-posix": "*",
"ext-redis": "*",
"ext-xml": "*",
"ext-zend-opcache": "*",
"ext-zip": "*"
},
"platform-dev": [],
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View File

@@ -44,14 +44,15 @@ return [
'single' => [
'driver' => 'single',
'tap' => [\App\Logging\NexusFormatter::class],
'path' => env('LOG_FILE', '/tmp/nexus.log'),
'path' => getLogFile(),
'level' => env('LOG_LEVEL', 'debug'),
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => env('LOG_FILE', '/tmp/nexus.log'),
// 'path' => env('LOG_FILE', '/tmp/nexus.log'),
'path' => getLogFile(),
'level' => env('LOG_LEVEL', 'debug'),
'tap' => [\App\Logging\NexusFormatter::class],
'days' => 14,
@@ -104,7 +105,8 @@ return [
],
'emergency' => [
'path' => env('LOG_FILE', '/tmp/nexus.log'),
// 'path' => env('LOG_FILE', '/tmp/nexus.log'),
'path' => getLogFile(),
],
],

View File

@@ -11,11 +11,8 @@ return new class extends Migration
*/
public function up(): void
{
Schema::create('personal_access_token_plains', function (Blueprint $table) {
$table->id();
$table->bigInteger('access_token_id')->unsigned();
$table->string("plain_text_token");
$table->timestamps();
Schema::table('sitelog', function (Blueprint $table) {
$table->integer('uid')->default(0);
});
}
@@ -24,6 +21,8 @@ return new class extends Migration
*/
public function down(): void
{
Schema::dropIfExists('personal_access_token_plains');
Schema::table('sitelog', function (Blueprint $table) {
$table->dropColumn('uid');
});
}
};

View File

@@ -1,6 +1,6 @@
<?php
defined('VERSION_NUMBER') || define('VERSION_NUMBER', '1.9.0');
defined('RELEASE_DATE') || define('RELEASE_DATE', '2025-04-05');
defined('RELEASE_DATE') || define('RELEASE_DATE', '2025-04-17');
defined('IN_TRACKER') || define('IN_TRACKER', false);
defined('PROJECTNAME') || define("PROJECTNAME","NexusPHP");
defined('NEXUSPHPURL') || define("NEXUSPHPURL","https://nexusphp.org");

View File

@@ -1,6 +1,7 @@
<?php
use App\Models\SearchBox;
use Carbon\CarbonInterface;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
@@ -722,10 +723,12 @@ function get_slr_color($ratio)
function write_log($text, $security = "normal")
{
$text = sqlesc($text);
$added = sqlesc(date("Y-m-d H:i:s"));
$security = sqlesc($security);
sql_query("INSERT INTO sitelog (added, txt, security_level) VALUES($added, $text, $security)") or sqlerr(__FILE__, __LINE__);
\App\Models\SiteLog::query()->insert([
'added' => now(),
'txt' => $text,
'security_level' => $security,
'uid' => get_user_id(),
]);
}
@@ -2219,9 +2222,8 @@ function validlang($langid) {
function get_if_restricted_is_open()
{
global $sptime;
// it's sunday
if($sptime == 'yes' && (date("w",time()) == '0' || (date("w",time()) == 6) && (date("G",time()) >=12 && date("G",time()) <=23)))
if(\App\Models\Setting::getIsUploadOpenAtWeekend() && (date("w",time()) == '0' || (date("w",time()) == 6) && (date("G",time()) >=12 && date("G",time()) <=23)))
{
return true;
}
@@ -2992,7 +2994,6 @@ function set_langfolder_cookie($folder, $expires = 0x7fffffff)
function get_protocol_prefix()
{
global $securelogin;
if (isHttps()) {
return "https://";
}
@@ -4277,7 +4278,18 @@ function permissiondenied($allowMinimumClass = null){
}
function gettime($time, $withago = true, $twoline = false, $forceago = false, $oneunit = false, $isfuturetime = false){
global $lang_functions, $CURUSER;
if (!IN_NEXUS) {
if (empty($time)) {
return null;
}
try {
return \Carbon\Carbon::parse($time)->diffForHumans();
} catch (\Exception $e) {
do_log($e->getMessage() . $e->getTraceAsString(), 'error');
return $time;
}
}
global $lang_functions, $CURUSER;
if (isset($CURUSER) && $CURUSER['timetype'] != 'timealive' && !$forceago){
$newtime = $time;
if ($twoline){
@@ -5910,7 +5922,8 @@ function insert_torrent_tags($torrentId, $tagIdArr, $sync = false)
$canSetSpecialTag = user_can('torrent-set-special-tag');
$dateTimeStringNow = date('Y-m-d H:i:s');
if ($sync) {
sql_query("delete from torrent_tags where torrent_id = $torrentId");
\App\Models\TorrentTag::query()->where("torrent_id", $torrentId)->delete();
// sql_query("delete from torrent_tags where torrent_id = $torrentId");
}
if (empty($tagIdArr)) {
return;
@@ -5926,7 +5939,8 @@ function insert_torrent_tags($torrentId, $tagIdArr, $sync = false)
}
$insertTagsSql .= implode(', ', $values);
do_log("[INSERT_TAGS], torrent: $torrentId with tags: " . nexus_json_encode($tagIdArr));
sql_query($insertTagsSql);
\Nexus\Database\NexusDB::statement($insertTagsSql);
// sql_query($insertTagsSql);
}
function get_smile($num)

View File

@@ -242,6 +242,9 @@ function getLogFile($append = '')
return $logFiles[$append];
}
$config = nexus_config('nexus');
if (!empty($config['log_file']) && in_array($config['log_files'], ["/dev/stdout", "/dev/stderr"])) {
return $logFiles[$append] = $config['log_files'];
}
$path = getenv('NEXUS_LOG_DIR', true);
$fromEnv = true;
if ($path === false) {
@@ -500,12 +503,14 @@ function getSchemeAndHttpHost(bool $fromConfig = false)
function getBaseUrl()
{
$url = getSchemeAndHttpHost();
$requestUri = $_SERVER['REQUEST_URI'];
$pos = strpos($requestUri, '?');
if ($pos !== false) {
$url .= substr($requestUri, 0, $pos);
} else {
$url .= $requestUri;
if (!isRunningInConsole()) {
$requestUri = $_SERVER['REQUEST_URI'];
$pos = strpos($requestUri, '?');
if ($pos !== false) {
$url .= substr($requestUri, 0, $pos);
} else {
$url .= $requestUri;
}
}
return trim($url, '/');
}
@@ -528,13 +533,10 @@ function api(...$args)
$msg = $args[1];
$data = $args[2];
}
if ($data instanceof \Illuminate\Http\Resources\Json\ResourceCollection || $data instanceof \Illuminate\Http\Resources\Json\JsonResource) {
if ($data instanceof \Illuminate\Http\Resources\Json\JsonResource) {
$data = $data->response()->getData(true);
if (isset($data['data']) && count($data) == 1) {
//单纯的集合,无分页等其数据
$data = $data['data'];
}
}
// dd($data);
$time = (float)number_format(microtime(true) - nexus()->getStartTimestamp(), 3);
$count = null;
$resultKey = 'ret';
@@ -630,7 +632,7 @@ function last_query($all = false)
function format_datetime($datetime, $format = 'Y-m-d H:i')
{
if (empty($datetime)) {
return '';
return null;
}
try {
$carbonTime = \Carbon\Carbon::parse($datetime);
@@ -1320,15 +1322,28 @@ function get_snatch_info($torrentId, $userId)
*/
function fire_event(string $name, \Illuminate\Database\Eloquent\Model $model, \Illuminate\Database\Eloquent\Model $oldModel = null): void
{
$prefix = "fire_event:";
$idKey = $prefix . \Illuminate\Support\Str::random();
$idKeyOld = "";
\Nexus\Database\NexusDB::cache_put($idKey, serialize($model), 3600*24*30);
if ($oldModel) {
$idKeyOld = $prefix . \Illuminate\Support\Str::random();
\Nexus\Database\NexusDB::cache_put($idKeyOld, serialize($oldModel), 3600*24*30);
if (!isset(\App\Enums\ModelEventEnum::$eventMaps[$name])) {
throw new \InvalidArgumentException("Event $name is not a valid event enumeration");
}
if (IN_NEXUS) {
$prefix = "fire_event:";
$idKey = $prefix . \Illuminate\Support\Str::random();
$idKeyOld = "";
\Nexus\Database\NexusDB::cache_put($idKey, serialize($model), 3600*24*30);
if ($oldModel) {
$idKeyOld = $prefix . \Illuminate\Support\Str::random();
\Nexus\Database\NexusDB::cache_put($idKeyOld, serialize($oldModel), 3600*24*30);
}
executeCommand("event:fire --name=$name --idKey=$idKey --idKeyOld=$idKeyOld", "string", true, false);
} else {
$eventClass = \App\Enums\ModelEventEnum::$eventMaps[$name]['event'];
$params = [$model];
if ($oldModel) {
$params[] = $oldModel;
}
call_user_func_array([$eventClass, "dispatch"], $params);
publish_model_event($name, $model->id);
}
executeCommand("event:fire --name=$name --idKey=$idKey --idKeyOld=$idKeyOld", "string", true, false);
}
/**

View File

@@ -12,6 +12,7 @@ $lang_log = array
'title_time_added' => "时间",
'col_date' => "日期",
'col_event' => "事件",
'col_user' => '用户',
'time_zone_note' => "<p>时间为北京时间。</p>\n",
'text_daily_log' => "&nbsp;常&nbsp;规&nbsp;日&nbsp;志&nbsp;",
'text_chronicle' => "&nbsp;史&nbsp;&nbsp;册&nbsp;",

View File

@@ -12,6 +12,7 @@ $lang_log = array
'title_time_added' => "時間",
'col_date' => "日期",
'col_event' => "事件",
'col_user' => '用戶',
'time_zone_note' => "<p>時間為北京時間。</p>\n",
'text_daily_log' => "&nbsp;常&nbsp;規&nbsp;日&nbsp;志&nbsp;",
'text_chronicle' => "&nbsp;史&nbsp;&nbsp;冊&nbsp;",

View File

@@ -43,6 +43,7 @@ $lang_log = array
'text_news_empty' => "<b>News is empty</b><br />",
'col_title' => "Title",
'col_body' => "Body",
'col_user' => 'User',
'std_delete_poll' => "Delete poll",
'std_delete_poll_confirmation' => "Do you really want to delete a poll? Click ",
'std_here_if_sure' => "<b>here</b></a> if you are sure.",

View File

@@ -148,20 +148,25 @@ class TechnicalInformation
public function renderOnDetailsPage()
{
global $lang_functions;
$videos = [
'Runtime' => $this->getRuntime(),
'Resolution' => $this->getResolution(),
'Bitrate' => $this->getBitrate(),
'HDR' => $this->getHDRFormat(),
'Bit depth' => $this->getBitDepth(),
'Frame rate' => $this->getFramerate(),
'Profile' => $this->getProfile(),
'Ref.Frames' => $this->getRefFrame(),
];
$videos = array_filter($videos);
$audios = $this->getAudios();
$subtitles = $this->getSubtitles();
// dd($videos, $audios, $subtitles);
// $videos = [
// 'Runtime' => $this->getRuntime(),
// 'Resolution' => $this->getResolution(),
// 'Bitrate' => $this->getBitrate(),
// 'HDR' => $this->getHDRFormat(),
// 'Bit depth' => $this->getBitDepth(),
// 'Frame rate' => $this->getFramerate(),
// 'Profile' => $this->getProfile(),
// 'Ref.Frames' => $this->getRefFrame(),
// ];
// $videos = array_filter($videos);
// $audios = $this->getAudios();
// $subtitles = $this->getSubtitles();
$summaryInfo = $this->getSummaryInfo();
$videos = $summaryInfo['videos'] ?: [];
$audios = $summaryInfo['audios'] ?: [];
$subtitles = $summaryInfo['subtitles'] ?: [];
// dd($summaryInfo, $videos, $audios, $subtitles);
if (empty($videos) && empty($audios) && empty($subtitles)) {
return sprintf('<div class="nexus-media-info-raw"><pre>%s</pre></div>', $this->mediaInfo);
}
@@ -187,6 +192,24 @@ class TechnicalInformation
return $result;
}
public function getSummaryInfo(): array
{
$videos = [
'Runtime' => $this->getRuntime(),
'Resolution' => $this->getResolution(),
'Bitrate' => $this->getBitrate(),
'HDR' => $this->getHDRFormat(),
'Bit depth' => $this->getBitDepth(),
'Frame rate' => $this->getFramerate(),
'Profile' => $this->getProfile(),
'Ref.Frames' => $this->getRefFrame(),
];
$videos = array_filter($videos) ?: null;
$audios = $this->getAudios() ?: null;
$subtitles = $this->getSubtitles() ?: null;
return compact('videos', 'audios', 'subtitles');
}
private function buildTdTable(array $parts)
{
$table = '<table style="border: none;"><tbody>';

View File

@@ -121,7 +121,7 @@ else {
list($pagertop, $pagerbottom, $limit) = pager($perpage, $count, "log.php?action=dailylog&".$addparam);
$res = sql_query("SELECT added, txt FROM sitelog $wherea ORDER BY added DESC $limit") or sqlerr(__FILE__, __LINE__);
$res = sql_query("SELECT * FROM sitelog $wherea ORDER BY added DESC $limit") or sqlerr(__FILE__, __LINE__);
if (mysql_num_rows($res) == 0)
print($lang_log['text_log_empty']);
else
@@ -130,7 +130,11 @@ else {
//echo $pagertop;
print("<table width=940 border=1 cellspacing=0 cellpadding=5>\n");
print("<tr><td class=colhead align=center><img class=\"time\" src=\"pic/trans.gif\" alt=\"time\" title=\"".$lang_log['title_time_added']."\" /></td><td class=colhead align=left>".$lang_log['col_event']."</td></tr>\n");
print("<tr><td class=colhead align=center><img class=\"time\" src=\"pic/trans.gif\" alt=\"time\" title=\"".$lang_log['title_time_added']."\" /></td><td class=colhead align=left>".$lang_log['col_event']);
if (user_can('confilog')){
print("<td class=colhead align=left>".$lang_log['col_user']."</td>");
}
print("</td></tr>\n");
while ($arr = mysql_fetch_assoc($res))
{
$color = "";
@@ -139,7 +143,11 @@ else {
if (strpos($arr['txt'],'was added to the Request section')) $color = "purple";
if (strpos($arr['txt'],'was edited by')) $color = "blue";
if (strpos($arr['txt'],'settings updated by')) $color = "darkred";
print("<tr><td class=\"rowfollow nowrap\" align=center>".gettime($arr['added'],true,false)."</td><td class=rowfollow align=left><font color='".$color."'>".htmlspecialchars($arr['txt'])."</font></td></tr>\n");
print("<tr><td class=\"rowfollow nowrap\" align=center>".gettime($arr['added'],true,false)."</td><td class=rowfollow align=left><font color='".$color."'>".htmlspecialchars($arr['txt'])."</font></td>");
if (user_can('confilog')){
print("<td class=rowfollow align=left>".($arr['uid'] > 0 ? get_username($arr['uid']) : "System")."</td>");
}
print("</tr>\n");
}
print("</table>");

View File

@@ -34,7 +34,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST")
failedlogins($lang_recover['std_missing_email_address'],true);
if (!check_email($email))
failedlogins($lang_recover['std_invalid_email_address'],true);
$res = sql_query("SELECT * FROM users WHERE email=" . sqlesc($email) . " LIMIT 1") or sqlerr(__FILE__, __LINE__);
$res = sql_query("SELECT * FROM users WHERE BINARY email=" . sqlesc($email) . " LIMIT 1") or sqlerr(__FILE__, __LINE__);
$arr = mysql_fetch_assoc($res);
if (!$arr) failedlogins($lang_recover['std_email_not_in_database'],true);
if ($arr['status'] == "pending") failedlogins($lang_recover['std_user_account_unconfirmed'],true);

View File

@@ -746,7 +746,7 @@ elseif ($action == 'torrentsettings')
tr($lang_settings['row_tax_factor'],"<input type='number' name=tax_factor style=\"width: 100px\" value={$TORRENT['tax_factor']}> ".$lang_settings['text_tax_factor_note'], 1);
tr($lang_settings['row_max_price'],"<input type='number' name=max_price style=\"width: 100px\" value={$TORRENT['max_price']}> ".$lang_settings['text_max_price_note'], 1);
yesorno($lang_settings['row_promotion_rules'], 'prorules', $TORRENT["prorules"], $lang_settings['text_promotion_rules_note']);
// yesorno($lang_settings['row_promotion_rules'], 'prorules', $TORRENT["prorules"], $lang_settings['text_promotion_rules_note']);
tr($lang_settings['row_random_promotion'], $lang_settings['text_random_promotion_note_one']."<ul><li><input type='text' style=\"width: 50px\" name=randomhalfleech value='".(isset($TORRENT["randomhalfleech"]) ? $TORRENT["randomhalfleech"] : 5 )."'>".$lang_settings['text_halfleech_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomfree value='".(isset($TORRENT["randomfree"]) ? $TORRENT["randomfree"] : 2 )."'>".$lang_settings['text_free_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomtwoup value='".(isset($TORRENT["randomtwoup"]) ? $TORRENT["randomtwoup"] : 2 )."'>".$lang_settings['text_twoup_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomtwoupfree value='".(isset($TORRENT["randomtwoupfree"]) ? $TORRENT["randomtwoupfree"] : 1 )."'>".$lang_settings['text_freetwoup_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomtwouphalfdown value='".(isset($TORRENT["randomtwouphalfdown"]) ? $TORRENT["randomtwouphalfdown"] : 0 )."'>".$lang_settings['text_twouphalfleech_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomthirtypercentdown value='".(isset($TORRENT["randomthirtypercentdown"]) ? $TORRENT["randomthirtypercentdown"] : 0 )."'>".$lang_settings['text_thirtypercentleech_chance_becoming']."</li></ul>".$lang_settings['text_random_promotion_note_two'], 1);
tr($lang_settings['row_large_torrent_promotion'], $lang_settings['text_torrent_larger_than']."<input type='text' style=\"width: 50px\" name=largesize value='".(isset($TORRENT["largesize"]) ? $TORRENT["largesize"] : 20 )."'>".$lang_settings['text_gb_promoted_to']."<select name=largepro>".promotion_selection((isset($TORRENT['largepro']) ? $TORRENT['largepro'] : 2), 1)."</select>".$lang_settings['text_by_system_upon_uploading']."<br />".$lang_settings['text_large_torrent_promotion_note'], 1);
tr($lang_settings['row_promotion_timeout'], $lang_settings['text_promotion_timeout_note_one']."<ul>

View File

@@ -83,7 +83,7 @@ if ($nfoaction == "update")
$Cache->delete_value('nfo_block_torrent_id_'.$id);
}
elseif ($nfoaction == "remove"){
$updateset[] = "nfo = ''";
$extraUpdate["nfo"] = "";
$Cache->delete_value('nfo_block_torrent_id_'.$id);
}
}

View File

@@ -29,7 +29,7 @@ if ($useChallengeResponse) {
}
}
$res = sql_query("SELECT id, passhash, secret, auth_key, enabled, status, two_step_secret, lang FROM users WHERE username = " . sqlesc($username));
$res = sql_query("SELECT id, passhash, secret, auth_key, enabled, status, two_step_secret, lang FROM users WHERE BINARY username = " . sqlesc($username));
$row = mysql_fetch_array($res);
if (!$row)

View File

@@ -134,7 +134,7 @@ if ($_POST["rulesverify"] != "yes" || $_POST["faqverify"] != "yes" || $_POST["ag
stderr($lang_takesignup['std_signup_failed'], $lang_takesignup['std_unqualified']);
// check if email addy is already in use
$a = (@mysql_fetch_row(@sql_query("select count(*) from users where email='".mysql_real_escape_string($email)."'"))) or sqlerr(__FILE__, __LINE__);
$a = (@mysql_fetch_row(@sql_query("select count(*) from users where BINARY email='".mysql_real_escape_string($email)."'"))) or sqlerr(__FILE__, __LINE__);
if ($a[0] != 0)
bark($lang_takesignup['std_email_address'].$email.$lang_takesignup['std_in_use']);

View File

@@ -275,34 +275,34 @@ else{ //ramdom torrent promotion
else
$sp_state = 1; //normal
}
if ($altname_main == 'yes'){
$cnname_part = unesc(trim($_POST["cnname"]));
$size_part = str_replace(" ", "", mksize($totallen));
$date_part = date("m.d.y");
$category_part = get_single_value("categories","name","WHERE id = ".sqlesc($catid));
$torrent = "".$date_part."".($_POST["name"] ? "[".$_POST["name"]."]" : "").($cnname_part ? "[".$cnname_part."]" : "");
}
//
//if ($altname_main == 'yes'){
//$cnname_part = unesc(trim($_POST["cnname"]));
//$size_part = str_replace(" ", "", mksize($totallen));
//$date_part = date("m.d.y");
//$category_part = get_single_value("categories","name","WHERE id = ".sqlesc($catid));
//$torrent = "【".$date_part."】".($_POST["name"] ? "[".$_POST["name"]."]" : "").($cnname_part ? "[".$cnname_part."]" : "");
//}
// some ugly code of automatically promoting torrents based on some rules
if ($prorules_torrent == 'yes'){
foreach ($promotionrules_torrent as $rule)
{
if (!array_key_exists('catid', $rule) || in_array($catid, $rule['catid']))
if (!array_key_exists('sourceid', $rule) || in_array($sourceid, $rule['sourceid']))
if (!array_key_exists('mediumid', $rule) || in_array($mediumid, $rule['mediumid']))
if (!array_key_exists('codecid', $rule) || in_array($codecid, $rule['codecid']))
if (!array_key_exists('standardid', $rule) || in_array($standardid, $rule['standardid']))
if (!array_key_exists('processingid', $rule) || in_array($processingid, $rule['processingid']))
if (!array_key_exists('teamid', $rule) || in_array($teamid, $rule['teamid']))
if (!array_key_exists('audiocodecid', $rule) || in_array($audiocodecid, $rule['audiocodecid']))
if (!array_key_exists('pattern', $rule) || preg_match($rule['pattern'], $torrent))
if (is_numeric($rule['promotion'])){
$sp_state = $rule['promotion'];
break;
}
}
}
//if ($prorules_torrent == 'yes'){
//foreach ($promotionrules_torrent as $rule)
//{
// if (!array_key_exists('catid', $rule) || in_array($catid, $rule['catid']))
// if (!array_key_exists('sourceid', $rule) || in_array($sourceid, $rule['sourceid']))
// if (!array_key_exists('mediumid', $rule) || in_array($mediumid, $rule['mediumid']))
// if (!array_key_exists('codecid', $rule) || in_array($codecid, $rule['codecid']))
// if (!array_key_exists('standardid', $rule) || in_array($standardid, $rule['standardid']))
// if (!array_key_exists('processingid', $rule) || in_array($processingid, $rule['processingid']))
// if (!array_key_exists('teamid', $rule) || in_array($teamid, $rule['teamid']))
// if (!array_key_exists('audiocodecid', $rule) || in_array($audiocodecid, $rule['audiocodecid']))
// if (!array_key_exists('pattern', $rule) || preg_match($rule['pattern'], $torrent))
// if (is_numeric($rule['promotion'])){
// $sp_state = $rule['promotion'];
// break;
// }
//}
//}
$dateTimeStringNow = \Carbon\Carbon::now()->toDateTimeString();
$torrentSavePath = getFullDirectory($torrent_dir);
@@ -452,15 +452,15 @@ $torrentRep = new \App\Repositories\TorrentRepository();
$torrentRep->addPiecesHashCache($id, $insert['pieces_hash']);
write_log("Torrent $id ($torrent) was uploaded by $anon");
$searchRep = new \App\Repositories\SearchRepository();
$searchRep->addTorrent($id);
$meiliSearch = new \App\Repositories\MeiliSearchRepository();
$meiliSearch->doImportFromDatabase($id);
//move to event listener
//$searchRep = new \App\Repositories\SearchRepository();
//$searchRep->addTorrent($id);
//
//$meiliSearch = new \App\Repositories\MeiliSearchRepository();
//$meiliSearch->doImportFromDatabase($id);
//trigger event
fire_event("torrent_created", \App\Models\Torrent::query()->find($id));
fire_event(\App\Enums\ModelEventEnum::TORRENT_CREATED, \App\Models\Torrent::query()->find($id));
//===notify people who voted on offer thanks CoLdFuSiOn :)
if ($is_offer)

View File

@@ -1150,7 +1150,7 @@ if ($res->count() > 0)
$token .= sprintf('<td>%s</td>', $tokenRecord->name);
$token .= sprintf('<td>%s</td>', $tokenRecord->abilitiesText);
$token .= sprintf('<td>%s</td>', $tokenRecord->created_at);
$token .= sprintf('<td><span style="cursor: pointer;margin-right: 10px" class="token-get" data-id="%s">获取</span><span style="cursor: pointer" title="%s" data-id="%s" class="token-del">删除</span></td>', $tokenRecord->id, $lang_functions['text_delete'], $tokenRecord->id);
$token .= sprintf('<td><img style="cursor: pointer" class="staff_delete token-del" src="pic/trans.gif" alt="D" title="%s" data-id="%s"></td>', $lang_functions['text_delete'], $tokenRecord->id);
$token .= "</tr>";
}
$token .= '</table>';
@@ -1184,13 +1184,16 @@ jQuery('#add-token-box-btn').on('click', function () {
jQuery('body').loading({stoppable: false});
let params = jQuery('#token-box-form').serialize()
jQuery.post('/web/token/add', params, function (response) {
jQuery('body').loading('stop');
console.log(response)
if (response.ret != 0) {
jQuery('body').loading('stop');
layer.alert(response.msg)
return
} else {
layer.alert(response.msg, function(index) {
layer.close(index);
window.location.reload()
})
}
window.location.reload()
}, 'json')
}
})
@@ -1211,19 +1214,6 @@ jQuery('#token-table').on('click', '.token-del', function () {
}, 'json')
})
});
jQuery('#token-table').on('click', '.token-get', function () {
let params = {id: jQuery(this).attr("data-id")}
jQuery('body').loading({stoppable: false});
jQuery.post('/web/token/get-plain', params, function (response) {
console.log(response)
jQuery('body').loading('stop');
if (response.ret != 0) {
layer.alert(response.msg)
} else {
layer.alert(response.data)
}
}, 'json')
});
JS;
\Nexus\Nexus::js($tokenBoxJs, 'footer', false);

View File

@@ -26,6 +26,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => 'Torrent be downloaded',
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => 'Receive reward',
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => 'Receive gift',
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => 'Upload torrent',
],
'fields' => [
'business_type' => 'Business type',

View File

@@ -1,6 +1,8 @@
<?php
return [
"label" => "Access token",
"permission" => "Permission",
];
return array (
'label' => 'Access Token',
'permission' => 'Permissions',
'maximum_allow_number_reached' => 'The number reaches the upper limit',
'create_success_tip' => 'The token was created successfully, this data is displayed only once, please save it properly<br/><br/>:token',
);

View File

@@ -0,0 +1,68 @@
<?php
return array (
'invalid_price' => 'Invalid price: :price',
'invalid_category' => 'Invalid type',
'invalid_section' => 'Invalid section',
'invalid_hr' => 'Invalid H&R value',
'invalid_pos_state' => 'Invalid position: :pos_state',
'invalid_pos_state_until' => 'Invalid position deadline',
'not_supported_sub_category_field' => 'Unsupported subcategory fields: :field',
'invalid_sub_category_value' => 'Subcategory field: :label(:field) value: :value invalid',
'invalid_tag' => 'Invalid tag::tag',
'invalid_pick_type' => 'Invalid recommendation::pick_type',
'require_name' => 'The title cannot be empty',
'price_too_much' => 'Price exceeds the allowable range',
'approval_deny_reach_upper_limit' => 'The number of torrent rejected for the current review: %s reaches the upper limit and publishing is not allowed.',
'special_section_not_enabled' => 'Special zone is not enabled.',
'paid_torrent_not_enabled' => 'The paid torrent is not enabled.',
'no_permission_to_set_torrent_hr' => 'No permission to set torrent H&R.',
'no_permission_to_set_torrent_pos_state' => 'There is no permission to set torrent top.',
'no_permission_to_set_torrent_price' => 'No permission to set torrent charges.',
'no_permission_to_pick_torrent' => 'No permission to recommend videos.',
'no_permission_to_be_anonymous' => 'No permission to publish anonymously.',
'torrent_save_dir_not_exists' => 'The torrent save directory does not exist.',
'torrent_save_dir_not_writable' => 'The torrent save directory is not writable.',
'save_torrent_file_failed' => 'Saving the torrent file failed.',
'upload_failed' => 'Upload failed!',
'missing_form_data' => 'Please fill in the required items',
'missing_torrent_file' => 'Missing torrent file',
'empty_filename' => 'The file name cannot be empty!',
'zero_byte_nfo' => 'NFO file is empty',
'nfo_too_big' => 'The NFO file is too large! Maximum allowable by 65,535 bytes.',
'nfo_upload_failed' => 'NFO file upload failed',
'blank_description' => 'You must fill in the introduction!',
'category_unselected' => 'You have to choose the type!',
'invalid_filename' => 'Invalid file name!',
'filename_not_torrent' => 'Invalid file name (not .torrent file).',
'empty_file' => 'Empty file!',
'not_bencoded_file' => 'What the hell are you doing? What you uploaded is not a Bencode file!',
'not_a_dictionary' => 'Not a directory',
'dictionary_is_missing_key' => 'Directory missing value',
'invalid_entry_in_dictionary' => 'Invalid directory entry',
'invalid_dictionary_entry_type' => 'Invalid directory item type',
'invalid_pieces' => 'Invalid file block',
'missing_length_and_files' => 'Missing length and file',
'filename_errors' => 'Error file name',
'uploaded_not_offered' => 'You can only upload torrent that pass the candidate. Please return to select the appropriate project in <b>your candidate</b> before uploading!',
'unauthorized_upload_freely' => 'You do not have the permission to upload freely!',
'torrent_existed' => 'The torrent already exists!id: :id',
'torrent_file_too_big' => 'The torrent file is too large! Maximum allowable',
'remake_torrent_note' => 'bytes. Please re-create the torrent file with a larger block size, or split the content into multiple torrent to publish.',
'email_notification_body' => 'Hello,
A new torrent has been uploaded.
Name::name
Size::size
Type::category
Uploader::upload_by
Introduction:
:description
View more detailed information and download it (you may need to log in), please click here: <b><a href=javascript:void(null) onclick=window.open(\':torrent_url\')>here</a></b>
:torrent_url
:site_name Website',
'email_notification_subject' => ':site_name New torrent notification',
);

View File

@@ -28,6 +28,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => '种子被下载',
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => '收到奖励',
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => '收到礼物',
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => '发布种子',
],
'fields' => [
'business_type' => '业务类型',

View File

@@ -223,4 +223,12 @@ return [
'text' => '获取种子列表',
'desc' => '获取种子列表',
],
'torrent:view' => [
'text' => '查看种子详情',
'desc' => '查看种子详情',
],
'user:view' => [
'text' => '查看用户基本信息',
'desc' => '查看用户基本信息',
],
];

View File

@@ -3,4 +3,6 @@
return [
"label" => "访问令牌",
"permission" => "权限",
"maximum_allow_number_reached" => "数量达到上限",
"create_success_tip" => "token 创建成功,此数据只展示一次,请妥善保存<br/><br/>:token",
];

View File

@@ -0,0 +1,56 @@
<?php
return [
'invalid_price' => '无效的价格::price',
'invalid_category' => '无效的分类',
'invalid_section' => '无效的分区',
'invalid_hr' => '无效的 H&R 值',
'invalid_pos_state' => '无效的位置::pos_state',
'invalid_pos_state_until' => '无效的位置截止时间',
'not_supported_sub_category_field' => '不支持的子分类字段::field',
'invalid_sub_category_value' => '子分类字段::label(:field) 的值::value 无效',
'invalid_tag' => '无效的标签::tag',
'invalid_pick_type' => '无效的推荐::pick_type',
'require_name' => '标题不能为空',
'price_too_much' => '价格超过允许范围',
'approval_deny_reach_upper_limit' => '当前审核被拒绝的种子数:%s 达到上限,不允许发布。',
'special_section_not_enabled' => '特别区未启用。',
'paid_torrent_not_enabled' => '收费种子未启用。',
'no_permission_to_set_torrent_hr' => '没有权限设置种子 H&R。',
'no_permission_to_set_torrent_pos_state' => '没有权限设置种子置顶。',
'no_permission_to_set_torrent_price' => '没有权限设置种子收费。',
'no_permission_to_pick_torrent' => '没有权限推荐影片。',
'no_permission_to_be_anonymous' => '没有权限匿名发布。',
'torrent_save_dir_not_exists' => '种子保存目录不存在。',
'torrent_save_dir_not_writable' => '种子保存目录不可写。',
'save_torrent_file_failed' => '保存种子文件失败。',
'upload_failed' => "上传失败!",
'missing_form_data' => "请填写必填项目",
'missing_torrent_file' => "缺少种子文件",
'empty_filename' => "文件名不能为空!",
'zero_byte_nfo' => "NFO文件为空",
'nfo_too_big' => "NFO文件过大最大允许65,535 bytes。",
'nfo_upload_failed' => "NFO文件上传失败",
'blank_description' => "你必须填写简介!",
'category_unselected' => "你必须选择类型!",
'invalid_filename' => "无效的文件名!",
'filename_not_torrent' => "无效的文件名(不是.torrent文件).",
'empty_file' => "空文件!",
'not_bencoded_file' => "你在搞什么鬼你上传的不是Bencode文件",
'not_a_dictionary' => "不是目录",
'dictionary_is_missing_key' => "目录缺少值",
'invalid_entry_in_dictionary' => "无效的目录项",
'invalid_dictionary_entry_type' => "无效的目录项类型",
'invalid_pieces' => "无效的文件块",
'missing_length_and_files' => "缺少长度和文件",
'filename_errors' => "文件名错误",
'uploaded_not_offered' => "你只能上传通过候选的种子,请返回在<b>你的候选</b>中选择合适项目后再上传!",
'unauthorized_upload_freely' => "你没有自由上传的权限!",
'torrent_existed' => "该种子已存在id: :id",
'torrent_file_too_big' => "种子文件过大!最大允许",
'remake_torrent_note' => " bytes。请使用更大的区块大小重新制作种子文件或者将内容分为多个种子发布。",
'email_notification_body' => "你好,\n一个新的种子已经上传.\n\n名称::name\n大小::size\n类型::category\n上传者::upload_by\n\n简介:\n:description\n\n查看更为详细的信息并下载(你可能需要登录),请点击这里:<b><a href=javascript:void(null) onclick=window.open(':torrent_url')>这里</a></b>\n:torrent_url\n\n:site_name 网站",
'email_notification_subject' => ':site_name 新种子通知',
];

View File

@@ -26,6 +26,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => '種子被下載',
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => '收到獎勵',
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => '收到禮物',
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => '發布種子',
],
'fields' => [
'business_type' => '業務類型',

View File

@@ -1,6 +1,8 @@
<?php
return [
"label" => "訪問令牌",
"permission" => "權限",
];
return array (
'label' => '訪問令牌',
'permission' => '權限',
'maximum_allow_number_reached' => '數量達到上限',
'create_success_tip' => 'token 創建成功,此數據只展示一次,請妥善保存<br/><br/>:token',
);

View File

@@ -0,0 +1,68 @@
<?php
return array (
'invalid_price' => '無效的價格::price',
'invalid_category' => '無效的分類',
'invalid_section' => '無效的分區',
'invalid_hr' => '無效的 H&R 值',
'invalid_pos_state' => '無效的位置::pos_state',
'invalid_pos_state_until' => '無效的位置截止時間',
'not_supported_sub_category_field' => '不支持的子分類字段::field',
'invalid_sub_category_value' => '子分類字段::label(:field) 的值::value 無效',
'invalid_tag' => '無效的標籤::tag',
'invalid_pick_type' => '無效的推薦::pick_type',
'require_name' => '標題不能為空',
'price_too_much' => '價格超過允許範圍',
'approval_deny_reach_upper_limit' => '當前審核被拒絕的種子數:%s 達到上限,不允許發布。',
'special_section_not_enabled' => '特別區未啟用。',
'paid_torrent_not_enabled' => '收費種子未啟用。',
'no_permission_to_set_torrent_hr' => '沒有權限設置種子 H&R。',
'no_permission_to_set_torrent_pos_state' => '沒有權限設置種子置頂。',
'no_permission_to_set_torrent_price' => '沒有權限設置種子收費。',
'no_permission_to_pick_torrent' => '沒有權限推薦影片。',
'no_permission_to_be_anonymous' => '沒有權限匿名發布。',
'torrent_save_dir_not_exists' => '種子保存目錄不存在。',
'torrent_save_dir_not_writable' => '種子保存目錄不可寫。',
'save_torrent_file_failed' => '保存種子文件失敗。',
'upload_failed' => '上傳失敗!',
'missing_form_data' => '請填寫必填項目',
'missing_torrent_file' => '缺少種子文件',
'empty_filename' => '文件名不能為空!',
'zero_byte_nfo' => 'NFO文件為空',
'nfo_too_big' => 'NFO文件過大最大允許65,535 bytes。',
'nfo_upload_failed' => 'NFO文件上傳失敗',
'blank_description' => '你必須填寫簡介!',
'category_unselected' => '你必須選擇類型!',
'invalid_filename' => '無效的文件名!',
'filename_not_torrent' => '無效的文件名(不是.torrent文件).',
'empty_file' => '空文件!',
'not_bencoded_file' => '你在搞什麼鬼你上傳的不是Bencode文件',
'not_a_dictionary' => '不是目錄',
'dictionary_is_missing_key' => '目錄缺少值',
'invalid_entry_in_dictionary' => '無效的目錄項',
'invalid_dictionary_entry_type' => '無效的目錄項類型',
'invalid_pieces' => '無效的文件塊',
'missing_length_and_files' => '缺少長度和文件',
'filename_errors' => '文件名錯誤',
'uploaded_not_offered' => '你只能上傳通過候選的種子,請返回在<b>你的候選</b>中選擇合適項目後再上傳!',
'unauthorized_upload_freely' => '你沒有自由上傳的權限!',
'torrent_existed' => '該種子已存在id: :id',
'torrent_file_too_big' => '種子文件過大!最大允許',
'remake_torrent_note' => 'bytes。請使用更大的區塊大小重新製作種子文件或者將內容分為多個種子發布。',
'email_notification_body' => '你好,
一個新的種子已經上傳.
名稱::name
大小::size
類型::category
上傳者::upload_by
簡介:
:description
查看更為詳細的信息並下載(你可能需要登錄),請點擊這裡:<b><a href=javascript:void(null) onclick=window.open(\':torrent_url\')>這裡</a></b>
:torrent_url
:site_name 網站',
'email_notification_subject' => ':site_name 新種子通知',
);

View File

@@ -17,87 +17,93 @@ use App\Enums\Permission\PermissionEnum;
Route::group(['middleware' => ['auth:sanctum']], function () {
Route::group(['middleware' => ['user']], function () {
Route::post('logout', [\App\Http\Controllers\AuthenticateController::class, 'logout']);
Route::group(['middleware' => []], function () {
// Route::post('logout', [\App\Http\Controllers\AuthenticateController::class, 'logout']);
// Route::get('user-me',[\App\Http\Controllers\UserController::class, 'me'])->name('user.me');
// Route::get('user-publish-torrent',[\App\Http\Controllers\UserController::class, 'publishTorrent']);
// Route::get('user-seeding-torrent',[\App\Http\Controllers\UserController::class, 'seedingTorrent']);
// Route::get('user-leeching-torrent',[\App\Http\Controllers\UserController::class, 'leechingTorrent']);
// Route::get('user-finished-torrent',[\App\Http\Controllers\UserController::class, 'finishedTorrent']);
// Route::get('user-not-finished-torrent',[\App\Http\Controllers\UserController::class, 'notFinishedTorrent']);
// Route::resource('messages', \App\Http\Controllers\MessageController::class);
// Route::get('messages-unread', [\App\Http\Controllers\MessageController::class, 'listUnread']);
// Route::resource('torrents', \App\Http\Controllers\TorrentController::class);
// Route::resource('comments', \App\Http\Controllers\CommentController::class);
// Route::resource('peers', \App\Http\Controllers\PeerController::class);
// Route::resource('files', \App\Http\Controllers\FileController::class);
// Route::resource('thanks', \App\Http\Controllers\ThankController::class);
// Route::resource('snatches', \App\Http\Controllers\SnatchController::class);
// Route::resource('bookmarks', \App\Http\Controllers\BookmarkController::class);
// Route::get('search-box', [\App\Http\Controllers\TorrentController::class, 'searchBox']);
// Route::resource('news', \App\Http\Controllers\NewsController::class);
// Route::get('attend', [\App\Http\Controllers\AttendanceController::class, 'attend']);
// Route::get('news-latest', [\App\Http\Controllers\NewsController::class, 'latest']);
// Route::resource('polls', \App\Http\Controllers\PollController::class);
// Route::get('polls-latest', [\App\Http\Controllers\PollController::class, 'latest']);
// Route::post('polls-vote', [\App\Http\Controllers\PollController::class, 'vote']);
// Route::resource('rewards', \App\Http\Controllers\RewardController::class);
// Route::get('notifications', [\App\Http\Controllers\ToolController::class, 'notifications']);
// Route::resource('over-forums', \App\Http\Controllers\OverForumController::class);
// Route::resource('forums', \App\Http\Controllers\ForumController::class);
// Route::resource('topics', \App\Http\Controllers\TopicController::class);
Route::get('sections', [\App\Http\Controllers\UploadController::class, 'sections'])->middleware(ability(PermissionEnum::UPLOAD));
Route::get('torrents/{section?}', [\App\Http\Controllers\TorrentController::class, 'index'])->middleware(ability(PermissionEnum::TORRENT_LIST));
Route::post('upload', [\App\Http\Controllers\TorrentController::class, 'store'])->middleware(ability(PermissionEnum::UPLOAD));
Route::get('detail/{id}', [\App\Http\Controllers\TorrentController::class, 'show'])->middleware(ability(PermissionEnum::TORRENT_VIEW));
Route::get('/profile/{id?}', [\App\Http\Controllers\UserController::class, 'show'])->middleware(ability(PermissionEnum::USER_VIEW));
Route::get('user-me',[\App\Http\Controllers\UserController::class, 'me'])->name('user.me');
Route::get('user-publish-torrent',[\App\Http\Controllers\UserController::class, 'publishTorrent']);
Route::get('user-seeding-torrent',[\App\Http\Controllers\UserController::class, 'seedingTorrent']);
Route::get('user-leeching-torrent',[\App\Http\Controllers\UserController::class, 'leechingTorrent']);
Route::get('user-finished-torrent',[\App\Http\Controllers\UserController::class, 'finishedTorrent']);
Route::get('user-not-finished-torrent',[\App\Http\Controllers\UserController::class, 'notFinishedTorrent']);
Route::resource('messages', \App\Http\Controllers\MessageController::class);
Route::get('messages-unread', [\App\Http\Controllers\MessageController::class, 'listUnread']);
Route::resource('torrents', \App\Http\Controllers\TorrentController::class);
Route::resource('comments', \App\Http\Controllers\CommentController::class);
Route::resource('peers', \App\Http\Controllers\PeerController::class);
Route::resource('files', \App\Http\Controllers\FileController::class);
Route::resource('thanks', \App\Http\Controllers\ThankController::class);
Route::resource('snatches', \App\Http\Controllers\SnatchController::class);
Route::resource('bookmarks', \App\Http\Controllers\BookmarkController::class);
Route::get('search-box', [\App\Http\Controllers\TorrentController::class, 'searchBox']);
Route::resource('news', \App\Http\Controllers\NewsController::class);
Route::get('attend', [\App\Http\Controllers\AttendanceController::class, 'attend']);
Route::get('news-latest', [\App\Http\Controllers\NewsController::class, 'latest']);
Route::resource('polls', \App\Http\Controllers\PollController::class);
Route::get('polls-latest', [\App\Http\Controllers\PollController::class, 'latest']);
Route::post('polls-vote', [\App\Http\Controllers\PollController::class, 'vote']);
Route::resource('rewards', \App\Http\Controllers\RewardController::class);
Route::get('notifications', [\App\Http\Controllers\ToolController::class, 'notifications']);
Route::resource('over-forums', \App\Http\Controllers\OverForumController::class);
Route::resource('forums', \App\Http\Controllers\ForumController::class);
Route::resource('topics', \App\Http\Controllers\TopicController::class);
Route::get('sections', [\App\Http\Controllers\UploadController::class, 'sections']);
Route::post('upload', [\App\Http\Controllers\UploadController::class, 'upload'])->middleware(ability(PermissionEnum::UPLOAD));
});
Route::group(['middleware' => ['admin']], function () {
Route::resource('agent-allows', \App\Http\Controllers\AgentAllowController::class);
Route::get('all-agent-allows', [\App\Http\Controllers\AgentAllowController::class, 'all']);
Route::post('agent-check', [\App\Http\Controllers\AgentAllowController::class, 'check']);
Route::resource('agent-denies', \App\Http\Controllers\AgentDenyController::class);
Route::resource('users', \App\Http\Controllers\UserController::class);
Route::get('user-base', [\App\Http\Controllers\UserController::class, 'base']);
Route::get('user-classes', [\App\Http\Controllers\UserController::class, 'classes']);
Route::get('user-invite-info', [\App\Http\Controllers\UserController::class, 'inviteInfo']);
Route::get('user-match-exams', [\App\Http\Controllers\UserController::class, 'matchExams']);
Route::get('user-mod-comment', [\App\Http\Controllers\UserController::class, 'modComment']);
Route::post('user-disable', [\App\Http\Controllers\UserController::class, 'disable']);
Route::post('user-enable', [\App\Http\Controllers\UserController::class, 'enable']);
Route::post('user-reset-password', [\App\Http\Controllers\UserController::class, 'resetPassword']);
Route::put('user-increment-decrement', [\App\Http\Controllers\UserController::class, 'incrementDecrement']);
Route::put('user-remove-two-step', [\App\Http\Controllers\UserController::class, 'removeTwoStepAuthentication']);
Route::resource('exams', \App\Http\Controllers\ExamController::class);
Route::get('exams-all', [\App\Http\Controllers\ExamController::class, 'all']);
Route::get('exam-indexes', [\App\Http\Controllers\ExamController::class, 'indexes']);
Route::resource('exam-users', \App\Http\Controllers\ExamUserController::class);
Route::put('exam-users-avoid', [\App\Http\Controllers\ExamUserController::class, 'avoid']);
Route::put('exam-users-recover', [\App\Http\Controllers\ExamUserController::class, 'recover']);
Route::put('exam-users-avoid-bulk', [\App\Http\Controllers\ExamUserController::class, 'bulkAvoid']);
Route::put('exam-users-delete-bulk', [\App\Http\Controllers\ExamUserController::class, 'bulkDelete']);
Route::get('dashboard/system-info', [\App\Http\Controllers\DashboardController::class, 'systemInfo']);
Route::get('dashboard/stat-data', [\App\Http\Controllers\DashboardController::class, 'statData']);
Route::get('dashboard/latest-user', [\App\Http\Controllers\DashboardController::class, 'latestUser']);
Route::get('dashboard/latest-torrent', [\App\Http\Controllers\DashboardController::class, 'latestTorrent']);
Route::resource('settings', \App\Http\Controllers\SettingController::class);
Route::resource('medals', \App\Http\Controllers\MedalController::class);
Route::resource('user-medals', \App\Http\Controllers\UserMedalController::class);
Route::resource('tags', \App\Http\Controllers\TagController::class);
Route::resource('hr', \App\Http\Controllers\HitAndRunController::class);
Route::get('hr-status', [\App\Http\Controllers\HitAndRunController::class, 'listStatus']);
Route::put('hr-pardon/{id}', [\App\Http\Controllers\HitAndRunController::class, 'pardon']);
Route::put('hr-delete', [\App\Http\Controllers\HitAndRunController::class, 'bulkDelete']);
Route::put('hr-pardon', [\App\Http\Controllers\HitAndRunController::class, 'bulkPardon']);
});
// Route::group(['middleware' => ['admin']], function () {
// Route::resource('agent-allows', \App\Http\Controllers\AgentAllowController::class);
// Route::get('all-agent-allows', [\App\Http\Controllers\AgentAllowController::class, 'all']);
// Route::post('agent-check', [\App\Http\Controllers\AgentAllowController::class, 'check']);
// Route::resource('agent-denies', \App\Http\Controllers\AgentDenyController::class);
//
// Route::resource('users', \App\Http\Controllers\UserController::class);
// Route::get('user-base', [\App\Http\Controllers\UserController::class, 'base']);
// Route::get('user-classes', [\App\Http\Controllers\UserController::class, 'classes']);
// Route::get('user-invite-info', [\App\Http\Controllers\UserController::class, 'inviteInfo']);
// Route::get('user-match-exams', [\App\Http\Controllers\UserController::class, 'matchExams']);
// Route::get('user-mod-comment', [\App\Http\Controllers\UserController::class, 'modComment']);
// Route::post('user-disable', [\App\Http\Controllers\UserController::class, 'disable']);
// Route::post('user-enable', [\App\Http\Controllers\UserController::class, 'enable']);
// Route::post('user-reset-password', [\App\Http\Controllers\UserController::class, 'resetPassword']);
// Route::put('user-increment-decrement', [\App\Http\Controllers\UserController::class, 'incrementDecrement']);
// Route::put('user-remove-two-step', [\App\Http\Controllers\UserController::class, 'removeTwoStepAuthentication']);
//
// Route::resource('exams', \App\Http\Controllers\ExamController::class);
// Route::get('exams-all', [\App\Http\Controllers\ExamController::class, 'all']);
// Route::get('exam-indexes', [\App\Http\Controllers\ExamController::class, 'indexes']);
//
// Route::resource('exam-users', \App\Http\Controllers\ExamUserController::class);
// Route::put('exam-users-avoid', [\App\Http\Controllers\ExamUserController::class, 'avoid']);
// Route::put('exam-users-recover', [\App\Http\Controllers\ExamUserController::class, 'recover']);
// Route::put('exam-users-avoid-bulk', [\App\Http\Controllers\ExamUserController::class, 'bulkAvoid']);
// Route::put('exam-users-delete-bulk', [\App\Http\Controllers\ExamUserController::class, 'bulkDelete']);
//
// Route::get('dashboard/system-info', [\App\Http\Controllers\DashboardController::class, 'systemInfo']);
// Route::get('dashboard/stat-data', [\App\Http\Controllers\DashboardController::class, 'statData']);
// Route::get('dashboard/latest-user', [\App\Http\Controllers\DashboardController::class, 'latestUser']);
// Route::get('dashboard/latest-torrent', [\App\Http\Controllers\DashboardController::class, 'latestTorrent']);
//
// Route::resource('settings', \App\Http\Controllers\SettingController::class);
// Route::resource('medals', \App\Http\Controllers\MedalController::class);
// Route::resource('user-medals', \App\Http\Controllers\UserMedalController::class);
// Route::resource('tags', \App\Http\Controllers\TagController::class);
// Route::resource('hr', \App\Http\Controllers\HitAndRunController::class);
// Route::get('hr-status', [\App\Http\Controllers\HitAndRunController::class, 'listStatus']);
// Route::put('hr-pardon/{id}', [\App\Http\Controllers\HitAndRunController::class, 'pardon']);
// Route::put('hr-delete', [\App\Http\Controllers\HitAndRunController::class, 'bulkDelete']);
// Route::put('hr-pardon', [\App\Http\Controllers\HitAndRunController::class, 'bulkPardon']);
// });
});
Route::post('login', [\App\Http\Controllers\AuthenticateController::class, 'login']);
//Route::post('login', [\App\Http\Controllers\AuthenticateController::class, 'login']);

View File

@@ -7,3 +7,4 @@ routes.php
routes.scanned.php
schedule-*
services.json
lang*.json