diff --git a/.env.example b/.env.example index 94676473..c7016a83 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,8 @@ APP_ENV=production APP_KEY= APP_DEBUG=false APP_URL=http://localhost +#list with timezones https://www.php.net/manual/en/timezones.php +APP_TIMEZONE=UTC DB_CONNECTION=mysql DB_HOST=127.0.0.1 @@ -31,8 +33,8 @@ DISCORD_GUILD_ID= DISCORD_ROLE_ID= #nesseary URL's -PTERODACTYL_URL=https://panel.bitsec.dev -PHPMYADMIN_URL=https://mysql.bitsec.dev +PTERODACTYL_URL=https://panel.controlpanel.gg +PHPMYADMIN_URL=https://mysql.controlpanel.gg #optional. remove to remove database button DISCORD_INVITE_URL=https://discord.gg/vrUYdxG4wZ #GOOGLE RECAPTCHA diff --git a/app/Classes/Pterodactyl.php b/app/Classes/Pterodactyl.php index a48b569f..c1726bf8 100644 --- a/app/Classes/Pterodactyl.php +++ b/app/Classes/Pterodactyl.php @@ -11,6 +11,7 @@ use Exception; use Illuminate\Http\Client\PendingRequest; use Illuminate\Http\Client\Response; use Illuminate\Support\Facades\Http; +use Illuminate\Validation\Validator; class Pterodactyl { @@ -32,11 +33,11 @@ class Pterodactyl * @param int $pterodactylId * @return mixed */ - public function getUser(int $pterodactylId){ + public function getUser(int $pterodactylId) + { $response = self::client()->get("/application/users/{$pterodactylId}"); - if ($response->failed()) { - return []; - } + + if ($response->failed()) return $response->json(); return $response->json()['attributes']; } @@ -50,7 +51,7 @@ class Pterodactyl $response = self::getAllocations($node); $freeAllocations = []; - if(isset($response['data'])){ + if (isset($response['data'])) { if (!empty($response['data'])) { foreach ($response['data'] as $allocation) { if (!$allocation['attributes']['assigned']) array_push($freeAllocations, $allocation); @@ -125,7 +126,7 @@ class Pterodactyl */ public static function getAllocations(Node $node) { - $per_page = Configuration::getValueByKey('ALLOCATION_LIMIT' , 200); + $per_page = Configuration::getValueByKey('ALLOCATION_LIMIT', 200); $response = self::client()->get("/application/nodes/{$node->id}/allocations?per_page={$per_page}"); if ($response->failed()) throw self::getException(); return $response->json(); @@ -167,7 +168,7 @@ class Pterodactyl "feature_limits" => [ "databases" => $server->product->databases, "backups" => $server->product->backups, - "allocations" => 1 + "allocations" => $server->product->allocations, ], "allocation" => [ "default" => $allocationId diff --git a/app/Console/Commands/MakeUserCommand.php b/app/Console/Commands/MakeUserCommand.php index ef3b1372..a06f0d18 100644 --- a/app/Console/Commands/MakeUserCommand.php +++ b/app/Console/Commands/MakeUserCommand.php @@ -6,6 +6,8 @@ use App\Classes\Pterodactyl; use App\Models\User; use Illuminate\Console\Command; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\ValidationException; class MakeUserCommand extends Command { @@ -44,26 +46,37 @@ class MakeUserCommand extends Command public function handle() { $ptero_id = $this->option('ptero_id') ?? $this->ask('Please specify your Pterodactyl ID.'); - $password = $this->option('password') ?? $this->ask('Please specify your password.'); + $password = $this->secret('password') ?? $this->ask('Please specify your password.'); - if (strlen($password) < 8) { - $this->alert('Your password need to be at least 8 characters long'); + // Validate user input + $validator = Validator::make([ + 'ptero_id' => $ptero_id, + 'password' => $password, + ], [ + 'ptero_id' => 'required|numeric|integer|min:1|max:2147483647', + 'password' => 'required|string|min:8|max:60', + ]); + + if ($validator->fails()) { + $this->error($validator->errors()->first()); return 0; } //TODO: Do something with response (check for status code and give hints based upon that) $response = $this->pterodactyl->getUser($ptero_id); - if ($response === []) { - $this->alert('It seems that your Pterodactyl ID is not correct. Rerun the command and input an correct ID'); + if (isset($response['errors'])) { + if (isset($response['errors'][0]['code'])) $this->error("code: {$response['errors'][0]['code']}"); + if (isset($response['errors'][0]['status'])) $this->error("status: {$response['errors'][0]['status']}"); + if (isset($response['errors'][0]['detail'])) $this->error("detail: {$response['errors'][0]['detail']}"); return 0; } $user = User::create([ - 'name' => $response['first_name'], - 'email' => $response['email'], - 'role' => 'admin', - 'password' => Hash::make($password), + 'name' => $response['first_name'], + 'email' => $response['email'], + 'role' => 'admin', + 'password' => Hash::make($password), 'pterodactyl_id' => $response['id'] ]); diff --git a/app/Events/UserUpdateCreditsEvent.php b/app/Events/UserUpdateCreditsEvent.php new file mode 100644 index 00000000..7194bd8a --- /dev/null +++ b/app/Events/UserUpdateCreditsEvent.php @@ -0,0 +1,32 @@ +user = $user; + } +} diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 2f21c014..110d2c0d 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin; +use App\Events\UserUpdateCreditsEvent; use App\Http\Controllers\Controller; use App\Models\Configuration; use App\Models\Payment; @@ -167,6 +168,8 @@ class PaymentController extends Controller //payment notification $user->notify(new ConfirmPaymentNotification($payment)); + event(new UserUpdateCreditsEvent($user)); + //redirect back to home return redirect()->route('home')->with('success', 'Your credit balance has been increased!'); } diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php index 0a1d98b6..6d333feb 100644 --- a/app/Http/Controllers/Admin/ServerController.php +++ b/app/Http/Controllers/Admin/ServerController.php @@ -90,8 +90,12 @@ class ServerController extends Controller */ public function destroy(Server $server) { - $server->delete(); - return redirect()->back()->with('success', 'server has been removed!'); + try { + $server->delete(); + return redirect()->route('admin.servers.index')->with('success', 'server removed'); + } catch (Exception $e) { + return redirect()->route('admin.servers.index')->with('error', 'An exception has occurred while trying to remove a resource "' . $e->getMessage() . '"'); + } } /** diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 496c1bf3..795ad553 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -3,8 +3,11 @@ namespace App\Http\Controllers\Admin; use App\Classes\Pterodactyl; +use App\Events\UserUpdateCreditsEvent; use App\Http\Controllers\Controller; use App\Models\User; +use App\Notifications\DynamicNotification; +use Spatie\QueryBuilder\QueryBuilder; use Exception; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; @@ -12,8 +15,11 @@ use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Notification; +use Illuminate\Support\HtmlString; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -50,6 +56,30 @@ class UserController extends Controller ]); } + /** + * Get a JSON response of users. + * + * @return \Illuminate\Support\Collection|\App\models\User + */ + public function json(Request $request) + { + $users = QueryBuilder::for(User::query()) + ->allowedFilters(['id', 'name', 'pterodactyl_id', 'email']) + ->paginate(25); + + if ($request->query('user_id')) { + $user = User::query()->findOrFail($request->input('user_id')); + $user->avatarUrl = $user->getAvatar(); + + return $user; + } + + return $users->map(function ($item) { + $item->avatarUrl = $item->getAvatar(); + + return $item; + }); + } /** * Show the form for editing the specified resource. * @@ -83,13 +113,12 @@ class UserController extends Controller "role" => Rule::in(['admin', 'mod', 'client', 'member']), ]); - if (empty($this->pterodactyl->getUser($request->input('pterodactyl_id')))) { + if (isset($this->pterodactyl->getUser($request->input('pterodactyl_id'))['errors'])) { throw ValidationException::withMessages([ 'pterodactyl_id' => ["User does not exists on pterodactyl's panel"] ]); } - if (!is_null($request->input('new_password'))) { $request->validate([ 'new_password' => 'required|string|min:8', @@ -102,9 +131,9 @@ class UserController extends Controller } $user->update($request->all()); + event(new UserUpdateCreditsEvent($user)); return redirect()->route('admin.users.index')->with('success', 'User updated!'); - } /** @@ -142,6 +171,56 @@ class UserController extends Controller return redirect()->route('admin.users.index'); } + /** + * Show the form for seding notifications to the specified resource. + * + * @param User $user + * @return Application|Factory|View|Response + */ + public function notifications(User $user) + { + return view('admin.users.notifications'); + } + + /** + * Notify the specified resource. + * + * @param Request $request + * @param User $user + * @return RedirectResponse + * @throws Exception + */ + public function notify(Request $request) + { + $data = $request->validate([ + "via" => "required|min:1|array", + "via.*" => "required|string|in:mail,database", + "all" => "required_without:users|boolean", + "users" => "required_without:all|min:1|array", + "users.*" => "exists:users,id", + "title" => "required|string|min:1", + "content" => "required|string|min:1" + ]); + + $mail = null; + $database = null; + if (in_array('database', $data["via"])) { + $database = [ + "title" => $data["title"], + "content" => $data["content"] + ]; + } + if (in_array('mail', $data["via"])) { + $mail = (new MailMessage) + ->subject($data["title"]) + ->line(new HtmlString($data["content"])); + } + $all = $data["all"] ?? false; + $users = $all ? User::all() : User::whereIn("id", $data["users"])->get(); + Notification::send($users, new DynamicNotification($data["via"], $database, $mail)); + return redirect()->route('admin.users.notifications')->with('success', 'Notification sent!'); + } + /** * * @throws Exception @@ -186,16 +265,16 @@ class UserController extends Controller }) ->editColumn('role', function (User $user) { switch ($user->role) { - case 'admin' : + case 'admin': $badgeColor = 'badge-danger'; break; - case 'mod' : + case 'mod': $badgeColor = 'badge-info'; break; - case 'client' : + case 'client': $badgeColor = 'badge-success'; break; - default : + default: $badgeColor = 'badge-secondary'; break; } diff --git a/app/Http/Controllers/Admin/VoucherController.php b/app/Http/Controllers/Admin/VoucherController.php index e1a71580..1aa2c59e 100644 --- a/app/Http/Controllers/Admin/VoucherController.php +++ b/app/Http/Controllers/Admin/VoucherController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin; +use App\Events\UserUpdateCreditsEvent; use App\Http\Controllers\Controller; use App\Models\Voucher; use Illuminate\Contracts\Foundation\Application; @@ -45,10 +46,10 @@ class VoucherController extends Controller { $request->validate([ 'memo' => 'nullable|string|max:191', - 'code' => 'required|string|alpha_dash|max:36|min:4', + 'code' => 'required|string|alpha_dash|max:36|min:4|unique:vouchers', 'uses' => 'required|numeric|max:2147483647|min:1', 'credits' => 'required|numeric|between:0,99999999', - 'expires_at' => ['nullable','date_format:d-m-Y','after:today',"before:10 years"], + 'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years', ]); Voucher::create($request->except('_token')); @@ -75,7 +76,7 @@ class VoucherController extends Controller */ public function edit(Voucher $voucher) { - return view('admin.vouchers.edit' , [ + return view('admin.vouchers.edit', [ 'voucher' => $voucher ]); } @@ -91,10 +92,10 @@ class VoucherController extends Controller { $request->validate([ 'memo' => 'nullable|string|max:191', - 'code' => 'required|string|alpha_dash|max:36|min:4', + 'code' => "required|string|alpha_dash|max:36|min:4|unique:vouchers,code,{$voucher->id}", 'uses' => 'required|numeric|max:2147483647|min:1', 'credits' => 'required|numeric|between:0,99999999', - 'expires_at' => ['nullable','date_format:d-m-Y','after:today',"before:10 years"], + 'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years', ]); $voucher->update($request->except('_token')); @@ -127,7 +128,7 @@ class VoucherController extends Controller ]); #get voucher by code - $voucher = Voucher::where('code' , '=' , $request->input('code'))->firstOrFail(); + $voucher = Voucher::where('code', '=', $request->input('code'))->firstOrFail(); #extra validations if ($voucher->getStatus() == 'USES_LIMIT_REACHED') throw ValidationException::withMessages([ @@ -138,19 +139,21 @@ class VoucherController extends Controller 'code' => 'This voucher has expired' ]); - if (!$request->user()->vouchers()->where('id' , '=' , $voucher->id)->get()->isEmpty()) throw ValidationException::withMessages([ + if (!$request->user()->vouchers()->where('id', '=', $voucher->id)->get()->isEmpty()) throw ValidationException::withMessages([ 'code' => 'You already redeemed this voucher code' ]); if ($request->user()->credits + $voucher->credits >= 99999999) throw ValidationException::withMessages([ - 'code' => "You can't redeem this voucher because you would exceed the credit limit" + 'code' => "You can't redeem this voucher because you would exceed the ".CREDITS_DISPLAY_NAME." limit" ]); #redeem voucher $voucher->redeem($request->user()); + event(new UserUpdateCreditsEvent($request->user())); + return response()->json([ - 'success' => "{$voucher->credits} credits have been added to your balance!" + 'success' => "{$voucher->credits} ".CREDITS_DISPLAY_NAME." have been added to your balance!" ]); } @@ -191,5 +194,4 @@ class VoucherController extends Controller ->rawColumns(['actions', 'code', 'status']) ->make(); } - } diff --git a/app/Http/Controllers/Api/NotificationController.php b/app/Http/Controllers/Api/NotificationController.php new file mode 100644 index 00000000..ceecbb24 --- /dev/null +++ b/app/Http/Controllers/Api/NotificationController.php @@ -0,0 +1,126 @@ +user : User::findOrFail($userId); + + return $user->notifications()->paginate($request->query("per_page", 50)); + } + + /** + * Display a specific notification + * + * @param int $userId + * @param int $notificationId + * @return JsonResponse + */ + public function view(int $userId, $notificationId) + { + $discordUser = DiscordUser::find($userId); + $user = $discordUser ? $discordUser->user : User::findOrFail($userId); + + $notification = $user->notifications()->where("id", $notificationId)->get()->first(); + + if (!$notification) { + return response()->json(["message" => "Notification not found."], 404); + } + + return $notification; + } + + /** + * Send a notification to an user. + * + * @param Request $request + * @return JsonResponse + */ + public function send(Request $request) + { + $data = $request->validate([ + "via" => ["required", new Delimited("in:mail,database")], + "all" => "required_without:users|boolean", + "users" => ["required_without:all", new Delimited("exists:users,id")], + "title" => "required|string|min:1", + "content" => "required|string|min:1" + ]); + $via = explode(",", $data["via"]); + $mail = null; + $database = null; + if (in_array("database", $via)) { + $database = [ + "title" => $data["title"], + "content" => $data["content"] + ]; + } + if (in_array("mail", $via)) { + $mail = (new MailMessage) + ->subject($data["title"]) + ->line(new HtmlString($data["content"])); + } + $all = $data["all"] ?? false; + $users = $all ? User::all() : User::whereIn("id", explode(",", $data["users"]))->get(); + Notification::send($users, new DynamicNotification($via, $database, $mail)); + return response()->json(["message" => "Notification successfully sent."]); + } + + /** + * Delete all notifications from an user + * + * @param int $userId + * @return JsonResponse + */ + public function delete(int $userId) + { + $discordUser = DiscordUser::find($userId); + $user = $discordUser ? $discordUser->user : User::findOrFail($userId); + + $count = $user->notifications()->delete(); + + return response()->json(["message" => "All notifications have been successfully deleted.", "count" => $count]); + } + + + /** + * Delete a specific notification + * + * @param int $userId + * @param int $notificationId + * @return JsonResponse + */ + public function deleteOne(int $userId, $notificationid) + { + $discordUser = DiscordUser::find($userId); + $user = $discordUser ? $discordUser->user : User::findOrFail($userId); + + $notification = $user->notifications()->where("id", $notificationid)->get()->first(); + + if (!$notification) { + return response()->json(["message" => "Notification not found."], 404); + } + + $notification->delete(); + return response()->json($notification); + } +} diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php index 7c905328..a5f7263c 100644 --- a/app/Http/Controllers/Api/UserController.php +++ b/app/Http/Controllers/Api/UserController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api; +use App\Events\UserUpdateCreditsEvent; use App\Http\Controllers\Controller; use App\Models\DiscordUser; use App\Models\User; @@ -10,6 +11,7 @@ use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Validation\Rule; +use Illuminate\Validation\ValidationException; class UserController extends Controller { @@ -60,6 +62,79 @@ class UserController extends Controller $user->update($request->all()); + event(new UserUpdateCreditsEvent($user)); + + return $user; + } + + /** + * increments the users credits or/and server_limit + * + * @param Request $request + * @param int $id + * @return User + * @throws ValidationException + */ + public function increment(Request $request, int $id) + { + $discordUser = DiscordUser::find($id); + $user = $discordUser ? $discordUser->user : User::findOrFail($id); + + $request->validate([ + "credits" => "sometimes|numeric|min:0|max:1000000", + "server_limit" => "sometimes|numeric|min:0|max:1000000", + ]); + + if($request->credits){ + if ($user->credits + $request->credits >= 99999999) throw ValidationException::withMessages([ + 'credits' => "You can't add this amount of credits because you would exceed the credit limit" + ]); + event(new UserUpdateCreditsEvent($user)); + $user->increment('credits', $request->credits); + } + + if($request->server_limit){ + if ($user->server_limit + $request->server_limit >= 2147483647) throw ValidationException::withMessages([ + 'server_limit' => "You cannot add this amount of servers because it would exceed the server limit." + ]); + $user->increment('server_limit', $request->server_limit); + } + + return $user; + } + + /** + * decrements the users credits or/and server_limit + * + * @param Request $request + * @param int $id + * @return User + * @throws ValidationException + */ + public function decrement(Request $request, int $id) + { + $discordUser = DiscordUser::find($id); + $user = $discordUser ? $discordUser->user : User::findOrFail($id); + + $request->validate([ + "credits" => "sometimes|numeric|min:0|max:1000000", + "server_limit" => "sometimes|numeric|min:0|max:1000000", + ]); + + if($request->credits){ + if($user->credits - $request->credits < 0) throw ValidationException::withMessages([ + 'credits' => "You can't remove this amount of credits because you would exceed the minimum credit limit" + ]); + $user->decrement('credits', $request->credits); + } + + if($request->server_limit){ + if($user->server_limit - $request->server_limit < 0) throw ValidationException::withMessages([ + 'server_limit' => "You cannot remove this amount of servers because it would exceed the minimum server." + ]); + $user->decrement('server_limit', $request->server_limit); + } + return $user; } @@ -67,7 +142,7 @@ class UserController extends Controller * Remove the specified resource from storage. * * @param int $id - * @return Application|ResponseFactory|Response|void + * @return Application|Response|ResponseFactory */ public function destroy(int $id) { diff --git a/app/Http/Controllers/Api/VoucherController.php b/app/Http/Controllers/Api/VoucherController.php index 9b70277f..f7acfdd5 100644 --- a/app/Http/Controllers/Api/VoucherController.php +++ b/app/Http/Controllers/Api/VoucherController.php @@ -41,10 +41,10 @@ class VoucherController extends Controller { $request->validate([ 'memo' => 'nullable|string|max:191', - 'code' => 'required|string|alpha_dash|max:36|min:4', + 'code' => 'required|string|alpha_dash|max:36|min:4|unique:vouchers', 'uses' => 'required|numeric|max:2147483647|min:1', 'credits' => 'required|numeric|between:0,99999999', - 'expires_at' => 'nullable|date_format:d-m-Y|after:today|before:10 years' + 'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years' ]); return Voucher::create($request->all()); @@ -85,10 +85,10 @@ class VoucherController extends Controller $request->validate([ 'memo' => 'nullable|string|max:191', - 'code' => 'required|string|alpha_dash|max:36|min:4', + 'code' => "required|string|alpha_dash|max:36|min:4|unique:vouchers,code,{$voucher->id}", 'uses' => 'required|numeric|max:2147483647|min:1', 'credits' => 'required|numeric|between:0,99999999', - 'expires_at' => 'nullable|date_format:d-m-Y|after:today|before:10 years' + 'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years' ]); $voucher->update($request->all()); @@ -116,7 +116,8 @@ class VoucherController extends Controller * @param Voucher $voucher * @return LengthAwarePaginator */ - public function users(Request $request, Voucher $voucher){ + public function users(Request $request, Voucher $voucher) + { $request->validate([ 'include' => [ 'nullable', @@ -125,7 +126,7 @@ class VoucherController extends Controller ] ]); - if($request->input('include') == 'discorduser'){ + if ($request->input('include') == 'discorduser') { return $voucher->users()->with('discordUser')->paginate($request->query('per_page') ?? 50); } diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index cf5db936..1ec74e14 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -88,7 +88,7 @@ class ServerController extends Controller //minimum credits if (Auth::user()->credits <= Configuration::getValueByKey('MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50)) { - return redirect()->route('servers.index')->with('error', "You do not have the required amount of credits to create a new server!"); + return redirect()->route('servers.index')->with('error', "You do not have the required amount of ".CREDITS_DISPLAY_NAME." to create a new server!"); } //Required Verification for creating an server @@ -111,7 +111,7 @@ class ServerController extends Controller $server->delete(); return redirect()->route('servers.index')->with('success', 'server removed'); } catch (Exception $e) { - return redirect()->route('servers.index')->with('error', 'An exception has occurred while trying to remove a resource'); + return redirect()->route('servers.index')->with('error', 'An exception has occurred while trying to remove a resource "' . $e->getMessage() . '"'); } } diff --git a/app/Http/Controllers/StoreController.php b/app/Http/Controllers/StoreController.php index 2aa0ed23..1b23d305 100644 --- a/app/Http/Controllers/StoreController.php +++ b/app/Http/Controllers/StoreController.php @@ -23,7 +23,7 @@ class StoreController extends Controller //Required Verification for creating an server if (Configuration::getValueByKey('FORCE_DISCORD_VERIFICATION', false) === 'true' && !Auth::user()->discordUser) { - return redirect()->route('profile.index')->with('error', "You are required to link your discord account before you can purchase credits."); + return redirect()->route('profile.index')->with('error', "You are required to link your discord account before you can purchase ".CREDITS_DISPLAY_NAME."."); } return view('store.index')->with([ diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 5e7b9418..572a8656 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -3,6 +3,7 @@ namespace App\Http; use App\Http\Middleware\ApiAuthToken; +use App\Http\Middleware\CreditsDisplayName; use App\Http\Middleware\isAdmin; use App\Http\Middleware\LastSeen; use Illuminate\Foundation\Http\Kernel as HttpKernel; @@ -40,12 +41,14 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, - LastSeen::class + LastSeen::class, + CreditsDisplayName::class ], 'api' => [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, + CreditsDisplayName::class ], ]; diff --git a/app/Http/Middleware/CreditsDisplayName.php b/app/Http/Middleware/CreditsDisplayName.php new file mode 100644 index 00000000..a6962f41 --- /dev/null +++ b/app/Http/Middleware/CreditsDisplayName.php @@ -0,0 +1,23 @@ +role == 'admin') { + if (Auth::user() && Auth::user()->role == 'admin') { return $next($request); } diff --git a/app/Listeners/UnsuspendServers.php b/app/Listeners/UnsuspendServers.php new file mode 100644 index 00000000..51f1ad60 --- /dev/null +++ b/app/Listeners/UnsuspendServers.php @@ -0,0 +1,30 @@ +user->credits > Configuration::getValueByKey('MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER' , 50)){ + /** @var Server $server */ + foreach ($event->user->servers as $server){ + if ($server->isSuspended()) $server->unSuspend(); + } + } + } +} diff --git a/app/Models/Server.php b/app/Models/Server.php index e48dc8a0..4d704cbe 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -70,7 +70,8 @@ class Server extends Model }); static::deleting(function (Server $server) { - Pterodactyl::client()->delete("/application/servers/{$server->pterodactyl_id}"); + $response = Pterodactyl::client()->delete("/application/servers/{$server->pterodactyl_id}"); + if ($response->failed() && !is_null($server->pterodactyl_id)) throw new Exception($response['errors'][0]['code']); }); } diff --git a/app/Models/User.php b/app/Models/User.php index a4f68ec4..6dc4efc8 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Classes\Pterodactyl; +use App\Events\UserUpdateCreditsEvent; use App\Notifications\Auth\QueuedVerifyEmail; use App\Notifications\WelcomeMessage; use Illuminate\Contracts\Auth\MustVerifyEmail; diff --git a/app/Notifications/DynamicNotification.php b/app/Notifications/DynamicNotification.php new file mode 100644 index 00000000..6e24c329 --- /dev/null +++ b/app/Notifications/DynamicNotification.php @@ -0,0 +1,63 @@ +via = $via; + $this->database = $database; + $this->mail = $mail; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via() + { + return $this->via; + } + + public function toMail() + { + return $this->mail; + } + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray() + { + return $this->database; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c220bc7f..8aa705e0 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,7 +4,9 @@ namespace App\Providers; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\Validator; use Illuminate\Support\ServiceProvider; +use Spatie\QueryBuilder\QueryBuilderRequest; class AppServiceProvider extends ServiceProvider { @@ -27,5 +29,28 @@ class AppServiceProvider extends ServiceProvider { Paginator::useBootstrap(); Schema::defaultStringLength(191); + QueryBuilderRequest::setArrayValueDelimiter('|'); + + Validator::extend('multiple_date_format', function ($attribute, $value, $parameters, $validator) { + + $ok = true; + + $result = []; + + // iterate through all formats + foreach ($parameters as $parameter) { + + //validate with laravels standard date format validation + $result[] = $validator->validateDateFormat($attribute, $value, [$parameter]); + } + + //if none of result array is true. it sets ok to false + if (!in_array(true, $result)) { + $ok = false; + $validator->setCustomMessages(['multiple_date_format' => 'The format must be one of ' . join(",", $parameters)]); + } + + return $ok; + }); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index ae9c40bc..d42ccae5 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,6 +2,8 @@ namespace App\Providers; +use App\Events\UserUpdateCreditsEvent; +use App\Listeners\UnsuspendServers; use App\Listeners\Verified; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; @@ -20,6 +22,9 @@ class EventServiceProvider extends ServiceProvider Registered::class => [ SendEmailVerificationNotification::class, ], + UserUpdateCreditsEvent::class => [ + UnsuspendServers::class + ], SocialiteWasCalled::class => [ // ... other providers 'SocialiteProviders\\Discord\\DiscordExtendSocialite@handle', diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore old mode 100644 new mode 100755 diff --git a/composer.json b/composer.json index cc28d042..3edec1b4 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "license": "MIT", "require": { "php": "^8.0|^7.4", + "ext-intl": "*", "biscolab/laravel-recaptcha": "^5.0", "doctrine/dbal": "^3.1", "fideloper/proxy": "^4.4", @@ -22,8 +23,9 @@ "paypal/rest-api-sdk-php": "^1.14", "socialiteproviders/discord": "^4.1", "spatie/laravel-activitylog": "^3.16", - "yajra/laravel-datatables-oracle": "~9.0", - "ext-intl": "*" + "spatie/laravel-query-builder": "^3.5", + "spatie/laravel-validation-rules": "^3.0", + "yajra/laravel-datatables-oracle": "~9.0" }, "require-dev": { "facade/ignition": "^2.5", diff --git a/composer.lock b/composer.lock index 48624e80..14abe8de 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dda32482531d11fdf6adc74fcba74715", + "content-hash": "b3b61a46d5d4d6560d052cfda863d12c", "packages": [ { "name": "asm89/stack-cors", @@ -3460,6 +3460,147 @@ ], "time": "2021-03-02T16:49:06+00:00" }, + { + "name": "spatie/laravel-query-builder", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-query-builder.git", + "reference": "4e5257be24139836dc092f618d7c73bcb1c00302" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/4e5257be24139836dc092f618d7c73bcb1c00302", + "reference": "4e5257be24139836dc092f618d7c73bcb1c00302", + "shasum": "" + }, + "require": { + "illuminate/database": "^6.20.13|^7.30.4|^8.22.2", + "illuminate/http": "^6.20.13|7.30.4|^8.22.2", + "illuminate/support": "^6.20.13|7.30.4|^8.22.2", + "php": "^7.3|^8.0" + }, + "require-dev": { + "ext-json": "*", + "laravel/legacy-factories": "^1.0.4", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^4.9|^5.8|^6.3", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\QueryBuilder\\QueryBuilderServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\QueryBuilder\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily build Eloquent queries from API requests", + "homepage": "https://github.com/spatie/laravel-query-builder", + "keywords": [ + "laravel-query-builder", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-query-builder/issues", + "source": "https://github.com/spatie/laravel-query-builder" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2021-07-05T14:17:44+00:00" + }, + { + "name": "spatie/laravel-validation-rules", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-validation-rules.git", + "reference": "43e15a70fb6148b0128d7981b0c0f3e99463b9fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-validation-rules/zipball/43e15a70fb6148b0128d7981b0c0f3e99463b9fb", + "reference": "43e15a70fb6148b0128d7981b0c0f3e99463b9fb", + "shasum": "" + }, + "require": { + "illuminate/support": "^6.0|^7.0|^8.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "laravel/legacy-factories": "^1.0.4", + "myclabs/php-enum": "^1.6", + "orchestra/testbench": "^4.5|^5.0|^6.0", + "phpunit/phpunit": "^9.3", + "spatie/enum": "^2.2|^3.0" + }, + "suggest": { + "league/iso3166": "Needed for the CountryCode rule" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\ValidationRules\\ValidationRulesServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\ValidationRules\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A set of useful Laravel validation rules", + "homepage": "https://github.com/spatie/laravel-validation-rules", + "keywords": [ + "laravel-validation-rules", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-validation-rules/issues", + "source": "https://github.com/spatie/laravel-validation-rules/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2020-11-30T15:23:31+00:00" + }, { "name": "swiftmailer/swiftmailer", "version": "v6.2.7", diff --git a/config/app.php b/config/app.php index d54c7e1d..592e97d2 100644 --- a/config/app.php +++ b/config/app.php @@ -2,6 +2,7 @@ return [ + 'version' => '0.5', /* |-------------------------------------------------------------------------- | Application Name @@ -67,7 +68,7 @@ return [ | */ - 'timezone' => 'UTC', + 'timezone' => env('APP_TIMEZONE', 'UTC'), /* |-------------------------------------------------------------------------- diff --git a/config/trustedproxy.php b/config/trustedproxy.php new file mode 100755 index 00000000..8cfd785e --- /dev/null +++ b/config/trustedproxy.php @@ -0,0 +1,54 @@ +getClientIp() + * always gets the originating client IP, no matter + * how many proxies that client's request has + * subsequently passed through. + */ + 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), + + /* + * Or, to trust all proxies that connect + * directly to your server, uncomment this: + */ + // 'proxies' => '*', + + /* + * Or, to trust ALL proxies, including those that + * are in a chain of forwarding, uncomment this: + */ + // 'proxies' => '**', + + /* + * Default Header Names + * + * Change these if the proxy does + * not send the default header names. + * + * Note that headers such as X-Forwarded-For + * are transformed to HTTP_X_FORWARDED_FOR format. + * + * The following are Symfony defaults, found in + * \Symfony\Component\HttpFoundation\Request::$trustedHeaders + */ + 'headers' => \Illuminate\Http\Request::HEADER_X_FORWARDED_ALL, +]; \ No newline at end of file diff --git a/database/seeders/Seeds/ConfigurationSeeder.php b/database/seeders/Seeds/ConfigurationSeeder.php index 7dc5e46b..f84bdd26 100644 --- a/database/seeders/Seeds/ConfigurationSeeder.php +++ b/database/seeders/Seeds/ConfigurationSeeder.php @@ -119,6 +119,15 @@ class ConfigurationSeeder extends Seeder 'description' => 'The maximum amount of allocations to pull per node for automatic deployment, if more allocations are being used than this limit is set to, no new servers can be created!' ]); + //credits display name + Configuration::firstOrCreate([ + 'key' => 'CREDITS_DISPLAY_NAME', + ], [ + 'value' => 'Credits', + 'type' => 'string', + 'description' => 'Set the display name of your currency :)' + ]); + } } diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index 2a29bbe8..96935a55 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -53,7 +53,7 @@
- +
- +
- +
Users
+ Notify
@@ -43,7 +45,7 @@ Name Role Email - Credits + {{CREDITS_DISPLAY_NAME}} Usage Servers Verified diff --git a/resources/views/admin/users/notifications.blade.php b/resources/views/admin/users/notifications.blade.php new file mode 100644 index 00000000..3a12e8fb --- /dev/null +++ b/resources/views/admin/users/notifications.blade.php @@ -0,0 +1,188 @@ +@extends('layouts.main') +@section('content') + +
+
+
+
+

