plugin management + user tables and torrents table text column migrate

This commit is contained in:
xiaomlove
2025-01-19 14:37:00 +08:00
parent 0f88ab8d82
commit 403a9447a9
66 changed files with 1432 additions and 786 deletions

View File

@@ -16,6 +16,7 @@ use App\Models\Invite;
use App\Models\LoginLog;
use App\Models\Medal;
use App\Models\Peer;
use App\Models\Post;
use App\Models\SearchBox;
use App\Models\Setting;
use App\Models\Snatch;
@@ -101,11 +102,7 @@ class Test extends Command
*/
public function handle()
{
$str = "1.abc.de";
$ext = "png";
$str = "202404/20240403215909f58f38ddd968a0e8a4bdd30690a9e92e.png";
$ext = pathinfo($str, PATHINFO_EXTENSION);
dd(basename($str), $ext);
$this->info("haha");
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands\Upgrade;
use Illuminate\Console\Command;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Nexus\Database\NexusDB;
class MigrateTorrentsTableTextColumn extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'upgrade:migrate_torrents_table_text_column';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Migrate torrents table text column';
/**
* Execute the console command.
*/
public function handle()
{
$columns = ["ori_descr", "descr", "nfo", "technical_info", "pt_gen"];
$sql = "alter table torrents ";
$drops = [];
foreach ($columns as $column) {
if (Schema::hasColumn("torrents", $column)) {
$drops[] = "drop column $column";
}
}
if (!empty($drops)) {
$sql .= implode(",", $drops);
NexusDB::statement($sql);
}
if (Schema::hasTable("torrent_extras")) {
NexusDB::statement("insert torrent_extras (torrent_id, descr, media_info, nfo, created_at) select id, descr, technical_info, nfo, now() from torrents on duplicate key update torrent_id = values(torrent_id)");
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Console\Commands\Upgrade;
use Illuminate\Console\Command;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class MigrateUsersTableCommentRelatedColumn extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'upgrade:migrate_users_table_comment_related_column';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Migrate users table comment related column';
/**
* Execute the console command.
*/
public function handle()
{
Schema::table('users', function (Blueprint $table) {
if (Schema::hasColumn('users', 'modcomment')) {
$table->dropColumn('modcomment');
}
if (Schema::hasColumn('users', 'bonuscomment')) {
$table->dropColumn('bonuscomment');
}
});
}
}

View File

@@ -42,6 +42,9 @@ class Handler extends ExceptionHandler
*/
public function register()
{
if (app()->runningInConsole()) {
return;
}
$this->reportable(function (InsufficientPermissionException $e) {
if (request()->expectsJson()) {
return response()->json(fail($e->getMessage(), request()->all()), 403);
@@ -55,9 +58,9 @@ class Handler extends ExceptionHandler
});
//Other Only handle in json request
// if (!request()->expectsJson()) {
// return;
// }
if (!request()->expectsJson()) {
return;
}
$this->renderable(function (AuthenticationException $e) {
return response()->json(fail($e->getMessage(), ['guards' => $e->guards()]), 401);

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Filament\Clusters;
use Filament\Clusters\Cluster;
class Plugin extends Cluster
{
protected static ?string $navigationIcon = 'heroicon-o-squares-2x2';
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Filament\Pages;
use Filament\Pages\Page;
class RunCommand extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.run-command';
protected static bool $shouldRegisterNavigation = false;
}

View File

@@ -35,7 +35,7 @@ class PluginResource extends Resource
return self::getNavigationLabel();
}
protected static bool $shouldRegisterNavigation = false;
protected static bool $shouldRegisterNavigation = true;
public static function form(Form $form): Form
{

View File

@@ -0,0 +1,123 @@
<?php
namespace App\Filament\Resources\System;
use App\Filament\Resources\System\PluginStoreResource\Pages;
use App\Filament\Resources\System\PluginStoreResource\RelationManagers;
use App\Livewire\InstallPluginModal;
use App\Models\Plugin;
use App\Models\PluginStore;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Infolists\Infolist;
use Filament\Resources\Resource;
use Filament\Support\Enums\FontWeight;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Infolists\Components;
use Filament\Infolists;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\HtmlString;
use Filament\Actions\Action;
use Livewire\Livewire;
class PluginStoreResource extends Resource
{
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'System';
public static function form(Form $form): Form
{
return $form
->schema([
//
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\Layout\Stack::make([
Tables\Columns\Layout\Stack::make([
Tables\Columns\TextColumn::make('title')
->weight(FontWeight::Bold)
,
Tables\Columns\TextColumn::make('description'),
]),
Tables\Columns\Layout\Stack::make([
Tables\Columns\TextColumn::make('version')
->formatStateUsing(fn (PluginStore $record) => sprintf("版本: %s | 更新时间: %s", $record->version, $record->release_date))
->color('gray')
,
])
])->space(3),
])
->contentGrid([
'md' => 2,
'xl' => 3,
])
->filters([
//
])
->actions([
Tables\Actions\ViewAction::make()
->modalHeading("详细介绍")
->modalContent(fn (PluginStore $record) => $record->getFullDescription())
->extraModalFooterActions([
Action::make("viewOnBlog")
->url(fn (PluginStore $record) => $record->getBlogPostUrl())
->extraAttributes(['target' => '_blank'])
,
])
,
Tables\Actions\Action::make("install")
->label("安装")
->modalHeading(fn (PluginStore $record) => sprintf("安装插件: %s", $record->title))
->modalContent(function (PluginStore $record) {
$infolist = new Infolist();
$infolist->record = $record;
$infolist->schema([
Infolists\Components\TextEntry::make('plugin_id')
->label(fn () => sprintf("进入目录: %s, 以 root 用户的身份依次执行以下命令进行安装: ", base_path()))
->html(true)
->formatStateUsing(function (PluginStore $record) {
$user = executeCommand("whoami");
$commands = [
sprintf("sudo -u %s composer config repositories.%s %s", $user, $record->plugin_id, $record->remote_url),
sprintf("sudo -u %s composer require %s:%s", $user, $record->package_name, $record->version),
sprintf("sudo -u %s php artisan plugin install %s", $user, $record->package_name),
];
return implode("<br/>", $commands);
})
,
]);
return $infolist;
})
->modalFooterActions(fn () => [])
,
])
->recordAction(null)
->paginated(false)
;
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListPluginStores::route('/'),
// 'create' => Pages\CreatePluginStore::route('/create'),
// 'edit' => Pages\EditPluginStore::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
use App\Filament\Resources\System\PluginStoreResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreatePluginStore extends CreateRecord
{
protected static string $resource = PluginStoreResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
use App\Filament\Resources\System\PluginStoreResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditPluginStore extends EditRecord
{
protected static string $resource = PluginStoreResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
use App\Filament\Resources\System\PluginStoreResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListPluginStores extends ListRecords
{
protected static string $resource = PluginStoreResource::class;
protected function getHeaderActions(): array
{
return [
// Actions\CreateAction::make(),
];
}
}

View File

@@ -130,8 +130,10 @@ class UserResource extends Resource
,
Infolists\Components\TextEntry::make('email')
->label(__("label.email"))
->copyable()
->placeholder("点击复制")
,
Infolists\Components\TextEntry::make('passkey'),
Infolists\Components\TextEntry::make('passkey')->limit(10)->copyable(),
Infolists\Components\TextEntry::make('added')->label(__("label.added")),
Infolists\Components\TextEntry::make('last_access')->label(__("label.last_access")),
Infolists\Components\TextEntry::make('inviter.username')->label(__("label.user.invite_by")),
@@ -141,7 +143,12 @@ class UserResource extends Resource
Infolists\Components\TextEntry::make('uploadedText')->label(__("label.uploaded")),
Infolists\Components\TextEntry::make('downloadedText')->label(__("label.downloaded")),
Infolists\Components\TextEntry::make('seedbonus')->label(__("label.user.seedbonus")),
])->columns(2),
Infolists\Components\TextEntry::make('seed_points')->label(__("label.user.seed_points")),
])
->columns(6)
->columnSpan(4)
,
Components\Group::make([
Infolists\Components\TextEntry::make('status')
->label(__('label.user.status'))
@@ -174,7 +181,8 @@ class UserResource extends Resource
->hintAction(self::buildActionCancelTwoStepAuthentication())
,
])
]),
->columnSpan(1)
])->columns(5),
]);
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers;
use App\Repositories\PluginRepository;
use App\Repositories\ToolRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\Process\Process;
use Telegram\Bot\Api;
use Telegram\Bot\Commands\HelpCommand;
class PluginController extends Controller
{
private $repository;
public function __construct(PluginRepository $repository)
{
$this->repository = $repository;
}
public function runCommandSSE(Request $request)
{
$commands = [
['pwd'], // Example command 1
['ls', '-la'], // Example command 2
['composer', '--version'], // Example command 3
['composer', 'why', 'ext-zip']
];
foreach ($commands as $command) {
$process = new Process($command);
$process->setTimeout(0); // Set timeout for each process
try {
$process->mustRun(function ($type, $buffer) {
if (Process::OUT === $type) {
echo "Output: " . $buffer; // 实时输出
} else { // Process::ERR === $type
echo "Error: " . $buffer;
}
});
} catch (\Symfony\Component\Process\Exception\ProcessFailedException $e) {
echo "Command failed: " . implode(' ', $command) . "\n";
echo $e->getMessage();
break; // Stop executing further commands
}
}
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers;
use App\Models\PluginStore;
use App\Repositories\ToolRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Symfony\Component\HttpFoundation\StreamedResponse;
class ServerSendEventController extends Controller
{
public function sse(Request $request)
{
$this->initSSE();
$response = response()->stream(function () use ($request) {
$action = $request->input('action');
$this->sendData("action: $action");
try {
if ($action == "install_plugin") {
$this->checkComposer();
$pluginId = $request->input('plugin_id');
$this->sendData("pluginId: $pluginId");
$this->doActionInstallPlugin($pluginId);
} else {
throw new \InvalidArgumentException("Invalid action: $action");
}
}catch (\Throwable $throwable) {
$this->sendData("error: " . $throwable->getMessage());
$this->sendData("close");
}
});
$response->headers->set('Content-Type', 'text/event-stream');
$response->headers->set('Cache-Control', 'no-cache');
$response->headers->set('Connection', 'keep-alive');
$response->headers->set('X-Accel-Buffering', 'no');
return $response;
}
private function initSSE(): void
{
@set_time_limit(0); // Disable time limit
// Prevent buffering
if(function_exists('apache_setenv')){
@apache_setenv('no-gzip', 1);
}
@ini_set('zlib.output_compression', 0);
@ini_set('implicit_flush', 1);
while (ob_get_level() != 0) {
ob_end_flush();
}
ob_implicit_flush(1);
}
private function sendData(string $data): void
{
echo sprintf("data: %s\n\n", $data);
@ob_flush();
@flush();
}
private function runCommand(string $command): void
{
$this->sendData("running $command, this may take a while...");
$process = Process::fromShellCommandline($command);
$process->setWorkingDirectory(base_path());
$process->setTimeout(null);
$process->mustRun(function ($type, $buffer) {
$this->sendData($buffer);
});
}
private function doActionInstallPlugin(string $pluginId): void
{
$pluginInfo = PluginStore::getInfo($pluginId);
if (!$pluginInfo) {
throw new \InvalidArgumentException("invalid plugin: $pluginId");
}
$this->sendData(sprintf("going to install plugin: %s, version: %s", $pluginInfo['title'], $pluginInfo['version']));
$commands = [];
$commands[] = sprintf("composer config repositories.%s git %s", $pluginInfo['plugin_id'], $pluginInfo['remote_url']);
$commands[] = sprintf("composer require %s:%s", $pluginInfo['package_name'], $pluginInfo['version']);
$commands[] = sprintf("php artisan plugin install %s", $pluginInfo['package_name']);
foreach ($commands as $command) {
$this->runCommand($command);
}
}
private function checkComposer(): void
{
$this->sendData("checking composer ...");
$composerCacheDir = executeCommand("composer config cache-vcs-dir -f " . base_path("composer.json"));
$this->sendData("composer cache-vcs-dir: $composerCacheDir");
if (!is_dir($composerCacheDir)) {
$this->sendData("going to mkdir: $composerCacheDir");
try {
mkdir($composerCacheDir, 0755, true);
$this->sendData("success create composer cache-vcs-dir: $composerCacheDir");
} catch (\Throwable $e) {
$this->sendData("fail to mkdir: " . $e->getMessage());
$this->sendData("Please execute the following command as root user:");
$this->sendData(sprintf("mkdir -p %s", $composerCacheDir));
throw new \RuntimeException("stop due to composer cache-vcs-dir: $composerCacheDir not exists");
}
}
if (!is_writable($composerCacheDir)) {
$this->sendData("cache directory: $composerCacheDir is not writable");
$this->sendData("Please execute the following command as root user:");
$user = executeCommand("whoami");
$this->sendData(sprintf("chown -R %s:%s %s", $user, $user, $composerCacheDir));
throw new \RuntimeException("stop due to composer cache-vcs-dir: $composerCacheDir not writable");
}
}
}

View File

@@ -2,9 +2,12 @@
namespace App\Http\Controllers;
use App\Models\PluginStore;
use App\Repositories\ToolRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\Process\Process;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Telegram\Bot\Api;
use Telegram\Bot\Commands\HelpCommand;

View File

@@ -2,6 +2,7 @@
namespace App\Http;
use App\Http\Middleware\Filament;
use App\Http\Middleware\Locale;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
@@ -49,7 +50,8 @@ class Kernel extends HttpKernel
],
'filament' => [
\Illuminate\Session\Middleware\StartSession::class,
\Filament\Http\Middleware\Authenticate::class,
// \Filament\Http\Middleware\Authenticate::class,
Filament::class,
],
];

