Referal System

This commit is contained in:
1Day 2022-06-02 16:11:24 +02:00
parent 0f5369043d
commit 378187aea0
13 changed files with 345 additions and 42 deletions

View file

@ -37,6 +37,8 @@ class Misc
'mailencryption' => 'nullable|string', 'mailencryption' => 'nullable|string',
'mailfromadress' => 'nullable|string', 'mailfromadress' => 'nullable|string',
'mailfromname' => 'nullable|string', 'mailfromname' => 'nullable|string',
'enable_referral' => 'nullable|string',
'referral_reward' => 'nullable|numeric',
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
@ -69,6 +71,9 @@ class Misc
"SETTINGS::MAIL:ENCRYPTION" => "mailencryption", "SETTINGS::MAIL:ENCRYPTION" => "mailencryption",
"SETTINGS::MAIL:FROM_ADDRESS" => "mailfromadress", "SETTINGS::MAIL:FROM_ADDRESS" => "mailfromadress",
"SETTINGS::MAIL:FROM_NAME" => "mailfromname", "SETTINGS::MAIL:FROM_NAME" => "mailfromname",
"SETTINGS::REFERRAL::ENABLED" => "enable_referral",
"SETTINGS::REFERRAL::REWARD" => "referral_reward"
]; ];

View file

@ -8,6 +8,7 @@ use App\Http\Controllers\Controller;
use App\Models\Settings; use App\Models\Settings;
use App\Models\User; use App\Models\User;
use App\Notifications\DynamicNotification; use App\Notifications\DynamicNotification;
use Illuminate\Support\Facades\DB;
use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\QueryBuilder;
use Exception; use Exception;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
@ -52,8 +53,18 @@ class UserController extends Controller
*/ */
public function show(User $user) public function show(User $user)
{ {
//QUERY ALL REFERRALS A USER HAS
//i am not proud of this at all.
$allReferals = array();
$referrals = DB::table("user_referrals")->where("referral_id","=",$user->id)->get();
foreach($referrals as $referral){
array_push($allReferals, $allReferals["id"] = User::query()->findOrFail($referral->registered_user_id));
}
array_pop($allReferals);
return view('admin.users.show')->with([ return view('admin.users.show')->with([
'user' => $user 'user' => $user,
'referrals' => $allReferals
]); ]);
} }
@ -258,6 +269,9 @@ class UserController extends Controller
->addColumn('servers', function (User $user) { ->addColumn('servers', function (User $user) {
return $user->servers->count(); return $user->servers->count();
}) })
->addColumn('referrals', function (User $user) {
return DB::table('user_referrals')->where("referral_id","=",$user->id)->count();
})
->addColumn('discordId', function (User $user) { ->addColumn('discordId', function (User $user) {
return $user->discordUser ? $user->discordUser->id : ''; return $user->discordUser ? $user->discordUser->id : '';
}) })
@ -307,7 +321,7 @@ class UserController extends Controller
->orderColumn('last_seen', function ($query) { ->orderColumn('last_seen', function ($query) {
$query->orderBy('last_seen', "desc"); $query->orderBy('last_seen', "desc");
}) })
->rawColumns(['avatar', 'name', 'credits', 'role', 'usage', 'actions', 'last_seen']) ->rawColumns(['avatar', 'name', 'credits', 'role', 'usage', 'referrals', 'actions', 'last_seen'])
->make(true); ->make(true);
} }
} }

View file