Users

+
+ +
+
+
+ + + +
+
+ +
+
+
+
+
+ @csrf + @method('POST') + +
+
+ + +
+ +
+ @error('all') +
+ {{$message}} +
+ @enderror + @error('users') +
+ {{$message}} +
+ @enderror +
+ +
+
+ + +
+ + + @error('via') +
+ {{$message}} +
+ @enderror +
+ +
+ + + @error('title') +
+ {{$message}} +
+ @enderror +
+
+ + + @error('content') +
+ {{$message}} +
+ @enderror +
+
+ +
+
+
+
+
+
+
+ + +
+ + + + +@endsection diff --git a/resources/views/admin/users/show.blade.php b/resources/views/admin/users/show.blade.php index 1e394359..f18dbd65 100644 --- a/resources/views/admin/users/show.blade.php +++ b/resources/views/admin/users/show.blade.php @@ -148,7 +148,7 @@
- +
diff --git a/resources/views/admin/vouchers/create.blade.php b/resources/views/admin/vouchers/create.blade.php index 1aca487d..6876f401 100644 --- a/resources/views/admin/vouchers/create.blade.php +++ b/resources/views/admin/vouchers/create.blade.php @@ -1,181 +1,170 @@ @extends('layouts.main') @section('content') - -
-
-
-
-

