mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-30 17:17:22 +08:00
Compare commits
100 Commits
f098ef72b5
...
php8
| Author | SHA1 | Date | |
|---|---|---|---|
| f823dc191a | |||
| 1372776ba1 | |||
| 7d18a7f76a | |||
| dc77ab7b40 | |||
| 94a35b81dc | |||
| dfe9436f1d | |||
| 4afcb1bb08 | |||
| e3376c3f1b | |||
| 00fdc2d08f | |||
| 4d54e08918 | |||
| 4d4af87dc9 | |||
| f271e28b15 | |||
| 9995767bf7 | |||
| d115a3879a | |||
| 431fbfff56 | |||
| 9e632811ef | |||
| db4982f8f7 | |||
| af66ec806d | |||
| 09b785902f | |||
| d79031f24d | |||
| 527a2d2162 | |||
| 3dae3aec8d | |||
| 5c9caa7a46 | |||
| a712a3f1e4 | |||
| f07e0a5013 | |||
| 8dec50fc6d | |||
| 1e997e411c | |||
| fb440c6d2b | |||
| 8207f1ed6f | |||
| c162fc81be | |||
| fd0a2014ba | |||
| eb3c03c97f | |||
| fc19ee5e7c | |||
| 4e9e4ae143 | |||
| e12cd0fab9 | |||
| f3a6d366d8 | |||
| 50259367d6 | |||
| ea176b1615 | |||
| 695d5f071f | |||
| dd8df70f7e | |||
| 5e163da05e | |||
| 3ced82fc0a | |||
| 7fc12723ec | |||
| 4f32c10d9f | |||
| 7494041005 | |||
| af2b7c77e0 | |||
| 4035f4084f | |||
| 0a568c0d19 | |||
| b2f21010dc | |||
| ef01571b9a | |||
| c91cf060b1 | |||
| 4d96d40a29 | |||
| 81400a34cc | |||
| f20771353e | |||
| da4a609ef7 | |||
| 0b8d4bee52 | |||
| 2cad4e1a83 | |||
| 8d98a7dd8b | |||
| 0351f92bf4 | |||
| 5f37d5c59a | |||
| 791c617bc0 | |||
| 290ad0f375 | |||
| 8602831d3a | |||
| c5b55dbda1 | |||
| 7f0f8cca16 | |||
| 00ec3d5e8d | |||
| eb248110fc | |||
| 33fd265a20 | |||
| 6180ae18df | |||
| f18fa80eac | |||
| 5c73e643a4 | |||
| ef397714b1 | |||
| 1d6740cc41 | |||
| 8e12cbf470 | |||
| d70a2c45ce | |||
| 50fd5ed2e0 | |||
| eb11239372 | |||
| 39853b221a | |||
| 5548b7ef94 | |||
| 6803bbe85f | |||
| 8a4538faea | |||
| b59377ab47 | |||
| e439e2eec8 | |||
| b3067e2b6a | |||
| 38f599d794 | |||
| 3ed1f0fa9d | |||
| 036e98a2d7 | |||
| dfdf64833f | |||
| e035ff1512 | |||
| b921beffd7 | |||
| 0307cb3e78 | |||
| d255499e83 | |||
| 88f2318699 | |||
| 7b0b51cb7e | |||
| f97d564ada | |||
| 7719535940 | |||
| 60f1a4970c | |||
| 2346afa291 | |||
| 70bc00b707 | |||
| ac83b68929 |
@@ -4,7 +4,7 @@ FROM openresty/openresty:alpine
|
||||
RUN apk add --no-cache gettext bash curl openssl socat
|
||||
|
||||
# 拷贝 entrypoint
|
||||
COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
COPY .docker/openresty/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
CMD ["/bin/sh", "/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
@@ -81,7 +81,7 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
|
||||
ENV LOG_CHANNEL=stderr
|
||||
ENV RUNNING_IN_DOCKER=1
|
||||
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
COPY .docker/php/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
|
||||
@@ -67,7 +67,10 @@ if [ "$SERVICE_NAME" = "php" ]; then
|
||||
cp -r "$SOURCE_DIR" "$TARGET_DIR"
|
||||
sed -i 's|LOG_FILE.*|LOG_FILE=php://stdout|g' "$ROOT_PATH/.env.example"
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
echo_info "update LOG_FILE + DB_HOST + REDIS_HOST ..."
|
||||
sed -i 's|LOG_FILE.*|LOG_FILE=php://stdout|g' "$ENV_FILE"
|
||||
sed -i 's|DB_HOST.*|DB_HOST=mysql|g' "$ENV_FILE"
|
||||
sed -i 's|REDIS_HOST.*|REDIS_HOST=redis|g' "$ENV_FILE"
|
||||
fi
|
||||
else
|
||||
echo_success ".env file: $ENV_FILE and vendor autoload file: $VENDOR_AUTOLOAD_FILE already exists, skip copy install file ..."
|
||||
|
||||
+3
-2
@@ -11,8 +11,9 @@ DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=nexusphp
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
DB_USERNAME=nexusphp
|
||||
DB_PASSWORD=nexusphp
|
||||
DB_SCHEMA=public
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=redis
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
English | [中文](/)
|
||||
|
||||
Complete PT website building solution. Based on NexusPHP + Laravel + Filament.
|
||||
Complete PT website building solution. Based on NexusPHP + Laravel + FilamentPHP.
|
||||
|
||||
Welcome to participate in internationalization work, click [here](https://github.com/xiaomlove/nexusphp/discussions/193) for more information
|
||||
|
||||
@@ -40,7 +40,7 @@ Welcome to participate in internationalization work, click [here](https://github
|
||||
- Section H&R
|
||||
- TGBot
|
||||
## System Requirements
|
||||
- PHP: 8.2|8.3|8.4, must have extensions: bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3
|
||||
- PHP: 8.2|8.3|8.4|8.5, must have extensions: bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3
|
||||
- Mysql: 5.7 latest version or above
|
||||
- Redis:4.0.0 or above
|
||||
- Others: supervisor, rsync
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
中文 | [English](/README-EN.md)
|
||||
|
||||
完整的 PT 建站解决方案。基于 NexusPHP + Laravel + Filament。
|
||||
完整的 PT 建站解决方案。基于 NexusPHP + Laravel + FilamentPHP。
|
||||
|
||||
欢迎参与国际化工作,点击 [这里](https://github.com/xiaomlove/nexusphp/discussions/193) 了解详情
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
- TGBot
|
||||
|
||||
## 系统要求
|
||||
- PHP: 8.2|8.3|8.4,必须扩展:bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3
|
||||
- PHP: 8.2|8.3|8.4|8.5,必须扩展:bcmath, ctype, curl, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, mysqli, gd, redis, pcntl, sockets, posix, gmp, zend opcache, zip, intl, pdo_sqlite, sqlite3
|
||||
- Mysql: 5.7 最新版或以上版本
|
||||
- Redis:4.0.0 或以上版本
|
||||
- 其他:supervisor, rsync
|
||||
|
||||
@@ -170,9 +170,9 @@ class CrowdinSync extends Command
|
||||
$this->uploadFile($filePath, $specificFile);
|
||||
$this->info("File {$specificFile} uploaded successfully.");
|
||||
} else {
|
||||
throw new \RuntimeException("please specify a file to upload");
|
||||
// throw new \RuntimeException("please specify a file to upload");
|
||||
// Upload all files in the source directory
|
||||
$files = File::allFiles($this->sourceDir);
|
||||
$files = File::allFiles($this->sourceDir . '/en');
|
||||
|
||||
$bar = $this->output->createProgressBar(count($files));
|
||||
$bar->start();
|
||||
|
||||
@@ -108,7 +108,7 @@ class NexusUpdate extends Command
|
||||
// $settings = $settingTableRows['settings'];
|
||||
$symbolicLinks = $settingTableRows['symbolic_links'];
|
||||
$fails = $settingTableRows['fails'];
|
||||
$mysqlInfo = $this->update->getMysqlVersionInfo();
|
||||
$mysqlInfo = $this->update->getDatabaseVersionInfo();
|
||||
$redisInfo = $this->update->getRedisVersionInfo();
|
||||
|
||||
if (!empty($fails)) {
|
||||
|
||||
@@ -29,7 +29,7 @@ class MigrateTorrentsTableTextColumn extends Command
|
||||
public function handle()
|
||||
{
|
||||
if (Schema::hasTable("torrent_extras") && Schema::hasColumn("torrents", "descr")) {
|
||||
NexusDB::statement("insert into torrent_extras (torrent_id, descr, media_info, nfo, pt_gen, created_at) select id, descr, technical_info, nfo, pt_gen, now() from torrents on duplicate key update torrent_id = values(torrent_id)");
|
||||
NexusDB::statement("insert into torrent_extras (torrent_id, descr, media_info, nfo, pt_gen, created_at) select id, descr, technical_info, nfo, pt_gen, now() from torrents " . NexusDB::upsertField(['torrent_id'], ['torrent_id']));
|
||||
}
|
||||
$columns = ["ori_descr", "descr", "nfo", "technical_info", "pt_gen"];
|
||||
$sql = "alter table torrents ";
|
||||
|
||||
@@ -6,6 +6,9 @@ use App\Jobs\CheckCleanup;
|
||||
use App\Jobs\CheckQueueFailedJobs;
|
||||
use App\Jobs\MaintainPluginState;
|
||||
use App\Jobs\ManagePlugin;
|
||||
use App\Jobs\RemoveUserDonorStatus;
|
||||
use App\Jobs\RemoveUserVipStatus;
|
||||
use App\Jobs\RemoveUserWarning;
|
||||
use App\Jobs\SaveIpLogCacheToDB;
|
||||
use App\Jobs\UpdateIsSeedBoxFromUserRecordsCache;
|
||||
use App\Utils\ThirdPartyJob;
|
||||
@@ -53,6 +56,9 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->job(new UpdateIsSeedBoxFromUserRecordsCache())->everySixHours();
|
||||
$schedule->job(new CheckCleanup())->everyFifteenMinutes();
|
||||
$schedule->job(new SaveIpLogCacheToDB())->hourly();
|
||||
$schedule->job(new RemoveUserWarning())->everyTwentySeconds();
|
||||
$schedule->job(new RemoveUserVipStatus())->everyMinute();
|
||||
$schedule->job(new RemoveUserDonorStatus())->everyMinute();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -16,5 +16,6 @@ enum PermissionEnum: string {
|
||||
case MANAGE_USER_BASIC_INFO = "prfmanage";
|
||||
case MANAGE_USER_CONFIDENTIAL_INFO = "cruprfmanage";
|
||||
case VIEW_USER_CONFIDENTIAL_INFO = "userprofile";
|
||||
case VIEW_USER_HISTORY = "viewhistory";
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Resources\System\SettingResource\Pages;
|
||||
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -41,8 +42,6 @@ class EditSetting extends Page implements HasForms
|
||||
|
||||
protected static string $resource = SettingResource::class;
|
||||
|
||||
protected string $view = 'filament.resources.system.setting-resource.pages.edit-hit-and-run';
|
||||
|
||||
public ?array $data = [];
|
||||
|
||||
public function getTitle(): string
|
||||
@@ -56,7 +55,7 @@ class EditSetting extends Page implements HasForms
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
public function content(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components($this->getFormSchema())
|
||||
@@ -76,7 +75,7 @@ class EditSetting extends Page implements HasForms
|
||||
}
|
||||
Arr::set($settings, 'captcha.attendance.enabled', $normalized);
|
||||
|
||||
$this->form->fill($settings);
|
||||
$this->content->fill($settings);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +84,10 @@ class EditSetting extends Page implements HasForms
|
||||
{
|
||||
return [
|
||||
Tabs::make('Heading')
|
||||
->tabs($this->getTabs())
|
||||
->tabs($this->getTabs()),
|
||||
Action::make('submit')
|
||||
->label(__('label.save'))
|
||||
->action(fn() => $this->submit()),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -93,7 +95,7 @@ class EditSetting extends Page implements HasForms
|
||||
{
|
||||
static::authorizeResourceAccess();
|
||||
|
||||
$formData = $this->form->getState();
|
||||
$formData = $this->content->getState();
|
||||
$notAutoloadNames = ['donation_custom'];
|
||||
$data = [];
|
||||
foreach ($formData as $prefix => $parts) {
|
||||
|
||||
@@ -5,21 +5,18 @@ namespace App\Filament\Resources\System;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use App\Filament\Resources\System\TorrentStateResource\Pages\ManageTorrentStates;
|
||||
use App\Filament\Resources\System\TorrentStateResource\Pages;
|
||||
use App\Filament\Resources\System\TorrentStateResource\RelationManagers;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\TorrentState;
|
||||
use Filament\Forms;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tables;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class TorrentStateResource extends Resource
|
||||
{
|
||||
@@ -46,13 +43,35 @@ class TorrentStateResource extends Resource
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('global_sp_state')
|
||||
->options(Torrent::listPromotionTypes(true))
|
||||
->options(function () {
|
||||
$options = Torrent::listPromotionTypes(true);
|
||||
unset($options[Torrent::PROMOTION_NORMAL]);
|
||||
return $options;
|
||||
})
|
||||
->label(__('label.torrent_state.global_sp_state'))
|
||||
->required(),
|
||||
DateTimePicker::make('begin')
|
||||
->label(__('label.begin')),
|
||||
->label(__('label.begin'))
|
||||
->required(),
|
||||
DateTimePicker::make('deadline')
|
||||
->label(__('label.deadline')),
|
||||
->label(__('label.deadline'))
|
||||
->required()
|
||||
->after('begin')
|
||||
->validationMessages([
|
||||
'after' => __('label.torrent_state.deadline_after_begin'),
|
||||
]),
|
||||
Select::make('notice_days')
|
||||
->label(__('label.torrent_state.notice_days'))
|
||||
->options(TorrentState::noticeOptions())
|
||||
->required()
|
||||
->default(TorrentState::NOTICE_NONE)
|
||||
->dehydrated(true)
|
||||
->native(false),
|
||||
Textarea::make('remark')
|
||||
->label(__('label.comment'))
|
||||
->rows(2)
|
||||
->columnSpanFull()
|
||||
->maxLength(255),
|
||||
])->columns(1);
|
||||
}
|
||||
|
||||
@@ -63,21 +82,65 @@ class TorrentStateResource extends Resource
|
||||
TextColumn::make('global_sp_state_text')->label(__('label.torrent_state.global_sp_state')),
|
||||
TextColumn::make('begin')->label(__('label.begin')),
|
||||
TextColumn::make('deadline')->label(__('label.deadline')),
|
||||
TextColumn::make('promotion_status')
|
||||
->label(__('label.torrent_state.status'))
|
||||
->state(function (TorrentState $record) {
|
||||
$now = Carbon::now();
|
||||
$begin = $record->begin ? Carbon::parse($record->begin) : null;
|
||||
$deadline = $record->deadline ? Carbon::parse($record->deadline) : null;
|
||||
|
||||
if ($deadline && $deadline->lt($now)) {
|
||||
return 'expired';
|
||||
}
|
||||
if ($begin && $begin->gt($now)) {
|
||||
return 'upcoming';
|
||||
}
|
||||
return 'ongoing';
|
||||
})
|
||||
->formatStateUsing(function (string $state) {
|
||||
return match ($state) {
|
||||
'expired' => __('label.torrent_state.status_expired'),
|
||||
'upcoming' => __('label.torrent_state.status_upcoming'),
|
||||
default => __('label.torrent_state.status_ongoing'),
|
||||
};
|
||||
})
|
||||
->badge()
|
||||
->sortable(query: function (Builder $query, string $direction): Builder {
|
||||
$now = Carbon::now()->toDateTimeString();
|
||||
// expired=0, ongoing=1, upcoming=2
|
||||
return $query->orderByRaw(
|
||||
"CASE
|
||||
WHEN deadline IS NOT NULL AND deadline < ? THEN 0
|
||||
WHEN begin IS NOT NULL AND begin > ? THEN 2
|
||||
ELSE 1
|
||||
END {$direction}",
|
||||
[$now, $now]
|
||||
);
|
||||
})
|
||||
->color(fn (string $state) => match ($state) {
|
||||
'expired' => 'danger',
|
||||
'upcoming' => 'info',
|
||||
default => 'success',
|
||||
})
|
||||
->icon(fn (string $state) => match ($state) {
|
||||
'expired' => 'heroicon-o-x-circle',
|
||||
'upcoming' => 'heroicon-o-clock',
|
||||
default => 'heroicon-o-check-circle',
|
||||
})
|
||||
->iconPosition('before'),
|
||||
TextColumn::make('notice_days_text')
|
||||
->label(__('label.torrent_state.notice_days')),
|
||||
TextColumn::make('remark')->label(__('label.comment'))->limit(50),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make()->after(function () {
|
||||
do_log("cache_del: global_promotion_state");
|
||||
NexusDB::cache_del(Setting::TORRENT_GLOBAL_STATE_CACHE_KEY);
|
||||
do_log("publish_model_event: global_promotion_state_updated");
|
||||
publish_model_event("global_promotion_state_updated", 0);
|
||||
}),
|
||||
// Tables\Actions\DeleteAction::make(),
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
// Tables\Actions\DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
namespace App\Filament\Resources\System\TorrentStateResource\Pages;
|
||||
|
||||
use App\Filament\Resources\System\TorrentStateResource;
|
||||
use Filament\Pages\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class ManageTorrentStates extends ManageRecords
|
||||
{
|
||||
@@ -14,7 +13,7 @@ class ManageTorrentStates extends ManageRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Actions\CreateAction::make(),
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\TorrentCustomFields\Pages;
|
||||
|
||||
use App\Filament\PageList;
|
||||
use App\Filament\Resources\TorrentCustomFields\TorrentCustomFieldResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTorrentCustomFields extends PageList
|
||||
{
|
||||
protected static string $resource = TorrentCustomFieldResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\TorrentCustomFields\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
use Nexus\Field\Field;
|
||||
|
||||
class TorrentCustomFieldForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('label.field.name'))
|
||||
->helperText(__('label.field.name_help'))
|
||||
->alphaDash()
|
||||
->required(),
|
||||
TextInput::make('label')
|
||||
->label(__('label.field.field_label'))
|
||||
->required(),
|
||||
Select::make('type')
|
||||
->options((new Field())->getTypeRadioOptions())
|
||||
->label(__('label.field.type'))
|
||||
->required(),
|
||||
Checkbox::make('required')
|
||||
->label(__('label.field.required')),
|
||||
Textarea::make('help')
|
||||
->label(__('label.field.help'))
|
||||
->rows(3),
|
||||
Textarea::make('options')
|
||||
->label(__('label.field.options'))
|
||||
->rows(3)
|
||||
->hiddenJs("\$get('type') !== 'radio' && \$get('type') !== 'checkbox' && \$get('type') !== 'select'")
|
||||
->helperText(__('label.field.options_help')),
|
||||
Checkbox::make('is_single_row')
|
||||
->label(__('label.field.is_single_row')),
|
||||
TextInput::make('priority')
|
||||
->label(__('label.priority'))
|
||||
->numeric(),
|
||||
Textarea::make('display')
|
||||
->label(__('label.field.display'))
|
||||
->rows(3)
|
||||
->helperText(__('label.search_box.custom_fields_display_help')),
|
||||
|
||||
])
|
||||
->columns(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\TorrentCustomFields\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class TorrentCustomFieldsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id'),
|
||||
TextColumn::make('name')->label(__('label.field.name')),
|
||||
TextColumn::make('label')->label(__('label.field.field_label')),
|
||||
TextColumn::make('type')->label(__('label.field.type')),
|
||||
IconColumn::make('required')->boolean()->label(__('label.field.required')),
|
||||
IconColumn::make('is_single_row')->boolean()->label(__('label.field.is_single_row')),
|
||||
TextColumn::make('priority')->label(__('label.priority')),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\TorrentCustomFields;
|
||||
|
||||
use App\Filament\Resources\TorrentCustomFields\Pages\ListTorrentCustomFields;
|
||||
use App\Filament\Resources\TorrentCustomFields\Schemas\TorrentCustomFieldForm;
|
||||
use App\Filament\Resources\TorrentCustomFields\Tables\TorrentCustomFieldsTable;
|
||||
use App\Models\TorrentCustomField;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
|
||||
class TorrentCustomFieldResource extends Resource
|
||||
{
|
||||
protected static ?string $model = TorrentCustomField::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
protected static string|null|UnitEnum $navigationGroup = 'Section';
|
||||
|
||||
protected static ?int $navigationSort = 12;
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('label.field.label');
|
||||
}
|
||||
|
||||
public static function getBreadcrumb(): string
|
||||
{
|
||||
return self::getNavigationLabel();
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return TorrentCustomFieldForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return TorrentCustomFieldsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListTorrentCustomFields::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Resources\User;
|
||||
|
||||
use App\Repositories\BonusRepository;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\Filter;
|
||||
@@ -9,7 +10,6 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use App\Filament\Resources\User\BonusLogResource\Pages\ManageBonusLogs;
|
||||
use App\Filament\Resources\User\BonusLogResource\Pages;
|
||||
use App\Filament\Resources\User\BonusLogResource\RelationManagers;
|
||||
use App\Models\BonusLogs;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
@@ -17,6 +17,7 @@ use Filament\Tables\Table;
|
||||
use Filament\Tables;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Arr;
|
||||
use function Filament\Support\get_model_label;
|
||||
|
||||
@@ -35,10 +36,10 @@ class BonusLogResource extends Resource
|
||||
return __('admin.sidebar.bonus_log');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return sprintf('%s(%s)', get_model_label(static::getModel()), __('bonus-log.exclude_seeding_bonus'));
|
||||
}
|
||||
// public static function getModelLabel(): string
|
||||
// {
|
||||
// return sprintf('%s(%s)', get_model_label(static::getModel()), __('bonus-log.exclude_seeding_bonus'));
|
||||
// }
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
@@ -51,8 +52,10 @@ class BonusLogResource extends Resource
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->records(function (int $page, int $recordsPerPage, array $filters) {
|
||||
return self::listRecords($page, $recordsPerPage, $filters);
|
||||
})
|
||||
->columns([
|
||||
TextColumn::make('id')->sortable(),
|
||||
TextColumn::make('uid')
|
||||
->formatStateUsing(fn ($state) => username_for_admin($state))
|
||||
->label(__('label.username'))
|
||||
@@ -79,8 +82,18 @@ class BonusLogResource extends Resource
|
||||
->label(__('label.created_at'))
|
||||
,
|
||||
])
|
||||
->defaultSort('id', 'desc')
|
||||
->filters([
|
||||
SelectFilter::make('category')
|
||||
->options(BonusLogs::listCategoryOptions(true))
|
||||
->default(BonusLogs::CATEGORY_COMMON)
|
||||
->selectablePlaceholder(false)
|
||||
->label(__('bonus-log.category'))
|
||||
,
|
||||
SelectFilter::make('business_type')
|
||||
->options(BonusLogs::listBusinessTypeOptions())
|
||||
->label(__('bonus-log.fields.business_type'))
|
||||
->searchable(true)
|
||||
,
|
||||
Filter::make('uid')
|
||||
->schema([
|
||||
TextInput::make('uid')
|
||||
@@ -91,20 +104,6 @@ class BonusLogResource extends Resource
|
||||
return $query->when($data['uid'], fn (Builder $query, $value) => $query->where("uid", $value));
|
||||
})
|
||||
,
|
||||
SelectFilter::make('business_type')
|
||||
->options(BonusLogs::listStaticProps(Arr::except(BonusLogs::$businessTypes, BonusLogs::$businessTypeBonus), 'bonus-log.business_types', true))
|
||||
->label(__('bonus-log.fields.business_type'))
|
||||
,
|
||||
// Tables\Filters\Filter::make('exclude_seeding_bonus')
|
||||
// ->toggle()
|
||||
// ->label(__('bonus-log.exclude_seeding_bonus'))
|
||||
// ->query(function (Builder $query, array $data) {
|
||||
// if ($data['isActive']) {
|
||||
// $query->whereNotIn("business_type", BonusLogs::$businessTypeBonus);
|
||||
// }
|
||||
// })
|
||||
// ->default()
|
||||
// ,
|
||||
])
|
||||
->recordActions([
|
||||
// Tables\Actions\EditAction::make(),
|
||||
@@ -121,4 +120,15 @@ class BonusLogResource extends Resource
|
||||
'index' => ManageBonusLogs::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
private static function listRecords(int $page, int $perPage, array $filters = []): LengthAwarePaginator
|
||||
{
|
||||
$rep = new BonusRepository();
|
||||
$category = $filters['category']['value'] ?: BonusLogs::CATEGORY_COMMON;
|
||||
$userId = intval($filters['userId']['value'] ?? 0);
|
||||
$businessType = intval($filters['businessType']['value'] ?? 0);
|
||||
$list = $rep->getList($category, $userId, $businessType, $page, $perPage);
|
||||
$count = $rep->getCount($category, $userId, $businessType);
|
||||
return new LengthAwarePaginator($list, $count, $perPage, $page);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Filament\Resources\User;
|
||||
|
||||
use Filament\Infolists\Components\RepeatableEntry;
|
||||
use Filament\Infolists\Components\RepeatableEntry\TableColumn;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\BooleanColumn;
|
||||
@@ -193,10 +195,21 @@ class ExamUserResource extends Resource
|
||||
->columns(2)
|
||||
,
|
||||
Group::make([
|
||||
ViewEntry::make('progressFormatted')
|
||||
->label('进度')
|
||||
->view('filament.resources.user.exam-user-resource.pages.detail-v3')
|
||||
])->columnSpan(1),
|
||||
RepeatableEntry::make('progressFormatted')
|
||||
->hiddenLabel()
|
||||
->table([
|
||||
TableColumn::make(__('label.exam.index_required_label')),
|
||||
TableColumn::make(__('label.exam.index_required_value')),
|
||||
TableColumn::make(__('label.exam.index_current_value')),
|
||||
TableColumn::make(__('label.exam.index_result')),
|
||||
])
|
||||
->schema([
|
||||
TextEntry::make('index_formatted'),
|
||||
TextEntry::make('require_value_formatted'),
|
||||
TextEntry::make('current_value_formatted'),
|
||||
TextEntry::make('index_result')->html(),
|
||||
])
|
||||
])->columnSpan(1),
|
||||
]),
|
||||
]);
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@ class UserMedalResource extends Resource
|
||||
DeleteAction::make()->using(function (NexusModel $record) {
|
||||
$record->delete();
|
||||
clear_user_cache($record->uid);
|
||||
send_admin_success_notification();
|
||||
})
|
||||
])
|
||||
->toolbarActions([
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\User;
|
||||
|
||||
use App\Filament\Resources\User\UserPasskeyResource\Pages;
|
||||
use App\Models\Passkey;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class UserPasskeyResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Passkey::class;
|
||||
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-key';
|
||||
|
||||
protected static string | \UnitEnum | null $navigationGroup = 'User';
|
||||
|
||||
protected static ?int $navigationSort = 12;
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('passkey.passkey');
|
||||
}
|
||||
|
||||
public static function getBreadcrumb(): string
|
||||
{
|
||||
return self::getNavigationLabel();
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')->sortable()
|
||||
,
|
||||
Tables\Columns\TextColumn::make('user_id')
|
||||
->formatStateUsing(fn($state) => username_for_admin($state))
|
||||
->label(__('label.username'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('aaguid')
|
||||
->label("AAGUID")
|
||||
,
|
||||
Tables\Columns\TextColumn::make('credential_id')
|
||||
->label(__('passkey.fields.credential_id'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('counter')
|
||||
->label(__('passkey.fields.counter'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->label(__('label.created_at'))
|
||||
,
|
||||
])
|
||||
->defaultSort('id', 'desc')
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('user_id')
|
||||
->form([
|
||||
TextInput::make('uid')
|
||||
->label(__('label.username'))
|
||||
->placeholder('UID')
|
||||
,
|
||||
])->query(function (Builder $query, array $data) {
|
||||
return $query->when($data['uid'], fn(Builder $query, $value) => $query->where("user_id", $value));
|
||||
})
|
||||
,
|
||||
Tables\Filters\Filter::make('credential_id')
|
||||
->form([
|
||||
TextInput::make('credential_id')
|
||||
->label(__('passkey.fields.credential_id'))
|
||||
->placeholder('Credential ID')
|
||||
,
|
||||
])->query(function (Builder $query, array $data) {
|
||||
return $query->when($data['credential_id'], fn(Builder $query, $value) => $query->where("credential_id", $value));
|
||||
})
|
||||
,
|
||||
])
|
||||
->recordActions([
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ManageUserPasskey::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\User\UserPasskeyResource\Pages;
|
||||
|
||||
use App\Filament\PageListSingle;
|
||||
use App\Filament\Resources\User\UserPasskeyResource;
|
||||
|
||||
class ManageUserPasskey extends PageListSingle
|
||||
{
|
||||
protected static string $resource = UserPasskeyResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -25,17 +25,8 @@ class CommentController extends Controller
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$torrentId = $request->torrent_id;
|
||||
$with = ['create_user', 'update_user'];
|
||||
$comments = Comment::query()
|
||||
->with($with)
|
||||
->where('torrent', $torrentId)
|
||||
->paginate();
|
||||
$comments = $this->repository->getList($request, Auth::user());
|
||||
$resource = CommentResource::collection($comments);
|
||||
// $resource->additional([
|
||||
// 'page_title' => nexus_trans('comment.index.page_title'),
|
||||
// ]);
|
||||
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
// \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\BootNexus::class,
|
||||
Locale::class,
|
||||
LogUserIp::class,
|
||||
];
|
||||
|
||||
|
||||
@@ -15,15 +15,13 @@ class CommentResource extends JsonResource
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
$descriptionArr = format_description($this->text);
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'description' => $descriptionArr,
|
||||
'images' => get_image_from_description($descriptionArr),
|
||||
'updated_at_human' => format_datetime($this->editdate),
|
||||
'created_at_human' => $this->added->format('Y-m-d H:i'),
|
||||
'text' => bbcode_attach_to_img($this->text),
|
||||
'updated_at' => format_datetime($this->editdate),
|
||||
'created_at' => format_datetime($this->added),
|
||||
'create_user' => new UserResource($this->whenLoaded('create_user')),
|
||||
'update_user' => new UserResource($this->whenLoaded('update_user')),
|
||||
'update_user' => $this->when($this->editedby > 0, new UserResource($this->whenLoaded('update_user'))),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,16 @@ class UserResource extends JsonResource
|
||||
'bonus_human' => number_format($this->seedbonus, 1),
|
||||
'seed_points' => floatval($this->seed_points),
|
||||
'seed_points_human' => number_format($this->seed_points, 1),
|
||||
'seed_points_per_hour' => floatval($this->seed_points_per_hour),
|
||||
'seed_points_per_hour_human' => number_format($this->seed_points_per_hour, 1),
|
||||
'seed_bonus_per_hour' => floatval($this->seed_bonus_per_hour),
|
||||
'seed_bonus_per_hour_human' => number_format($this->seed_bonus_per_hour, 1),
|
||||
'seedtime' => $this->seedtime,
|
||||
'seedtime_text' => mkprettytime($this->seedtime),
|
||||
'leechtime' => $this->leechtime,
|
||||
'leechtime_text' => mkprettytime($this->leechtime),
|
||||
'share_ratio' => get_ratio($this->id),
|
||||
'seeding_leeching_data' => $this->whenHas('seeding_leeching_data'),
|
||||
'inviter' => new UserResource($this->whenLoaded('inviter')),
|
||||
'valid_medals' => MedalResource::collection($this->whenLoaded('valid_medals')),
|
||||
];
|
||||
|
||||
@@ -47,7 +47,7 @@ class CalculateUserSeedBonus implements ShouldQueue
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public $timeout = 3600;
|
||||
public $timeout = 120;
|
||||
|
||||
/**
|
||||
* 获取任务时,应该通过的中间件。
|
||||
@@ -72,6 +72,7 @@ class CalculateUserSeedBonus implements ShouldQueue
|
||||
$this->requestId, $this->beginUid, $this->endUid, $this->idStr, $this->idRedisKey
|
||||
);
|
||||
do_log("$logPrefix, job start ...");
|
||||
|
||||
$haremAdditionFactor = Setting::get('bonus.harem_addition');
|
||||
$officialAdditionFactor = Setting::get('bonus.official_addition');
|
||||
$donortimes_bonus = Setting::get('bonus.donortimes');
|
||||
@@ -96,7 +97,7 @@ class CalculateUserSeedBonus implements ShouldQueue
|
||||
$logFile = getLogFile("seed-bonus-points");
|
||||
do_log("$logPrefix, [GET_UID_REAL], count: " . count($results) . ", logFile: $logFile");
|
||||
$fd = fopen($logFile, 'a');
|
||||
$seedPointsUpdates = $seedPointsPerHourUpdates = $seedBonusUpdates = [];
|
||||
$seedPointsUpdates = $seedPointsPerHourUpdates = $seedBonusPerHourUpdates = $seedBonusUpdates = [];
|
||||
$seedingTorrentCountUpdates = $seedingTorrentSizeUpdates = [];
|
||||
$logStr = "";
|
||||
$bonusLogInsert = [];
|
||||
@@ -159,6 +160,7 @@ class CalculateUserSeedBonus implements ShouldQueue
|
||||
// NexusDB::statement($sql);
|
||||
$seedPointsUpdates[] = sprintf("when %d then ifnull(seed_points, 0) + %f", $uid, $seed_points);
|
||||
$seedPointsPerHourUpdates[] = sprintf("when %d then %f", $uid, $seedBonusResult['seed_points']);
|
||||
$seedBonusPerHourUpdates[] = sprintf("when %d then %f", $uid, $seedBonusResult['seed_bonus']);
|
||||
$seedingTorrentCountUpdates[] = sprintf("when %d then %f", $uid, $seedBonusResult['torrent_peer_count']);
|
||||
$seedingTorrentSizeUpdates[] = sprintf("when %d then %f", $uid, $seedBonusResult['size']);
|
||||
$seedBonusUpdates[] = sprintf("when %d then seedbonus + %f", $uid, $all_bonus);
|
||||
@@ -177,8 +179,8 @@ class CalculateUserSeedBonus implements ShouldQueue
|
||||
}
|
||||
$nowStr = now()->toDateTimeString();
|
||||
$sql = sprintf(
|
||||
"update users set seed_points = case id %s end, seed_points_per_hour = case id %s end, seedbonus = case id %s end, seeding_torrent_count = case id %s end, seeding_torrent_size = case id %s end, seed_points_updated_at = '%s' where id in (%s)",
|
||||
implode(" ", $seedPointsUpdates), implode(" ", $seedPointsPerHourUpdates), implode(" ", $seedBonusUpdates), implode(" ", $seedingTorrentCountUpdates), implode(" ", $seedingTorrentSizeUpdates), $nowStr, $idStr
|
||||
"update users set seed_points = case id %s end, seed_points_per_hour = case id %s end, seed_bonus_per_hour = case id %s end, seedbonus = case id %s end, seeding_torrent_count = case id %s end, seeding_torrent_size = case id %s end, seed_points_updated_at = '%s' where id in (%s)",
|
||||
implode(" ", $seedPointsUpdates), implode(" ", $seedPointsPerHourUpdates), implode(" ", $seedBonusPerHourUpdates), implode(" ", $seedBonusUpdates), implode(" ", $seedingTorrentCountUpdates), implode(" ", $seedingTorrentSizeUpdates), $nowStr, $idStr
|
||||
);
|
||||
$result = NexusDB::statement($sql);
|
||||
if ($delIdRedisKey) {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ModelEventEnum;
|
||||
use App\Models\Message;
|
||||
use App\Models\User;
|
||||
use App\Models\UserModifyLog;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
|
||||
class RemoveUserDonorStatus
|
||||
{
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$users = User::query()
|
||||
->with('language')
|
||||
->where('donor', 'yes')
|
||||
->whereNotNull('donoruntil')
|
||||
->where('donoruntil', '<', now())
|
||||
->get();
|
||||
$userModifyLogs = [];
|
||||
foreach ($users as $user) {
|
||||
$locale = $user->locale;
|
||||
$userModifyLogs[] = [
|
||||
'user_id' => $user->id,
|
||||
'content' => "donor status removed by - AutoSystem",
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
$user->donor = 'no';
|
||||
do_log(sprintf("update user %s => %s", $user->id, json_encode($user->getDirty())));
|
||||
$user->save();
|
||||
clear_user_cache($user->id);
|
||||
publish_model_event(ModelEventEnum::USER_UPDATED, $user->id);
|
||||
$subject = nexus_trans("cleanup.msg_donor_status_removed", [], $locale);
|
||||
$msg = nexus_trans("cleanup.msg_donor_status_removed_body", [], $locale);
|
||||
Message::add([
|
||||
'sender' => 0,
|
||||
'receiver' => $user->id,
|
||||
'added' => now(),
|
||||
'subject' => $subject,
|
||||
'msg' => $msg,
|
||||
]);
|
||||
}
|
||||
if (!empty($userModifyLogs)) {
|
||||
UserModifyLog::query()->insert($userModifyLogs);
|
||||
}
|
||||
do_log("remove donor status if time's up, success handle user count: " . $users->count());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ModelEventEnum;
|
||||
use App\Models\Message;
|
||||
use App\Models\User;
|
||||
use App\Models\UserModifyLog;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
|
||||
class RemoveUserVipStatus
|
||||
{
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$users = User::query()
|
||||
->with('language')
|
||||
->where('vip_added', 'yes')
|
||||
->where('vip_until', '<', now())
|
||||
->get();
|
||||
$userModifyLogs = [];
|
||||
foreach ($users as $user) {
|
||||
$locale = $user->locale;
|
||||
$userModifyLogs[] = [
|
||||
'user_id' => $user->id,
|
||||
'content' => "VIP status removed by - AutoSystem",
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
$message = [];
|
||||
$user->vip_added = 'no';
|
||||
$user->vip_until = null;
|
||||
if ($user->class <= User::CLASS_VIP) {
|
||||
$user->class = User::CLASS_USER;
|
||||
$subject = nexus_trans("cleanup.msg_vip_status_removed", [], $locale);
|
||||
$msg = nexus_trans("cleanup.msg_vip_status_removed_body", [], $locale);
|
||||
$message = [
|
||||
'sender' => 0,
|
||||
'receiver' => $user->id,
|
||||
'added' => now(),
|
||||
'subject' => $subject,
|
||||
'msg' => $msg,
|
||||
];
|
||||
}
|
||||
do_log(sprintf("update user %s => %s", $user->id, json_encode($user->getDirty())));
|
||||
$user->save();
|
||||
clear_user_cache($user->id);
|
||||
publish_model_event(ModelEventEnum::USER_UPDATED, $user->id);
|
||||
if (!empty($message)) {
|
||||
Message::add($message);
|
||||
}
|
||||
}
|
||||
if (!empty($userModifyLogs)) {
|
||||
UserModifyLog::query()->insert($userModifyLogs);
|
||||
}
|
||||
do_log("remove VIP status if time's up, success handle user count: " . $users->count());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ModelEventEnum;
|
||||
use App\Models\Message;
|
||||
use App\Models\User;
|
||||
use App\Models\UserModifyLog;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
|
||||
class RemoveUserWarning
|
||||
{
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$users = User::query()
|
||||
->with('language')
|
||||
->where('enabled', 'yes')
|
||||
->where('warned', 'yes')
|
||||
->where('warneduntil', '<', now())
|
||||
->get();
|
||||
$userModifyLogs = [];
|
||||
foreach ($users as $user) {
|
||||
$locale = $user->locale;
|
||||
$userModifyLogs[] = [
|
||||
'user_id' => $user->id,
|
||||
'content' => "Warning removed by System.",
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
$user->warned = 'no';
|
||||
$user->warneduntil = null;
|
||||
do_log(sprintf("update user %s => %s", $user->id, json_encode($user->getDirty())));
|
||||
$user->save();
|
||||
clear_user_cache($user->id);
|
||||
publish_model_event(ModelEventEnum::USER_UPDATED, $user->id);
|
||||
$subject = nexus_trans("cleanup.msg_warning_removed", [], $locale);
|
||||
$msg = nexus_trans("cleanup.msg_your_warning_removed", [], $locale);
|
||||
Message::add([
|
||||
'sender' => 0,
|
||||
'receiver' => $user->id,
|
||||
'added' => now(),
|
||||
'subject' => $subject,
|
||||
'msg' => $msg,
|
||||
]);
|
||||
}
|
||||
if (!empty($userModifyLogs)) {
|
||||
UserModifyLog::query()->insert($userModifyLogs);
|
||||
}
|
||||
do_log("remove warning of users, success handle user count: " . $users->count());
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,24 @@ namespace App\Models;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class BonusLogs extends NexusModel
|
||||
{
|
||||
protected $table = 'bonus_logs';
|
||||
|
||||
protected $fillable = ['uid', 'business_type', 'old_total_value', 'value', 'new_total_value', 'comment'];
|
||||
protected $fillable = ['uid', 'business_type', 'old_total_value', 'value', 'new_total_value', 'comment', 'created_at', 'updated_at'];
|
||||
|
||||
public $timestamps = true;
|
||||
const CATEGORY_COMMON = 'common';
|
||||
const CATEGORY_SEEDING = 'seeding';
|
||||
|
||||
const DEFAULT_BONUS_CANCEL_ONE_HIT_AND_RUN = 10000;
|
||||
const DEFAULT_BONUS_BUY_ATTENDANCE_CARD = 1000;
|
||||
const DEFAULT_BONUS_BUY_TEMPORARY_INVITE = 500;
|
||||
const DEFAULT_BONUS_BUY_RAINBOW_ID = 5000;
|
||||
const DEFAULT_BONUS_BUY_CHANGE_USERNAME_CARD = 100000;
|
||||
const DEFAULT_BONUS_SELF_ENABLE = 100000;
|
||||
|
||||
//扣除类,1开始
|
||||
const BUSINESS_TYPE_CANCEL_HIT_AND_RUN = 1;
|
||||
@@ -42,6 +46,8 @@ class BonusLogs extends NexusModel
|
||||
const BUSINESS_TYPE_TASK_NOT_PASS_DEDUCT = 20;
|
||||
const BUSINESS_TYPE_TASK_PASS_REWARD = 21;
|
||||
const BUSINESS_TYPE_REWARD_TORRENT = 22;
|
||||
const BUSINESS_TYPE_CLAIMED_UNREACHED = 23;
|
||||
const BUSINESS_TYPE_SELF_ENABLE = 24;
|
||||
|
||||
//获得类,普通获得,1000 起步
|
||||
const BUSINESS_TYPE_ROLE_WORK_SALARY = 1000;
|
||||
@@ -50,6 +56,7 @@ class BonusLogs extends NexusModel
|
||||
const BUSINESS_TYPE_RECEIVE_GIFT = 1003;
|
||||
const BUSINESS_TYPE_UPLOAD_TORRENT = 1004;
|
||||
const BUSINESS_TYPE_TORRENT_BE_REWARD = 1005;
|
||||
const BUSINESS_TYPE_CLAIMED_REACHED = 1006;
|
||||
|
||||
//获得类,做种获得,10000 起
|
||||
const BUSINESS_TYPE_SEEDING_BASIC = 10000;
|
||||
@@ -81,6 +88,8 @@ class BonusLogs extends NexusModel
|
||||
self::BUSINESS_TYPE_TASK_NOT_PASS_DEDUCT => ['text' => 'Task failure penalty'],
|
||||
self::BUSINESS_TYPE_TASK_PASS_REWARD => ['text' => 'Task success reward'],
|
||||
self::BUSINESS_TYPE_REWARD_TORRENT => ['text' => 'Reward torrent'],
|
||||
self::BUSINESS_TYPE_CLAIMED_UNREACHED => ['text' => 'Claimed torrent unreached'],
|
||||
self::BUSINESS_TYPE_SELF_ENABLE => ['text' => 'Self enable'],
|
||||
|
||||
self::BUSINESS_TYPE_ROLE_WORK_SALARY => ['text' => 'Role work salary'],
|
||||
self::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => ['text' => 'Torrent be downloaded'],
|
||||
@@ -88,6 +97,7 @@ class BonusLogs extends NexusModel
|
||||
self::BUSINESS_TYPE_RECEIVE_GIFT => ['text' => 'Receive gift'],
|
||||
self::BUSINESS_TYPE_UPLOAD_TORRENT => ['text' => 'Upload torrent'],
|
||||
self::BUSINESS_TYPE_TORRENT_BE_REWARD => ['text' => 'Torrent be reward'],
|
||||
self::BUSINESS_TYPE_CLAIMED_REACHED => ['text' => 'Claimed torrent reached'],
|
||||
|
||||
self::BUSINESS_TYPE_SEEDING_BASIC => ['text' => 'Seeding basic'],
|
||||
self::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => ['text' => 'Seeding donor addition'],
|
||||
@@ -96,7 +106,7 @@ class BonusLogs extends NexusModel
|
||||
self::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION => ['text' => 'Seeding medal addition'],
|
||||
];
|
||||
|
||||
public static array $businessTypeBonus = [
|
||||
public static array $businessTypeSeeding = [
|
||||
self::BUSINESS_TYPE_SEEDING_BASIC,
|
||||
self::BUSINESS_TYPE_SEEDING_DONOR_ADDITION,
|
||||
self::BUSINESS_TYPE_SEEDING_OFFICIAL_ADDITION,
|
||||
@@ -104,6 +114,28 @@ class BonusLogs extends NexusModel
|
||||
self::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION
|
||||
];
|
||||
|
||||
public static function listBusinessTypeOptions($category = ''): array
|
||||
{
|
||||
$source = BonusLogs::$businessTypes;
|
||||
if ($category == self::CATEGORY_COMMON) {
|
||||
$source = Arr::except(BonusLogs::$businessTypes, BonusLogs::$businessTypeSeeding);
|
||||
} else if ($category == self::CATEGORY_SEEDING) {
|
||||
$source = Arr::only(BonusLogs::$businessTypes, BonusLogs::$businessTypeSeeding);
|
||||
}
|
||||
return self::listStaticProps($source, 'bonus-log.business_types', true);
|
||||
}
|
||||
|
||||
public static function listCategoryOptions(bool $includeSeeding): array
|
||||
{
|
||||
$result = [
|
||||
self::CATEGORY_COMMON => nexus_trans('bonus-log.category_common')
|
||||
];
|
||||
if ($includeSeeding) {
|
||||
$result[self::CATEGORY_SEEDING] = nexus_trans('bonus-log.category_seeding');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getBusinessTypeTextAttribute()
|
||||
{
|
||||
return nexus_trans('bonus-log.business_types.' . $this->business_type);
|
||||
|
||||
@@ -235,6 +235,7 @@ class Exam extends NexusModel
|
||||
|
||||
public function getRecurringBegin(Carbon $time): Carbon
|
||||
{
|
||||
$time = $time->copy();
|
||||
$recurring = $this->recurring;
|
||||
if ($recurring == self::RECURRING_WEEKLY) {
|
||||
return $time->startOfWeek();
|
||||
@@ -248,6 +249,7 @@ class Exam extends NexusModel
|
||||
|
||||
public function getRecurringEnd(Carbon $time): Carbon
|
||||
{
|
||||
$time = $time->copy();
|
||||
$recurring = $this->recurring;
|
||||
if ($recurring == self::RECURRING_WEEKLY) {
|
||||
return $time->endOfWeek();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class Faq extends NexusModel
|
||||
{
|
||||
protected $table = 'faq';
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class IpLog extends NexusModel
|
||||
private function getIpLocation(string $ip)
|
||||
{
|
||||
$result = get_ip_location_from_geoip($ip);
|
||||
$out = $result['name'];
|
||||
$out = $result['name'] ?? '';
|
||||
$suffix = [];
|
||||
if (!empty($result['city_en'])) {
|
||||
$suffix[] = $result['city_en'];
|
||||
|
||||
@@ -15,7 +15,10 @@ class NexusModel extends Model
|
||||
|
||||
protected $perPage = 50;
|
||||
|
||||
protected $connection = NexusDB::ELOQUENT_CONNECTION_NAME;
|
||||
public function getConnectionName()
|
||||
{
|
||||
return NexusDB::getConnectionName();
|
||||
}
|
||||
|
||||
protected function usernameForAdmin(): Attribute
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Passkey extends NexusModel
|
||||
{
|
||||
protected $table = 'user_passkeys';
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = [
|
||||
'id', 'user_id', 'aaguid', 'credential_id', 'public_key', 'counter',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function getAaguidFormatted(): string
|
||||
{
|
||||
$guid = $this->aaguid;
|
||||
return sprintf(
|
||||
'%s-%s-%s-%s-%s',
|
||||
substr($guid, 0, 8),
|
||||
substr($guid, 8, 4),
|
||||
substr($guid, 12, 4),
|
||||
substr($guid, 16, 4),
|
||||
substr($guid, 20, 12)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use App\Models\Traits\NexusActivityLogTrait;
|
||||
|
||||
class RegImage extends NexusModel
|
||||
{
|
||||
protected $table = 'regimages';
|
||||
|
||||
protected $fillable = ['imagehash', 'imagestring', 'dateline'];
|
||||
}
|
||||
@@ -334,4 +334,9 @@ class Setting extends NexusModel
|
||||
{
|
||||
return self::get('main.showimdbinfo') == 'yes';
|
||||
}
|
||||
|
||||
public static function getSelfEnableBonus(): int
|
||||
{
|
||||
return (int)self::get("bonus.self_enable", BonusLogs::DEFAULT_BONUS_SELF_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
+41
-1
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Repositories\TagRepository;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class Torrent extends NexusModel
|
||||
{
|
||||
@@ -186,6 +187,37 @@ class Torrent extends NexusModel
|
||||
self::NFO_VIEW_STYLE_WINDOWS => ['text' => 'Windows-vy'],
|
||||
];
|
||||
|
||||
public function scopeWhereInfoHash($query, string $binaryHash)
|
||||
{
|
||||
if (NexusDB::isPgsql()) {
|
||||
return $query->whereRaw(
|
||||
"info_hash = decode(?, 'hex')",
|
||||
[bin2hex($binaryHash)]
|
||||
);
|
||||
} elseif (NexusDB::isMysql()) {
|
||||
return $query->where('info_hash', $binaryHash);
|
||||
}
|
||||
throw new \RuntimeException("Not supported database");
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写获取 info_hash 的方法,确保从数据库读出时是正确的格式
|
||||
* 注意:不要使用 getInfoHashAttribute(),不带缓存,第1次有值,第2次指针到头,数据是空!!!
|
||||
*/
|
||||
public function infoHash(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function ($value) {
|
||||
// PostgreSQL 返回 bytea 时可能是十六进制流或资源
|
||||
if (is_resource($value)) {
|
||||
rewind($value);
|
||||
return stream_get_contents($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
)->shouldCache();
|
||||
}
|
||||
|
||||
public function getPickInfoAttribute()
|
||||
{
|
||||
$info = self::$pickTypes[$this->picktype] ?? null;
|
||||
@@ -521,8 +553,16 @@ class Torrent extends NexusModel
|
||||
|
||||
public function tags(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
{
|
||||
$idsString = TagRepository::getOrderByFieldIdString();
|
||||
if (NexusDB::isPgsql()) {
|
||||
$orderByRaw = "array_position(ARRAY[$idsString]::int[], tags.id)";
|
||||
} else if (NexusDB::isMysql()) {
|
||||
$orderByRaw = "FIELD(tags.id, $idsString)";
|
||||
} else {
|
||||
throw new \RuntimeException("Unsupported database");
|
||||
}
|
||||
return $this->belongsToMany(Tag::class, 'torrent_tags', 'torrent_id', 'tag_id')
|
||||
->orderByRaw(sprintf("field(`tags`.`id`,%s)", TagRepository::getOrderByFieldIdString()));
|
||||
->orderByRaw($orderByRaw);
|
||||
}
|
||||
|
||||
public function reward_logs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
|
||||
+306
-1
@@ -4,17 +4,322 @@ namespace App\Models;
|
||||
|
||||
|
||||
use App\Models\Traits\NexusActivityLogTrait;
|
||||
use App\Models\Setting;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class TorrentState extends NexusModel
|
||||
{
|
||||
use NexusActivityLogTrait;
|
||||
|
||||
protected $fillable = ['global_sp_state', 'deadline', 'begin'];
|
||||
public const NOTICE_NONE = 0;
|
||||
public const NOTICE_UNLIMITED = -1;
|
||||
|
||||
protected $fillable = ['global_sp_state', 'deadline', 'begin', 'remark', 'notice_days'];
|
||||
|
||||
protected $table = 'torrents_state';
|
||||
|
||||
protected $casts = [
|
||||
'begin' => 'datetime',
|
||||
'deadline' => 'datetime',
|
||||
'notice_days' => 'integer',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
parent::booted();
|
||||
|
||||
static::saving(function (TorrentState $state) {
|
||||
$state->validateTimeRange();
|
||||
$state->ensureNoOverlap();
|
||||
});
|
||||
|
||||
static::saved(function () {
|
||||
static::flushCache();
|
||||
});
|
||||
|
||||
static::deleted(function () {
|
||||
static::flushCache();
|
||||
});
|
||||
}
|
||||
|
||||
public function getGlobalSpStateTextAttribute()
|
||||
{
|
||||
return Torrent::$promotionTypes[$this->global_sp_state]['text'] ?? '';
|
||||
}
|
||||
|
||||
public function getNoticeDaysTextAttribute(): string
|
||||
{
|
||||
return self::noticeOptions()[$this->notice_days] ?? '';
|
||||
}
|
||||
|
||||
public function scopeActive(Builder $query, ?Carbon $moment = null): Builder
|
||||
{
|
||||
$moment = $moment ?? Carbon::now();
|
||||
|
||||
return $query
|
||||
->where('global_sp_state', '!=', Torrent::PROMOTION_NORMAL)
|
||||
->where(function (Builder $query) use ($moment) {
|
||||
$query->whereNull('begin')->orWhere('begin', '<=', $moment);
|
||||
})
|
||||
->where(function (Builder $query) use ($moment) {
|
||||
$query->whereNull('deadline')->orWhere('deadline', '>=', $moment);
|
||||
})
|
||||
->orderBy('begin')
|
||||
->orderBy('id');
|
||||
}
|
||||
|
||||
public function scopeUpcoming(Builder $query, ?Carbon $moment = null): Builder
|
||||
{
|
||||
$moment = $moment ?? Carbon::now();
|
||||
|
||||
return $query
|
||||
->where('global_sp_state', '!=', Torrent::PROMOTION_NORMAL)
|
||||
->whereNotNull('begin')
|
||||
->where('begin', '>', $moment)
|
||||
->orderBy('begin')
|
||||
->orderBy('id');
|
||||
}
|
||||
|
||||
public static function current(?Carbon $moment = null): ?self
|
||||
{
|
||||
return self::query()->active($moment)->first();
|
||||
}
|
||||
|
||||
public static function next(?Carbon $moment = null): ?self
|
||||
{
|
||||
return self::query()->upcoming($moment)->first();
|
||||
}
|
||||
|
||||
public static function cachedStates(): array
|
||||
{
|
||||
return NexusDB::remember(Setting::TORRENT_GLOBAL_STATE_CACHE_KEY, 600, function () {
|
||||
return self::query()
|
||||
->where('global_sp_state', '!=', Torrent::PROMOTION_NORMAL)
|
||||
->orderByRaw('begin is null')
|
||||
->orderBy('begin')
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
public static function flushCache(): void
|
||||
{
|
||||
do_log("cache_del: " . Setting::TORRENT_GLOBAL_STATE_CACHE_KEY);
|
||||
NexusDB::cache_del(Setting::TORRENT_GLOBAL_STATE_CACHE_KEY);
|
||||
do_log("publish_model_event: global_promotion_state_updated");
|
||||
publish_model_event("global_promotion_state_updated", 0);
|
||||
}
|
||||
|
||||
public static function resolveTimeline(?Carbon $moment = null): array
|
||||
{
|
||||
$moment = $moment ?? Carbon::now();
|
||||
$states = self::cachedStates();
|
||||
$current = null;
|
||||
$upcoming = null;
|
||||
|
||||
foreach ($states as $state) {
|
||||
$begin = self::parseDateTimeValue($state['begin'] ?? null);
|
||||
$deadline = self::parseDateTimeValue($state['deadline'] ?? null);
|
||||
$noticeDays = (int)($state['notice_days'] ?? self::NOTICE_NONE);
|
||||
|
||||
$hasBegun = !$begin || $begin->lessThanOrEqualTo($moment);
|
||||
$notExpired = !$deadline || $deadline->greaterThanOrEqualTo($moment);
|
||||
|
||||
if ($hasBegun && $notExpired) {
|
||||
if (!$current) {
|
||||
$current = $state;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($begin && $begin->greaterThan($moment)) {
|
||||
if (!self::isWithinNoticeWindow($begin, $noticeDays, $moment)) {
|
||||
continue;
|
||||
}
|
||||
if (!$upcoming) {
|
||||
$upcoming = $state;
|
||||
continue;
|
||||
}
|
||||
$upcomingBegin = self::parseDateTimeValue($upcoming['begin'] ?? null);
|
||||
if ($upcomingBegin && $begin->lessThan($upcomingBegin)) {
|
||||
$upcoming = $state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'current' => $current,
|
||||
'upcoming' => $upcoming,
|
||||
];
|
||||
}
|
||||
|
||||
protected function validateTimeRange(): void
|
||||
{
|
||||
$begin = self::parseDateTimeValue($this->begin);
|
||||
$deadline = self::parseDateTimeValue($this->deadline);
|
||||
|
||||
if ($begin && $deadline && $deadline->lessThanOrEqualTo($begin)) {
|
||||
throw ValidationException::withMessages([
|
||||
self::errorFieldKey('deadline') => __('label.torrent_state.deadline_after_begin'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function ensureNoOverlap(): void
|
||||
{
|
||||
self::validateNoOverlap($this->attributesToArray(), $this->id);
|
||||
}
|
||||
|
||||
protected function getRangeForComparison(TorrentState $state): array
|
||||
{
|
||||
$min = Carbon::createFromTimestamp(0);
|
||||
$max = Carbon::create(9999, 12, 31, 23, 59, 59);
|
||||
|
||||
$begin = self::parseDateTimeValue($state->begin) ?? $min;
|
||||
|
||||
$deadline = self::parseDateTimeValue($state->deadline) ?? $max;
|
||||
|
||||
return [
|
||||
'begin' => $begin,
|
||||
'end' => $deadline,
|
||||
];
|
||||
}
|
||||
|
||||
protected static function parseDateTimeValue(mixed $value): ?Carbon
|
||||
{
|
||||
if ($value instanceof Carbon) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (empty($value) || $value === '0000-00-00 00:00:00') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Carbon::parse($value);
|
||||
}
|
||||
|
||||
public static function validateNoOverlap(array $attributes, ?int $ignoreId = null): void
|
||||
{
|
||||
$globalState = (int) Arr::get($attributes, 'global_sp_state', Torrent::PROMOTION_NORMAL);
|
||||
if ($globalState === Torrent::PROMOTION_NORMAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
$range = self::getRangeForArray($attributes);
|
||||
|
||||
$conflicts = self::query()
|
||||
->where('global_sp_state', '!=', Torrent::PROMOTION_NORMAL)
|
||||
->when($ignoreId, fn (Builder $query) => $query->whereKeyNot($ignoreId))
|
||||
->get(['id', 'begin', 'deadline']);
|
||||
|
||||
$beginConflict = $conflicts->first(function (TorrentState $state) use ($range) {
|
||||
$other = $state->getRangeForComparison($state);
|
||||
return $range['begin']->greaterThanOrEqualTo($other['begin']) && $range['begin']->lessThanOrEqualTo($other['end']);
|
||||
});
|
||||
|
||||
$endConflict = $conflicts->first(function (TorrentState $state) use ($range) {
|
||||
$other = $state->getRangeForComparison($state);
|
||||
return $range['end']->greaterThanOrEqualTo($other['begin']) && $range['end']->lessThanOrEqualTo($other['end']);
|
||||
});
|
||||
|
||||
$coverageConflict = $conflicts->first(function (TorrentState $state) use ($range) {
|
||||
$other = $state->getRangeForComparison($state);
|
||||
return $range['begin']->lt($other['begin']) && $range['end']->gt($other['end']);
|
||||
});
|
||||
|
||||
if ($beginConflict || $endConflict || $coverageConflict) {
|
||||
$errors = [];
|
||||
|
||||
if ($beginConflict) {
|
||||
$errors[self::errorFieldKey('begin')] = self::buildOverlapMessage($beginConflict);
|
||||
}
|
||||
|
||||
if ($endConflict) {
|
||||
$errors[self::errorFieldKey('deadline')] = self::buildOverlapMessage($endConflict);
|
||||
}
|
||||
|
||||
if (empty($errors) && $coverageConflict) {
|
||||
$msg = self::buildOverlapMessage($coverageConflict);
|
||||
$errors[self::errorFieldKey('begin')] = $msg;
|
||||
$errors[self::errorFieldKey('deadline')] = $msg;
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
$msg = __('label.torrent_state.time_overlaps');
|
||||
$errors[self::errorFieldKey('begin')] = $msg;
|
||||
$errors[self::errorFieldKey('deadline')] = $msg;
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages($errors);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function getRangeForArray(array $attributes): array
|
||||
{
|
||||
$min = Carbon::createFromTimestamp(0);
|
||||
$max = Carbon::create(9999, 12, 31, 23, 59, 59);
|
||||
|
||||
$begin = self::parseDateTimeValue($attributes['begin'] ?? null) ?? $min;
|
||||
$deadline = self::parseDateTimeValue($attributes['deadline'] ?? null) ?? $max;
|
||||
|
||||
return [
|
||||
'begin' => $begin,
|
||||
'end' => $deadline,
|
||||
];
|
||||
}
|
||||
|
||||
protected static function errorFieldKey(string $field): string
|
||||
{
|
||||
$prefix = 'mountedActions.0.data.';
|
||||
|
||||
return $prefix . $field;
|
||||
}
|
||||
|
||||
protected static function buildOverlapMessage(TorrentState $conflict): string
|
||||
{
|
||||
$begin = self::parseDateTimeValue($conflict->begin);
|
||||
$deadline = self::parseDateTimeValue($conflict->deadline);
|
||||
|
||||
$beginText = $begin ? $begin->toDateTimeString() : '-∞';
|
||||
$deadlineText = $deadline ? $deadline->toDateTimeString() : '∞';
|
||||
|
||||
return __('label.torrent_state.time_overlaps_with', [
|
||||
'id' => $conflict->id,
|
||||
'begin' => $beginText,
|
||||
'end' => $deadlineText,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function noticeOptions(): array
|
||||
{
|
||||
return [
|
||||
self::NOTICE_NONE => __('label.torrent_state.notice_none'),
|
||||
1 => __('label.torrent_state.notice_day', ['days' => 1]),
|
||||
3 => __('label.torrent_state.notice_day', ['days' => 3]),
|
||||
7 => __('label.torrent_state.notice_day', ['days' => 7]),
|
||||
15 => __('label.torrent_state.notice_day', ['days' => 15]),
|
||||
30 => __('label.torrent_state.notice_day', ['days' => 30]),
|
||||
self::NOTICE_UNLIMITED => __('label.torrent_state.notice_unlimited'),
|
||||
];
|
||||
}
|
||||
|
||||
protected static function isWithinNoticeWindow(?Carbon $begin, int $noticeDays, Carbon $now): bool
|
||||
{
|
||||
if (!$begin) {
|
||||
return true;
|
||||
}
|
||||
if ($noticeDays === self::NOTICE_NONE) {
|
||||
return false;
|
||||
}
|
||||
if ($noticeDays === self::NOTICE_UNLIMITED) {
|
||||
return true;
|
||||
}
|
||||
return $begin->copy()->subDays($noticeDays)->lessThanOrEqualTo($now);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,13 +85,23 @@ class TrackerUrl extends NexusModel
|
||||
if ($redis->exists($notFoundFlagKey)) {
|
||||
return false;
|
||||
}
|
||||
self::saveUrlCache();
|
||||
$result = call_user_func_array([$redis, $command], $params);
|
||||
if ($result !== false) {
|
||||
return $result;
|
||||
$lockKey = "$notFoundFlagKey:lock";
|
||||
if (!$redis->set($lockKey, 1, ["nx", "ex" => 5])) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
self::saveUrlCache();
|
||||
$result = call_user_func_array([$redis, $command], $params);
|
||||
if ($result !== false) {
|
||||
return $result;
|
||||
}
|
||||
//只从 db 拉取一次,仍然没有即标记不存在, 有效期 15 分钟
|
||||
$redis->setex($notFoundFlagKey, 900, date("Y-m-d H:i:s"));
|
||||
} catch (\Throwable $throwable) {
|
||||
do_log($throwable->getMessage(), 'error');
|
||||
} finally {
|
||||
$redis->del($lockKey);
|
||||
}
|
||||
//只从 db 拉取一次,仍然没有即标记不存在, 有效期 15 分钟
|
||||
$redis->setex($notFoundFlagKey, 900, date("Y-m-d H:i:s"));
|
||||
do_log(sprintf("redis command %s with args %s no result", $command, json_encode($params)), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
+30
-5
@@ -6,6 +6,7 @@ use App\Exceptions\NexusException;
|
||||
use App\Http\Middleware\Locale;
|
||||
use App\Models\Traits\NexusActivityLogTrait;
|
||||
use App\Repositories\ExamRepository;
|
||||
use App\Repositories\TokenRepository;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -30,8 +31,6 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
|
||||
protected $perPage = 50;
|
||||
|
||||
protected $connection = NexusDB::ELOQUENT_CONNECTION_NAME;
|
||||
|
||||
const STATUS_CONFIRMED = 'confirmed';
|
||||
const STATUS_PENDING = 'pending';
|
||||
|
||||
@@ -108,6 +107,11 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
|
||||
private const USER_ENABLE_LATELY = "user_enable_lately:%s";
|
||||
|
||||
public function getConnectionName()
|
||||
{
|
||||
return NexusDB::getConnectionName();
|
||||
}
|
||||
|
||||
public static function getUserEnableLatelyCacheKey(int $userId): string
|
||||
{
|
||||
return sprintf(self::USER_ENABLE_LATELY, $userId);
|
||||
@@ -368,7 +372,6 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
{
|
||||
return $query->where('donor', 'yes')->where(function (Builder $query) {
|
||||
return $query->whereNull('donoruntil')
|
||||
->orWhere('donoruntil', '0000-00-00 00:00:00')
|
||||
->orWhere('donoruntil', '>=', now());
|
||||
});
|
||||
}
|
||||
@@ -635,10 +638,32 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
return is_null($this->original['notifs']) || str_contains($this->notifs, "[{$name}]");
|
||||
}
|
||||
|
||||
public function tokenCan(string $ability)
|
||||
public function tokenCan(string $ability): bool
|
||||
{
|
||||
$redis = NexusDB::redis();
|
||||
return $redis->sismember(Setting::USER_TOKEN_PERMISSION_ALLOWED_CACHE_KRY, $ability)
|
||||
$cacheKey = Setting::USER_TOKEN_PERMISSION_ALLOWED_CACHE_KRY;
|
||||
if (!$redis->exists($cacheKey)) {
|
||||
$lockKey = "$cacheKey:lock";
|
||||
if ($redis->set($lockKey, 1, ['nx', 'ex' => 5])) {
|
||||
try {
|
||||
if (!$redis->exists($cacheKey)) {
|
||||
$abilities = TokenRepository::listUserTokenPermissions(false);
|
||||
do_log("load user token permissions: " . json_encode($abilities), 'alert');
|
||||
if (!empty($abilities)) {
|
||||
$redis->sadd($cacheKey, ...$abilities);
|
||||
} else {
|
||||
$redis->sadd($cacheKey, "__NO_USER_TOKEN_PERMISSION__");
|
||||
$redis->expire($cacheKey, 900);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $throwable) {
|
||||
do_log($throwable->getMessage(), 'error');
|
||||
} finally {
|
||||
$redis->del($lockKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $redis->sismember($cacheKey, $ability)
|
||||
&& $this->accessToken && $this->accessToken->can($ability);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ class UserBanLog extends NexusModel
|
||||
|
||||
protected $fillable = ['uid', 'username', 'operator', 'reason'];
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
public static function clearUserBanLogDuplicate()
|
||||
{
|
||||
$lists = UserBanLog::query()
|
||||
|
||||
@@ -42,7 +42,7 @@ class TorrentStatePolicy extends BasePolicy
|
||||
*/
|
||||
public function create(User $user)
|
||||
{
|
||||
return false;
|
||||
return $this->can($user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +66,7 @@ class TorrentStatePolicy extends BasePolicy
|
||||
*/
|
||||
public function delete(User $user, TorrentState $torrentState)
|
||||
{
|
||||
|
||||
return $this->can($user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,7 @@ class TorrentStatePolicy extends BasePolicy
|
||||
*/
|
||||
public function restore(User $user, TorrentState $torrentState)
|
||||
{
|
||||
|
||||
return $this->can($user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ class TorrentStatePolicy extends BasePolicy
|
||||
*/
|
||||
public function forceDelete(User $user, TorrentState $torrentState)
|
||||
{
|
||||
//
|
||||
return $this->can($user);
|
||||
}
|
||||
|
||||
private function can(User $user)
|
||||
|
||||
@@ -16,6 +16,7 @@ use App\Models\SecondIcon;
|
||||
use App\Models\Source;
|
||||
use App\Models\Standard;
|
||||
use App\Models\Team;
|
||||
use App\Models\TorrentCustomField;
|
||||
use App\Models\User;
|
||||
use App\Policies\CodecPolicy;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
@@ -34,6 +35,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
Category::class => CodecPolicy::class,
|
||||
Icon::class => CodecPolicy::class,
|
||||
SecondIcon::class => CodecPolicy::class,
|
||||
TorrentCustomField::class => CodecPolicy::class,
|
||||
|
||||
Codec::class => CodecPolicy::class,
|
||||
AudioCodec::class => CodecPolicy::class,
|
||||
|
||||
@@ -244,8 +244,8 @@ class AttendanceRepository extends BaseRepository
|
||||
return 0;
|
||||
}
|
||||
$sql = sprintf(
|
||||
"insert into `%s` (`uid`, `points`, `date`) values %s on duplicate key update `uid` = values(`uid`)",
|
||||
$table, implode(',', $insert)
|
||||
'insert into %s (uid, points, "date") values %s %s',
|
||||
$table, implode(',', $insert), NexusDB::upsertField(['uid'], ['uid'])
|
||||
);
|
||||
NexusDB::statement($sql);
|
||||
$insertCount = count($insert);
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Models\UserMedal;
|
||||
use App\Models\UserMeta;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Nexus\Database\ClickHouse;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class BonusRepository extends BaseRepository
|
||||
@@ -362,5 +363,71 @@ class BonusRepository extends BaseRepository
|
||||
});
|
||||
}
|
||||
|
||||
public function getCount(string $category = '', int $userId = 0, int $businessType = 0): int
|
||||
{
|
||||
if ($category == BonusLogs::CATEGORY_COMMON) {
|
||||
$query = $this->buildQuery($userId, $businessType);
|
||||
return $query->count();
|
||||
} else if ($category == BonusLogs::CATEGORY_SEEDING) {
|
||||
list($whereStr, $binds) = $this->buildWhereStrAndBinds($userId, $businessType);
|
||||
return ClickHouse::count("bonus_logs", $whereStr, $binds);
|
||||
}
|
||||
throw new \InvalidArgumentException("Invalid category: $category");
|
||||
}
|
||||
|
||||
public function getList(string $category = '', int $userId = 0, int $businessType = 0, int $page = 1, int $perPage = 50)
|
||||
{
|
||||
if ($category == BonusLogs::CATEGORY_COMMON) {
|
||||
$query = $this->buildQuery($userId, $businessType);
|
||||
return $query->orderBy("id", "desc")->forPage($page, $perPage)->get();
|
||||
} else if ($category == BonusLogs::CATEGORY_SEEDING) {
|
||||
list($whereStr, $binds) = $this->buildWhereStrAndBinds($userId, $businessType);
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$rows = ClickHouse::list("select * from bonus_logs $whereStr order by created_at desc limit $offset, $perPage", $binds);
|
||||
$result = [];
|
||||
$id = 1;//fake id
|
||||
foreach ($rows as $row) {
|
||||
$record = new BonusLogs($row);
|
||||
$record->id = $id;
|
||||
$result[] = $record;
|
||||
$id++;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
throw new \InvalidArgumentException("Invalid category: $category");
|
||||
}
|
||||
|
||||
private function buildWhereStrAndBinds(int $userId = 0, int $businessType = 0)
|
||||
{
|
||||
$whereArr = [];
|
||||
$binds = [];
|
||||
if ($userId > 0) {
|
||||
$whereArr[] = "uid = :uid";
|
||||
$binds['uid'] = $userId;
|
||||
}
|
||||
if ($businessType > 0) {
|
||||
$whereArr[] = "business_type = :business_type";
|
||||
$binds["business_type"] = $businessType;
|
||||
}
|
||||
if (empty($whereArr)) {
|
||||
$whereStr = "";
|
||||
} else {
|
||||
$whereStr = sprintf("where %s", implode(' AND ', $whereArr));
|
||||
}
|
||||
return [$whereStr, $binds];
|
||||
}
|
||||
|
||||
private function buildQuery(int $userId = 0, int $businessType = 0): Builder
|
||||
{
|
||||
$query = BonusLogs::query();
|
||||
if ($userId > 0) {
|
||||
$query->where('uid', $userId);
|
||||
}
|
||||
if ($businessType > 0) {
|
||||
$query->where('business_type', $businessType);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Jobs\SettleClaim;
|
||||
use App\Models\BonusLogs;
|
||||
use App\Models\Claim;
|
||||
use App\Models\Message;
|
||||
use App\Models\Snatch;
|
||||
@@ -260,16 +261,24 @@ class ClaimRepository extends BaseRepository
|
||||
|
||||
//Wrap with transaction
|
||||
DB::transaction(function () use ($uid, $unReachedIdArr, $toUpdateIdArr, $bonusFinal, $totalDeduct, $uploadedCaseWhen, $seedTimeCaseWhen, $message, $now) {
|
||||
//get latest
|
||||
$oldBonus = User::query()->find($uid, ['seedbonus'])->seedbonus;
|
||||
$delta = 0;
|
||||
//Increase user bonus
|
||||
User::query()->where('id', $uid)->increment('seedbonus', $bonusFinal);
|
||||
do_log("Increase user bonus: $bonusFinal", 'alert');
|
||||
$delta += $bonusFinal;
|
||||
do_log("Increase user: $uid bonus: $bonusFinal", 'alert');
|
||||
BonusLogs::add($uid, $oldBonus, $bonusFinal, $oldBonus + $bonusFinal, "", BonusLogs::BUSINESS_TYPE_CLAIMED_REACHED);
|
||||
$oldBonus += $bonusFinal;
|
||||
|
||||
//Handle unreached
|
||||
if (!empty($unReachedIdArr)) {
|
||||
Claim::query()->whereIn('id', $unReachedIdArr)->delete();
|
||||
User::query()->where('id', $uid)->decrement('seedbonus', $totalDeduct);
|
||||
do_log("Deduct user bonus: $totalDeduct", 'alert');
|
||||
$delta -= $totalDeduct;
|
||||
do_log("Deduct user: $uid bonus: $totalDeduct", 'alert');
|
||||
BonusLogs::add($uid, $oldBonus, $totalDeduct, $oldBonus - $totalDeduct, "", BonusLogs::BUSINESS_TYPE_CLAIMED_UNREACHED);
|
||||
$oldBonus -= $totalDeduct;
|
||||
}
|
||||
User::query()->where('id', $uid)->increment('seedbonus', $delta);
|
||||
|
||||
//Update claim `last_settle_at` and init `seed_time_begin` & `uploaded_begin`
|
||||
if (!empty($toUpdateIdArr)) {
|
||||
|
||||
@@ -37,7 +37,7 @@ class CleanupRepository extends BaseRepository
|
||||
|
||||
private static int $oneTaskSeconds = 0;
|
||||
|
||||
private static int $scanSize = 1000;
|
||||
private static int $scanSize = 500;
|
||||
|
||||
public static function recordBatch(\Redis $redis, $uid, $torrentId)
|
||||
{
|
||||
|
||||
@@ -9,26 +9,27 @@ use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Hamcrest\Core\Set;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class CommentRepository extends BaseRepository
|
||||
{
|
||||
public function getList(array $params)
|
||||
public function getList(Request $request, Authenticatable $user)
|
||||
{
|
||||
$query = Comment::query()->with(['create_user', 'update_user']);
|
||||
if (!empty($params['torrent_id'])) {
|
||||
$query->where('torrent', $params['torrent_id']);
|
||||
if (!empty($request->torrent_id)) {
|
||||
$query->where('torrent', $request->torrent_id);
|
||||
}
|
||||
if (!empty($params['offer_id'])) {
|
||||
$query->where('offer', $params['offer_id']);
|
||||
if (!empty($request->offer_id)) {
|
||||
$query->where('offer', $request->offer_id);
|
||||
}
|
||||
if (!empty($params['request_id'])) {
|
||||
$query->where('request', $params['request_id']);
|
||||
if (!empty($request->request_id)) {
|
||||
$query->where('request', $request->request_id);
|
||||
}
|
||||
list($sortField, $sortType) = $this->getSortFieldAndType($params);
|
||||
$query->orderBy($sortField, $sortType);
|
||||
return $query->paginate();
|
||||
$query->orderBy('id', 'asc');
|
||||
return $query->paginate($this->getPerPageFromRequest($request));
|
||||
}
|
||||
|
||||
public function store(array $params, User $user)
|
||||
|
||||
@@ -47,10 +47,11 @@ class DashboardRepository extends BaseRepository
|
||||
'value' => PHP_VERSION,
|
||||
];
|
||||
$name = 'mysql_version';
|
||||
$databaseInfo = NexusDB::getDatabaseVersionInfo();
|
||||
$result[$name] = [
|
||||
'name' => $name,
|
||||
'text' => nexus_trans("dashboard.system_info.$name"),
|
||||
'value' => NexusDB::select('select version() as info')[0]['info'],
|
||||
'value' => sprintf("%s: %s", $databaseInfo['dbType'], $databaseInfo['version']),
|
||||
];
|
||||
// $name = 'os';
|
||||
// $result[$name] = [
|
||||
|
||||
@@ -31,8 +31,8 @@ class ExamRepository extends BaseRepository
|
||||
|
||||
public function store(array $params)
|
||||
{
|
||||
$this->checkIndexes($params);
|
||||
$this->checkBeginEnd($params);
|
||||
$diffInHours = $this->checkBeginEnd($params);
|
||||
$this->checkIndexes($params, $diffInHours);
|
||||
$this->checkFilters($params);
|
||||
/**
|
||||
* does not limit this
|
||||
@@ -48,8 +48,8 @@ class ExamRepository extends BaseRepository
|
||||
|
||||
public function update(array $params, $id)
|
||||
{
|
||||
$this->checkIndexes($params);
|
||||
$this->checkBeginEnd($params);
|
||||
$diffInHours = $this->checkBeginEnd($params);
|
||||
$this->checkIndexes($params, $diffInHours);
|
||||
$this->checkFilters($params);
|
||||
/**
|
||||
* does not limit this
|
||||
@@ -76,7 +76,7 @@ class ExamRepository extends BaseRepository
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function checkIndexes(array $params): bool
|
||||
private function checkIndexes(array $params, float $examDuration): bool
|
||||
{
|
||||
if (empty($params['indexes'])) {
|
||||
throw new \InvalidArgumentException("Require index.");
|
||||
@@ -94,6 +94,14 @@ class ExamRepository extends BaseRepository
|
||||
'Invalid require value for index: %s.', $index['index']
|
||||
));
|
||||
}
|
||||
if ($index['index'] == Exam::INDEX_SEED_TIME_AVERAGE) {
|
||||
if ($index['require_value'] > $examDuration) {
|
||||
throw new \InvalidArgumentException(nexus_trans(
|
||||
'admin.resources.exam.index_seed_time_average_require_value_invalid',
|
||||
['index_seed_time_average_require_value' => $index['require_value'], 'duration' => $examDuration]
|
||||
));
|
||||
}
|
||||
}
|
||||
$validIndex[$index['index']] = $index;
|
||||
}
|
||||
if (empty($validIndex)) {
|
||||
@@ -102,28 +110,40 @@ class ExamRepository extends BaseRepository
|
||||
return true;
|
||||
}
|
||||
|
||||
private function checkBeginEnd(array $params): bool
|
||||
/**
|
||||
* check if begin/end valid, if yes, return diff in hours, else throw InvalidArgumentException
|
||||
* @param array $params
|
||||
* @return float
|
||||
*/
|
||||
private function checkBeginEnd(array $params): float
|
||||
{
|
||||
if (
|
||||
!empty($params['begin']) && !empty($params['end'])
|
||||
&& empty($params['duration'])
|
||||
&& empty($params['recurring'])
|
||||
) {
|
||||
return true;
|
||||
$begin = Carbon::parse($params['begin']);
|
||||
$end = Carbon::parse($params['end']);
|
||||
return round($begin->diffInHours($end, true));
|
||||
}
|
||||
if (
|
||||
empty($params['begin']) && empty($params['end'])
|
||||
&& isset($params['duration']) && ctype_digit((string)$params['duration']) && $params['duration'] > 0
|
||||
&& empty($params['recurring'])
|
||||
) {
|
||||
return true;
|
||||
//unit: day
|
||||
return round(floatval($params['duration']) * 24);
|
||||
}
|
||||
if (
|
||||
empty($params['begin']) && empty($params['end'])
|
||||
&& empty($params['duration'])
|
||||
&& !empty($params['recurring'])
|
||||
) {
|
||||
return true;
|
||||
$exam = new Exam(['recurring' => $params['recurring']]);
|
||||
$now = Carbon::now();
|
||||
$begin = $exam->getRecurringBegin($now);
|
||||
$end = $exam->getRecurringEnd($now);
|
||||
return round($begin->diffInHours($end, true));
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(nexus_trans("exam.time_condition_invalid"));
|
||||
@@ -249,8 +269,12 @@ class ExamRepository extends BaseRepository
|
||||
$now = Carbon::now();
|
||||
$query = Exam::query()
|
||||
->where('status', Exam::STATUS_ENABLED)
|
||||
->whereRaw("if(begin is not null and end is not null, begin <= '$now' and end >= '$now', duration > 0 or recurring is not null)")
|
||||
;
|
||||
->whereRaw('
|
||||
CASE
|
||||
WHEN begin IS NOT NULL AND "end" IS NOT NULL
|
||||
THEN begin <= ? AND "end" >= ?
|
||||
ELSE duration > 0 OR recurring IS NOT NULL
|
||||
END', [$now, $now]);
|
||||
|
||||
if (!is_null($excludeId)) {
|
||||
$query->whereNotIn('id', Arr::wrap($excludeId));
|
||||
@@ -672,8 +696,8 @@ class ExamRepository extends BaseRepository
|
||||
if ($index['index'] == Exam::INDEX_SEED_TIME_AVERAGE) {
|
||||
$torrentCountsRes = Snatch::query()
|
||||
->where('userid', $user->id)
|
||||
->where('completedat', '>=', $begin)
|
||||
->where('completedat', '<=', $end)
|
||||
->where('last_action', '>=', $begin)
|
||||
->where('last_action', '<=', $end)
|
||||
->selectRaw("count(distinct(torrentid)) as counts")
|
||||
->first();
|
||||
do_log("special index: {$index['index']}, get torrent count by: " . last_query());
|
||||
@@ -1025,13 +1049,13 @@ class ExamRepository extends BaseRepository
|
||||
if ($donateStatus == User::DONATE_YES) {
|
||||
$baseQuery->where(function (Builder $query) {
|
||||
$query->where('donor', 'yes')->where(function (Builder $query) {
|
||||
$query->where('donoruntil', '0000-00-00 00:00:00')->orWhereNull('donoruntil')->orWhere('donoruntil', '>=', Carbon::now());
|
||||
$query->whereNull('donoruntil')->orWhere('donoruntil', '>=', Carbon::now());
|
||||
});
|
||||
});
|
||||
} elseif ($donateStatus == User::DONATE_NO) {
|
||||
$baseQuery->where(function (Builder $query) {
|
||||
$query->where('donor', 'no')->orWhere(function (Builder $query) {
|
||||
$query->where('donoruntil', '!=','0000-00-00 00:00:00')->whereNotNull('donoruntil')->where('donoruntil', '<', Carbon::now());
|
||||
$query->whereNotNull('donoruntil')->where('donoruntil', '<', Carbon::now());
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -1119,10 +1143,22 @@ class ExamRepository extends BaseRepository
|
||||
->orderBy("$examUserTable.id", "asc");
|
||||
if (!$ignoreTimeRange) {
|
||||
$whenThens = [];
|
||||
$whenThens[] = "when $examUserTable.`end` is not null then $examUserTable.`end` < '$now'";
|
||||
$whenThens[] = "when $examTable.`end` is not null then $examTable.`end` < '$now'";
|
||||
$whenThens[] = "when $examTable.duration > 0 then date_add($examUserTable.created_at, interval $examTable.duration day) < '$now'";
|
||||
$baseQuery->whereRaw(sprintf("case %s else false end", implode(" ", $whenThens)));
|
||||
$params = [];
|
||||
|
||||
$whenThens[] = "WHEN $examUserTable.\"end\" IS NOT NULL THEN $examUserTable.\"end\" < ?";
|
||||
$params[] = $now;
|
||||
|
||||
$whenThens[] = "WHEN $examTable.\"end\" IS NOT NULL THEN $examTable.\"end\" < ?";
|
||||
$params[] = $now;
|
||||
|
||||
if (NexusDB::isMysql()) {
|
||||
$whenThens[] = "when $examTable.duration > 0 then date_add($examUserTable.created_at, interval $examTable.duration day) < ?";
|
||||
} elseif (NexusDB::isPgsql()) {
|
||||
$whenThens[] = "WHEN $examTable.duration > 0 THEN ($examUserTable.created_at + ($examTable.duration || ' day')::INTERVAL) < ?";
|
||||
}
|
||||
$params[] = $now;
|
||||
|
||||
$baseQuery->whereRaw(sprintf("CASE %s ELSE false END", implode(" ", $whenThens)), $params);
|
||||
}
|
||||
|
||||
$size = 1000;
|
||||
|
||||
@@ -400,7 +400,7 @@ class HitAndRunRepository extends BaseRepository
|
||||
->selectRaw("count(*) as counts, uid")
|
||||
->where('status', HitAndRun::STATUS_UNREACHED)
|
||||
->groupBy('uid')
|
||||
->having("counts", '>=', $disableCounts)
|
||||
->havingRaw("count(*) >= $disableCounts")
|
||||
;
|
||||
if ($setting['diff_in_section']) {
|
||||
$query->whereHas('torrent.basic_category', function (Builder $query) use ($setting) {
|
||||
|
||||
@@ -199,7 +199,7 @@ class SeedBoxRepository extends BaseRepository
|
||||
$size = 1000;
|
||||
$page = 1;
|
||||
$logPrefix = "isAllowed: $isAllowed->name, field: $field->name, page: $page, size: $size";
|
||||
$selectRaw = sprintf("uid, group_concat(%s) as str", $field == IpAsnEnum::ASN ? 'asn' : 'ip');
|
||||
$selectRaw = sprintf("uid, %s as str", NexusDB::groupConcatField($field == IpAsnEnum::ASN ? 'asn' : 'ip'));
|
||||
while (true) {
|
||||
$list = SeedBoxRecord::getValidQuery(TypeEnum::USER, $isAllowed, $field)
|
||||
->selectRaw($selectRaw)
|
||||
|
||||
@@ -45,8 +45,8 @@ class SettingRepository extends BaseRepository
|
||||
return true;
|
||||
}
|
||||
$sql = sprintf(
|
||||
"insert into `%s` (`name`, `value`) values %s on duplicate key update `value` = values(`value`)",
|
||||
$settingModel->getTable(), implode(', ', $values)
|
||||
'insert into %s (name, "value") values %s %s',
|
||||
$settingModel->getTable(), implode(', ', $values), NexusDB::upsertField(['name'], ['value'])
|
||||
);
|
||||
$result = DB::insert($sql);
|
||||
do_log("sql: $sql, result: $result");
|
||||
|
||||
@@ -133,7 +133,7 @@ class TagRepository extends BaseRepository
|
||||
}
|
||||
$page++;
|
||||
}
|
||||
$sql .= sprintf("%s on duplicate key update updated_at = values(updated_at)", implode(', ', $values));
|
||||
$sql .= sprintf("%s %s", implode(', ', $values), NexusDB::upsertField(['torrent_id', 'tag_id'], ['updated_at']));
|
||||
do_log("migrate sql: $sql");
|
||||
NexusDB::statement($sql);
|
||||
do_log("[MIGRATE_TORRENT_TAG] done!");
|
||||
|
||||
@@ -511,8 +511,9 @@ class ToolRepository extends BaseRepository
|
||||
$stickyPromotionExists = NexusDB::hasTable($stickyPromotionParticipatorsTable);
|
||||
$claimTableExists = NexusDB::hasTable($claimTable);
|
||||
$hitAndRunTableExists = NexusDB::hasTable($hitAndRunTable);
|
||||
$idsField = NexusDB::groupConcatField('id');
|
||||
while (true) {
|
||||
$snatchRes = NexusDB::select("select userid, torrentid, group_concat(id) as ids from snatched group by userid, torrentid having(count(*)) > 1 limit $size");
|
||||
$snatchRes = NexusDB::select("select userid, torrentid, $idsField as ids from snatched group by userid, torrentid having(count(*)) > 1 limit $size");
|
||||
if (empty($snatchRes)) {
|
||||
break;
|
||||
}
|
||||
@@ -542,8 +543,9 @@ class ToolRepository extends BaseRepository
|
||||
public function removeDuplicatePeer()
|
||||
{
|
||||
$size = 2000;
|
||||
$idsField = NexusDB::groupConcatField('id');
|
||||
while (true) {
|
||||
$results = NexusDB::select("select torrent, userid, group_concat(id) as ids from peers group by torrent, peer_id, userid having(count(*)) > 1 limit $size");
|
||||
$results = NexusDB::select("select torrent, userid, $idsField as ids from peers group by torrent, peer_id, userid having(count(*)) > 1 limit $size");
|
||||
if (empty($results)) {
|
||||
do_log("[DELETE_DUPLICATED_PEERS], no data: ". last_query());
|
||||
break;
|
||||
|
||||
@@ -749,7 +749,7 @@ class TorrentRepository extends BaseRepository
|
||||
$values[] = sprintf("(%s, %s, '%s', '%s')", $torrentId, $tagId, $time, $time);
|
||||
}
|
||||
}
|
||||
$sql .= implode(', ', $values) . " on duplicate key update updated_at = values(updated_at)";
|
||||
$sql .= implode(', ', $values) . " " . NexusDB::upsertField(['torrent_id', 'tag_id'], ['updated_at']);
|
||||
if ($remove) {
|
||||
TorrentTag::query()->whereIn('torrent_id', $idArr)->delete();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Passkey;
|
||||
use Exception;
|
||||
use lbuchs\WebAuthn\WebAuthn;
|
||||
use Nexus\Database\NexusDB;
|
||||
use Nexus\Nexus;
|
||||
use RuntimeException;
|
||||
|
||||
class UserPasskeyRepository extends BaseRepository
|
||||
{
|
||||
|
||||
public static function createWebAuthn()
|
||||
{
|
||||
$formats = ['android-key', 'android-safetynet', 'apple', 'fido-u2f', 'packed', 'tpm', 'none'];
|
||||
$rpId = explode(':', nexus()->getRequestHost())[0];
|
||||
return new WebAuthn(get_setting('basic.SITENAME'), $rpId, $formats);
|
||||
}
|
||||
|
||||
private static function putChallenge($challenge): string
|
||||
{
|
||||
$challengeId = bin2hex(random_bytes(32));
|
||||
NexusDB::cache_put("passkey_challenge_{$challengeId}", $challenge, 120);
|
||||
return $challengeId;
|
||||
}
|
||||
|
||||
private static function getChallenge($challengeId)
|
||||
{
|
||||
$challenge = NexusDB::cache_get("passkey_challenge_{$challengeId}") ?? null;
|
||||
if ($challenge == null) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_timeout'));
|
||||
}
|
||||
NexusDB::cache_del("passkey_challenge_{$challengeId}");
|
||||
return $challenge;
|
||||
}
|
||||
|
||||
public static function getCreateArgs($userId, $userName)
|
||||
{
|
||||
$WebAuthn = self::createWebAuthn();
|
||||
|
||||
$passkey = Passkey::query()->where('user_id', '=', $userId)->get();
|
||||
|
||||
$credentialIds = array_map(function ($item) {
|
||||
return hex2bin($item['credential_id']);
|
||||
}, $passkey->toArray());
|
||||
|
||||
$createArgs = $WebAuthn->getCreateArgs(bin2hex($userId), $userName, $userName, 120, true, 'preferred', null, $credentialIds);
|
||||
$challengeId = self::putChallenge($WebAuthn->getChallenge());
|
||||
|
||||
return [
|
||||
'challengeId' => $challengeId,
|
||||
'options' => $createArgs,
|
||||
];
|
||||
}
|
||||
|
||||
public static function processCreate($userId, $challengeId, $clientDataJSON, $attestationObject)
|
||||
{
|
||||
$challenge = self::getChallenge($challengeId);
|
||||
$clientDataJSON = !empty($clientDataJSON) ? base64_decode($clientDataJSON) : null;
|
||||
$attestationObject = !empty($attestationObject) ? base64_decode($attestationObject) : null;
|
||||
|
||||
$WebAuthn = self::createWebAuthn();
|
||||
|
||||
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $challenge, false, true, false);
|
||||
|
||||
self::insertUserPasskey(
|
||||
$userId,
|
||||
bin2hex($data->AAGUID),
|
||||
bin2hex($data->credentialId),
|
||||
$data->credentialPublicKey,
|
||||
$data->signatureCounter
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getGetArgs()
|
||||
{
|
||||
$WebAuthn = self::createWebAuthn();
|
||||
|
||||
$getArgs = $WebAuthn->getGetArgs(null, 120, true, true, true, true, true, 'preferred');
|
||||
$challengeId = self::putChallenge($WebAuthn->getChallenge());
|
||||
|
||||
return [
|
||||
'challengeId' => $challengeId,
|
||||
'options' => $getArgs,
|
||||
];
|
||||
}
|
||||
|
||||
public static function insertUserPasskey($userId, $AAGUID, $credentialId, $publicKey, $counter)
|
||||
{
|
||||
$params = [
|
||||
'user_id' => $userId,
|
||||
'AAGUID' => $AAGUID,
|
||||
'credential_id' => $credentialId,
|
||||
'public_key' => $publicKey,
|
||||
'counter' => $counter,
|
||||
];
|
||||
Passkey::query()->create($params);
|
||||
}
|
||||
|
||||
public static function processGet($challengeId, $id, $clientDataJSON, $authenticatorData, $signature, $userHandle)
|
||||
{
|
||||
$challenge = self::getChallenge($challengeId);
|
||||
$clientDataJSON = !empty($clientDataJSON) ? base64_decode($clientDataJSON) : null;
|
||||
$id = !empty($id) ? base64_decode($id) : null;
|
||||
$authenticatorData = !empty($authenticatorData) ? base64_decode($authenticatorData) : null;
|
||||
$signature = !empty($signature) ? base64_decode($signature) : null;
|
||||
$userHandle = !empty($userHandle) ? base64_decode($userHandle) : null;
|
||||
|
||||
$WebAuthn = self::createWebAuthn();
|
||||
|
||||
$passkey = Passkey::query()->where('credential_id', '=', bin2hex($id))->first();
|
||||
if ($passkey === null) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_unknown'));
|
||||
}
|
||||
|
||||
if ($userHandle !== bin2hex($passkey->user_id)) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_invalid'));
|
||||
}
|
||||
|
||||
try {
|
||||
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $passkey->public_key, $challenge, null, false, true);
|
||||
} catch (Exception $e) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_error') . "\n" . $e->getMessage());
|
||||
}
|
||||
|
||||
$user = $passkey->user;
|
||||
if (!$user) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_user_not_found'));
|
||||
}
|
||||
$user->checkIsNormal();
|
||||
|
||||
$ip = getip();
|
||||
$userRep = new UserRepository();
|
||||
$userRep->saveLoginLog($user->id, $ip, 'Web', true);
|
||||
|
||||
logincookie($user->id, $user->auth_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function delete($userId, $credentialId)
|
||||
{
|
||||
return Passkey::query()->where('user_id', '=', $userId)->where('credential_id', '=', $credentialId)->delete();
|
||||
}
|
||||
|
||||
public static function getList($userId)
|
||||
{
|
||||
return Passkey::query()->where('user_id', '=', $userId)->get();
|
||||
}
|
||||
|
||||
public static function getAaguids()
|
||||
{
|
||||
return NexusDB::remember("aaguids", 60 * 60 * 24 * 14, function () {
|
||||
return json_decode(file_get_contents("https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/refs/heads/main/combined_aaguid.json"), true);
|
||||
});
|
||||
}
|
||||
|
||||
private static $passkeyvg = 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20216%20216%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%2F%3E%3Cpath%20style%3D%22fill%3Anone%22%20d%3D%22M0%200h216v216H0z%22%2F%3E%3Cpath%20d%3D%22M172.32%2096.79c0%2013.78-8.48%2025.5-20.29%2029.78l7.14%2011.83-10.57%2013%2010.57%2012.71-17.04%2022.87-12.01-12.82V125.7c-10.68-4.85-18.15-15.97-18.15-28.91%200-17.4%2013.51-31.51%2030.18-31.51%2016.66%200%2030.17%2014.11%2030.17%2031.51m-30.18%204.82c4.02%200%207.28-3.4%207.28-7.6s-3.26-7.61-7.28-7.61-7.28%203.4-7.28%207.61c-.01%204.2%203.26%207.6%207.28%207.6%22%20style%3D%22fill-rule%3Aevenodd%3Bclip-rule%3Aevenodd%3Bfill%3A%23353535%22%2F%3E%3Cpath%20d%3D%22M172.41%2096.88c0%2013.62-8.25%2025.23-19.83%2029.67l6.58%2011.84-9.73%2013%209.73%2012.71-17.03%2023.05v-85.54c4.02%200%207.28-3.41%207.28-7.6%200-4.2-3.26-7.61-7.28-7.61V65.28c16.73%200%2030.28%2014.15%2030.28%2031.6m-52.17%2034.55c-9.75-8-16.3-20.3-17.2-34.27H50.8c-10.96%200-19.84%209.01-19.84%2020.13v25.17c0%205.56%204.44%2010.07%209.92%2010.07h69.44c5.48%200%209.92-4.51%209.92-10.07z%22%20style%3D%22fill-rule%3Aevenodd%3Bclip-rule%3Aevenodd%22%2F%3E%3Cpath%20d%3D%22M73.16%2091.13c-2.42-.46-4.82-.89-7.11-1.86-8.65-3.63-13.69-10.32-15.32-19.77-1.12-6.47-.59-12.87%202.03-18.92%203.72-8.6%2010.39-13.26%2019.15-14.84%205.24-.94%2010.46-.73%2015.5%201.15%207.59%202.82%2012.68%208.26%2015.03%2016.24%202.38%208.05%202.03%2016.1-1.56%2023.72-3.72%207.96-10.21%2012.23-18.42%2013.9-.68.14-1.37.27-2.05.41-2.41-.03-4.83-.03-7.25-.03%22%20style%3D%22fill%3A%23141313%22%2F%3E%3C%2Fsvg%3E';
|
||||
|
||||
public static function renderLogin()
|
||||
{
|
||||
printf('<p id="passkey_box"><button type="button" id="passkey_login"><img style="width:32px" src="%s" alt="%s"><br>%s</button></p>', self::$passkeyvg, nexus_trans('passkey.passkey'), nexus_trans('passkey.passkey'));
|
||||
?>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
if (Passkey.conditionalSupported()) {
|
||||
Passkey.isCMA().then(async (isCMA) => {
|
||||
if (isCMA) startPasskeyLogin(true);
|
||||
});
|
||||
}
|
||||
document.getElementById('passkey_login').addEventListener('click', () => {
|
||||
if (!Passkey.supported()) {
|
||||
layer.alert('<?php echo nexus_trans('passkey.passkey_not_supported'); ?>');
|
||||
} else {
|
||||
startPasskeyLogin(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
const startPasskeyLogin = (conditional) => {
|
||||
Passkey.checkRegistration(conditional, () => {
|
||||
layer.load(2, {shade: 0.3});
|
||||
}).then(() => {
|
||||
if (location.search) {
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
location.href = searchParams.get('returnto') || '/index.php';
|
||||
} else {
|
||||
location.href = '/index.php';
|
||||
}
|
||||
}).catch((e) => {
|
||||
if (e.name === 'NotAllowedError' || e.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
layer.alert(e.message);
|
||||
}).finally(() => {
|
||||
layer.closeAll('loading');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function renderList($id)
|
||||
{
|
||||
$passkeys = self::getList($id);
|
||||
printf('<button type="button" id="passkey_create">%s</button><br>%s', nexus_trans('passkey.passkey_create'), nexus_trans('passkey.passkey_desc'));
|
||||
?>
|
||||
<table>
|
||||
<?php
|
||||
if (empty($passkeys)) {
|
||||
printf('<tr><td>%s</td></tr>', nexus_trans('passkey.passkey_empty'));
|
||||
} else {
|
||||
$AAGUIDS = self::getAaguids();
|
||||
foreach ($passkeys as $passkey) {
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;padding:4px">
|
||||
<?php
|
||||
$meta = $AAGUIDS[$passkey->getAaguidFormatted()];
|
||||
if (isset($meta)) {
|
||||
printf('<img style="width: 32px" src="%s" alt="%s" /><div style="margin-right:4px"><b>%s</b> (%s)', $meta['icon_dark'], $meta['name'], $meta['name'], $passkey->credential_id);
|
||||
} else {
|
||||
printf('<img style="width: 32px" src="%s" alt="%s" /><div style="margin-right:4px"><b>%s</b>', self::$passkeyvg, $passkey->credential_id, $passkey->credential_id);
|
||||
}
|
||||
printf('<br><b>%s</b>%s</div>', nexus_trans('passkey.passkey_created_at'), gettime($passkey->created_at));
|
||||
printf('<button type="button" style="margin-left:auto" data-passkey-id="%s">%s</button>', $passkey->credential_id, nexus_trans('passkey.passkey_delete'))
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
} ?>
|
||||
</table>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.getElementById('passkey_create').addEventListener('click', () => {
|
||||
if (!Passkey.supported()) {
|
||||
layer.alert('<?php echo nexus_trans('passkey.passkey_not_supported'); ?>');
|
||||
} else {
|
||||
layer.load(2, {shade: 0.3});
|
||||
Passkey.createRegistration().then(() => {
|
||||
location.reload();
|
||||
}).catch((e) => {
|
||||
layer.alert(e.message);
|
||||
}).finally(() => {
|
||||
layer.closeAll('loading');
|
||||
});
|
||||
}
|
||||
})
|
||||
document.querySelectorAll('button[data-passkey-id]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const credentialId = button.getAttribute('data-passkey-id');
|
||||
layer.confirm('<?php echo nexus_trans('passkey.passkey_delete_confirm'); ?>', {}, function () {
|
||||
layer.load(2, {shade: 0.3});
|
||||
Passkey.deleteRegistration(credentialId).then(() => {
|
||||
location.reload();
|
||||
}).catch((e) => {
|
||||
layer.alert(e.message);
|
||||
}).finally(() => {
|
||||
layer.closeAll('loading');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
Nexus::js('js/passkey.js', 'footer', true);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Enums\ModelEventEnum;
|
||||
use App\Enums\RedisKeysEnum;
|
||||
use App\Exceptions\InsufficientPermissionException;
|
||||
use App\Exceptions\NexusException;
|
||||
use App\Http\Resources\ExamUserResource;
|
||||
@@ -29,13 +28,15 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Str;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class UserRepository extends BaseRepository
|
||||
{
|
||||
private static array $allowIncludes = ['inviter', 'valid_medals'];
|
||||
private static array $allowIncludeFields = ['seeding_leeching_data'];
|
||||
private static array $allowIncludeCounts = [];
|
||||
public function getList(array $params)
|
||||
{
|
||||
$query = User::query();
|
||||
@@ -66,28 +67,34 @@ class UserRepository extends BaseRepository
|
||||
{
|
||||
//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)
|
||||
->allowIncludes(self::$allowIncludes)
|
||||
->allowIncludeCounts(self::$allowIncludeCounts)
|
||||
->allowIncludeFields(self::$allowIncludeFields)
|
||||
;
|
||||
$query = $apiQueryBuilder->build();
|
||||
$user = $query->findOrFail($id);
|
||||
Gate::authorize('view', $user);
|
||||
return $this->appendIncludeFields($apiQueryBuilder, $currentUser, $user);
|
||||
$userList = $this->appendIncludeFields($apiQueryBuilder, $currentUser, [$user]);
|
||||
return $userList[0];
|
||||
}
|
||||
|
||||
private function appendIncludeFields(ApiQueryBuilder $apiQueryBuilder, Authenticatable $currentUser, User $user): User
|
||||
private function appendIncludeFields(ApiQueryBuilder $apiQueryBuilder, Authenticatable $currentUser, $userList)
|
||||
{
|
||||
// $id = $torrent->id;
|
||||
// if ($apiQueryBuilder->hasIncludeField('has_bookmarked')) {
|
||||
// $torrent->has_bookmarked = (int)$user->bookmarks()->where('torrentid', $id)->exists();;
|
||||
// }
|
||||
|
||||
return $user;
|
||||
$idArr = [];
|
||||
foreach ($userList as $user) {
|
||||
$idArr[] = $user->id;
|
||||
}
|
||||
if ($hasFieldSeedingData = $apiQueryBuilder->hasIncludeField('seeding_leeching_data')) {
|
||||
$seedingData = $this->listUserSeedingLeechingData($idArr);
|
||||
}
|
||||
foreach ($userList as $user) {
|
||||
$id = $user->id;
|
||||
if ($hasFieldSeedingData && isset($seedingData[$id])) {
|
||||
$user->seeding_leeching_data = $seedingData[$id];
|
||||
}
|
||||
}
|
||||
return $userList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,9 +226,9 @@ class UserRepository extends BaseRepository
|
||||
'operator' => $operator->id,
|
||||
];
|
||||
$modCommentText = sprintf("%s - Disable by %s, reason: %s.", now()->format('Y-m-d'), $operator->username, $reason);
|
||||
DB::transaction(function () use ($targetUser, $banLog, $modCommentText) {
|
||||
NexusDB::transaction(function () use ($targetUser, $banLog, $modCommentText) {
|
||||
$targetUser->updateWithModComment(['enabled' => User::ENABLED_NO], $modCommentText);
|
||||
UserBanLog::query()->insert($banLog);
|
||||
UserBanLog::query()->create($banLog);
|
||||
});
|
||||
do_log("user: $uid, $modCommentText");
|
||||
$this->clearCache($targetUser);
|
||||
@@ -259,7 +266,7 @@ class UserRepository extends BaseRepository
|
||||
|
||||
private function setEnableLatelyCache(int $userId): void
|
||||
{
|
||||
NexusDB::cache_put(User::getUserEnableLatelyCacheKey($userId), now()->toDateTimeString());
|
||||
NexusDB::cache_put(User::getUserEnableLatelyCacheKey($userId), now()->toDateTimeString(), 86400);
|
||||
}
|
||||
|
||||
public function getInviteInfo($id)
|
||||
@@ -814,4 +821,38 @@ class UserRepository extends BaseRepository
|
||||
return $loginLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* get user seeding/leeching count and size
|
||||
*
|
||||
* @see calculate_seed_bonus()
|
||||
* @param array $userIdArr
|
||||
* @return array
|
||||
*/
|
||||
private function listUserSeedingLeechingData(array $userIdArr)
|
||||
{
|
||||
$minSize = get_setting('bonus.min_size', 0);
|
||||
$idStr = implode(",", $userIdArr);
|
||||
$sql = "select peers.userid, peers.seeder, torrents.size from torrents LEFT JOIN peers ON peers.torrent = torrents.id WHERE peers.userid in ($idStr) and torrents.size > $minSize group by peers.torrent, peers.peer_id, peers.userid, peers.seeder";
|
||||
$data = NexusDB::select($sql);
|
||||
$result = [];
|
||||
foreach ($data as $row) {
|
||||
if (!isset($result[$row['userid']])) {
|
||||
$result[$row['userid']] = [
|
||||
'seeding_count' => 0,
|
||||
'seeding_size' => 0,
|
||||
'leeching_count' => 0,
|
||||
'leeching_size' => 0,
|
||||
];
|
||||
}
|
||||
if ($row['seeder'] == 'yes') {
|
||||
$result[$row['userid']]['seeding_count'] += 1;
|
||||
$result[$row['userid']]['seeding_size'] += $row['size'];
|
||||
} else {
|
||||
$result[$row['userid']]['leeching_count'] += 1;
|
||||
$result[$row['userid']]['leeching_size'] += $row['size'];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services\Captcha\Drivers;
|
||||
|
||||
use App\Models\RegImage;
|
||||
use App\Services\Captcha\CaptchaDriverInterface;
|
||||
use App\Services\Captcha\Exceptions\CaptchaValidationException;
|
||||
|
||||
@@ -67,16 +68,11 @@ class ImageCaptchaDriver implements CaptchaDriverInterface
|
||||
$random = random_str();
|
||||
$imagehash = md5($random);
|
||||
$dateline = time();
|
||||
|
||||
$sql = sprintf(
|
||||
"INSERT INTO `regimages` (`imagehash`, `imagestring`, `dateline`) VALUES ('%s', '%s', '%s')",
|
||||
mysql_real_escape_string($imagehash),
|
||||
mysql_real_escape_string($random),
|
||||
mysql_real_escape_string((string) $dateline)
|
||||
);
|
||||
|
||||
sql_query($sql);
|
||||
|
||||
RegImage::query()->insert([
|
||||
'imagehash' => $imagehash,
|
||||
'dateline' => $dateline,
|
||||
'imagestring' => $random,
|
||||
]);
|
||||
return $imagehash;
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -19,7 +19,7 @@
|
||||
"files": []
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2 <8.5",
|
||||
"php": ">=8.2 <8.6",
|
||||
"ext-bcmath": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-gd": "*",
|
||||
@@ -35,25 +35,27 @@
|
||||
"ext-xml": "*",
|
||||
"ext-zend-opcache": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-pdo": "*",
|
||||
"calebporzio/sushi": "^2.5",
|
||||
"cybercog/laravel-clickhouse": "dev-master",
|
||||
"elasticsearch/elasticsearch": "^7.16",
|
||||
"filament/filament": "~4.0",
|
||||
"filament/filament": "~5.0",
|
||||
"flowframe/laravel-trend": "^0.4",
|
||||
"geoip2/geoip2": "~2.0",
|
||||
"google/auth": "1.44.0",
|
||||
"imdbphp/imdbphp": "^8.0",
|
||||
"imdbphp/imdbphp": "8.3.1",
|
||||
"irazasyed/telegram-bot-sdk": "^3.11",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/horizon": "^5.31",
|
||||
"laravel/passport": "^12.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.5",
|
||||
"lbuchs/webauthn": "^2.2",
|
||||
"league/flysystem-sftp-v3": "^3.0",
|
||||
"meilisearch/meilisearch-php": "^1.0",
|
||||
"orangehill/iseed": "^3.0",
|
||||
"phpgangsta/googleauthenticator": "dev-master",
|
||||
"rhilip/bencode": "^2.0",
|
||||
"rhilip/bencode": "^2.5.0",
|
||||
"rlanvin/php-ip": "^3.0",
|
||||
"spatie/laravel-activitylog": "^4.10",
|
||||
"stichoza/google-translate-php": "^5.2"
|
||||
@@ -86,7 +88,8 @@
|
||||
"@php artisan key:generate --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan filament:upgrade"
|
||||
"@php artisan filament:upgrade",
|
||||
"@php artisan livewire:publish --assets"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
|
||||
Generated
+902
-763
File diff suppressed because it is too large
Load Diff
@@ -24,12 +24,12 @@ return [
|
||||
*/
|
||||
|
||||
'connection' => [
|
||||
'host' => env('CLICKHOUSE_HOST', 'localhost'),
|
||||
'port' => env('CLICKHOUSE_HTTP_PORT', 8123),
|
||||
'username' => env('CLICKHOUSE_USER', 'default'),
|
||||
'password' => env('CLICKHOUSE_PASSWORD', ''),
|
||||
'host' => nexus_env('CLICKHOUSE_HOST', 'localhost'),
|
||||
'port' => nexus_env('CLICKHOUSE_HTTP_PORT', 8123),
|
||||
'username' => nexus_env('CLICKHOUSE_USER', 'default'),
|
||||
'password' => nexus_env('CLICKHOUSE_PASSWORD', ''),
|
||||
'options' => [
|
||||
'database' => env('CLICKHOUSE_DATABASE', 'default'),
|
||||
'database' => nexus_env('CLICKHOUSE_DATABASE', 'default'),
|
||||
'timeout' => 1,
|
||||
'connectTimeOut' => 2,
|
||||
],
|
||||
@@ -42,7 +42,7 @@ return [
|
||||
*/
|
||||
|
||||
'migrations' => [
|
||||
'table' => env('CLICKHOUSE_MIGRATION_TABLE', 'migrations'),
|
||||
'path' => database_path('clickhouse-migrations'),
|
||||
'table' => nexus_env('CLICKHOUSE_MIGRATION_TABLE', 'migrations'),
|
||||
'path' => __DIR__ . '/../database/clickhouse-migrations',
|
||||
],
|
||||
];
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ return [
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'schema' => 'public',
|
||||
'schema' => env('DB_SCHEMA', 'public'),
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ return [
|
||||
'maxJobs' => 0,
|
||||
'memory' => 128,
|
||||
'tries' => 1,
|
||||
'timeout' => 600,
|
||||
'timeout' => 120,
|
||||
'nice' => 0,
|
||||
],
|
||||
],
|
||||
|
||||
@@ -8,6 +8,45 @@ return [
|
||||
|
||||
'log_split' => nexus_env('LOG_SPLIT', 'daily'),
|
||||
|
||||
'database' => [
|
||||
'default' => nexus_env('DB_CONNECTION', 'mysql'),
|
||||
'connections' => [
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => nexus_env('DATABASE_URL'),
|
||||
'host' => nexus_env('DB_HOST', '127.0.0.1'),
|
||||
'port' => (int)nexus_env('DB_PORT', 3306),
|
||||
'username' => nexus_env('DB_USERNAME', 'root'),
|
||||
'password' => nexus_env('DB_PASSWORD', ''),
|
||||
'database' => nexus_env('DB_DATABASE', 'nexusphp'),
|
||||
'unix_socket' => nexus_env('DB_SOCKET', ''),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => false,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => nexus_env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => nexus_env('DATABASE_URL'),
|
||||
'host' => nexus_env('DB_HOST', '127.0.0.1'),
|
||||
'port' => nexus_env('DB_PORT', '5432'),
|
||||
'database' => nexus_env('DB_DATABASE', 'nexusphp'),
|
||||
'username' => nexus_env('DB_USERNAME', 'nexusphp'),
|
||||
'password' => nexus_env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'schema' => nexus_env('DB_SCHEMA', 'public'),
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => nexus_env('DATABASE_URL'),
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ return [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
'queue' => env('REDIS_QUEUE', 'nexus_queue'),
|
||||
'retry_after' => 90,
|
||||
'retry_after' => 150,
|
||||
'block_for' => null,
|
||||
'after_commit' => true,
|
||||
],
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'expiration' => 129600,
|
||||
'expiration' => 5256000,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -16,7 +16,7 @@ return new class extends Migration
|
||||
$table->unsignedBigInteger('user_id')->index();
|
||||
$table->unsignedBigInteger('client_id');
|
||||
$table->text('scopes')->nullable();
|
||||
$table->boolean('revoked');
|
||||
$table->smallInteger('revoked');
|
||||
$table->dateTime('expires_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ return new class extends Migration
|
||||
$table->unsignedBigInteger('client_id');
|
||||
$table->string('name')->nullable();
|
||||
$table->text('scopes')->nullable();
|
||||
$table->boolean('revoked');
|
||||
$table->smallInteger('revoked');
|
||||
$table->timestamps();
|
||||
$table->dateTime('expires_at')->nullable();
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ return new class extends Migration
|
||||
Schema::create('oauth_refresh_tokens', function (Blueprint $table) {
|
||||
$table->string('id', 100)->primary();
|
||||
$table->string('access_token_id', 100)->index();
|
||||
$table->boolean('revoked');
|
||||
$table->smallInteger('revoked');
|
||||
$table->dateTime('expires_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ return new class extends Migration
|
||||
$table->string('secret', 100)->nullable();
|
||||
$table->string('provider')->nullable();
|
||||
$table->text('redirect');
|
||||
$table->boolean('personal_access_client');
|
||||
$table->boolean('password_client');
|
||||
$table->boolean('revoked');
|
||||
$table->smallInteger('personal_access_client');
|
||||
$table->smallInteger('password_client');
|
||||
$table->smallInteger('revoked');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class CreateAdvertisementsTable extends Migration
|
||||
}
|
||||
Schema::create('advertisements', function (Blueprint $table) {
|
||||
$table->mediumIncrements('id');
|
||||
$table->boolean('enabled')->default(0);
|
||||
$table->smallInteger('enabled')->default(0);
|
||||
$table->enum('type', ['bbcodes', 'xhtml', 'text', 'image', 'flash']);
|
||||
$table->enum('position', ['header', 'footer', 'belownav', 'belowsearchbox', 'torrentdetail', 'comment', 'interoverforums', 'forumpost', 'popup']);
|
||||
$table->tinyInteger('displayorder')->default(0);
|
||||
|
||||
@@ -17,7 +17,7 @@ class CreateAgentAllowedExceptionTable extends Migration
|
||||
return;
|
||||
}
|
||||
Schema::create('agent_allowed_exception', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('family_id')->default(0)->index('family_id');
|
||||
$table->unsignedTinyInteger('family_id')->default(0)->index();
|
||||
$table->string('name', 100)->default('');
|
||||
$table->string('peer_id', 20)->default('');
|
||||
$table->string('agent', 100)->default('');
|
||||
|
||||
@@ -22,15 +22,15 @@ class CreateAttachmentsTable extends Migration
|
||||
$table->unsignedSmallInteger('width')->default(0);
|
||||
$table->dateTime('added')->nullable();
|
||||
$table->string('filename')->default('');
|
||||
$table->char('dlkey', 32)->index('idx_delkey');
|
||||
$table->char('dlkey', 32)->index();
|
||||
$table->string('filetype', 50)->default('');
|
||||
$table->unsignedBigInteger('filesize')->default(0);
|
||||
$table->string('location')->default('');
|
||||
$table->mediumInteger('downloads')->default(0);
|
||||
$table->boolean('isimage')->unsigned()->default(0);
|
||||
$table->boolean('thumb')->unsigned()->default(0);
|
||||
$table->smallInteger('isimage')->unsigned()->default(0);
|
||||
$table->smallInteger('thumb')->unsigned()->default(0);
|
||||
$table->index(['userid', 'id'], 'pid');
|
||||
$table->index(['added', 'isimage', 'downloads'], 'dateline');
|
||||
$table->index(['added', 'isimage', 'downloads'], );
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class CreateAttendanceTable extends Migration
|
||||
}
|
||||
Schema::create('attendance', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedInteger('uid')->default(0)->index('idx_uid');
|
||||
$table->unsignedInteger('uid')->default(0)->index();
|
||||
$table->dateTime('added')->index();
|
||||
$table->unsignedInteger('points')->default(0);
|
||||
$table->unsignedInteger('days')->default(1);
|
||||
|
||||
@@ -23,7 +23,7 @@ class CreateBansTable extends Migration
|
||||
$table->string('comment')->default('');
|
||||
$table->bigInteger('first')->default(0);
|
||||
$table->bigInteger('last')->default(0);
|
||||
$table->index(['first', 'last'], 'first_last');
|
||||
$table->index(['first', 'last'], );
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class CreateBlocksTable extends Migration
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('userid')->default(0);
|
||||
$table->unsignedMediumInteger('blockid')->default(0);
|
||||
$table->unique(['userid', 'blockid'], 'userfriend');
|
||||
$table->unique(['userid', 'blockid'],);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class CreateBookmarksTable extends Migration
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('torrentid')->default(0);
|
||||
$table->unsignedMediumInteger('userid')->default(0);
|
||||
$table->index(['userid', 'torrentid'], 'userid_torrentid');
|
||||
$table->index(['userid', 'torrentid'], );
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class CreateCategoriesTable extends Migration
|
||||
$table->string('image')->default('');
|
||||
$table->unsignedSmallInteger('sort_index')->default(0);
|
||||
$table->integer('icon_id')->default(0);
|
||||
$table->index(['mode', 'sort_index'], 'mode_sort');
|
||||
$table->index(['mode', 'sort_index'], );
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class CreateCheatersTable extends Migration
|
||||
$table->unsignedMediumInteger('leechers')->default(0);
|
||||
$table->unsignedTinyInteger('hit')->default(0);
|
||||
$table->unsignedMediumInteger('dealtby')->default(0);
|
||||
$table->boolean('dealtwith')->default(0);
|
||||
$table->smallInteger('dealtwith')->default(0);
|
||||
$table->string('comment')->default('');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class CreateChronicleTable extends Migration
|
||||
Schema::create('chronicle', function (Blueprint $table) {
|
||||
$table->mediumIncrements('id');
|
||||
$table->unsignedMediumInteger('userid')->default(0);
|
||||
$table->dateTime('added')->nullable()->index('added');
|
||||
$table->dateTime('added')->nullable()->index();
|
||||
$table->text('txt')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class CreateCommentsTable extends Migration
|
||||
}
|
||||
Schema::create('comments', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('user')->default(0)->index('user');
|
||||
$table->unsignedMediumInteger('user')->default(0)->index();
|
||||
$table->unsignedMediumInteger('torrent')->default(0);
|
||||
$table->dateTime('added')->nullable();
|
||||
$table->text('text')->nullable();
|
||||
@@ -28,8 +28,8 @@ class CreateCommentsTable extends Migration
|
||||
$table->unsignedMediumInteger('offer')->default(0);
|
||||
$table->integer('request')->default(0);
|
||||
$table->enum('anonymous', ['yes', 'no'])->default('no');
|
||||
$table->index(['torrent', 'id'], 'torrent_id');
|
||||
$table->index(['offer', 'id'], 'offer_id');
|
||||
$table->index(['torrent', 'id'], );
|
||||
$table->index(['offer', 'id'], );
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class CreateFilesTable extends Migration
|
||||
}
|
||||
Schema::create('files', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('torrent')->default(0)->index('torrent');
|
||||
$table->unsignedMediumInteger('torrent')->default(0)->index();
|
||||
$table->string('filename')->default('');
|
||||
$table->unsignedBigInteger('size')->default(0);
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ class CreateForummodsTable extends Migration
|
||||
}
|
||||
Schema::create('forummods', function (Blueprint $table) {
|
||||
$table->smallIncrements('id');
|
||||
$table->unsignedSmallInteger('forumid')->default(0)->index('forumid');
|
||||
$table->unsignedSmallInteger('forumid')->default(0)->index();
|
||||
$table->unsignedMediumInteger('userid')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class CreateFriendsTable extends Migration
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('userid')->default(0);
|
||||
$table->unsignedMediumInteger('friendid')->default(0);
|
||||
$table->unique(['userid', 'friendid'], 'userfriend');
|
||||
$table->unique(['userid', 'friendid']);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class CreateInvitesTable extends Migration
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('inviter')->default(0);
|
||||
$table->string('invitee', 80)->default('');
|
||||
$table->char('hash', 32)->index('hash');
|
||||
$table->char('hash', 32)->index();
|
||||
$table->dateTime('time_invited')->nullable();
|
||||
$table->tinyInteger('valid')->default(1);
|
||||
$table->integer('invitee_register_uid')->nullable();
|
||||
|
||||
@@ -19,7 +19,7 @@ class CreateIplogTable extends Migration
|
||||
Schema::create('iplog', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('ip', 64)->default('');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index('userid');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index();
|
||||
$table->dateTime('access')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ class CreateMagicTable extends Migration
|
||||
}
|
||||
Schema::create('magic', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->integer('torrentid')->default(0)->index('idx_torrentid');
|
||||
$table->integer('userid')->default(0)->index('idx_userid');
|
||||
$table->integer('torrentid')->default(0)->index();
|
||||
$table->integer('userid')->default(0)->index();
|
||||
$table->integer('value')->default(0);
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->useCurrent();
|
||||
|
||||
@@ -18,8 +18,8 @@ class CreateMessagesTable extends Migration
|
||||
}
|
||||
Schema::create('messages', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('sender')->default(0)->index('sender');
|
||||
$table->unsignedMediumInteger('receiver')->default(0)->index('receiver');
|
||||
$table->unsignedMediumInteger('sender')->default(0)->index();
|
||||
$table->unsignedMediumInteger('receiver')->default(0)->index();
|
||||
$table->dateTime('added')->nullable();
|
||||
$table->string('subject', 128)->default('');
|
||||
$table->text('msg')->nullable();
|
||||
|
||||
@@ -19,7 +19,7 @@ class CreateNewsTable extends Migration
|
||||
Schema::create('news', function (Blueprint $table) {
|
||||
$table->mediumIncrements('id');
|
||||
$table->unsignedMediumInteger('userid')->default(0);
|
||||
$table->dateTime('added')->nullable()->index('added');
|
||||
$table->dateTime('added')->nullable()->index();
|
||||
$table->text('body')->nullable();
|
||||
$table->string('title')->default('');
|
||||
$table->enum('notify', ['yes', 'no'])->default('no');
|
||||
|
||||
@@ -18,7 +18,7 @@ class CreateOffersTable extends Migration
|
||||
}
|
||||
Schema::create('offers', function (Blueprint $table) {
|
||||
$table->mediumIncrements('id');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index('userid');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index();
|
||||
$table->string('name', 225)->default('');
|
||||
$table->text('descr')->nullable();
|
||||
$table->dateTime('added')->nullable();
|
||||
|
||||
@@ -19,7 +19,7 @@ class CreateOffervotesTable extends Migration
|
||||
Schema::create('offervotes', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('offerid')->default(0);
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index('userid');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index();
|
||||
$table->enum('vote', ['yeah', 'against'])->default('yeah');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class CreatePeersTable extends Migration
|
||||
Schema::create('peers', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedMediumInteger('torrent')->default(0);
|
||||
$table->char('peer_id', 20)->charset('binary')->index();
|
||||
$table->binary('peer_id', 20)->index();
|
||||
$table->string('ip', 64)->default('');
|
||||
$table->unsignedSmallInteger('port')->default(0);
|
||||
$table->unsignedBigInteger('uploaded')->default(0);
|
||||
@@ -36,7 +36,7 @@ class CreatePeersTable extends Migration
|
||||
$table->unsignedBigInteger('downloadoffset')->default(0);
|
||||
$table->unsignedBigInteger('uploadoffset')->default(0);
|
||||
$table->char('passkey', 32)->default('');
|
||||
$table->unique(['torrent', 'peer_id']);
|
||||
$table->unique(['torrent', 'peer_id', 'userid']);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ class CreatePollanswersTable extends Migration
|
||||
}
|
||||
Schema::create('pollanswers', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('pollid')->default(0)->index('pollid');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index('userid');
|
||||
$table->unsignedTinyInteger('selection')->default(0)->index('selection');
|
||||
$table->unsignedMediumInteger('pollid')->default(0)->index();
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index();
|
||||
$table->unsignedTinyInteger('selection')->default(0)->index();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,15 +19,14 @@ class CreatePostsTable extends Migration
|
||||
Schema::create('posts', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('topicid')->default(0);
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index('userid');
|
||||
$table->dateTime('added')->nullable()->index('added');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index();
|
||||
$table->dateTime('added')->nullable()->index();
|
||||
$table->text('body')->nullable();
|
||||
$table->text('ori_body')->nullable();
|
||||
$table->unsignedMediumInteger('editedby')->default(0);
|
||||
$table->dateTime('editdate')->nullable();
|
||||
$table->index(['topicid', 'id'], 'topicid_id');
|
||||
$table->index(['topicid', 'id']);
|
||||
});
|
||||
\Illuminate\Support\Facades\DB::statement('alter table posts add fulltext body(body)');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,8 +18,8 @@ class CreateReadpostsTable extends Migration
|
||||
}
|
||||
Schema::create('readposts', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index('userid');
|
||||
$table->unsignedMediumInteger('topicid')->default(0)->index('topicid');
|
||||
$table->unsignedMediumInteger('userid')->default(0)->index();
|
||||
$table->unsignedMediumInteger('topicid')->default(0)->index();
|
||||
$table->unsignedInteger('lastpostread')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class CreateReportsTable extends Migration
|
||||
$table->enum('type', ['torrent', 'user', 'offer', 'request', 'post', 'comment', 'subtitle'])->default('torrent');
|
||||
$table->string('reason')->default('');
|
||||
$table->unsignedMediumInteger('dealtby')->default(0);
|
||||
$table->boolean('dealtwith')->default(0);
|
||||
$table->smallInteger('dealtwith')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user