Compare commits

...

100 Commits

Author SHA1 Message Date
xiaomlove f823dc191a change docker CONTEXT 2026-04-28 03:04:51 +07:00
xiaomlove 1372776ba1 pg support: group_concat 2026-04-25 13:44:23 +07:00
xiaomlove 7d18a7f76a pg support of duplicate key update 2026-04-25 03:22:38 +07:00
xiaomlove dc77ab7b40 pg support 2026-04-23 01:04:30 +07:00
xiaomlove 94a35b81dc migrations remove index name 2026-04-20 22:41:09 +07:00
xiaomlove dfe9436f1d remove debug 2026-04-18 12:18:46 +07:00
xiaomlove 4afcb1bb08 announce support pg 2026-04-18 12:15:14 +07:00
xiaomlove e3376c3f1b support pg problem fix 2026-04-16 04:03:52 +07:00
xiaomlove 00fdc2d08f support pg 2026-04-15 03:27:30 +07:00
xiaomlove 4d54e08918 migrations boolean() -> smallInteger() 2026-04-14 13:12:56 +07:00
xiaomlove 4d4af87dc9 update migrations to support pg 2026-04-13 14:17:19 +07:00
xiaomlove f271e28b15 DBPdo instead of DBMysqli 2026-04-13 03:12:04 +07:00
xiaomlove 9995767bf7 release v1.10.2 2026-04-11 12:07:31 +07:00
xiaomlove d115a3879a takelogin bark when self-enable feature is disable 2026-04-09 13:37:05 +07:00
xiaomlove 431fbfff56 fix self enable switch 2026-04-09 11:48:21 +07:00
xiaomlove 9e632811ef self enable add switch 2026-04-09 10:52:58 +07:00
xiaomlove db4982f8f7 self enable 2026-03-29 21:42:43 +07:00
xiaomlove af66ec806d imdb force agent 2026-03-28 14:37:25 +07:00
xiaomlove 09b785902f Merge pull request #428 from ex-hentai/filament-views
replace filament custom views
2026-02-27 08:28:09 +08:00
NekoCH d79031f24d replace filament custom views 2026-02-26 21:50:59 +08:00
xiaomlove 527a2d2162 release 1.10.1 2026-02-17 15:04:05 +08:00
xiaomlove 3dae3aec8d imdbphp/imdbphp lock 8.3.1 2026-02-17 14:36:24 +08:00
xiaomlove 5c9caa7a46 update release date 2026-02-01 14:41:43 +07:00
xiaomlove a712a3f1e4 exam index seed time average think about all active torrent 2026-02-01 13:52:49 +07:00
xiaomlove f07e0a5013 update translations 2026-02-01 01:26:00 +07:00
xiaomlove 8dec50fc6d fix bonus-log 2026-01-31 20:06:43 +07:00
xiaomlove 1e997e411c bonus logs show category seeding and common 2026-01-31 19:45:13 +07:00
xiaomlove fb440c6d2b Merge pull request #427 from ex-hentai/passkey-2
fix possible replay attack with passkey login
2026-01-31 14:38:30 +07:00
NekoCH 8207f1ed6f fix possible replay attack with passkey login 2026-01-31 15:28:25 +08:00
xiaomlove c162fc81be userdetails.php disable change vip_added + vip_until 2026-01-31 12:35:50 +07:00
xiaomlove fd0a2014ba update .env.example DB_USERNAME 2026-01-31 12:34:44 +07:00
xiaomlove eb3c03c97f update composer.lock 2026-01-31 01:36:13 +07:00
xiaomlove fc19ee5e7c Update run all activity log migration 2026-01-31 00:34:34 +07:00
xiaomlove 4e9e4ae143 fix some upgrade issue 2026-01-31 00:08:23 +07:00
xiaomlove e12cd0fab9 job remove sqlesc 2026-01-30 15:28:05 +07:00
xiaomlove f3a6d366d8 add job RemoveUserVipStatus + RemoveUserDonorStatus + RemoveUserWarning 2026-01-30 15:03:52 +07:00
xiaomlove 50259367d6 middleware Locale use as global 2026-01-29 21:43:35 +07:00
xiaomlove ea176b1615 bonus logs 2026-01-29 20:24:36 +07:00
xiaomlove 695d5f071f merge 1.9 2026-01-23 17:01:55 +07:00
xiaomlove dd8df70f7e update version to 1.9.14 2026-01-23 15:38:56 +07:00
xiaomlove 5e163da05e fix bbcode_attach_to_img driver local 2026-01-23 13:10:24 +07:00
xiaomlove 3ced82fc0a bbcode_attach_to_img 2026-01-23 13:01:16 +07:00
xiaomlove 7fc12723ec Merge pull request #425 from ex-hentai/filament-v5
update to filament v5
2026-01-19 14:17:21 +07:00
NekoCH 4f32c10d9f update to filament v5 2026-01-18 19:18:45 +08:00
xiaomlove 7494041005 fix get_ip_location_from_geoip() missing continent_en 2026-01-15 23:24:28 +07:00
xiaomlove af2b7c77e0 Merge pull request #423 from ex-hentai/torrent-file
use TorrentFile from Rhilip\Bencode
2026-01-14 17:25:03 +07:00
NekoCH 4035f4084f use TorrentFile from Rhilip\Bencode 2026-01-14 16:18:16 +08:00
xiaomlove 0a568c0d19 add more index to hit and runs table 2026-01-14 11:37:36 +07:00
xiaomlove b2f21010dc Merge pull request #424 from specialpointcentral/php8
Fix Filament UserPasskeyResource compatibility
2026-01-11 16:34:43 +07:00
Qi HU ef01571b9a Fix Filament UserPasskeyResource compatibility
Signed-off-by: Qi HU <github@spcsky.com>
2026-01-10 11:48:37 +09:00
xiaomlove c91cf060b1 delete torrent extra when delete torrent 2026-01-07 16:00:14 +07:00
xiaomlove 4d96d40a29 Merge remote-tracking branch 'origin/php8' into php8 2026-01-07 01:34:59 +07:00
xiaomlove 81400a34cc Merge branch '1.9' into php8 2026-01-07 01:31:11 +07:00
xiaomlove f20771353e new bonus type serchable 2026-01-07 01:30:21 +07:00
xiaomlove da4a609ef7 claim settle add bonus log 2026-01-07 01:20:44 +07:00
xiaomlove 0b8d4bee52 Merge pull request #421 from ex-hentai/rss-cache-1
filter get params in torrent rss
2026-01-04 21:57:56 +07:00
NekoCH 2cad4e1a83 filter get params in torrent rss 2026-01-04 21:32:48 +08:00
xiaomlove 8d98a7dd8b ptgen fill small title add field: chinese_title 2025-12-31 02:39:07 +07:00
xiaomlove 0351f92bf4 [API] comments list 2025-12-30 17:49:56 +07:00
xiaomlove 5f37d5c59a usercp add seedbox cancel ip help text 2025-12-30 15:39:15 +07:00
xiaomlove 791c617bc0 token permission cache load from db when not exists 2025-12-30 12:56:56 +07:00
xiaomlove 290ad0f375 Merge remote-tracking branch 'origin/php8' into php8 2025-12-29 00:03:48 +07:00
xiaomlove 8602831d3a merge 1.9 2025-12-29 00:02:46 +07:00
xiaomlove c5b55dbda1 delete user medal send notification 2025-12-28 23:59:32 +07:00
xiaomlove 7f0f8cca16 Merge pull request #417 from ex-hentai/customfield
migrate custom field management to filament
2025-12-28 22:12:38 +07:00
NekoCH 00ec3d5e8d migrate custom field management to filament
fix xss
2025-12-28 22:46:55 +08:00
xiaomlove eb248110fc release v1.9.13 2025-12-28 19:58:10 +07:00
xiaomlove 33fd265a20 pt gen support rabbitwit/PT-Gen-Refactor 2025-12-28 15:21:06 +07:00
xiaomlove 6180ae18df fix takeconfirm conusr 2025-12-23 16:20:47 +07:00
xiaomlove f18fa80eac Merge pull request #409 from ex-hentai/count
add uploaded torrents count to invite
2025-12-21 14:33:24 +07:00
NekoCH 5c73e643a4 add uploaded torrents count to invite 2025-12-20 22:52:35 +08:00
xiaomlove ef397714b1 Merge pull request #413 from ex-hentai/preview
fix preview get stuck
2025-12-20 16:16:13 +07:00
xiaomlove 1d6740cc41 Merge pull request #412 from ex-hentai/flash
remove flash stuff
2025-12-20 16:15:25 +07:00
xiaomlove 8e12cbf470 Merge pull request #411 from ex-hentai/detached
fix voting for non-existent offers
2025-12-20 16:14:52 +07:00
xiaomlove d70a2c45ce Merge pull request #410 from ex-hentai/top
better image zooming
2025-12-20 16:14:06 +07:00
xiaomlove 50fd5ed2e0 Merge pull request #408 from ex-hentai/rss-cache
cache rss user content
2025-12-20 16:12:13 +07:00
NekoCH eb11239372 fix preview get stuck 2025-12-20 17:05:53 +08:00
NekoCH 39853b221a fix voting for non-existent offers 2025-12-20 17:01:50 +08:00
NekoCH 5548b7ef94 remove flash stuff 2025-12-20 16:59:43 +08:00
NekoCH 6803bbe85f better image zooming 2025-12-20 16:56:32 +08:00
NekoCH 8a4538faea cache rss user content 2025-12-20 16:44:10 +08:00
xiaomlove b59377ab47 Merge pull request #405 from ex-hentai/passkey
support login with Passkeys
2025-12-20 13:17:43 +07:00
xiaomlove e439e2eec8 Merge pull request #403 from specialpointcentral/php8
Some fixups
2025-12-20 13:16:33 +07:00
xiaomlove b3067e2b6a Merge pull request #407 from ex-hentai/compact
always response in compact format
2025-12-20 13:12:16 +07:00
NekoCH 38f599d794 fix php version 2025-12-20 14:11:27 +08:00
xiaomlove 3ed1f0fa9d Merge pull request #406 from ex-hentai/email
validate email with filter_var
2025-12-20 13:08:34 +07:00
NekoCH 036e98a2d7 always response in compact format 2025-12-20 14:05:08 +08:00
NekoCH dfdf64833f validate email with filter_var 2025-12-20 14:02:51 +08:00
NekoCH e035ff1512 passkey support 2025-12-20 13:59:10 +08:00
Qi HU b921beffd7 Enable IYUU PTGen v2.0.0
Signed-off-by: Qi HU <github@spcsky.com>
2025-12-18 16:40:55 +08:00
Qi HU 0307cb3e78 Tune seed bonus job batch and timeouts
Signed-off-by: Qi HU <github@spcsky.com>
2025-12-18 16:40:50 +08:00
xiaomlove d255499e83 fix media info xss 2025-12-15 19:35:37 +07:00
xiaomlove 88f2318699 setEnableLatelyCache() 24 hours 2025-12-11 15:52:40 +07:00
xiaomlove 7b0b51cb7e Merge pull request #401 from specialpointcentral/php8
feat: add notice lead time for global promotions
2025-12-11 11:46:02 +07:00
xiaomlove f97d564ada fix torrenttable() action render 2025-12-11 11:27:12 +07:00
Qi HU 7719535940 feat: add notice lead time for global promotions 2025-12-11 12:21:34 +08:00
xiaomlove 60f1a4970c Merge pull request #400 from specialpointcentral/php8
feat: enhance torrent state scheduling and display
2025-12-07 16:56:39 +07:00
Qi HU 2346afa291 feat: enhance torrent state scheduling and display
Signed-off-by: Qi HU <github@spcsky.com>
2025-12-07 17:07:47 +08:00
xiaomlove 70bc00b707 Fixed torrent list page display issues when users have edit permissions but no delete permissions 2025-12-02 21:40:40 +07:00
xiaomlove ac83b68929 UserResource add seeding leeching data + seed bonus per hour 2025-11-21 00:41:03 +07:00
701 changed files with 53812 additions and 7173 deletions
+1 -1
View File
@@ -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"]
+1 -1
View File
@@ -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"]
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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
- Redis4.0.0 or above
- Others: supervisor, rsync
+2 -2
View File
@@ -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 最新版或以上版本
- Redis4.0.0 或以上版本
- 其他:supervisor, rsync
+2 -2
View File
@@ -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();
+1 -1
View File
@@ -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
View File
@@ -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();
}
+1
View File
@@ -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 [
];
}
}
+1 -10
View File
@@ -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);
}
+1
View File
@@ -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,
];
+4 -6
View File
@@ -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'))),
];
}
}
+5
View File
@@ -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')),
];
+6 -4
View File
@@ -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) {
+62
View File
@@ -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());
}
}
+69
View File
@@ -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());
}
}
+63
View File
@@ -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());
}
}
+34 -2
View File
@@ -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);
+2
View File
@@ -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();
+9
View File
@@ -0,0 +1,9 @@
<?php
namespace App\Models;
class Faq extends NexusModel
{
protected $table = 'faq';
}
+1 -1
View File
@@ -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'];
+4 -1
View File
@@ -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
{
+33
View File
@@ -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)
);
}
}
+13
View File
@@ -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'];
}
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
+16 -6
View File
@@ -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
View File
@@ -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);
}
+2
View File
@@ -8,6 +8,8 @@ class UserBanLog extends NexusModel
protected $fillable = ['uid', 'username', 'operator', 'reason'];
public $timestamps = true;
public static function clearUserBanLogDuplicate()
{
$lists = UserBanLog::query()
+4 -4
View File
@@ -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)
+2
View File
@@ -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,
+2 -2
View File
@@ -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);
+67
View File
@@ -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;
}
}
+13 -4
View File
@@ -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)) {
+1 -1
View File
@@ -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)
{
+11 -10
View File
@@ -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)
+2 -1
View File
@@ -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] = [
+55 -19
View File
@@ -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;
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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");
+1 -1
View File
@@ -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!");
+4 -2
View File
@@ -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;
+1 -1
View File
@@ -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();
}
+274
View File
@@ -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);
}
}
+60 -19
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -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
View File
@@ -74,7 +74,7 @@ return [
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'schema' => 'public',
'schema' => env('DB_SCHEMA', 'public'),
'sslmode' => 'prefer',
],
+1 -1
View File
@@ -191,7 +191,7 @@ return [
'maxJobs' => 0,
'memory' => 128,
'tries' => 1,
'timeout' => 600,
'timeout' => 120,
'nice' => 0,
],
],
+39
View File
@@ -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
View File
@@ -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
View File
@@ -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