Vouchers

-
-
- -
+ +
+
+
+
+

Vouchers

+
+
+
-
- +
+
+ - -
-
+ +
+
-
-
-
-
-
- Voucher details -
-
-
-
- @csrf +
+
+
+
+
+ Voucher details +
+
+
+ + @csrf -
- - - @error('memo') -
- {{$message}} -
- @enderror +
+ + + @error('memo') +
+ {{$message}}
+ @enderror +
-
- - - @error('credits') -
- {{$message}} -
- @enderror +
+ + + @error('credits') +
+ {{$message}}
+ @enderror +
-
- -
- -
- -
+
+ +
+ +
+
- @error('code') -
- {{$message}} -
- @enderror
- -
- -
- -
- -
-
- @error('uses') -
- {{$message}} -
- @enderror + @error('code') +
+ {{$message}}
+ @enderror +
-
- -
- -
-
-
+
+ +
+ +
+
- @error('expires_at') -
- {{$message}} -
- @enderror
+ @error('uses') +
+ {{$message}} +
+ @enderror +
-
- +
+ +
+ +
+
+
- -
+ @error('expires_at') +
+ {{$message}} +
+ @enderror +
+ +
+ +
+
- - -
-
- + + + +
+
+ - + return result; + } + @endsection diff --git a/resources/views/admin/vouchers/edit.blade.php b/resources/views/admin/vouchers/edit.blade.php index 467a30de..7447f237 100644 --- a/resources/views/admin/vouchers/edit.blade.php +++ b/resources/views/admin/vouchers/edit.blade.php @@ -1,186 +1,170 @@ @extends('layouts.main') @section('content') - -
-
-
-
-

