diff --git a/web/app/Console/Kernel.php b/web/app/Console/Kernel.php index 7bd625f..3258384 100644 --- a/web/app/Console/Kernel.php +++ b/web/app/Console/Kernel.php @@ -17,7 +17,7 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->job(new UpdateUsageCounters)->everyMinute(); + $schedule->job(new UpdateUsageCounters)->everyMinute()->evenInMaintenanceMode(); } /** diff --git a/web/app/Http/Controllers/Api/CommentsController.php b/web/app/Http/Controllers/Api/CommentsController.php index b1cc2b7..eec55f6 100644 --- a/web/app/Http/Controllers/Api/CommentsController.php +++ b/web/app/Http/Controllers/Api/CommentsController.php @@ -46,7 +46,6 @@ class CommentsController extends Controller ]; foreach($comments as $comment) { - // TODO: XlXi: user profile link $poster = [ 'name' => $comment->user->username, 'thumbnail' => 'https://www.virtubrick.local/images/testing/headshot.png', diff --git a/web/app/Http/Controllers/Api/ShopController.php b/web/app/Http/Controllers/Api/ShopController.php index 5f02ccc..7619bf8 100644 --- a/web/app/Http/Controllers/Api/ShopController.php +++ b/web/app/Http/Controllers/Api/ShopController.php @@ -65,7 +65,7 @@ class ShopController extends Controller $assets = self::getAssets($valid['assetTypeId'], (isset($valid['gearGenreId']) ? $valid['gearGenreId'] : null)); $assets = $assets->orderByDesc('created_at') - ->paginate(30); + ->paginate(35); $data = []; foreach($assets as $asset) { diff --git a/web/app/Http/Controllers/Api/ThumbnailController.php b/web/app/Http/Controllers/Api/ThumbnailController.php index 92e3949..a251606 100644 --- a/web/app/Http/Controllers/Api/ThumbnailController.php +++ b/web/app/Http/Controllers/Api/ThumbnailController.php @@ -31,11 +31,10 @@ class ThumbnailController extends Controller private function userValidationRules() { - // TODO: Fail validation if user is moderated. return [ 'id' => [ 'required', - Rule::exists('App\Models\User', 'id') + Rule::exists('App\Models\User', 'id'), ], 'position' => ['sometimes', 'regex:/(Full|Bust)/i'], 'type' => 'regex:/(3D|2D)/i' @@ -55,7 +54,13 @@ class ThumbnailController extends Controller $valid['type'] = strtolower($valid['type']); if($renderType == 'User') { - if($valid['position'] == null) + if($model->hasActivePunishment() && $model->getPunishment()->isDeletion()) + { + $validator->errors()->add('id', 'User is moderated'); + return ValidationHelper::generateValidatorError($validator); + } + + if(!array_key_exists('position', $valid)) $valid['position'] = 'Full'; $valid['position'] = strtolower($valid['position']); @@ -109,9 +114,9 @@ class ThumbnailController extends Controller return $this->handleRender($request, 'Asset'); } - public function renderUser() + public function renderUser(Request $request) { - return handleRender($request, 'User'); + return $this->handleRender($request, 'User'); } public function tryAsset() diff --git a/web/app/Http/Controllers/Web/Auth/UserModerationController.php b/web/app/Http/Controllers/Web/Auth/UserModerationController.php deleted file mode 100644 index e37a7a6..0000000 --- a/web/app/Http/Controllers/Web/Auth/UserModerationController.php +++ /dev/null @@ -1,14 +0,0 @@ -with('punishment', Auth::user()->getPunishment()); + } + + public function reactivate(Request $request) + { + $punishment = Auth::user()->getPunishment(); + if(!$punishment || !$punishment->expired()) + return redirect()->back(); + + $punishment->active = false; + $punishment->save(); + + return redirect()->back(); + } +} diff --git a/web/app/Http/Controllers/Web/ProfileController.php b/web/app/Http/Controllers/Web/ProfileController.php index f33b60a..c649ea5 100644 --- a/web/app/Http/Controllers/Web/ProfileController.php +++ b/web/app/Http/Controllers/Web/ProfileController.php @@ -11,6 +11,9 @@ class ProfileController extends Controller { protected function index(Request $request, User $user) { + if($user->hasActivePunishment() && $user->getPunishment()->isDeletion()) + abort(404); + return view('web.user.profile')->with([ 'title' => sprintf('Profile of %s', $user->username), 'user' => $user diff --git a/web/app/Http/Kernel.php b/web/app/Http/Kernel.php index 94403a3..e07d5f9 100644 --- a/web/app/Http/Kernel.php +++ b/web/app/Http/Kernel.php @@ -25,7 +25,7 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, - \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class ]; /** @@ -43,14 +43,15 @@ class Kernel extends HttpKernel // XlXi: Yeah no, the double session protector was stupid. //\App\Http\Middleware\DoubleSessionProtector::class, // Prevents DDoS attacks. \App\Http\Middleware\DailyReward::class, - \App\Http\Middleware\LastSeenMiddleware::class + \App\Http\Middleware\LastSeenMiddleware::class, + \App\Http\Middleware\UserPunishmentMiddleware::class ], 'api' => [ - \App\Http\Middleware\PreventRequestsDuringMaintenance::class, - // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, + + \App\Http\Middleware\UserPunishmentMiddleware::class ], ]; @@ -74,7 +75,6 @@ class Kernel extends HttpKernel 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'roleset' => \App\Http\Middleware\Roleset::class, - 'banned' => \App\Http\Middleware\CheckBan::class, 'lastseen' => \App\Http\Middleware\LastSeenMiddleware::class, 'csrf' => \App\Http\Middleware\VerifyCsrfToken::class, ]; diff --git a/web/app/Http/Middleware/CheckBan.php b/web/app/Http/Middleware/CheckBan.php deleted file mode 100644 index df2ad1c..0000000 --- a/web/app/Http/Middleware/CheckBan.php +++ /dev/null @@ -1,31 +0,0 @@ -banId != null) { - if($request->route()->getName() != 'moderation.notice' && $request->route()->getName() != 'logout') { - return redirect() - ->to(route('moderation.notice', [], 302)); - } - } else { - return redirect('/', 302); - } - - return $next($request); - } -} diff --git a/web/app/Http/Middleware/UserPunishmentMiddleware.php b/web/app/Http/Middleware/UserPunishmentMiddleware.php new file mode 100644 index 0000000..2db0ade --- /dev/null +++ b/web/app/Http/Middleware/UserPunishmentMiddleware.php @@ -0,0 +1,52 @@ +route()->getName(), 'punishment.'); + if(Auth::user() && Auth::user()->hasActivePunishment()) + { + if($isPunishmentRoute || $request->route()->getName() == 'auth.logout') + return $next($request); + + if(in_array('api', $request->route()->middleware())) + { + if($request->route()->getName() == 'content') // cdn.virtubrick.net + return $next($request); + + return response(['errors' => [['code' => 0, 'message' => 'User is moderated']]], 403) + ->header('Cache-Control', 'private') + ->header('Content-Type', 'application/json; charset=utf-8'); + } + + // Not an API route. + if(!$isPunishmentRoute) + return redirect()->route('punishment.notice', ['ReturnUrl' => url()->full()]); + } + elseif($isPunishmentRoute) + { + $returnUrl = $request->input('ReturnUrl'); + + if(!$returnUrl) + $returnUrl = '/'; + + return redirect($returnUrl); + } + + return $next($request); + } +} diff --git a/web/app/Models/Punishment.php b/web/app/Models/Punishment.php new file mode 100644 index 0000000..9cc3281 --- /dev/null +++ b/web/app/Models/Punishment.php @@ -0,0 +1,84 @@ + + */ + protected $casts = [ + 'expiration' => 'datetime', + 'created_at' => 'datetime', + 'updated_at' => 'datetime' + ]; + + public function punishment_type() + { + return $this->belongsTo(PunishmentType::class, 'punishment_type_id'); + } + + public function user() + { + return $this->belongsTo(User::class, 'user_id'); + } + + public function moderator() + { + return $this->belongsTo(User::class, 'moderator_id'); + } + + public function pardoner() + { + return $this->belongsTo(User::class, 'pardoner_id'); + } + + public function context() + { + return $this->hasMany(PunishmentContext::class, 'punishment_id'); + } + + public function expired() + { + if($this->user->hasRoleset('Owner')) + return true; + + if(!$this->expiration) + return false; + + return !$this->isDeletion() && Carbon::now()->greaterThan($this->expiration); + } + + public function isDeletion() + { + return $this->punishment_type->time === null; + } + + public function reviewed() + { + return $this->created_at->isoFormat('lll'); + } + + public function expirationStr() + { + if(!$this->expiration) + return 'Never'; + + return $this->created_at->isoFormat('lll'); + } + + public static function activeFor($userId) + { + return self::where('user_id', $userId) + ->where('active', true) + ->orderByDesc('id'); + } +} diff --git a/web/app/Models/PunishmentContext.php b/web/app/Models/PunishmentContext.php new file mode 100644 index 0000000..128b10c --- /dev/null +++ b/web/app/Models/PunishmentContext.php @@ -0,0 +1,11 @@ +id)->exists(); + } + + public function getPunishment() + { + return Punishment::activeFor($this->id)->first(); + } + + public function punishments() + { + return $this->hasMany(Punishment::class, 'user_id'); + } + public function _hasRolesetInternal($roleName) { $roleset = Roleset::where('Name', $roleName)->first(); diff --git a/web/app/View/Components/Admin/ModerationStatus.php b/web/app/View/Components/Admin/ModerationStatus.php new file mode 100644 index 0000000..047eb7b --- /dev/null +++ b/web/app/View/Components/Admin/ModerationStatus.php @@ -0,0 +1,28 @@ +dateTime('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); - $table->unsignedBigInteger('banId')->nullable(); $table->string('biography')->nullable(); diff --git a/web/database/migrations/2023_01_07_011658_create_punishments_table.php b/web/database/migrations/2023_01_07_011658_create_punishments_table.php new file mode 100644 index 0000000..036a6bc --- /dev/null +++ b/web/database/migrations/2023_01_07_011658_create_punishments_table.php @@ -0,0 +1,40 @@ +id(); + + $table->unsignedTinyInteger('punishment_type_id'); + $table->boolean('active'); + $table->string('user_note'); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('moderator_id'); + $table->unsignedBigInteger('pardoner_id')->nullable(); + $table->timestamp('expiration')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('punishments'); + } +}; diff --git a/web/database/migrations/2023_01_07_014022_create_punishment_types_table.php b/web/database/migrations/2023_01_07_014022_create_punishment_types_table.php new file mode 100644 index 0000000..fe8a09d --- /dev/null +++ b/web/database/migrations/2023_01_07_014022_create_punishment_types_table.php @@ -0,0 +1,35 @@ +id(); + + $table->string('label'); + $table->integer('time')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('punishment_types'); + } +}; diff --git a/web/database/migrations/2023_01_07_014948_create_punishment_contexts_table.php b/web/database/migrations/2023_01_07_014948_create_punishment_contexts_table.php new file mode 100644 index 0000000..7d274e7 --- /dev/null +++ b/web/database/migrations/2023_01_07_014948_create_punishment_contexts_table.php @@ -0,0 +1,37 @@ +id(); + + $table->unsignedBigInteger('punishment_id'); + $table->string('user_note'); + $table->longText('description')->nullable(); + $table->string('content_hash')->nullable()->comment('Will display an image from the CDN.'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('punishment_contexts'); + } +}; diff --git a/web/database/seeders/DatabaseSeeder.php b/web/database/seeders/DatabaseSeeder.php index 92226c6..96d6c36 100644 --- a/web/database/seeders/DatabaseSeeder.php +++ b/web/database/seeders/DatabaseSeeder.php @@ -18,7 +18,8 @@ class DatabaseSeeder extends Seeder WebConfigurationSeeder::class, AssetTypeSeeder::class, UsageCounterSeeder::class, - RolesetSeeder::class + RolesetSeeder::class, + PunishmentTypeSeeder::class //FFlagSeeder::class ]); } diff --git a/web/database/seeders/PunishmentTypeSeeder.php b/web/database/seeders/PunishmentTypeSeeder.php new file mode 100644 index 0000000..66d1d86 --- /dev/null +++ b/web/database/seeders/PunishmentTypeSeeder.php @@ -0,0 +1,27 @@ + 'Reminder', 'time' => 0]); + PunishmentType::create(['label' => 'Warning', 'time' => 0]); + PunishmentType::create(['label' => 'Banned for 1 Day', 'time' => 1]); + PunishmentType::create(['label' => 'Banned for 3 Days', 'time' => 3]); + PunishmentType::create(['label' => 'Banned for 7 Days', 'time' => 7]); + PunishmentType::create(['label' => 'Banned for 14 Days', 'time' => 14]); + PunishmentType::create(['label' => 'Account Deleted']); + } +} diff --git a/web/resources/js/components/Shop.js b/web/resources/js/components/Shop.js index 1b3aca9..91bd98a 100644 --- a/web/resources/js/components/Shop.js +++ b/web/resources/js/components/Shop.js @@ -150,7 +150,9 @@ class ShopCategoryButton extends Component { } handleClick() { - this.props.navigateCategory(this.props.id, this.data); + this.props.setPage(1, true, () => { + this.props.navigateCategory(this.props.id, this.data); + }); } render() { @@ -177,7 +179,7 @@ class ShopCategories extends Component { return (
{{ $label }}
\ No newline at end of file diff --git a/web/resources/views/web/admin/useradmin.blade.php b/web/resources/views/web/admin/useradmin.blade.php index bf5d69c..4f6a77f 100644 --- a/web/resources/views/web/admin/useradmin.blade.php +++ b/web/resources/views/web/admin/useradmin.blade.php @@ -61,7 +61,7 @@ @endifOK (TODO)
Your account has been suspended for violating our Terms of Service.
-Suspention Date: 5/6/2022 9:35 PM
-Note: testing
-+ Your account has been {{ $punishment->isDeletion() ? 'closed ' : 'temporarily restricted' }} for violating our Terms of Service. + @if(!$punishment->isDeletion()) + Your account will be terminated if you do not abide by the rules. + @endif +
+ +Reviewed: {{ $punishment->reviewed() }}
+Moderator Note: {{ $punishment->user_note }}
+Reason: {{ $context->user_note }}
+ @if($context->description) +Offensive Item: {{ $context->description }}
+ @endif + @if($context->content_hash) +By checking the "I Agree" checkbox below, you agree to abide by {{ config('app.name') }}'s Terms of Service. Your account will be permantently suspended if you continue breaking the Terms of Service.
- + @endforeach + +By checking the "I Agree" checkbox below, you agree to abide by {{ config('app.name') }}'s Terms of Service.
+ + + @elseif(!$punishment->isDeletion()) +You will be able to reactivate your account in {{ $punishment->expiration->diffForHumans(['syntax' => Carbon\CarbonInterface::DIFF_ABSOLUTE]) }}.
+ @endif + + Logout -You will be able to reactivate your account in 0 Seconds.
If you believe you have been unfairly moderated, please contact us at contact us at support@virtubrick.net and we'll be happy to help.
- - +