improved api endpoints

added includes and filters on most GET request routes
added discord id support for notifications
added register user endpoint
This commit is contained in:
AVMG20 2021-10-12 21:36:13 +02:00
parent 3665b9ff55
commit 1f8b34e684
5 changed files with 127 additions and 25 deletions

View file

@ -6,11 +6,13 @@ use App\Http\Controllers\Controller;
use App\Models\DiscordUser; use App\Models\DiscordUser;
use App\Models\User; use App\Models\User;
use App\Notifications\DynamicNotification; use App\Notifications\DynamicNotification;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Notification;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use Illuminate\Validation\ValidationException;
use Spatie\ValidationRules\Rules\Delimited; use Spatie\ValidationRules\Rules\Delimited;
class NotificationController extends Controller class NotificationController extends Controller
@ -61,13 +63,14 @@ class NotificationController extends Controller
$data = $request->validate([ $data = $request->validate([
"via" => ["required", new Delimited("in:mail,database")], "via" => ["required", new Delimited("in:mail,database")],
"all" => "required_without:users|boolean", "all" => "required_without:users|boolean",
"users" => ["required_without:all", new Delimited("exists:users,id")], "users" => ["required_without:all"],
"title" => "required|string|min:1", "title" => "required|string|min:1",
"content" => "required|string|min:1" "content" => "required|string|min:1"
]); ]);
$via = explode(",", $data["via"]); $via = explode(",", $data["via"]);
$mail = null; $mail = null;
$database = null; $database = null;
if (in_array("database", $via)) { if (in_array("database", $via)) {
$database = [ $database = [
"title" => $data["title"], "title" => $data["title"],
@ -79,10 +82,28 @@ class NotificationController extends Controller
->subject($data["title"]) ->subject($data["title"])
->line(new HtmlString($data["content"])); ->line(new HtmlString($data["content"]));
} }
$all = $data["all"] ?? false; $all = $data["all"] ?? false;
$users = $all ? User::all() : User::whereIn("id", explode(",", $data["users"]))->get(); if ($all) {
$users = User::all();
} else {
$userIds = explode(",", $data["users"]);
$users = User::query()
->whereIn("id", $userIds)
->orWhereHas('discordUser' , function (Builder $builder) use ($userIds) {
$builder->whereIn('id' , $userIds);
})
->get();
}
if ($users->count() == 0) {
throw ValidationException::withMessages([
'users' => ['No users found!'],
]);
}
Notification::send($users, new DynamicNotification($via, $database, $mail)); Notification::send($users, new DynamicNotification($via, $database, $mail));
return response()->json(["message" => "Notification successfully sent."]); return response()->json(["message" => "Notification successfully sent.", 'user_count' => $users->count()]);
} }
/** /**

View file

@ -6,11 +6,16 @@ use App\Http\Controllers\Controller;
use App\Models\Server; use App\Models\Server;
use Exception; use Exception;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Spatie\QueryBuilder\QueryBuilder;
class ServerController extends Controller class ServerController extends Controller
{ {
const ALLOWED_INCLUDES = ['product', 'user'];
const ALLOWED_FILTERS = ['name', 'suspended', 'identifier', 'pterodactyl_id', 'user_id' , 'product_id'];
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
@ -19,19 +24,29 @@ class ServerController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
return Server::with('product')->paginate($request->query('per_page') ?? 50); $query = QueryBuilder::for(Server::class)
->allowedIncludes(self::ALLOWED_INCLUDES)
->allowedFilters(self::ALLOWED_FILTERS);
return $query->paginate($request->input('per_page') ?? 50);
} }
/** /**
* Display the specified resource. * Display the specified resource.
* *
* @param Server $server * @param Server $server
* @return Server * @return Server|Collection
*/ */
public function show(Server $server) public function show(Server $server)
{ {
return $server->load('product'); $query = QueryBuilder::for(Server::class)
->where('id' ,'=' , $server->id)
->allowedIncludes(self::ALLOWED_INCLUDES);
return $query->get();
} }

View file