View File

@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Validation\UnauthorizedException;
class Admin
{

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Livewire;
use App\Models\PluginStore;
use Livewire\Component;
use Symfony\Component\Process\Process;
class InstallPluginModal extends Component
{
// public PluginStore $record;
public $output = ''; // 存储命令输出
// public function mount(PluginStore $record)
// {
// $this->recored = $record;
// $this->output = sprintf("点击按钮以开始安装 %s ...", $record->title);
// }
public function executeCommand($command)
{
$this->output = ''; // 清空之前的输出
// $command = "whereis composer";
// $process = new Process([$command]);
// $process->setTimeout(60); // 设置超时时间(秒)
//
// $process->start(); // 启动异步进程
//
// foreach ($process as $type => $data) {
// if ($type === Process::OUT) {
// $this->output .= $data; // 实时追加标准输出
// } elseif ($type === Process::ERR) {
// $this->output .= "[ERROR]: $data"; // 实时追加错误输出
// }
//
//// $this->dispatch('updateTextarea', $this->output); // 通知前端更新
// }
$process = new Process(['/usr/local/bin/composer', 'info']);
$process->setTimeout(3600); // 可选,设置超时时间
$basePath = base_path();
do_log("base path: $basePath");
$process->setWorkingDirectory($basePath);
try {
$process->mustRun(function ($type, $buffer) {
if (Process::OUT === $type) {
$this->output = $buffer;
$this->dispatch('updateTextarea', $this->output);
} else { // Process::ERR === $type
do_log("executeCommand, ERR: " . $buffer, 'error');
}
});
} catch (\Exception $e) {
do_log($e->getMessage(), 'error');
}
}
public function render()
{
return view('livewire.install-plugin-modal');
}
}

View File

@@ -3,6 +3,8 @@
namespace App\Models;
use Carbon\Carbon;
class BonusLogs extends NexusModel
{
protected $table = 'bonus_logs';
@@ -41,6 +43,7 @@ class BonusLogs extends NexusModel
const BUSINESS_TYPE_ROLE_WORK_SALARY = 1000;
const BUSINESS_TYPE_TORRENT_BE_DOWNLOADED = 1001;
const BUSINESS_TYPE_RECEIVE_REWARD = 1002;
public static array $businessTypes = [
self::BUSINESS_TYPE_CANCEL_HIT_AND_RUN => ['text' => 'Cancel H&R'],
@@ -65,6 +68,7 @@ class BonusLogs extends NexusModel
self::BUSINESS_TYPE_ROLE_WORK_SALARY => ['text' => 'Role work salary'],
self::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => ['text' => 'Torrent be downloaded'],
self::BUSINESS_TYPE_RECEIVE_REWARD => ['text' => 'Receive reward'],
];
public function getBusinessTypeTextAttribute()
@@ -102,5 +106,23 @@ class BonusLogs extends NexusModel
return $result ?? self::DEFAULT_BONUS_BUY_CHANGE_USERNAME_CARD;
}
public static function add(int $userId, float $old, float $delta, float $new, string $comment, int $businessType)
{
if (!isset(self::$businessTypes[$businessType])) {
throw new \InvalidArgumentException("Invalid business type: $businessType");
}
$nowStr = Carbon::now()->toDateTimeString();
return self::query()->create([
'business_type' => $businessType,
'uid' => $userId,
'old_total_value' => $old,
'value' => $delta,
'new_total_value' => $new,
'comment' => sprintf("[%s] %s", self::$businessTypes[$businessType]['text'], $comment),
'created_at' => $nowStr,
'updated_at' => $nowStr,
]);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Models;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\HtmlString;
use Sushi\Sushi;
class PluginStore extends Model
{
use Sushi;
const PLUGIN_LIST_API = "https://nppl.nexusphp.workers.dev";
const BLOG_POST_INFO_API = "https://nexusphp.org/wp-json/wp/v2/posts/%d";
const BLOG_POST_URL = "https://nexusphp.org/?p=%d";
public function getRows()
{
return Http::get(self::PLUGIN_LIST_API)->json();
}
public function getBlogPostUrl(): string
{
return sprintf(self::BLOG_POST_URL, $this->post_id);
}
public function getFullDescription(): Htmlable
{
$url = $this->getBlogPostInfoUrl($this->post_id);
$logPrefix = sprintf("post_id: %s, url: %s", $this->post_id, $url);
$defaultContent = "无法获取详细信息 ...";
try {
$result = Http::get($url)->json();
do_log("$logPrefix, result: " . json_encode($result));
$content = $result['content']['rendered'] ?? $result['message'] ?? $defaultContent;
} catch (\Exception $e) {
do_log(sprintf(
"%s, error: %s",
$logPrefix, $e->getMessage() . $e->getTraceAsString()
), 'error');
$content = $defaultContent;
}
return new HtmlString($content);
}
private function getBlogPostInfoUrl(int $postId): string
{
return sprintf(self::BLOG_POST_INFO_API, $postId);
}
public static function getInfo(string $id)
{
return Http::get(self::PLUGIN_LIST_API . "/plugin/$id")->json();
}
}

View File

@@ -5,7 +5,7 @@ namespace App\Models;
class Post extends NexusModel
{
public function user()
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class, 'userid');
}

View File

@@ -9,11 +9,11 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
class Torrent extends NexusModel
{
protected $fillable = [
'name', 'filename', 'save_as', 'descr', 'small_descr', 'ori_descr',
'name', 'filename', 'save_as', 'small_descr',
'category', 'source', 'medium', 'codec', 'standard', 'processing', 'team', 'audiocodec',
'size', 'added', 'type', 'numfiles', 'owner', 'nfo', 'sp_state', 'promotion_time_type',
'promotion_until', 'anonymous', 'url', 'pos_state', 'cache_stamp', 'picktype', 'picktime',
'last_reseed', 'pt_gen', 'technical_info', 'leechers', 'seeders', 'cover', 'last_action',
'last_reseed', 'leechers', 'seeders', 'cover', 'last_action',
'times_completed', 'approval_status', 'banned', 'visible', 'pos_state_until', 'price',
];
@@ -25,7 +25,6 @@ class Torrent extends NexusModel
protected $casts = [
'added' => 'datetime',
'pt_gen' => 'array',
'promotion_until' => 'datetime',
'pos_state_until' => 'datetime',
];
@@ -241,7 +240,7 @@ class Torrent extends NexusModel
public static function getFieldsForList($appendTableName = false): array|bool
{
$fields = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, pt_gen, hr, approval_status, cover, price';
$fields = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, hr, approval_status, cover, price';
$fields = preg_split('/[,\s]+/', $fields);
if ($appendTableName) {
foreach ($fields as &$value) {
@@ -513,4 +512,9 @@ class Torrent extends NexusModel
{
return $this->hasMany(TorrentOperationLog::class, 'torrent_id');
}
public function extra(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(TorrentExtra::class, 'torrent_id');
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Nexus\Database\NexusDB;
class TorrentExtra extends NexusModel
{
public $timestamps = true;
protected $fillable = ['torrent_id', 'descr', 'ori_descr', 'media_info'];
public function torrent()
{
return $this->belongsTo(Torrent::class, 'torrent_id');
}
}

View File

@@ -182,9 +182,9 @@ class User extends Authenticatable implements FilamentUser, HasName
* @var array
*/
protected $fillable = [
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'modcomment', 'enabled', 'status',
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'enabled', 'status',
'leechwarn', 'leechwarnuntil', 'page', 'class', 'uploaded', 'downloaded', 'clientselect', 'showclienterror', 'last_home',
'seedbonus', 'bonuscomment', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
'seedbonus', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
'seed_points_per_hour', 'passkey',
];
@@ -229,7 +229,7 @@ class User extends Authenticatable implements FilamentUser, HasName
'uploaded', 'downloaded', 'seedbonus', 'seedtime', 'leechtime',
'invited_by', 'enabled', 'seed_points', 'last_access', 'invites',
'lang', 'attendance_card', 'privacy', 'noad', 'downloadpos', 'donoruntil', 'donor',
'bonuscomment', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
'seed_points_per_hour'
];
@@ -535,6 +535,11 @@ class User extends Authenticatable implements FilamentUser, HasName
return $this->examAndTasks()->wherePivot("status", ExamUser::STATUS_NORMAL);
}
public function modifyLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(UserModifyLog::class, "user_id");
}
public function getAvatarAttribute($value)
{
if ($value) {
@@ -560,10 +565,17 @@ class User extends Authenticatable implements FilamentUser, HasName
throw new \RuntimeException('This method only works when user exists !');
}
//@todo how to do prepare bindings here ?
$comment = addslashes($comment);
do_log("update: " . json_encode($update) . ", $commentField: $comment", 'notice');
$update[$commentField] = NexusDB::raw("if($commentField = '', '$comment', concat_ws('\n', '$comment', $commentField))");
return $this->update($update);
// $comment = addslashes($comment);
// do_log("update: " . json_encode($update) . ", $commentField: $comment", 'notice');
// $update[$commentField] = NexusDB::raw("if($commentField = '', '$comment', concat_ws('\n', '$comment', $commentField))");
if ($commentField != "modcomment") {
throw new \RuntimeException("unsupported commentField: $commentField !");
}
return NexusDB::transaction(function () use ($update, $comment) {
$this->modifyLogs()->create(['content' => $comment]);
return $this->update($update);
});
}
public function canAccessAdmin(): bool

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
class UserModifyLog extends NexusModel
{
protected $fillable = ['user_id', 'content', ];
public $timestamps = true;
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class, "user_id");
}
}

View File

@@ -22,6 +22,8 @@ use Illuminate\View\Middleware\ShareErrorsFromSession;
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Table;
use Livewire\Livewire;
use NexusPlugin\TelegramBot\Filament\TelegramBotBindsResource;
use NexusPlugin\TelegramBot\Filament\TelegramBotResource;
class AppPanelProvider extends PanelProvider
{
@@ -29,7 +31,7 @@ class AppPanelProvider extends PanelProvider
{
return $panel
->default()
->id('app')
->id('admin')
->homeUrl("/")
->sidebarWidth("15rem")
->topbar(true)
@@ -40,6 +42,10 @@ class AppPanelProvider extends PanelProvider
->colors([
'primary' => Color::Amber,
])
->resources([
// TelegramBotResource::class,
// TelegramBotBindsResource::class
])
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([
@@ -51,6 +57,7 @@ class AppPanelProvider extends PanelProvider
// Widgets\AccountWidget::class,
// Widgets\FilamentInfoWidget::class,
])
->discoverClusters(app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters')
->middleware([
// EncryptCookies::class,
\App\Http\Middleware\EncryptCookies::class,

View File

@@ -2,6 +2,7 @@
namespace App\Providers;
use App\Http\Middleware\Filament;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
@@ -47,14 +48,15 @@ class RouteServiceProvider extends ServiceProvider
->namespace($this->namespace)
->group(base_path('routes/web.php'));
Route::prefix('api')
->namespace($this->namespace)
->group(base_path('routes/tracker.php'));
Route::prefix('api')
->namespace($this->namespace)
->middleware('throttle:third-party')
->group(base_path('routes/third-party.php'));
Route::prefix('admin')
->namespace($this->namespace)
->middleware([Filament::class])
->group(base_path('routes/admin.php'));
});
}

View File

@@ -381,6 +381,14 @@ class TorrentRepository extends BaseRepository
return md5($passkey . date('Ymd') . $user['id']);
}
/**
* @deprecated
* @param $id
* @param $uid
* @param $initializeIfNotExists
* @return string
* @throws NexusException
*/
public function getTrackerReportAuthKey($id, $uid, $initializeIfNotExists = false): string
{
$key = $this->getTrackerReportAuthKeySecret($id, $uid, $initializeIfNotExists);
@@ -389,6 +397,8 @@ class TorrentRepository extends BaseRepository
}
/**
* @deprecated
*
* check tracker report authkey
* if valid, the result will be the date the key generate, else if will be empty string
*

View File

@@ -227,6 +227,13 @@ class TrackerRepository extends BaseRepository
}
/**
* @deprecated
*
* @param $authkey
* @return array
* @throws TrackerException
*/
protected function checkAuthkey($authkey)
{
$arr = explode('|', $authkey);