mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-24 12:07:23 +08:00
temporary invite
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\GenerateTemporaryInvite;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class InviteAddTemporary extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'invite:tmp {uid} {days} {count}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Add temporary invite to user, argument: uid(Multiple comma separated), days, count';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$uid = $this->argument('uid');
|
||||
$days = $this->argument('days');
|
||||
$count = $this->argument('count');
|
||||
$this->info("uid: $uid, days: $days, count: $count");
|
||||
$uidArr = preg_split('/[\s,]+/', $uid);
|
||||
GenerateTemporaryInvite::dispatch($uidArr, $days, $count);
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ use App\Models\Exam;
|
||||
use App\Models\ExamProgress;
|
||||
use App\Models\ExamUser;
|
||||
use App\Models\HitAndRun;
|
||||
use App\Models\Invite;
|
||||
use App\Models\Medal;
|
||||
use App\Models\Peer;
|
||||
use App\Models\SearchBox;
|
||||
@@ -94,10 +95,9 @@ class Test extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$rep = new WorkRepository();
|
||||
$rep->settleRole(4);
|
||||
$uid = "2,3,4";
|
||||
$uidArr = preg_split('/[\s,]+/', $uid);
|
||||
dd($uidArr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\User;
|
||||
|
||||
use App\Filament\Resources\User\InviteResource\Pages;
|
||||
use App\Filament\Resources\User\InviteResource\RelationManagers;
|
||||
use App\Models\Invite;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Resources\Table;
|
||||
use Filament\Tables;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class InviteResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Invite::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-user-add';
|
||||
|
||||
protected static ?string $navigationGroup = 'User';
|
||||
|
||||
protected static ?int $navigationSort = 7;
|
||||
|
||||
protected static function getNavigationLabel(): string
|
||||
{
|
||||
return __('admin.sidebar.invite');
|
||||
}
|
||||
|
||||
public static function getBreadcrumb(): string
|
||||
{
|
||||
return self::getNavigationLabel();
|
||||
}
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id'),
|
||||
Tables\Columns\TextColumn::make('inviter')
|
||||
->label(__('invite.fields.inviter'))
|
||||
->formatStateUsing(fn ($state) => username_for_admin($state))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('invitee')
|
||||
->label(__('invite.fields.invitee'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('hash')
|
||||
,
|
||||
Tables\Columns\TextColumn::make('time_invited')
|
||||
->label(__('invite.fields.time_invited'))
|
||||
,
|
||||
Tables\Columns\IconColumn::make('valid')
|
||||
->label(__('invite.fields.valid'))
|
||||
->boolean()
|
||||
,
|
||||
Tables\Columns\TextColumn::make('invitee_register_uid')
|
||||
->label(__('invite.fields.invitee_register_uid'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('invitee_register_email')
|
||||
->label(__('invite.fields.invitee_register_email'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('invitee_register_email')
|
||||
->label(__('invite.fields.invitee_register_email'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('invitee_register_username')
|
||||
->label(__('invite.fields.invitee_register_username'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('expired_at')
|
||||
->label(__('invite.fields.expired_at'))
|
||||
->formatStateUsing(fn ($state) => format_datetime($state))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->label(__('label.created_at'))
|
||||
->formatStateUsing(fn ($state) => format_datetime($state))
|
||||
,
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
// Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
// Tables\Actions\DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListInvites::route('/'),
|
||||
'create' => Pages\CreateInvite::route('/create'),
|
||||
'edit' => Pages\EditInvite::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\User\InviteResource\Pages;
|
||||
|
||||
use App\Filament\Resources\User\InviteResource;
|
||||
use Filament\Pages\Actions;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateInvite extends CreateRecord
|
||||
{
|
||||
protected static string $resource = InviteResource::class;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\User\InviteResource\Pages;
|
||||
|
||||
use App\Filament\Resources\User\InviteResource;
|
||||
use Filament\Pages\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditInvite extends EditRecord
|
||||
{
|
||||
protected static string $resource = InviteResource::class;
|
||||
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\User\InviteResource\Pages;
|
||||
|
||||
use App\Filament\PageList;
|
||||
use App\Filament\Resources\User\InviteResource;
|
||||
use App\Models\Invite;
|
||||
use Filament\Pages\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListInvites extends PageList
|
||||
{
|
||||
protected static string $resource = InviteResource::class;
|
||||
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
// Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return Invite::query()->with(['inviter_user']);
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,14 @@
|
||||
namespace App\Filament\Resources\User\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\User\UserResource;
|
||||
use App\Models\Invite;
|
||||
use App\Models\Medal;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMeta;
|
||||
use App\Repositories\ExamRepository;
|
||||
use App\Repositories\MedalRepository;
|
||||
use App\Repositories\UserRepository;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Resources\Pages\Concerns\HasRelationManagers;
|
||||
use Filament\Resources\Pages\Concerns\InteractsWithRecord;
|
||||
use Filament\Resources\Pages\Page;
|
||||
@@ -129,20 +131,44 @@ class UserProfile extends ViewRecord
|
||||
'invites' => __('label.user.invites'),
|
||||
'seedbonus' => __('label.user.seedbonus'),
|
||||
'attendance_card' => __('label.user.attendance_card'),
|
||||
])->label(__('admin.resources.user.actions.change_bonus_etc_field_label'))->inline()->required(),
|
||||
'tmp_invites' => __('label.user.tmp_invites'),
|
||||
])
|
||||
->label(__('admin.resources.user.actions.change_bonus_etc_field_label'))
|
||||
->inline()
|
||||
->required()
|
||||
->reactive()
|
||||
,
|
||||
Forms\Components\Radio::make('action')->options([
|
||||
'Increment' => __("admin.resources.user.actions.change_bonus_etc_action_increment"),
|
||||
'Decrement' => __("admin.resources.user.actions.change_bonus_etc_action_decrement"),
|
||||
])->label(__('admin.resources.user.actions.change_bonus_etc_action_label'))->inline()->required(),
|
||||
])
|
||||
->label(__('admin.resources.user.actions.change_bonus_etc_action_label'))
|
||||
->inline()
|
||||
->required()
|
||||
,
|
||||
Forms\Components\TextInput::make('value')->integer()->required()
|
||||
->label(__('admin.resources.user.actions.change_bonus_etc_value_label'))
|
||||
->helperText(__('admin.resources.user.actions.change_bonus_etc_value_help')),
|
||||
Forms\Components\Textarea::make('reason')->label(__('admin.resources.user.actions.change_bonus_etc_reason_label')),
|
||||
->helperText(__('admin.resources.user.actions.change_bonus_etc_value_help'))
|
||||
,
|
||||
|
||||
Forms\Components\TextInput::make('duration')->integer()
|
||||
->label(__('admin.resources.user.actions.change_bonus_etc_duration_label'))
|
||||
->helperText(__('admin.resources.user.actions.change_bonus_etc_duration_help'))
|
||||
->hidden(fn (\Closure $get) => $get('field') != 'tmp_invites')
|
||||
,
|
||||
|
||||
Forms\Components\Textarea::make('reason')
|
||||
->label(__('admin.resources.user.actions.change_bonus_etc_reason_label'))
|
||||
,
|
||||
])
|
||||
->action(function ($data) {
|
||||
$userRep = $this->getRep();
|
||||
try {
|
||||
$userRep->incrementDecrement(Auth::user(), $this->record->id, $data['action'], $data['field'], $data['value'], $data['reason']);
|
||||
if ($data['field'] == 'tmp_invites') {
|
||||
$userRep->addTemporaryInvite(Auth::user(), $this->record->id, $data['action'], $data['value'], $data['duration'], $data['reason']);
|
||||
} else {
|
||||
$userRep->incrementDecrement(Auth::user(), $this->record->id, $data['action'], $data['field'], $data['value'], $data['reason']);
|
||||
}
|
||||
$this->notify('success', 'Success!');
|
||||
$this->emitSelf(self::EVENT_RECORD_UPDATED, $this->record->id);
|
||||
} catch (\Exception $exception) {
|
||||
@@ -298,6 +324,7 @@ class UserProfile extends ViewRecord
|
||||
{
|
||||
return [
|
||||
'props' => $this->listUserProps(),
|
||||
'temporary_invite_count' => $this->countTemporaryInvite()
|
||||
];
|
||||
}
|
||||
|
||||
@@ -319,4 +346,13 @@ class UserProfile extends ViewRecord
|
||||
}
|
||||
return $props;
|
||||
}
|
||||
|
||||
private function countTemporaryInvite()
|
||||
{
|
||||
return Invite::query()->where('inviter', $this->record->id)
|
||||
->where('invitee', '')
|
||||
->whereNotNull('expired_at')
|
||||
->where('expired_at', '>', Carbon::now())
|
||||
->count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Invite;
|
||||
use App\Repositories\ToolRepository;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GenerateTemporaryInvite implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private int $count;
|
||||
|
||||
private array $uidArr;
|
||||
|
||||
private int $days;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $uidArr, int $days, int $count)
|
||||
{
|
||||
$this->uidArr = $uidArr;
|
||||
$this->days = $days;
|
||||
$this->count = $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function retryUntil()
|
||||
{
|
||||
return now()->addHours(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$toolRep = new ToolRepository();
|
||||
foreach ($this->uidArr as $uid) {
|
||||
try {
|
||||
$hashArr = $toolRep->generateUniqueInviteHash([], $this->count, $this->count);
|
||||
$data = [];
|
||||
foreach($hashArr as $hash) {
|
||||
$data[] = [
|
||||
'inviter' => $uid,
|
||||
'invitee' => '',
|
||||
'hash' => $hash,
|
||||
'valid' => 0,
|
||||
'expired_at' => Carbon::now()->addDays($this->days),
|
||||
'created_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
if (!empty($data)) {
|
||||
Invite::query()->insert($data);
|
||||
}
|
||||
do_log("success add $this->count temporary invite ($this->days days) to $uid");
|
||||
} catch (\Exception $exception) {
|
||||
do_log("fail add $this->count temporary invite ($this->days days) to $uid: " . $exception->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,10 @@ class Invite extends NexusModel
|
||||
const VALID_YES = 1;
|
||||
const VALID_NO = 0;
|
||||
|
||||
protected $casts = [
|
||||
'expired_at' => 'datetime',
|
||||
];
|
||||
|
||||
public static $validInfo = [
|
||||
self::VALID_NO => ['text' => 'No'],
|
||||
self::VALID_YES => ['text' => 'Yes'],
|
||||
|
||||
@@ -360,6 +360,15 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
return $this->belongsTo(User::class, 'invited_by');
|
||||
}
|
||||
|
||||
public function temporary_invites()
|
||||
{
|
||||
return $this->hasMany(Invite::class, 'inviter')
|
||||
->where('invitee', '')
|
||||
->whereNotNull('expired_at')
|
||||
->where('expired_at', '>=', Carbon::now())
|
||||
;
|
||||
}
|
||||
|
||||
public function send_messages()
|
||||
{
|
||||
return $this->hasMany(Message::class, 'sender');
|
||||
|
||||
@@ -153,6 +153,11 @@ class ClaimRepository extends BaseRepository
|
||||
|
||||
public function settleUser($uid, $force = false, $test = false): bool
|
||||
{
|
||||
$shouldDoSettle = apply_filter('user_should_do_claim_settle', true, $uid);
|
||||
if (!$shouldDoSettle) {
|
||||
do_log("uid: $uid, filter: user_should_do_claim_settle => false");
|
||||
return false;
|
||||
}
|
||||
$user = User::query()->with('language')->findOrFail($uid);
|
||||
$list = Claim::query()->where('uid', $uid)->with(['snatch', 'torrent' => fn ($query) => $query->select(Torrent::$commentFields)])->get();
|
||||
$now = Carbon::now();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Invite;
|
||||
use App\Models\Message;
|
||||
use App\Models\News;
|
||||
use App\Models\Poll;
|
||||
@@ -11,6 +12,7 @@ use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Nexus\Database\NexusDB;
|
||||
use Nexus\Plugin\Plugin;
|
||||
use NexusPlugin\Permission\PermissionRepository;
|
||||
@@ -416,4 +418,25 @@ class ToolRepository extends BaseRepository
|
||||
$uidPermissionsCached[$uid] = $result;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function generateUniqueInviteHash(array $hashArr, int $total, int $left, int $deep = 0): array
|
||||
{
|
||||
do_log("total: $total, left: $left, deep: $deep");
|
||||
if ($deep > 10) {
|
||||
throw new \RuntimeException("deep: $deep > 10");
|
||||
}
|
||||
if (count($hashArr) >= $total) {
|
||||
return array_slice(array_values($hashArr), 0, $total);
|
||||
}
|
||||
for ($i = 0; $i < $left; $i++) {
|
||||
$hash = Str::random(32);
|
||||
$hashArr[$hash] = $hash;
|
||||
}
|
||||
$exists = Invite::query()->whereIn('hash', array_values($hashArr))->get(['id', 'hash']);
|
||||
foreach($exists as $value) {
|
||||
unset($hashArr[$value->hash]);
|
||||
}
|
||||
return $this->generateUniqueInviteHash($hashArr, $total, $total - count($hashArr), ++$deep);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Exceptions\NexusException;
|
||||
use App\Http\Resources\ExamUserResource;
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Models\ExamUser;
|
||||
use App\Models\Invite;
|
||||
use App\Models\Message;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
@@ -566,4 +567,81 @@ class UserRepository extends BaseRepository
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addTemporaryInvite(User $operator, int $uid, string $action, int $count, int|null $days, string|null $reason = '')
|
||||
{
|
||||
do_log("uid: $uid, action: $action, count: $count, days: $days, reason: $reason");
|
||||
$action = strtolower($action);
|
||||
if ($count <= 0 || ($action == 'increment' && $days <= 0)) {
|
||||
throw new \InvalidArgumentException("days or count lte 0");
|
||||
}
|
||||
$targetUser = User::query()->findOrFail($uid, User::$commonFields);
|
||||
$this->checkPermission($operator, $targetUser);
|
||||
$toolRep = new ToolRepository();
|
||||
$locale = $targetUser->locale;
|
||||
|
||||
$changeType = nexus_trans("nexus.$action", [], $locale);
|
||||
$subject = nexus_trans('message.temporary_invite_change.subject', ['change_type' => $changeType], $locale);
|
||||
$body = nexus_trans('message.temporary_invite_change.body', [
|
||||
'change_type' => $changeType,
|
||||
'count' => $count,
|
||||
'operator' => $operator->username,
|
||||
'reason' => $reason,
|
||||
], $locale);
|
||||
$message = [
|
||||
'sender' => 0,
|
||||
'receiver' => $targetUser->id,
|
||||
'subject' => $subject,
|
||||
'msg' => $body,
|
||||
'added' => Carbon::now(),
|
||||
];
|
||||
$inviteData = [];
|
||||
if ($action == 'increment') {
|
||||
$hashArr = $toolRep->generateUniqueInviteHash([], $count, $count);
|
||||
foreach ($hashArr as $hash) {
|
||||
$inviteData[] = [
|
||||
'inviter' => $uid,
|
||||
'invitee' => '',
|
||||
'hash' => $hash,
|
||||
'valid' => 0,
|
||||
'expired_at' => Carbon::now()->addDays($days),
|
||||
'created_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
NexusDB::transaction(function () use ($uid, $message, $inviteData, $count) {
|
||||
if (!empty($inviteData)) {
|
||||
Invite::query()->insert($inviteData);
|
||||
do_log("[INSERT TEMPORARY INVITE] to $uid, count: $count");
|
||||
} else {
|
||||
Invite::query()->where('inviter', $uid)
|
||||
->where('invitee', '')
|
||||
->orderBy('expired_at', 'asc')
|
||||
->limit($count)
|
||||
->delete()
|
||||
;
|
||||
do_log("[DELETE TEMPORARY INVITE] of $uid, count: $count");
|
||||
}
|
||||
Message::add($message);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getInviteBtnText(int $uid)
|
||||
{
|
||||
if (Setting::get('main.invitesystem') != 'yes') {
|
||||
throw new NexusException(nexus_trans('invite.send_deny_reasons.invite_system_closed'));
|
||||
}
|
||||
$permission = 'sendinvite';
|
||||
if (!user_can($permission, false, $uid)) {
|
||||
$requireClass = get_setting("authority.$permission");
|
||||
throw new NexusException(nexus_trans('invite.send_deny_reasons.no_permission', ['class' => User::getClassText($requireClass)]));
|
||||
}
|
||||
$userInfo = User::query()->findOrFail($uid, User::$commonFields);
|
||||
$temporaryInviteCount = $userInfo->temporary_invites()->count();
|
||||
if ($userInfo->invites + $temporaryInviteCount < 1) {
|
||||
throw new NexusException(nexus_trans('invite.send_deny_reasons.invite_not_enough'));
|
||||
}
|
||||
return nexus_trans('invite.send_allow_text');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user