@ -2,8 +2,10 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Classes\Pterodactyl;
use App\Events\UserUpdateCreditsEvent; use App\Events\UserUpdateCreditsEvent;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Configuration;
use App\Models\DiscordUser; use App\Models\DiscordUser;
use App\Models\User; use App\Models\User;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
@ -11,16 +13,21 @@ use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\QueryBuilder;
class UserController extends Controller class UserController extends Controller
{ {
const ALLOWED_INCLUDES = ['servers', 'payments', 'vouchers', 'discordUser']; const ALLOWED_INCLUDES = ['servers', 'notifications', 'payments', 'vouchers', 'discordUser'];
const ALLOWED_FILTERS = ['email', 'pterodactyl_id', 'role', 'suspended']; const ALLOWED_FILTERS = ['name', 'server_limit', 'email', 'pterodactyl_id', 'role', 'suspended'];
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
@ -46,14 +53,14 @@ class UserController extends Controller
public function show(int $id) public function show(int $id)
{ {
$discordUser = DiscordUser::find($id); $discordUser = DiscordUser::find($id);
$user = $discordUser ? $discordUser->user : User::findOrFail($id); $user = $discordUser ? $discordUser->user : User::findOrFail($id);
$query = QueryBuilder::for($user) $query = QueryBuilder::for($user)
->with('discordUser') ->with('discordUser')
->allowedIncludes(self::ALLOWED_INCLUDES) ->allowedIncludes(self::ALLOWED_INCLUDES)
->where('users.id' , '=' , $id) ->where('users.id', '=', $id)
->orWhereHas('discordUser' , function (Builder $builder) use ($id) { ->orWhereHas('discordUser', function (Builder $builder) use ($id) {
$builder->where('id' , '=' , $id); $builder->where('id', '=', $id);
}); });
return $query->get(); return $query->get();
@ -158,6 +165,51 @@ class UserController extends Controller
return $user; return $user;
} }
/**
* @throws RequestException
*/
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:30', 'min:4', 'alpha_num', 'unique:users'],
'email' => ['required', 'string', 'email', 'max:64', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'max:191'],
]);
$user = User::create([
'name' => $request->input('name'),
'email' => $request->input('email'),
'credits' => Configuration::getValueByKey('INITIAL_CREDITS', 150),
'server_limit' => Configuration::getValueByKey('INITIAL_SERVER_LIMIT', 1),
'password' => Hash::make($request->input('password')),
]);
$response = Pterodactyl::client()->post('/application/users', [
"external_id" => App::environment('local') ? Str::random(16) : (string)$user->id,
"username" => $user->name,
"email" => $user->email,
"first_name" => $user->name,
"last_name" => $user->name,
"password" => $request->input('password'),
"root_admin" => false,
"language" => "en"
]);
if ($response->failed()) {
$user->delete();
throw ValidationException::withMessages([
'pterodactyl_error_message' => $response->toException()->getMessage(),
'pterodactyl_error_status' => $response->toException()->getCode()
]);
}
$user->update([
'pterodactyl_id' => $response->json()['attributes']['id']
]);
return $user;
}
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *

View file

@ -3,22 +3,32 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Server;
use App\Models\Voucher; use App\Models\Voucher;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Spatie\QueryBuilder\QueryBuilder;
class VoucherController extends Controller class VoucherController extends Controller
{ {
const ALLOWED_INCLUDES = ['users'];
const ALLOWED_FILTERS = ['code', 'memo', 'credits', 'uses'];
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @return Response * @return LengthAwarePaginator
*/ */
public function index(Request $request) public function index(Request $request)
{ {
return Voucher::paginate($request->query('per_page') ?? 50); $query = QueryBuilder::for(Voucher::class)
->allowedIncludes(self::ALLOWED_INCLUDES)
->allowedFilters(self::ALLOWED_FILTERS);
return $query->paginate($request->input('per_page') ?? 50);
} }
/** /**
@ -40,10 +50,10 @@ class VoucherController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$request->validate([ $request->validate([
'memo' => 'nullable|string|max:191', 'memo' => 'nullable|string|max:191',
'code' => 'required|string|alpha_dash|max:36|min:4|unique:vouchers', 'code' => 'required|string|alpha_dash|max:36|min:4|unique:vouchers',
'uses' => 'required|numeric|max:2147483647|min:1', 'uses' => 'required|numeric|max:2147483647|min:1',
'credits' => 'required|numeric|between:0,99999999', 'credits' => 'required|numeric|between:0,99999999',
'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years' 'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years'
]); ]);
@ -54,11 +64,15 @@ class VoucherController extends Controller
* Display the specified resource. * Display the specified resource.
* *
* @param int $id * @param int $id
* @return Response * @return Collection|Voucher
*/ */
public function show(int $id) public function show(int $id)
{ {
return Voucher::findOrFail($id); $query = QueryBuilder::for(Voucher::class)
->where('id' ,'=' , $id)
->allowedIncludes(self::ALLOWED_INCLUDES);
return $query->get();
} }
/** /**
@ -84,10 +98,10 @@ class VoucherController extends Controller
$voucher = Voucher::findOrFail($id); $voucher = Voucher::findOrFail($id);
$request->validate([ $request->validate([
'memo' => 'nullable|string|max:191', 'memo' => 'nullable|string|max:191',
'code' => "required|string|alpha_dash|max:36|min:4|unique:vouchers,code,{$voucher->id}", 'code' => "required|string|alpha_dash|max:36|min:4|unique:vouchers,code,{$voucher->id}",
'uses' => 'required|numeric|max:2147483647|min:1', 'uses' => 'required|numeric|max:2147483647|min:1',
'credits' => 'required|numeric|between:0,99999999', 'credits' => 'required|numeric|between:0,99999999',
'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years' 'expires_at' => 'nullable|multiple_date_format:d-m-Y H:i:s,d-m-Y|after:now|before:10 years'
]); ]);

View file

@ -20,7 +20,7 @@ use Illuminate\Support\Facades\Route;
Route::middleware('api.token')->group(function () { Route::middleware('api.token')->group(function () {
Route::patch('/users/{user}/increment', [UserController::class, 'increment']); Route::patch('/users/{user}/increment', [UserController::class, 'increment']);
Route::patch('/users/{user}/decrement', [UserController::class, 'decrement']); Route::patch('/users/{user}/decrement', [UserController::class, 'decrement']);
Route::resource('users', UserController::class)->except(['store', 'create']); Route::resource('users', UserController::class)->except(['create']);
Route::patch('/servers/{server}/suspend', [ServerController::class, 'suspend']); Route::patch('/servers/{server}/suspend', [ServerController::class, 'suspend']);
Route::patch('/servers/{server}/unsuspend', [ServerController::class, 'unSuspend']); Route::patch('/servers/{server}/unsuspend', [ServerController::class, 'unSuspend']);