@ -6,9 +6,13 @@ use App\Classes\Pterodactyl;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Settings; use App\Models\Settings;
use App\Models\User; use App\Models\User;
use App\Notifications\ReferralNotification;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Carbon\Carbon;
use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -78,6 +82,18 @@ class RegisterController extends Controller
return Validator::make($data, $validationRules); return Validator::make($data, $validationRules);
} }
/**
* Create a unique Referral Code for User
* @return string
*/
protected function createReferralCode(){
$referralcode = STR::random(8);
if (User::where('referral_code', '=', $referralcode)->exists()) {
$this->createReferralCode();
}
return $referralcode;
}
/** /**
* Create a new user instance after a valid registration. * Create a new user instance after a valid registration.
* *
@ -92,6 +108,8 @@ class RegisterController extends Controller
'credits' => config('SETTINGS::USER:INITIAL_CREDITS', 150), 'credits' => config('SETTINGS::USER:INITIAL_CREDITS', 150),
'server_limit' => config('SETTINGS::USER:INITIAL_SERVER_LIMIT', 1), 'server_limit' => config('SETTINGS::USER:INITIAL_SERVER_LIMIT', 1),
'password' => Hash::make($data['password']), 'password' => Hash::make($data['password']),
'referral_code' => $this->createReferralCode(),
]); ]);
$response = Pterodactyl::client()->post('/application/users', [ $response = Pterodactyl::client()->post('/application/users', [
@ -116,7 +134,24 @@ class RegisterController extends Controller
'pterodactyl_id' => $response->json()['attributes']['id'] 'pterodactyl_id' => $response->json()['attributes']['id']
]); ]);
//INCREMENT REFERRAL-USER CREDITS
if(!empty($data['referral_code'])){
$ref_code = $data['referral_code'];
$new_user = $user->id;
if($ref_user = User::query()->where('referral_code', '=', $ref_code)->first()) {
$ref_user->increment('credits', config("SETTINGS::REFERRAL::REWARD"));
$ref_user->notify(new ReferralNotification($ref_user->id,$new_user));
//INSERT INTO USER_REFERRALS TABLE
DB::table('user_referrals')->insert([
'referral_id' => $ref_user->id,
'registered_user_id' => $user->id,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]);
}
}
return $user; return $user;
} }

View file

@ -60,7 +60,8 @@ class User extends Authenticatable implements MustVerifyEmail
'pterodactyl_id', 'pterodactyl_id',
'discord_verified_at', 'discord_verified_at',
'avatar', 'avatar',
'suspended' 'suspended',
'referral_code'
]; ];
/** /**

View file

@ -0,0 +1,59 @@
<?php
namespace App\Notifications;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Auth;
class ReferralNotification extends Notification
{
use Queueable;
/**
* @var User
*/
private $user;
/**
* Create a new notification instance.
*
* @param User $user
*/
public function __construct(int $user, int $ref_user)
{
$this->user = User::findOrFail($user);
$this->ref_user = User::findOrFail($ref_user);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['database'];
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
'title' => __("Someone registered using your Code!"),
'content' => "
<p>You received ".config('SETTINGS::REFERRAL::REWARD')." ".config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME')."</p>
<p>because ".$this->ref_user->name." registered with your Referral-Code!</p>
<p>Thank you very much for supporting us!.</p>
<p>".config('app.name', 'Laravel')."</p>
",
];
}
}

View file

@ -0,0 +1,53 @@
<?php
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
class ReferralCode extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('referral_code')->lenght(8)->nullable();
});
$existing_user = User::where('referral_code', '')->get();
foreach ($existing_user as $user) {
$random = STR::random(8);
if (User::where('referral_code', '=', $random)->doesntExist()) {
DB::table("users")
->where("id", "=", $user->id)
->update(['referral_code' => $random]);
}else{
$random = STR::random(8);
DB::table("users")
->where("id", "=", $user->id)
->update(['referral_code' => $random]);
}
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('referral_code');
});
}
}

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class TableUserReferrals extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_referrals', function (Blueprint $table) {
$table->unsignedBigInteger('referral_id');
$table->unsignedBigInteger('registered_user_id');
$table->foreign('referral_id')->references('id')->on('users');
$table->foreign('registered_user_id')->references('id')->on('users');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_referrals');
}
}

View file

@ -465,5 +465,19 @@ class SettingsSeeder extends Seeder
'type' => 'string', 'type' => 'string',
'description' => 'Mailer From Name' 'description' => 'Mailer From Name'
]); ]);
Settings::firstOrCreate([
'key' => 'SETTINGS::REFERRAL::ENABLED',
], [
'value' =>"true",
'type' => 'string',
'description' => 'Enable or disable the referral system'
]);
Settings::firstOrCreate([
'key' => 'SETTINGS::REFERRAL::REWARD',
], [
'value' =>100,
'type' => 'integer',
'description' => 'Credit reward a user should receive when a user registers with his referral code'
]);
} }
} }

View file

