Merge pull request #16 from ControlPanel-gg/api

API implementation
This commit is contained in:
AVMG 2021-06-06 22:24:39 +02:00 committed by GitHub
commit bc99dd6f32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 684 additions and 117 deletions

View file

@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\ApplicationApi;
use Exception;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
class ApplicationApiController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Application|Factory|View|Response
*/
public function index()
{
return view('admin.api.index');
}
/**
* Show the form for creating a new resource.
*
* @return Application|Factory|View|Response
*/
public function create()
{
return view('admin.api.create');
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return RedirectResponse
*/
public function store(Request $request)
{
$request->validate([
'memo' => 'nullable|string|max:60'
]);
ApplicationApi::create([
'memo' => $request->input('memo')
]);
return redirect()->route('admin.api.index')->with('success', 'api key created!');
}
/**
* Display the specified resource.
*
* @param ApplicationApi $applicationApi
* @return Response
*/
public function show(ApplicationApi $applicationApi)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param ApplicationApi $applicationApi
* @return Application|Factory|View|Response
*/
public function edit(ApplicationApi $applicationApi)
{
return view('admin.api.edit' , [
'applicationApi' => $applicationApi
]);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param ApplicationApi $applicationApi
* @return RedirectResponse
*/
public function update(Request $request, ApplicationApi $applicationApi)
{
$request->validate([
'memo' => 'nullable|string|max:60'
]);
$applicationApi->update($request->all());
return redirect()->route('admin.api.index')->with('success', 'api key updated!');
}
/**
* Remove the specified resource from storage.
*
* @param ApplicationApi $applicationApi
* @return RedirectResponse
*/
public function destroy(ApplicationApi $applicationApi)
{
$applicationApi->delete();
return redirect()->back()->with('success', 'api key has been removed!');
}
/**
* @param Request $request
* @return JsonResponse|mixed
* @throws Exception
*/
public function dataTable(Request $request)
{
$query = ApplicationApi::query();
return datatables($query)
->addColumn('actions', function (ApplicationApi $apiKey) {
return '
<a data-content="Edit" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.api.edit', $apiKey->token) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a>
<form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.api.destroy', $apiKey->token) . '">
' . csrf_field() . '
' . method_field("DELETE") . '
<button data-content="Delete" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button>
</form>
';
})
->editColumn('token' , function (ApplicationApi $apiKey) {
return "<code>{$apiKey->token}</code>";
})
->editColumn('last_used' , function (ApplicationApi $apiKey) {
return $apiKey->last_used ? $apiKey->last_used->diffForHumans() : '';
})
->rawColumns(['actions' , 'token'])
->make();
}
}

View file

@ -114,7 +114,7 @@ class ServerController extends Controller
*/
public function dataTable(Request $request)
{
$query = Server::with(['user', 'product', 'egg']);
$query = Server::with(['user', 'product']);
if ($request->has('product')) $query->where('product_id', '=', $request->input('product'));
if ($request->has('user')) $query->where('user_id', '=', $request->input('user'));
$query->select('servers.*');

View file

@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Server;
use Exception;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ServerController extends Controller
{
/**
* Display a listing of the resource.
*
* @param Request $request
* @return LengthAwarePaginator
*/
public function index(Request $request)
{
return Server::with('product')->paginate($request->query('per_page') ?? 50);
}
/**
* Display the specified resource.
*
* @param Server $server
* @return Server
*/
public function show(Server $server)
{
return $server->load('product');
}
/**
* Remove the specified resource from storage.
*
* @param Server $server
* @return Server
*/
public function destroy(Server $server)
{
$server->delete();
return $server;
}
/**
* suspend server
* @param Server $server
* @return Server|JsonResponse
*/
public function suspend(Server $server)
{
try {
$server->suspend();
} catch (Exception $exception) {
return response()->json(['message' => $exception->getMessage()], 500);
}
return $server->load('product');
}
/**
* unsuspend server
* @param Server $server
* @return Server|JsonResponse
*/
public function unSuspend(Server $server)
{
try {
$server->unSuspend();
} catch (Exception $exception) {
return response()->json(['message' => $exception->getMessage()], 500);
}
return $server->load('product');
}
}

View file

@ -1,79 +1,80 @@
<?php
//
//namespace App\Http\Controllers\Api;
//
//use App\Http\Controllers\Controller;
//use App\Models\DiscordUser;
//use App\Models\User;
//use Illuminate\Contracts\Foundation\Application;
//use Illuminate\Contracts\Routing\ResponseFactory;
//use Illuminate\Http\Request;
//use Illuminate\Http\Response;
//use Illuminate\Validation\Rule;
//
//class UserController extends Controller
//{
// /**
// * Display a listing of the resource.
// *
// * @return Response
// */
// public function index(Request $request)
// {
// return User::paginate($request->query('per_page') ?? 50);
// }
//
//
// /**
// * Display the specified resource.
// *
// * @param int $id
// * @return User
// */
// public function show(int $id)
// {
// $discordUser = DiscordUser::find($id);
// return $discordUser ? $discordUser->user : User::findOrFail($id);
// }
//
//
// /**
// * Update the specified resource in storage.
// *
// * @param Request $request
// * @param int $id
// * @return User
// */
// public function update(Request $request, int $id)
// {
// $discordUser = DiscordUser::find($id);
// $user = $discordUser ? $discordUser->user : User::findOrFail($id);
//
// $request->validate([
// "name" => "sometimes|string|min:4|max:30",
// "email" => "sometimes|string|email",
// "credits" => "sometimes|numeric|min:0|max:1000000",
// "server_limit" => "sometimes|numeric|min:0|max:1000000",
// "role" => ['sometimes', Rule::in(['admin', 'mod', 'client', 'member'])],
// ]);
//
// $user->update($request->all());
//
// return $user;
// }
//
// /**
// * Remove the specified resource from storage.
// *
// * @param int $id
// * @return Application|ResponseFactory|Response|void
// */
// public function destroy(int $id)
// {
// $discordUser = DiscordUser::find($id);
// $user = $discordUser ? $discordUser->user : User::findOrFail($id);
//
// $user->delete();
// return response($user, 200);
// }
//}
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\DiscordUser;
use App\Models\User;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\Rule;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* @param Request $request
* @return Response
*/
public function index(Request $request)
{
return User::paginate($request->query('per_page') ?? 50);
}
/**
* Display the specified resource.
*
* @param int $id
* @return User
*/
public function show(int $id)
{
$discordUser = DiscordUser::find($id);
return $discordUser ? $discordUser->user : User::findOrFail($id);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param int $id
* @return User
*/
public function update(Request $request, int $id)
{
$discordUser = DiscordUser::find($id);
$user = $discordUser ? $discordUser->user : User::findOrFail($id);
$request->validate([
"name" => "sometimes|string|min:4|max:30",
"email" => "sometimes|string|email",
"credits" => "sometimes|numeric|min:0|max:1000000",
"server_limit" => "sometimes|numeric|min:0|max:1000000",
"role" => ['sometimes', Rule::in(['admin', 'mod', 'client', 'member'])],
]);
$user->update($request->all());
return $user;
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Application|ResponseFactory|Response|void
*/
public function destroy(int $id)
{
$discordUser = DiscordUser::find($id);
$user = $discordUser ? $discordUser->user : User::findOrFail($id);
$user->delete();
return response($user, 200);
}
}

View file

@ -58,7 +58,7 @@ class RegisterController extends Controller
//check if registered cookie exists as extra defense
if (isset($_COOKIE['4b3403665fea6'])) {
$data['registered'] = true;
$data['registered'] = env('APP_ENV') == 'local' ? false : true;
}
return Validator::make($data, [

View file

@ -2,6 +2,7 @@
namespace App\Http;
use App\Http\Middleware\ApiAuthToken;
use App\Http\Middleware\isAdmin;
use App\Http\Middleware\LastSeen;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
@ -65,6 +66,7 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'admin' => isAdmin::class
'admin' => isAdmin::class,
'api.token' => ApiAuthToken::class
];
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use App\Models\ApplicationApi;
use Closure;
use Illuminate\Http\Request;
class ApiAuthToken
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$token = ApplicationApi::find($request->bearerToken());
if (is_null($token)) return response()->json(['message' => 'Invalid Authorization token'], 401);
$token->updateLastUsed();
return $next($request);
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Hidehalo\Nanoid\Client;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ApplicationApi extends Model
{
use HasFactory;
protected $fillable = ['token', 'memo' , 'last_used'];
protected $primaryKey = 'token';
public $incrementing = false;
protected $dates = ['last_used'];
public static function boot()
{
parent::boot();
static::creating(function (ApplicationApi $applicationApi) {
$client = new Client();
$applicationApi->{$applicationApi->getKeyName()} = $client->generateId(48);
});
}
public function updateLastUsed(){
$this->update(['last_used' => now()]);
}
}

View file

@ -30,7 +30,7 @@ class Server extends Model
/**
* @var string[]
*/
protected static $ignoreChangedAttributes = ['pterodactyl_id' , 'identifier' , 'updated_at'];
protected static $ignoreChangedAttributes = ['pterodactyl_id', 'identifier', 'updated_at'];
/**
* @var string[]
@ -59,16 +59,17 @@ class Server extends Model
/**
*
*/
public static function boot() {
public static function boot()
{
parent::boot();
static::creating(function(Server $server) {
static::creating(function (Server $server) {
$client = new Client();
$server->{$server->getKeyName()} = $client->generateId($size = 21);
});
static::deleting(function(Server $server) {
static::deleting(function (Server $server) {
Pterodactyl::client()->delete("/application/servers/{$server->pterodactyl_id}");
});
}
@ -76,7 +77,8 @@ class Server extends Model
/**
* @return bool
*/
public function isSuspended(){
public function isSuspended()
{
return !is_null($this->suspended);
}
@ -84,7 +86,8 @@ class Server extends Model
/**
* @return PromiseInterface|Response
*/
public function getPterodactylServer(){
public function getPterodactylServer()
{
return Pterodactyl::client()->get("/application/servers/{$this->pterodactyl_id}");
}
@ -92,7 +95,8 @@ class Server extends Model
*
* @throws Exception
*/
public function suspend(){
public function suspend()
{
$response = Pterodactyl::suspendServer($this);
if ($response->successful()) {
@ -120,40 +124,20 @@ class Server extends Model
return $this;
}
/**
* @return HasOne
*/
public function product(){
return $this->hasOne(Product::class , 'id' , 'product_id');
}
/**
* @return HasOne
*/
public function nest(){
return $this->hasOne(Nest::class , 'id' , 'nest_id');
}
/**
* @return HasOne
*/
public function egg(){
return $this->hasOne(Egg::class , 'id' , 'egg_id');
}
/**
* @return HasOne
*/
public function location(){
return $this->hasOne(Location::class , 'id' , 'location_id');
public function product()
{
return $this->hasOne(Product::class, 'id', 'product_id');
}
/**
* @return BelongsTo
*/
public function user(){
return $this->belongsTo(User::class , 'user_id' , 'id');
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Database\Factories;
use App\Models\ApplicationApi;
use Illuminate\Database\Eloquent\Factories\Factory;
class ApplicationApiFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = ApplicationApi::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}

View file

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateApplicationApisTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('application_apis', function (Blueprint $table) {
$table->string('token')->unique()->primary();
$table->string('memo')->nullable();
$table->timestamp('last_used')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('application_apis');
}
}

View file

@ -0,0 +1,63 @@
@extends('layouts.main')
@section('content')
<!-- CONTENT HEADER -->
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Application API</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{route('admin.api.index')}}">Application API</a></li>
<li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.api.create')}}">Create</a>
</li>
</ol>
</div>
</div>
</div>
</section>
<!-- END CONTENT HEADER -->
<!-- MAIN CONTENT -->
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-body">
<form action="{{route('admin.api.store')}}" method="POST">
@csrf
<div class="form-group">
<label for="memo">Memo</label>
<input value="{{old('memo')}}" id="memo" name="memo" type="text"
class="form-control @error('memo') is-invalid @enderror">
@error('memo')
<div class="invalid-feedback">
{{$message}}
</div>
@enderror
</div>
<div class="form-group text-right">
<button type="submit" class="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- END CONTENT -->
@endsection

View file

@ -0,0 +1,64 @@
@extends('layouts.main')
@section('content')
<!-- CONTENT HEADER -->
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Application API</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{route('admin.api.index')}}">Application API</a></li>
<li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.api.edit' , $applicationApi->token)}}">Edit</a>
</li>
</ol>
</div>
</div>
</div>
</section>
<!-- END CONTENT HEADER -->
<!-- MAIN CONTENT -->
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-body">
<form action="{{route('admin.api.update' , $applicationApi->token)}}" method="POST">
@csrf
@method('PATCH')
<div class="form-group">
<label for="memo">Memo</label>
<input value="{{$applicationApi->memo}}" id="memo" name="memo" type="text"
class="form-control @error('memo') is-invalid @enderror">
@error('memo')
<div class="invalid-feedback">
{{$message}}
</div>
@enderror
</div>
<div class="form-group text-right">
<button type="submit" class="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- END CONTENT -->
@endsection

View file

@ -0,0 +1,87 @@
@extends('layouts.main')
@section('content')
<!-- CONTENT HEADER -->
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Application API</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li>
<li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.api.index')}}">Application API</a></li>
</ol>
</div>
</div>
</div>
</section>
<!-- END CONTENT HEADER -->
<!-- MAIN CONTENT -->
<section class="content">
<div class="container-fluid">
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between">
<h5 class="card-title"><i class="fa fa-gamepad mr-2"></i>Application API</h5>
<a href="{{route('admin.api.create')}}" class="btn btn-sm btn-primary"><i
class="fas fa-plus mr-1"></i>Create new</a>
</div>
</div>
<div class="card-body table-responsive">
<table id="datatable" class="table table-striped">
<thead>
<tr>
<th>Token</th>
<th>Memo</th>
<th>Last used</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<!-- END CUSTOM CONTENT -->
</section>
<!-- END CONTENT -->
<script>
function submitResult() {
return confirm("Are you sure you wish to delete?") !== false;
}
document.addEventListener("DOMContentLoaded", function () {
$('#datatable').DataTable({
processing: true,
serverSide: true,
stateSave: true,
ajax: "{{route('admin.api.datatable')}}",
order: [[ 2, "desc" ]],
columns: [
{data: 'token'},
{data: 'memo'},
{data: 'last_used'},
{data: 'actions' , sortable : false},
],
fnDrawCallback: function( oSettings ) {
$('[data-toggle="popover"]').popover();
}
});
});
</script>
@endsection

View file

@ -218,6 +218,14 @@
<li class="nav-header">Dashboard</li>
<li class="nav-item">
<a href="{{route('admin.api.index')}}"
class="nav-link @if(Request::routeIs('admin.api.*')) active @endif">
<i class="nav-icon fa fa-gamepad"></i>
<p>Application API</p>
</a>
</li>
<li class="nav-item">
<a href="{{route('admin.configurations.index')}}"
class="nav-link @if(Request::routeIs('admin.configurations.*')) active @endif">

View file

@ -1,6 +1,7 @@
<?php
//use App\Http\Controllers\Api\UserController;
use App\Http\Controllers\Api\ServerController;
use App\Http\Controllers\Api\UserController;
use App\Http\Controllers\Api\VerifyController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
@ -15,12 +16,18 @@ use Illuminate\Support\Facades\Route;
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::post('/verify' , [VerifyController::class , 'verify']);
Route::post('/verify', [VerifyController::class, 'verify']);
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
Route::middleware('api.token')->group(function () {
Route::resource('users', UserController::class)->except(['store', 'create']);
Route::patch('/servers/{server}/suspend', [ServerController::class, 'suspend']);
Route::patch('/servers/{server}/unsuspend', [ServerController::class, 'unSuspend']);
Route::resource('servers', ServerController::class)->except(['store', 'create', 'edit', 'update']);
});
//Route::resource('users' , UserController::class);

View file

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\Admin\ActivityLogController;
use App\Http\Controllers\Admin\ApplicationApiController;
use App\Http\Controllers\Admin\ConfigurationController;
use App\Http\Controllers\Admin\NestsController;
use App\Http\Controllers\Admin\NodeController;
@ -99,6 +100,11 @@ Route::middleware('auth')->group(function () {
Route::patch('settings/update/icons', [SettingsController::class , 'updateIcons'])->name('settings.update.icons');
Route::resource('settings', SettingsController::class)->only('index');
Route::get('api/datatable', [ApplicationApiController::class, 'datatable'])->name('api.datatable');
Route::resource('api', ApplicationApiController::class)->parameters([
'api' => 'applicationApi',
]);
});
Route::get('/home', [HomeController::class, 'index'])->name('home');