Vouchers

-
-
- -
+ +
+
+
+
+

Vouchers

+
+
+
-
- +
+
+ - -
-
+ +
+
-
-
-
-
-
- Voucher details -
-
-
-
- @csrf - @method('PATCH') +
+
+
+
+
+ Voucher details +
+
+
+ + @csrf + @method('PATCH') -
- - - @error('memo') -
- {{$message}} -
- @enderror +
+ + + @error('memo') +
+ {{$message}}
+ @enderror +
-
- - - @error('credits') -
- {{$message}} -
- @enderror +
+ + + @error('credits') +
+ {{$message}}
+ @enderror +
-
- -
- -
- -
+
+ +
+ +
+
- @error('code') -
- {{$message}} -
- @enderror
- -
- -
- -
- -
-
- @error('uses') -
- {{$message}} -
- @enderror + @error('code') +
+ {{$message}}
+ @enderror +
-
- -
- -
-
-
+
+ +
+ +
+
- @error('expires_at') -
- {{$message}} +
+ @error('uses') +
+ {{$message}} +
+ @enderror +
+ +
+ +
+ +
+
- @enderror
+ @error('expires_at') +
+ {{$message}} +
+ @enderror +
-
- -
- -
+
+ +
+
-
-
- + +
+
+ - + return result; + } + @endsection diff --git a/resources/views/admin/vouchers/index.blade.php b/resources/views/admin/vouchers/index.blade.php index bfe4cef6..3b8b604a 100644 --- a/resources/views/admin/vouchers/index.blade.php +++ b/resources/views/admin/vouchers/index.blade.php @@ -42,7 +42,7 @@ Status Code Memo - Credits + {{CREDITS_DISPLAY_NAME}} Used / Uses Expires diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 8f47bbf2..5c13ea05 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -41,7 +41,7 @@
- Credits + {{CREDITS_DISPLAY_NAME}} {{Auth::user()->Credits()}}
@@ -58,7 +58,7 @@
- Credit usage + {{CREDITS_DISPLAY_NAME}} usage {{number_format($useage, 2, '.', '')}} per month
diff --git a/resources/views/layouts/main.blade.php b/resources/views/layouts/main.blade.php index c9ff4bfd..1f01b180 100644 --- a/resources/views/layouts/main.blade.php +++ b/resources/views/layouts/main.blade.php @@ -19,6 +19,9 @@ {{-- datetimepicker --}} + {{-- select2 --}} + + @@ -316,7 +319,7 @@ @@ -344,6 +347,9 @@ + +