@ -184,7 +184,33 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-3 px-3">
<div class="row mb-2">
<div class="col text-center">
<h1>Referral</h1>
</div>
</div>
<div class="custom-control mb-3 p-0">
<div class="col m-0 p-0 d-flex justify-content-between align-items-center">
<div>
<input value="true" id="enable_referral" name="enable_referral"
{{ config('SETTINGS::REFERRAL::ENABLED') == 'true' ? 'checked' : '' }}
type="checkbox">
<label for="enable_referral">{{ __('Enable Referral') }} </label>
</div>
</div>
</div>
<div class="form-group mb-3">
<div class="custom-control p-0">
<label for="referral_reward">{{ __('Referral reward in') }} {{ config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME', 'Credits') }}:</label>
<input x-model="referral_reward" id="referral_reward" name="referral_reward"
type="number" value="{{ config('SETTINGS::REFERRAL::REWARD') }}"
class="form-control @error('referral_reward') is-invalid @enderror">
</div>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">

View file

@ -47,6 +47,7 @@
<th>{{__('Email')}}</th> <th>{{__('Email')}}</th>
<th>{{CREDITS_DISPLAY_NAME}}</th> <th>{{CREDITS_DISPLAY_NAME}}</th>
<th>{{__('Servers')}}</th> <th>{{__('Servers')}}</th>
<th>{{__("Referrals")}}</th>
<th>{{__('Verified')}}</th> <th>{{__('Verified')}}</th>
<th>{{__('Last seen')}}</th> <th>{{__('Last seen')}}</th>
<th></th> <th></th>
@ -90,6 +91,7 @@
{data: 'email', name: 'users.email'}, {data: 'email', name: 'users.email'},
{data: 'credits' , name : 'users.credits'}, {data: 'credits' , name : 'users.credits'},
{data: 'servers' , sortable : false}, {data: 'servers' , sortable : false},
{data: 'referrals'},
{data: 'verified' , sortable : false}, {data: 'verified' , sortable : false},
{data: 'last_seen'}, {data: 'last_seen'},
{data: 'actions' , sortable : false}, {data: 'actions' , sortable : false},

View file

@ -244,8 +244,37 @@
@include('admin.servers.table' , ['filter' => '?user=' . $user->id]) @include('admin.servers.table' , ['filter' => '?user=' . $user->id])
</div> </div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-user-check mr-2"></i>{{__('Referals')}}</h5>
</div>
<div class="card-body table-responsive">
@foreach($referrals as $referral)
<div class="col-lg-6">
<div class="row">
<div class="col-lg-4">
<label>User ID: {{$referral->id}}</label>
</div>
<div class="col-lg-4">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
<i class="fas fa-user-check mr-2"></i><a href="{{route("admin.users.show",$referral->id)}}">{{$referral->name}}</a>
</span>
</div>
<div class="col-lg-4">
<span style="max-width: 250px;" class="d-inline-block text-truncate">
<i class="fas fa-clock mr-2"></i>{{$referral->created_at->diffForHumans()}}
</span>
</div>
</div>
</div>
@endforeach
</div>
</div>
</div> </div>
<!-- END CUSTOM CONTENT --> <!-- END CUSTOM CONTENT -->

View file

@ -91,7 +91,16 @@
</div> </div>
</div> </div>
</div> </div>
@if(config('SETTINGS::REFERRAL::ENABLED') == "true")
<div class="input-group mb-3">
<input type="text" value="{{ \Request::get('ref') }}" class="form-control" name="referral_code" placeholder="{{__('Referral code')}} ( {{__("optional")}} )">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-user-check"></span>
</div>
</div>
</div>
@endif
<div class="input-group mb-3"> <div class="input-group mb-3">
{!! htmlFormSnippet() !!} {!! htmlFormSnippet() !!}
@error('g-recaptcha-response') @error('g-recaptcha-response')

View file

@ -12,7 +12,7 @@
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li> <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item"><a class="text-muted" <li class="breadcrumb-item"><a class="text-muted"
href="{{ route('profile.index') }}">{{ __('Profile') }}</a> href="{{ route('profile.index') }}">{{ __('Profile') }}</a>
</li> </li>
</ol> </ol>
</div> </div>
@ -33,7 +33,7 @@
</h5> </h5>
{{ __('You have not yet verified your email address') }} {{ __('You have not yet verified your email address') }}
<a class="text-primary" <a class="text-primary"
href="{{ route('verification.send') }}">{{ __('Click here to resend verification email') }}</a> href="{{ route('verification.send') }}">{{ __('Click here to resend verification email') }}</a>
<br> <br>
{{ __('Please contact support If you didnt receive your verification email.') }} {{ __('Please contact support If you didnt receive your verification email.') }}
@ -43,16 +43,18 @@
@if (is_null(Auth::user()->discordUser) && strtolower($force_discord_verification) == 'true') @if (is_null(Auth::user()->discordUser) && strtolower($force_discord_verification) == 'true')
@if (!empty(config('SETTINGS::DISCORD:CLIENT_ID')) && !empty(config('SETTINGS::DISCORD:CLIENT_SECRET'))) @if (!empty(config('SETTINGS::DISCORD:CLIENT_ID')) && !empty(config('SETTINGS::DISCORD:CLIENT_SECRET')))
<div class="alert alert-warning p-2 m-2"> <div class="alert alert-warning p-2 m-2">
<h5><i class="icon fas fa-exclamation-circle"></i>{{ __('Required Discord verification!') }} <h5>
<i class="icon fas fa-exclamation-circle"></i>{{ __('Required Discord verification!') }}
</h5> </h5>
{{ __('You have not yet verified your discord account') }} {{ __('You have not yet verified your discord account') }}
<a class="text-primary" <a class="text-primary"
href="{{ route('auth.redirect') }}">{{ __('Login with discord') }}</a> <br> href="{{ route('auth.redirect') }}">{{ __('Login with discord') }}</a> <br>
{{ __('Please contact support If you face any issues.') }} {{ __('Please contact support If you face any issues.') }}
</div> </div>
@else @else
<div class="alert alert-danger p-2 m-2"> <div class="alert alert-danger p-2 m-2">
<h5><i class="icon fas fa-exclamation-circle"></i>{{ __('Required Discord verification!') }} <h5>
<i class="icon fas fa-exclamation-circle"></i>{{ __('Required Discord verification!') }}
</h5> </h5>
{{ __('Due to system settings you are required to verify your discord account!') }} <br> {{ __('Due to system settings you are required to verify your discord account!') }} <br>
{{ __('It looks like this hasnt been set-up correctly! Please contact support.') }}' {{ __('It looks like this hasnt been set-up correctly! Please contact support.') }}'
@ -72,9 +74,10 @@
<div class="row"> <div class="row">
<div class="col-12 col-sm-auto mb-4"> <div class="col-12 col-sm-auto mb-4">
<div class="slim rounded-circle border-secondary border text-gray-dark" <div class="slim rounded-circle border-secondary border text-gray-dark"
data-label="Change your avatar" data-max-file-size="3" data-label="Change your avatar" data-max-file-size="3"
data-save-initial-image="true" style="width: 140px;height:140px; cursor: pointer" data-save-initial-image="true"
data-size="140,140"> style="width: 140px;height:140px; cursor: pointer"
data-size="140,140">
<img src="{{ $user->getAvatar() }}" alt="avatar"> <img src="{{ $user->getAvatar() }}" alt="avatar">
</div> </div>
</div> </div>
@ -84,10 +87,11 @@
<p class="mb-0">{{ $user->email }} <p class="mb-0">{{ $user->email }}
@if ($user->hasVerifiedEmail()) @if ($user->hasVerifiedEmail())
<i data-toggle="popover" data-trigger="hover" data-content="Verified" <i data-toggle="popover" data-trigger="hover" data-content="Verified"
class="text-success fas fa-check-circle"></i> class="text-success fas fa-check-circle"></i>
@else @else
<i data-toggle="popover" data-trigger="hover" data-content="Not verified" <i data-toggle="popover" data-trigger="hover"
class="text-danger fas fa-exclamation-circle"></i> data-content="Not verified"
class="text-danger fas fa-exclamation-circle"></i>
@endif @endif
</p> </p>
@ -95,8 +99,23 @@
<span class="badge badge-primary"><i <span class="badge badge-primary"><i
class="fa fa-coins mr-2"></i>{{ $user->Credits() }}</span> class="fa fa-coins mr-2"></i>{{ $user->Credits() }}</span>
</div> </div>
<div class="mt-1">
@if(config('SETTINGS::REFERRAL::ENABLED') == "true" && $user->role != "member")
<span class="badge badge-success"><i
class="fa fa-user-check mr-2"></i>
{{_("Referral URL")}} : {{route("register")}}?ref={{$user->referral_code}}</span>
@else
<span class="badge badge-warning"><i
class="fa fa-user-check mr-2"></i>
{{_("Make a purchase to reveal your referral-URL")}}</span>
@endif
</div>
</div> </div>
<div class="text-center text-sm-right"><span <div class="text-center text-sm-right"><span
class="badge badge-secondary">{{ $user->role }}</span> class="badge badge-secondary">{{ $user->role }}</span>
<div class="text-muted"> <div class="text-muted">
@ -107,7 +126,7 @@
</div> </div>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item"><a href="javasript:void(0)" <li class="nav-item"><a href="javasript:void(0)"
class="active nav-link">{{ __('Settings') }}</a> class="active nav-link">{{ __('Settings') }}</a>
</li> </li>
</ul> </ul>
<div class="tab-content pt-3"> <div class="tab-content pt-3">
@ -123,22 +142,22 @@
</span> </span>
@endforeach @endforeach
@endif @endif
@if( $errors->has('pterodactyl_error_status') ) @if( $errors->has('pterodactyl_error_status') )
@foreach( $errors->get('pterodactyl_error_status') as $err ) @foreach( $errors->get('pterodactyl_error_status') as $err )
<span class="text-danger" role="alert"> <span class="text-danger" role="alert">
<small><strong>{{ $err }}</strong></small> <small><strong>{{ $err }}</strong></small>
</span> </span>
@endforeach @endforeach
@endif @endif
<div class="form-group"><label>{{__('Name')}}</label> <input <div class="form-group"><label>{{__('Name')}}</label> <input
class="form-control @error('name') is-invalid @enderror" class="form-control @error('name') is-invalid @enderror"
type="text" name="name" placeholder="{{ $user->name }}" type="text" name="name" placeholder="{{ $user->name }}"
value="{{ $user->name }}"> value="{{ $user->name }}">
@error('name') @error('name')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
</div> </div>
@ -151,9 +170,9 @@
value="{{ $user->email }}"> value="{{ $user->email }}">
@error('email') @error('email')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
</div> </div>
@ -169,12 +188,13 @@
<label>{{ __('Current Password') }}</label> <label>{{ __('Current Password') }}</label>
<input <input
class="form-control @error('current_password') is-invalid @enderror" class="form-control @error('current_password') is-invalid @enderror"
name="current_password" type="password" placeholder="••••••"> name="current_password" type="password"
placeholder="••••••">
@error('current_password') @error('current_password')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
</div> </div>
@ -187,9 +207,9 @@
name="new_password" type="password" placeholder="••••••"> name="new_password" type="password" placeholder="••••••">
@error('new_password') @error('new_password')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
</div> </div>
@ -204,9 +224,9 @@
placeholder="••••••"> placeholder="••••••">
@error('new_password_confirmation') @error('new_password_confirmation')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
</div> </div>
@ -245,9 +265,10 @@
</p> </p>
</div> </div>
<div class="p-3"><img width="100px" <div class="p-3"><img width="100px"
height="100px" class="rounded-circle" height="100px"
src="{{ $user->discordUser->getAvatar() }}" class="rounded-circle"
alt="avatar"></div> src="{{ $user->discordUser->getAvatar() }}"
alt="avatar"></div>
</div> </div>
<div class="small-box-footer"> <div class="small-box-footer">
<a href="{{ route('auth.redirect') }}"> <a href="{{ route('auth.redirect') }}">
@ -265,7 +286,7 @@
<div class="row"> <div class="row">
<div class="col d-flex justify-content-end"> <div class="col d-flex justify-content-end">
<button class="btn btn-primary" <button class="btn btn-primary"
type="submit">{{ __('Save Changes') }}</button> type="submit">{{ __('Save Changes') }}</button>
</div> </div>
</div> </div>