diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index e49923ba..496c1bf3 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -78,7 +78,7 @@ class UserController extends Controller "name" => "required|string|min:4|max:30", "pterodactyl_id" => "required|numeric|unique:users,pterodactyl_id,{$user->id}", "email" => "required|string|email", - "credits" => "required|numeric|min:0|max:999999", + "credits" => "required|numeric|min:0|max:99999999", "server_limit" => "required|numeric|min:0|max:1000000", "role" => Rule::in(['admin', 'mod', 'client', 'member']), ]); diff --git a/app/Http/Controllers/Admin/VoucherController.php b/app/Http/Controllers/Admin/VoucherController.php new file mode 100644 index 00000000..8aaab14f --- /dev/null +++ b/app/Http/Controllers/Admin/VoucherController.php @@ -0,0 +1,196 @@ +validate([ + 'memo' => 'nullable|string|max:191', + 'code' => 'required|string|alpha_dash|max:36|min:4', + '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"], + ]); + + Voucher::create($request->except('_token')); + + return redirect()->route('admin.vouchers.index')->with('success', 'voucher has been created!'); + } + + /** + * Display the specified resource. + * + * @param Voucher $voucher + * @return Response + */ + public function show(Voucher $voucher) + { + // + } + + /** + * Show the form for editing the specified resource. + * + * @param Voucher $voucher + * @return Application|Factory|View + */ + public function edit(Voucher $voucher) + { + return view('admin.vouchers.edit' , [ + 'voucher' => $voucher + ]); + } + + /** + * Update the specified resource in storage. + * + * @param Request $request + * @param Voucher $voucher + * @return RedirectResponse + */ + public function update(Request $request, Voucher $voucher) + { + $request->validate([ + 'memo' => 'nullable|string|max:191', + 'code' => 'required|string|alpha_dash|max:36|min:4', + '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"], + ]); + + $voucher->update($request->except('_token')); + + return redirect()->route('admin.vouchers.index')->with('success', 'voucher has been updated!'); + } + + /** + * Remove the specified resource from storage. + * + * @param Voucher $voucher + * @return RedirectResponse + */ + public function destroy(Voucher $voucher) + { + $voucher->delete(); + return redirect()->back()->with('success', 'voucher has been removed!'); + } + + /** + * @param Request $request + * @return JsonResponse + * @throws ValidationException + */ + public function redeem(Request $request) + { + #general validations + $request->validate([ + 'code' => 'required|exists:vouchers,code' + ]); + + #get voucher by code + $voucher = Voucher::where('code' , '=' , $request->input('code'))->firstOrFail(); + + #extra validations + if ($voucher->getStatus() == 'USES_LIMIT_REACHED') throw ValidationException::withMessages([ + 'code' => 'This voucher has reached the maximum amount of uses' + ]); + + if ($voucher->getStatus() == 'EXPIRED') throw ValidationException::withMessages([ + 'code' => 'This voucher has expired' + ]); + + 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" + ]); + + #redeem voucher + $voucher->redeem($request->user()); + + return response()->json([ + 'success' => "{$voucher->credits} credits have been added to your balance!" + ]); + } + + public function dataTable() + { + $query = Voucher::query(); + + return datatables($query) + ->addColumn('actions', function (Voucher $voucher) { + return ' + + +
+ '; + }) + ->addColumn('status', function (Voucher $voucher) { + $color = 'success'; + if ($voucher->getStatus() != 'VALID') $color = 'danger'; + return '' . $voucher->getStatus() . ''; + }) + ->editColumn('uses', function (Voucher $voucher) { + $userCount = $voucher->users()->count(); + return "{$userCount} / {$voucher->uses}"; + }) + ->editColumn('credits', function (Voucher $voucher) { + return number_format($voucher->credits, 2, '.', ''); + }) + ->editColumn('expires_at', function (Voucher $voucher) { + if (!$voucher->expires_at) return ""; + return $voucher->expires_at ? $voucher->expires_at->diffForHumans() : ''; + }) + ->editColumn('code', function (Voucher $voucher) { + return "{$voucher->code}
";
+ })
+ ->rawColumns(['actions', 'code', 'status'])
+ ->make();
+ }
+
+}
diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index 18a0d088..d80fb5bf 100644
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
+use Illuminate\Http\Request;
class LoginController extends Controller
{
@@ -37,4 +38,34 @@ class LoginController extends Controller
{
$this->middleware('guest')->except('logout');
}
+
+ public function login(Request $request)
+ {
+ $request->validate([
+ $this->username() => 'required|string',
+ 'password' => 'required|string',
+ 'g-recaptcha-response' => ['required','recaptcha'],
+ ]);
+
+ // If the class is using the ThrottlesLogins trait, we can automatically throttle
+ // the login attempts for this application. We'll key this by the username and
+ // the IP address of the client making these requests into this application.
+ if (method_exists($this, 'hasTooManyLoginAttempts') &&
+ $this->hasTooManyLoginAttempts($request)) {
+ $this->fireLockoutEvent($request);
+
+ return $this->sendLockoutResponse($request);
+ }
+
+ if ($this->attemptLogin($request)) {
+ return $this->sendLoginResponse($request);
+ }
+
+ // If the login attempt was unsuccessful we will increment the number of attempts
+ // to login and redirect the user back to the login form. Of course, when this
+ // user surpasses their maximum number of attempts they will get locked out.
+ $this->incrementLoginAttempts($request);
+
+ return $this->sendFailedLoginResponse($request);
+ }
}
diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php
index 952238bd..bdeb024c 100644
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -20,7 +20,8 @@ class ProfileController extends Controller
return view('profile.index')->with([
'user' => Auth::user(),
'credits_reward_after_verify_discord' => Configuration::getValueByKey('CREDITS_REWARD_AFTER_VERIFY_DISCORD'),
- 'discord_verify_command' => Configuration::getValueByKey('DISCORD_VERIFY_COMMAND')
+ 'force_email_verification' => Configuration::getValueByKey('FORCE_EMAIL_VERIFICATION'),
+ 'force_discord_verification' => Configuration::getValueByKey('FORCE_DISCORD_VERIFICATION'),
]);
}
diff --git a/app/Models/User.php b/app/Models/User.php
index 61605aea..ff4b4df6 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -7,17 +7,30 @@ use App\Notifications\Auth\QueuedVerifyEmail;
use App\Notifications\WelcomeMessage;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Activitylog\Traits\CausesActivity;
use Spatie\Activitylog\Traits\LogsActivity;
+/**
+ * Class User
+ * @package App\Models
+ */
class User extends Authenticatable implements MustVerifyEmail
{
use HasFactory, Notifiable, LogsActivity, CausesActivity;
+ /**
+ * @var string[]
+ */
protected static $logAttributes = ['name', 'email'];
+ /**
+ * @var string[]
+ */
protected static $ignoreChangedAttributes = [
'remember_token',
'credits',
@@ -68,6 +81,9 @@ class User extends Authenticatable implements MustVerifyEmail
'last_seen' => 'datetime',
];
+ /**
+ *
+ */
public static function boot()
{
parent::boot();
@@ -89,24 +105,38 @@ class User extends Authenticatable implements MustVerifyEmail
}
});
+ $user->vouchers()->detach();
+
Pterodactyl::client()->delete("/application/users/{$user->pterodactyl_id}");
});
}
+ /**
+ *
+ */
public function sendEmailVerificationNotification()
{
$this->notify(new QueuedVerifyEmail);
}
+ /**
+ * @return string
+ */
public function credits()
{
return number_format($this->credits, 2, '.', '');
}
+ /**
+ * @return string
+ */
public function getAvatar(){
return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
}
+ /**
+ * @return string
+ */
public function creditUsage()
{
$usage = 0;
@@ -118,6 +148,9 @@ class User extends Authenticatable implements MustVerifyEmail
return number_format($usage, 2, '.', '');
}
+ /**
+ * @return array|string|string[]
+ */
public function getVerifiedStatus(){
$status = '';
if ($this->hasVerifiedEmail()) $status .= 'email ';
@@ -126,15 +159,31 @@ class User extends Authenticatable implements MustVerifyEmail
return $status;
}
+ /**
+ * @return BelongsToMany
+ */
+ public function vouchers(){
+ return $this->belongsToMany(Voucher::class);
+ }
+
+ /**
+ * @return HasOne
+ */
public function discordUser(){
return $this->hasOne(DiscordUser::class);
}
+ /**
+ * @return HasMany
+ */
public function servers()
{
return $this->hasMany(Server::class);
}
+ /**
+ * @return HasMany
+ */
public function payments()
{
return $this->hasMany(Payment::class);
diff --git a/app/Models/Voucher.php b/app/Models/Voucher.php
new file mode 100644
index 00000000..bc267c5e
--- /dev/null
+++ b/app/Models/Voucher.php
@@ -0,0 +1,98 @@
+users()->detach();
+ });
+ }
+
+ /**
+ * @return BelongsToMany
+ */
+ public function users()
+ {
+ return $this->belongsToMany(User::class);
+ }
+
+ /**
+ * @return string
+ */
+ public function getStatus()
+ {
+ if ($this->users()->count() >= $this->uses) return 'USES_LIMIT_REACHED';
+ if (!is_null($this->expires_at)) {
+ if ($this->expires_at->isPast()) return 'EXPIRED';
+ }
+
+ return 'VALID';
+ }
+
+ /**
+ * @param User $user
+ * @return float
+ * @throws Exception
+ */
+ public function redeem(User $user)
+ {
+ try {
+ $user->increment('credits', $this->credits);
+ $this->users()->attach($user);
+ $this->logRedeem($user);
+ } catch (Exception $exception) {
+ throw $exception;
+ }
+
+ return $this->credits;
+ }
+
+ /**
+ * @param User $user
+ * @return null
+ */
+ private function logRedeem(User $user)
+ {
+ activity()
+ ->performedOn($this)
+ ->causedBy($user)
+ ->log('redeemed');
+
+ return null;
+ }
+}
diff --git a/database/factories/VoucherFactory.php b/database/factories/VoucherFactory.php
new file mode 100644
index 00000000..9a381296
--- /dev/null
+++ b/database/factories/VoucherFactory.php
@@ -0,0 +1,34 @@
+ $this->faker->word(),
+ 'code' => Str::random(36),
+ 'credits' => $this->faker->numberBetween(100, 1000),
+ 'uses' => $this->faker->numberBetween(1, 1000),
+ 'expires_at' => now()->addDays($this->faker->numberBetween(1, 90))->format('d-m-Y')
+ ];
+
+ }
+}
diff --git a/database/migrations/2021_07_09_190453_create_vouchers_table.php b/database/migrations/2021_07_09_190453_create_vouchers_table.php
new file mode 100644
index 00000000..30a58d92
--- /dev/null
+++ b/database/migrations/2021_07_09_190453_create_vouchers_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->string('code', 36)->unique();
+ $table->string('memo')->nullable();
+ $table->unsignedFloat('credits', 10);
+ $table->unsignedInteger('uses')->default(1);
+ $table->timestamp('expires_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('vouchers');
+ }
+}
diff --git a/database/migrations/2021_07_09_191913_create_user_voucher_table.php b/database/migrations/2021_07_09_191913_create_user_voucher_table.php
new file mode 100644
index 00000000..b75f7d26
--- /dev/null
+++ b/database/migrations/2021_07_09_191913_create_user_voucher_table.php
@@ -0,0 +1,32 @@
+foreignId('user_id')->constrained();
+ $table->foreignId('voucher_id')->constrained();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('user_voucher');
+ }
+}
diff --git a/database/migrations/2021_07_10_062140_update_credits_to_users_table.php b/database/migrations/2021_07_10_062140_update_credits_to_users_table.php
new file mode 100644
index 00000000..bce7b344
--- /dev/null
+++ b/database/migrations/2021_07_10_062140_update_credits_to_users_table.php
@@ -0,0 +1,32 @@
+unsignedFloat('credits', 10)->change();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->unsignedFloat('credits')->change();
+ });
+ }
+}
diff --git a/resources/views/admin/activitylogs/index.blade.php b/resources/views/admin/activitylogs/index.blade.php
index d617153a..e3cc7946 100644
--- a/resources/views/admin/activitylogs/index.blade.php
+++ b/resources/views/admin/activitylogs/index.blade.php
@@ -78,6 +78,9 @@
@case('created')
@break
+ @case('redeemed')
+
+ @break
@case('deleted')
@break
diff --git a/resources/views/admin/users/edit.blade.php b/resources/views/admin/users/edit.blade.php
index 59988edf..0358f189 100644
--- a/resources/views/admin/users/edit.blade.php
+++ b/resources/views/admin/users/edit.blade.php
@@ -72,7 +72,7 @@
Status | +Code | +Memo | +Credits | +Used / Uses | +Expires | ++ |
---|