diff --git a/app/Conductors/ShortlinkConductor.php b/app/Conductors/ShortlinkConductor.php new file mode 100644 index 0000000..9a32bcf --- /dev/null +++ b/app/Conductors/ShortlinkConductor.php @@ -0,0 +1,58 @@ +user(); + return ($user !== null && $user->hasPermission('admin/shortlinks') === true); + } + + /** + * Return if the current model is updatable. + * + * @param Model $model The model. + * @return boolean Allow updating model. + */ + public static function updatable(Model $model) + { + $user = auth()->user(); + return ($user !== null && $user->hasPermission('admin/shortlinks') === true); + } + + /** + * Return if the current model is destroyable. + * + * @param Model $model The model. + * @return boolean Allow deleting model. + */ + public static function destroyable(Model $model) + { + $user = auth()->user(); + return ($user !== null && $user->hasPermission('admin/shortlinks') === true); + } +} diff --git a/app/Http/Controllers/Api/ShortlinkController.php b/app/Http/Controllers/Api/ShortlinkController.php new file mode 100644 index 0000000..6cbc898 --- /dev/null +++ b/app/Http/Controllers/Api/ShortlinkController.php @@ -0,0 +1,117 @@ +middleware('auth:sanctum') + ->only(['store','update','destroy']); + } + + /** + * Display a listing of the resource. + * + * @param \Illuminate\Http\Request $request The endpoint request. + * @return \Illuminate\Http\Response + */ + public function index(Request $request) + { + list($collection, $total) = ShortlinkConductor::request($request); + + return $this->respondAsResource( + $collection, + ['isCollection' => true, + 'appendData' => ['total' => $total] + ], + function ($options) { + return $options['total'] === 0; + } + ); + } + + /** + * Display the specified resource. + * + * @param \Illuminate\Http\Request $request The endpoint request. + * @param \App\Models\Shortlink $shortlink The request shortlink. + * @return \Illuminate\Http\Response + */ + public function show(Request $request, Shortlink $shortlink) + { + if (ShortlinkConductor::viewable($shortlink) === true) { + return $this->respondAsResource(ShortlinkConductor::model($request, $shortlink)); + } + + return $this->respondForbidden(); + } + + /** + * Store a new media resource + * + * @param \App\Http\Requests\ShortlinkRequest $request The shortlink. + * @return \Illuminate\Http\Response + */ + public function store(ShortlinkRequest $request) + { + if (ShortlinkConductor::creatable() === true) { + $shortlink = Shortlink::create($request->all()); + + return $this->respondAsResource( + ShortlinkConductor::model($request, $shortlink), + ['respondCode' => HttpResponseCodes::HTTP_ACCEPTED] + ); + }//end if + + return $this->respondForbidden(); + } + + /** + * Update the media resource in storage. + * + * @param \App\Http\Requests\ShortlinkRequest $request The update request. + * @param \App\Models\Shortlink $medium The specified shortlink. + * @return \Illuminate\Http\Response + */ + public function update(ShortlinkRequest $request, Shortlink $shortlink) + { + if (ShortlinkConductor::updatable($shortlink) === true) { + $shortlink->update($request->all()); + return $this->respondAsResource(ShortlinkConductor::model($request, $shortlink)); + }//end if + + return $this->respondForbidden(); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Shortlink $medium Specified shortlink. + * @return \Illuminate\Http\Response + */ + public function destroy(Shortlink $shortlink) + { + if (ShortlinkConductor::destroyable($shortlink) === true) { + $shortlink->delete(); + return $this->respondNoContent(); + } + + return $this->respondForbidden(); + } +} diff --git a/app/Http/Requests/ShortlinkRequest.php b/app/Http/Requests/ShortlinkRequest.php new file mode 100644 index 0000000..6c62c68 --- /dev/null +++ b/app/Http/Requests/ShortlinkRequest.php @@ -0,0 +1,36 @@ + + */ + public function postRules() + { + return [ + 'code' => 'required|string|max:255|min:2|unique:shortlinks', + 'url' => 'required|string|max:255|min:2', + ]; + } + + /** + * Get the validation rules that apply to PUT request. + * + * @return array + */ + public function putRules() + { + $shortlink = $this->route('shortlink'); + + return [ + 'code' => ['required', 'string', 'max:255', 'min:2', Rule::unique('shortlinks')->ignore($shortlink->id)], + 'url' => 'required|string|max:255|min:2', + ]; + } +} diff --git a/app/Models/Shortlink.php b/app/Models/Shortlink.php new file mode 100644 index 0000000..8eb3c53 --- /dev/null +++ b/app/Models/Shortlink.php @@ -0,0 +1,39 @@ + + */ + protected $fillable = [ + 'code', + 'url', + ]; +} diff --git a/resources/js/helpers/api.types.ts b/resources/js/helpers/api.types.ts index 233ee28..b3c0589 100644 --- a/resources/js/helpers/api.types.ts +++ b/resources/js/helpers/api.types.ts @@ -120,3 +120,19 @@ export interface LogsDiscordResponse { error: string; }; } + +export interface Shortlink { + id: number; + code: string; + url: string; + used: number; +} + +export interface ShortlinkCollection { + shortlinks: Array; + total: number; +} + +export interface ShortlinkResponse { + shortlink: Shortlink; +} diff --git a/resources/js/router/index.js b/resources/js/router/index.js index 5b217ec..4399659 100644 --- a/resources/js/router/index.js +++ b/resources/js/router/index.js @@ -345,6 +345,41 @@ export const routes = [ }, ], }, + { + path: "shortlinks", + children: [ + { + path: "", + name: "dashboard-shortlink-list", + meta: { + title: "Shortlink", + middleware: "authenticated", + }, + component: () => + import("@/views/dashboard/ShortlinkList.vue"), + }, + { + path: "create", + name: "dashboard-shortlink-create", + meta: { + title: "Create Shortlink", + middleware: "authenticated", + }, + component: () => + import("@/views/dashboard/ShortlinkEdit.vue"), + }, + { + path: ":id", + name: "dashboard-shortlink-edit", + meta: { + title: "Edit Shortlink", + middleware: "authenticated", + }, + component: () => + import("@/views/dashboard/ShortlinkEdit.vue"), + }, + ], + }, { path: "discord-bot-logs", name: "dashboard-discord-bot-logs", diff --git a/resources/js/views/dashboard/Dashboard.vue b/resources/js/views/dashboard/Dashboard.vue index 0325f63..c241f89 100644 --- a/resources/js/views/dashboard/Dashboard.vue +++ b/resources/js/views/dashboard/Dashboard.vue @@ -56,6 +56,13 @@

Minecraft

--> + + +

Shortlinks

+
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/js/views/dashboard/ShortlinkList.vue b/resources/js/views/dashboard/ShortlinkList.vue new file mode 100644 index 0000000..cf1941c --- /dev/null +++ b/resources/js/views/dashboard/ShortlinkList.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/routes/api.php b/routes/api.php index 4d58c50..4d2a0ab 100644 --- a/routes/api.php +++ b/routes/api.php @@ -9,7 +9,7 @@ use App\Http\Controllers\Api\LogController; use App\Http\Controllers\Api\MediaController; use App\Http\Controllers\Api\OCRController; use App\Http\Controllers\Api\ArticleController; -use App\Http\Controllers\Api\SubscriptionController; +use App\Http\Controllers\Api\ShortlinkController; use App\Http\Controllers\Api\UserController; /* @@ -46,6 +46,8 @@ Route::apiAttachmentResource('events', EventController::class); Route::post('/contact', [ContactController::class, 'send']); +Route::apiResource('/shortlinks', ShortlinkController::class); + Route::get('/logs/{name}', [LogController::class, 'show']); Route::get('/ocr', [OCRController::class, 'show']);