Compare commits

...

82 Commits

Author SHA1 Message Date
xiaomlove e5fdea8dac Merge pull request #442 from fei152/feat/cloudflare-imgbed
feat: add CloudFlare ImgBed attachment driver
2026-07-26 03:04:55 +07:00
fei152 5b83fa1722 feat: add CloudFlare ImgBed attachment driver 2026-07-23 04:15:04 +08:00
xiaomlove 78f00fb474 fix ExamRepository pg 兼容 2026-07-12 20:02:25 +07:00
xiaomlove ee30a70cb9 fix userdetails page user clients 2026-06-28 03:23:09 +07:00
xiaomlove dd2716427e update README 2026-06-16 17:10:06 +07:00
xiaomlove c50867a71a NexusDB compatible with binary field 2026-06-14 16:24:18 +07:00
xiaomlove fab32bf63d send staff message to telegram 2026-06-12 17:47:08 +07:00
xiaomlove e4cdb26c2f api add middleware: CheckSiteStatus 2026-05-31 03:35:13 +07:00
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 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 f97d564ada fix torrenttable() action render 2025-12-11 11:27:12 +07: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
693 changed files with 52740 additions and 6804 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
+3 -3
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,8 +40,8 @@ Welcome to participate in internationalization work, click [here](https://github
- Section H&R
- TGBot
## System Requirements
- 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
- 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, pdo_pgsql
- Database: MySQL 5.7 latest version or above/PostgreSQL 16 or above
- Redis4.0.0 or above
- Others: supervisor, rsync
+3 -3
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,8 +40,8 @@
- TGBot
## 系统要求
- 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 最新版或以上版本
- 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, pdo_pgsql
- Database: MySQL 5.7 最新版或以上版本/PostgreSQL 16 或以上版本
- 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();
}
+6
View File
@@ -14,6 +14,7 @@ use App\Events\HitAndRunUpdated;
use App\Events\MessageCreated;
use App\Events\NewsCreated;
use App\Events\SnatchedUpdated;
use App\Events\StaffMessageCreated;
use App\Events\TorrentCreated;
use App\Events\TorrentDeleted;
use App\Events\TorrentUpdated;
@@ -28,6 +29,7 @@ use App\Models\HitAndRun;
use App\Models\Message;
use App\Models\News;
use App\Models\Snatch;
use App\Models\StaffMessage;
use App\Models\Torrent;
use App\Models\User;
@@ -59,6 +61,8 @@ final class ModelEventEnum {
const SNATCHED_UPDATED = 'snatched_updated';
const MESSAGE_CREATED = 'message_created';
const STAFF_MESSAGE_CREATED = 'staff_message_created';
public static array $eventMaps = [
self::TORRENT_CREATED => ['event' => TorrentCreated::class, 'model' => Torrent::class],
self::TORRENT_UPDATED => ['event' => TorrentUpdated::class, 'model' => Torrent::class],
@@ -87,5 +91,7 @@ final class ModelEventEnum {
self::AGENT_DENY_CREATED => ['event' => AgentDenyCreated::class, 'model' => AgentDeny::class],
self::AGENT_DENY_UPDATED => ['event' => AgentDenyUpdated::class, 'model' => AgentDeny::class],
self::AGENT_DENY_DELETED => ['event' => AgentDenyDeleted::class, 'model' => AgentDeny::class],
self::STAFF_MESSAGE_CREATED => ['event' => StaffMessageCreated::class, 'model' => StaffMessage::class],
];
}
+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";
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class StaffMessageCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public ?Model $model = null;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Model $model)
{
$this->model = $model;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
@@ -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) {
@@ -499,7 +501,12 @@ class EditSetting extends Page implements HasForms
$schema = [];
$name = "$id.driver";
$schema[] = Radio::make($name)
->options(['local' => 'local', 'chevereto' => 'chevereto', 'lsky' => 'lsky'])
->options([
'local' => 'local',
'chevereto' => 'chevereto',
'lsky' => 'lsky',
'cloudflare_imgbed' => 'CloudFlare ImgBed',
])
->inline(true)
->label(__("label.setting.$name"))
->helperText(__("label.setting.{$name}_help"))
@@ -517,6 +524,8 @@ class EditSetting extends Page implements HasForms
$field = "upload_token";
$driverSchemas[] = TextInput::make("$driverId.$field")
->label(__("label.setting.$id.$field"))
->password()
->revealable()
;
$field = "base_url";
$driverSchemas[] = TextInput::make("$driverId.$field")
@@ -537,6 +546,8 @@ class EditSetting extends Page implements HasForms
$field = "upload_token";
$driverSchemas[] = TextInput::make("$driverId.$field")
->label(__("label.setting.$id.$field"))
->password()
->revealable()
;
$field = "base_url";
$driverSchemas[] = TextInput::make("$driverId.$field")
@@ -545,6 +556,53 @@ class EditSetting extends Page implements HasForms
$driverSection = Section::make($driverName)->schema($driverSchemas);
$schema[] = $driverSection;
//CloudFlare ImgBed
$driverName = "cloudflare_imgbed";
$driverId = sprintf("%s_%s", $id, $driverName);
$driverSchemas = [];
$field = "upload_api_endpoint";
$driverSchemas[] = TextInput::make("$driverId.$field")
->label(__("label.setting.$id.$field"))
->helperText(__("label.setting.$id.cloudflare_imgbed_endpoint_help"))
;
$field = "upload_token";
$driverSchemas[] = TextInput::make("$driverId.$field")
->label(__("label.setting.$id.$field"))
->helperText(__("label.setting.$id.cloudflare_imgbed_token_help"))
->password()
->revealable()
;
$field = "base_url";
$driverSchemas[] = TextInput::make("$driverId.$field")
->label(__("label.setting.$id.$field"))
->helperText(__("label.setting.$id.cloudflare_imgbed_base_url_help"))
;
$field = "upload_channel";
$driverSchemas[] = Select::make("$driverId.$field")
->options([
'telegram' => 'Telegram',
'cfr2' => 'Cloudflare R2',
's3' => 'S3',
'discord' => 'Discord',
'huggingface' => 'Hugging Face',
'webdav' => 'WebDAV',
])
->placeholder(__("label.setting.$id.upload_channel_default"))
->label(__("label.setting.$id.$field"))
;
$field = "channel_name";
$driverSchemas[] = TextInput::make("$driverId.$field")
->label(__("label.setting.$id.$field"))
->helperText(__("label.setting.$id.channel_name_help"))
;
$field = "upload_folder";
$driverSchemas[] = TextInput::make("$driverId.$field")
->label(__("label.setting.$id.$field"))
->helperText(__("label.setting.$id.upload_folder_help"))
;
$driverSection = Section::make('CloudFlare ImgBed')->schema($driverSchemas);
$schema[] = $driverSection;
return $schema;
}
@@ -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([
@@ -4,9 +4,11 @@ namespace App\Filament\Resources\User;
use App\Filament\Resources\User\UserPasskeyResource\Pages;
use App\Models\Passkey;
use Filament\Forms;
use Filament\Forms\Form;
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;
@@ -15,9 +17,9 @@ class UserPasskeyResource extends Resource
{
protected static ?string $model = Passkey::class;
protected static ?string $navigationIcon = 'heroicon-o-key';
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-key';
protected static ?string $navigationGroup = 'User';
protected static string | \UnitEnum | null $navigationGroup = 'User';
protected static ?int $navigationSort = 12;
@@ -31,10 +33,10 @@ class UserPasskeyResource extends Resource
return self::getNavigationLabel();
}
public static function form(Form $form): Form
public static function form(Schema $schema): Schema
{
return $form
->schema([
return $schema
->components([
//
]);
}
@@ -49,7 +51,7 @@ class UserPasskeyResource extends Resource
->formatStateUsing(fn($state) => username_for_admin($state))
->label(__('label.username'))
,
Tables\Columns\TextColumn::make('AAGUID')
Tables\Columns\TextColumn::make('aaguid')
->label("AAGUID")
,
Tables\Columns\TextColumn::make('credential_id')
@@ -66,7 +68,7 @@ class UserPasskeyResource extends Resource
->filters([
Tables\Filters\Filter::make('user_id')
->form([
Forms\Components\TextInput::make('uid')
TextInput::make('uid')
->label(__('label.username'))
->placeholder('UID')
,
@@ -76,7 +78,7 @@ class UserPasskeyResource extends Resource
,
Tables\Filters\Filter::make('credential_id')
->form([
Forms\Components\TextInput::make('credential_id')
TextInput::make('credential_id')
->label(__('passkey.fields.credential_id'))
->placeholder('Credential ID')
,
@@ -85,11 +87,11 @@ class UserPasskeyResource extends Resource
})
,
])
->actions([
Tables\Actions\DeleteAction::make(),
->recordActions([
DeleteAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
->toolbarActions([
DeleteBulkAction::make(),
]);
}
+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);
}
+3
View File
@@ -2,6 +2,7 @@
namespace App\Http;
use App\Http\Middleware\CheckSiteStatus;
use App\Http\Middleware\Filament;
use App\Http\Middleware\Locale;
use App\Http\Middleware\LogUserIp;
@@ -25,6 +26,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,
];
@@ -47,6 +49,7 @@ class Kernel extends HttpKernel
'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
CheckSiteStatus::class,
],
'filament' => [
\Illuminate\Session\Middleware\StartSession::class,
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use App\Exceptions\NexusException;
use App\Models\Setting;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckSiteStatus
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$user = $request->user();
if ($user && $user->class < User::CLASS_ADMINISTRATOR && !Setting::getIsSiteOnline()) {
throw new NexusException(nexus_trans('misc.site_down_for_maintenance'));
}
return $next($request);
}
}
+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')),
];
+4 -3
View File
@@ -97,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 = [];
@@ -160,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);
@@ -178,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
{
+4 -3
View File
@@ -9,7 +9,7 @@ class Passkey extends NexusModel
public $timestamps = true;
protected $fillable = [
'id', 'user_id', 'AAGUID', 'credential_id', 'public_key', 'counter',
'id', 'user_id', 'aaguid', 'credential_id', 'public_key', 'counter',
];
public function user()
@@ -17,8 +17,9 @@ class Passkey extends NexusModel
return $this->belongsTo(User::class, 'user_id');
}
public function AAGUID() {
$guid = $this->AAGUID;
public function getAaguidFormatted(): string
{
$guid = $this->aaguid;
return sprintf(
'%s-%s-%s-%s-%s',
substr($guid, 0, 8),
+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'];
}
+10
View File
@@ -334,4 +334,14 @@ 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);
}
public static function getIsSiteOnline(): bool
{
return self::get('main.site_online') == 'yes';
}
}
+13 -2
View File
@@ -2,8 +2,7 @@
namespace App\Models;
use App\Repositories\ToolRepository;
use Google\Service\Testing\ToolResultsExecution;
use App\Enums\ModelEventEnum;
class StaffMessage extends NexusModel
{
@@ -27,4 +26,16 @@ class StaffMessage extends NexusModel
return $this->belongsTo(User::class, 'answeredby');
}
public static function add(int $sender, string $subject, string $msg)
{
$record = self::query()->create([
'sender' => $sender,
'subject' => $subject,
'msg' => $msg,
'added' => now(),
]);
fire_event(ModelEventEnum::STAFF_MESSAGE_CREATED, $record);
return $record;
}
}
+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
+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()
+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);
+1 -1
View File
@@ -31,7 +31,7 @@ class BaseRepository
return $perPage;
}
protected function handleAnonymous($username, $user, User $authenticator, Torrent $torrent = null)
protected function handleAnonymous($username, $user, User $authenticator, ?Torrent $torrent = null)
{
if (!$user) {
return "";
+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)) {
+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] = [
+81 -22
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,24 @@ 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)")
;
->where(function ($q) use ($now) {
$q->where(function ($sub) use ($now) {
// 如果 begin 和 end 都不为空,则判断时间
$sub->whereNotNull('begin')
->whereNotNull('end')
->where('begin', '<=', $now)
->where('end', '>=', $now);
})->orWhere(function ($sub) {
// 如果不满足上面的条件(即 begin 或 end 任意一个为空)
$sub->where(function ($inner) {
$inner->whereNull('begin')
->orWhereNull('end');
})->where(function ($inner) {
$inner->where('duration', '>', 0)
->orWhereNotNull('recurring');
});
});
});
if (!is_null($excludeId)) {
$query->whereNotIn('id', Arr::wrap($excludeId));
@@ -261,6 +297,7 @@ class ExamRepository extends BaseRepository
if (!is_null($type)) {
$query->where("type", $type);
}
return $query->orderBy('priority', 'desc')->orderBy('id', 'asc')->get();
}
@@ -672,8 +709,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 +1062,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 {
@@ -1107,22 +1144,44 @@ class ExamRepository extends BaseRepository
public function cronjobCheckout($ignoreTimeRange = false): int
{
$now = Carbon::now()->toDateTimeString();
$now = Carbon::now(); // 保持 Carbon 对象即可,Laravel 会自动序列化
$examUserTable = (new ExamUser())->getTable();
$examTable = (new Exam())->getTable();
$userTable = (new User())->getTable();
$baseQuery = ExamUser::query()
->join($examTable, "$examUserTable.exam_id", "=", "$examTable.id")
->where("$examUserTable.status", ExamUser::STATUS_NORMAL)
->selectRaw("$examUserTable.*")
->select("$examUserTable.*") // 替换 selectRaw
->with(['exam', 'user', 'user.language'])
->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)));
$baseQuery->where(function ($query) use ($examUserTable, $examTable, $now) {
$query->where(function ($q) use ($examUserTable, $now) {
// 条件 1: exam_user.end 不为空且小于当前时间
$q->whereNotNull("$examUserTable.end")
->where("$examUserTable.end", '<', $now);
})
->orWhere(function ($q) use ($examTable, $now) {
// 条件 2: exam.end 不为空且小于当前时间
$q->whereNotNull("$examTable.end")
->where("$examTable.end", '<', $now);
})
->orWhere(function ($q) use ($examUserTable, $examTable, $now) {
// 条件 3: exam.duration > 0 且过期
// 因为涉及到列与列的计算,这里需要用 whereRaw,但我们可以针对多数据库做自适应
$q->where("$examTable.duration", '>', 0);
if (NexusDB::isPgsql()) {
// PG 写法:使用 || 拼接字符串再转为 INTERVAL
$q->whereRaw("$examUserTable.created_at + ($examTable.duration || ' day')::INTERVAL < ?", [$now]);
} else {
// MySQL 写法
$q->whereRaw("DATE_ADD($examUserTable.created_at, INTERVAL $examTable.duration DAY) < ?", [$now]);
}
});
});
}
$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();
}
+42 -20
View File
@@ -15,7 +15,25 @@ class UserPasskeyRepository extends BaseRepository
public static function createWebAuthn()
{
$formats = ['android-key', 'android-safetynet', 'apple', 'fido-u2f', 'packed', 'tpm', 'none'];
return new WebAuthn(get_setting('basic.SITENAME'), nexus()->getRequestHost(), $formats);
$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)
@@ -28,20 +46,22 @@ class UserPasskeyRepository extends BaseRepository
return hex2bin($item['credential_id']);
}, $passkey->toArray());
$createArgs = $WebAuthn->getCreateArgs(bin2hex($userId), $userName, $userName, 60 * 4, true, 'preferred', null, $credentialIds);
$createArgs = $WebAuthn->getCreateArgs(bin2hex($userId), $userName, $userName, 120, true, 'preferred', null, $credentialIds);
$challengeId = self::putChallenge($WebAuthn->getChallenge());
NexusDB::cache_put("{$userId}_passkey_challenge", $WebAuthn->getChallenge(), 60 * 4);
return $createArgs;
return [
'challengeId' => $challengeId,
'options' => $createArgs,
];
}
public static function processCreate($userId, $clientDataJSON, $attestationObject)
public static function processCreate($userId, $challengeId, $clientDataJSON, $attestationObject)
{
$WebAuthn = self::createWebAuthn();
$challenge = self::getChallenge($challengeId);
$clientDataJSON = !empty($clientDataJSON) ? base64_decode($clientDataJSON) : null;
$attestationObject = !empty($attestationObject) ? base64_decode($attestationObject) : null;
$challenge = NexusDB::cache_get("{$userId}_passkey_challenge") ?? null;
$WebAuthn = self::createWebAuthn();
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $challenge, false, true, false);
@@ -60,9 +80,13 @@ class UserPasskeyRepository extends BaseRepository
{
$WebAuthn = self::createWebAuthn();
$getArgs = $WebAuthn->getGetArgs(null, 60 * 4, true, true, true, true, true, 'preferred');
$getArgs = $WebAuthn->getGetArgs(null, 120, true, true, true, true, true, 'preferred');
$challengeId = self::putChallenge($WebAuthn->getChallenge());
return $getArgs;
return [
'challengeId' => $challengeId,
'options' => $getArgs,
];
}
public static function insertUserPasskey($userId, $AAGUID, $credentialId, $publicKey, $counter)
@@ -77,21 +101,19 @@ class UserPasskeyRepository extends BaseRepository
Passkey::query()->create($params);
}
public static function processGet($challenge, $id, $clientDataJSON, $authenticatorData, $signature, $userHandle)
public static function processGet($challengeId, $id, $clientDataJSON, $authenticatorData, $signature, $userHandle)
{
$WebAuthn = self::createWebAuthn();
$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;
$challenge = !empty($challenge) ? base64_decode($challenge) : null;
$WebAuthn = self::createWebAuthn();
$passkey = Passkey::query()->where('credential_id', '=', bin2hex($id))->first();
$credentialPublicKey = $passkey->public_key;
if ($credentialPublicKey === null) {
if ($passkey === null) {
throw new RuntimeException(nexus_trans('passkey.passkey_unknown'));
}
@@ -100,7 +122,7 @@ class UserPasskeyRepository extends BaseRepository
}
try {
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, 'preferred');
$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());
}
@@ -197,7 +219,7 @@ class UserPasskeyRepository extends BaseRepository
<td>
<div style="display:flex;align-items:center;padding:4px">
<?php
$meta = $AAGUIDS[$passkey->AAGUID()];
$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 {
+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;
}
+6 -4
View File
@@ -35,14 +35,15 @@
"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",
@@ -54,7 +55,7 @@
"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"
@@ -87,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
+548 -480
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',
],
+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
@@ -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);
});
}
@@ -18,7 +18,7 @@ class CreateRequestsTable extends Migration
}
Schema::create('requests', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('userid')->default(0)->index('userid');
$table->unsignedInteger('userid')->default(0)->index();
$table->string('request', 225)->default('');
$table->text('descr');
$table->unsignedInteger('comments')->default(0);
@@ -31,7 +31,7 @@ class CreateRequestsTable extends Migration
$table->string('ori_descr')->default('');
$table->integer('ori_amount')->default(0);
$table->dateTime('added')->nullable();
$table->index(['finish', 'userid'], 'finish, userid');
$table->index(['finish', 'userid']);
});
}
@@ -18,7 +18,7 @@ class CreateResreqTable extends Migration
}
Schema::create('resreq', function (Blueprint $table) {
$table->integer('id', true);
$table->integer('reqid')->default(0)->index('reqid');
$table->integer('reqid')->default(0)->index();
$table->integer('torrentid')->default(0);
$table->enum('chosen', ['yes', 'no'])->default('no');
});
@@ -23,7 +23,7 @@ class CreateSearchboxFieldsTable extends Migration
$table->integer('field_id')->default(0);
$table->timestamp('created_at')->useCurrent();
$table->timestamp('update_at')->useCurrent();
$table->unique(['searchbox_id', 'field_type', 'field_id'], 'uniq_searchbox_type_id');
$table->unique(['searchbox_id', 'field_type', 'field_id']);
});
}

Some files were not shown because too many files have changed in this diff Show More