added shortlinks on frontend
This commit is contained in:
58
app/Conductors/ShortlinkConductor.php
Normal file
58
app/Conductors/ShortlinkConductor.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Conductors;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Foundation\Auth\User;
|
||||||
|
|
||||||
|
class ShortlinkConductor extends Conductor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Model Class
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $class = '\App\Models\Shortlink';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default sorting field
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $sort = 'created_at';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is creatable.
|
||||||
|
*
|
||||||
|
* @return boolean Allow creating model.
|
||||||
|
*/
|
||||||
|
public static function creatable()
|
||||||
|
{
|
||||||
|
$user = auth()->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
app/Http/Controllers/Api/ShortlinkController.php
Normal file
117
app/Http/Controllers/Api/ShortlinkController.php
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Conductors\MediaConductor;
|
||||||
|
use App\Conductors\ShortlinkConductor;
|
||||||
|
use App\Enum\HttpResponseCodes;
|
||||||
|
use App\Http\Requests\MediaRequest;
|
||||||
|
use App\Http\Requests\ShortlinkRequest;
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Models\Shortlink;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Laravel\Sanctum\PersonalAccessToken;
|
||||||
|
|
||||||
|
class ShortlinkController extends ApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* ApplicationController constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
36
app/Http/Requests/ShortlinkRequest.php
Normal file
36
app/Http/Requests/ShortlinkRequest.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ShortlinkRequest extends BaseRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Apply the additional POST base rules to this request
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
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<string, mixed>
|
||||||
|
*/
|
||||||
|
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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/Models/Shortlink.php
Normal file
39
app/Models/Shortlink.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enum\HttpResponseCodes;
|
||||||
|
use App\Jobs\MoveMediaJob;
|
||||||
|
use App\Jobs\OptimizeMediaJob;
|
||||||
|
use App\Jobs\StoreUploadedFileJob;
|
||||||
|
use App\Traits\Uuids;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
|
class Shortlink extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'code',
|
||||||
|
'url',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -120,3 +120,19 @@ export interface LogsDiscordResponse {
|
|||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Shortlink {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
url: string;
|
||||||
|
used: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShortlinkCollection {
|
||||||
|
shortlinks: Array<Shortlink>;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShortlinkResponse {
|
||||||
|
shortlink: Shortlink;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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",
|
path: "discord-bot-logs",
|
||||||
name: "dashboard-discord-bot-logs",
|
name: "dashboard-discord-bot-logs",
|
||||||
|
|||||||
@@ -56,6 +56,13 @@
|
|||||||
<img src="/img/minecraft-grass-block.png" />
|
<img src="/img/minecraft-grass-block.png" />
|
||||||
<h3>Minecraft</h3>
|
<h3>Minecraft</h3>
|
||||||
</router-link> -->
|
</router-link> -->
|
||||||
|
<router-link
|
||||||
|
v-if="userStore.permissions.includes('logs/discord')"
|
||||||
|
:to="{ name: 'dashboard-shortlink-list' }"
|
||||||
|
class="admin-card discord">
|
||||||
|
<ion-icon name="link-outline" />
|
||||||
|
<h3>Shortlinks</h3>
|
||||||
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="userStore.permissions.includes('logs/discord')"
|
v-if="userStore.permissions.includes('logs/discord')"
|
||||||
:to="{ name: 'dashboard-discord-bot-logs' }"
|
:to="{ name: 'dashboard-discord-bot-logs' }"
|
||||||
|
|||||||
183
resources/js/views/dashboard/ShortlinkEdit.vue
Normal file
183
resources/js/views/dashboard/ShortlinkEdit.vue
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<template>
|
||||||
|
<SMMastHead
|
||||||
|
:title="pageHeading"
|
||||||
|
:back-link="
|
||||||
|
route.params.id || isCreating
|
||||||
|
? { name: 'dashboard-shortlink-list' }
|
||||||
|
: { name: 'dashboard' }
|
||||||
|
"
|
||||||
|
:back-title="
|
||||||
|
route.params.id || isCreating
|
||||||
|
? 'Back to Shortlinks'
|
||||||
|
: 'Back to Dashboard'
|
||||||
|
" />
|
||||||
|
<SMContainer>
|
||||||
|
<SMForm :model-value="form" @submit="handleSubmit">
|
||||||
|
<SMRow>
|
||||||
|
<SMColumn><SMInput control="code" /></SMColumn>
|
||||||
|
<SMColumn
|
||||||
|
><SMInput type="static" v-model="used" label="Times used"
|
||||||
|
/></SMColumn>
|
||||||
|
</SMRow>
|
||||||
|
<SMRow>
|
||||||
|
<SMColumn><SMInput control="url" /></SMColumn>
|
||||||
|
</SMRow>
|
||||||
|
<SMRow>
|
||||||
|
<SMColumn>
|
||||||
|
<SMButtonRow>
|
||||||
|
<template #right>
|
||||||
|
<SMButton type="submit" :label="saveButtonLabel" />
|
||||||
|
</template>
|
||||||
|
</SMButtonRow>
|
||||||
|
</SMColumn>
|
||||||
|
</SMRow>
|
||||||
|
</SMForm>
|
||||||
|
</SMContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, reactive, ref } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import SMButton from "../../components/SMButton.vue";
|
||||||
|
import SMForm from "../../components/SMForm.vue";
|
||||||
|
import SMInput from "../../components/SMInput.vue";
|
||||||
|
import { api } from "../../helpers/api";
|
||||||
|
import { ShortlinkResponse } from "../../helpers/api.types";
|
||||||
|
import { Form, FormControl } from "../../helpers/form";
|
||||||
|
import { And, Length, Max, Min, Required } from "../../helpers/validate";
|
||||||
|
import SMMastHead from "../../components/SMMastHead.vue";
|
||||||
|
import { useToastStore } from "../../store/ToastStore";
|
||||||
|
import SMButtonRow from "../../components/SMButtonRow.vue";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const isCreating = route.path.endsWith("/create");
|
||||||
|
|
||||||
|
let form = reactive(
|
||||||
|
Form({
|
||||||
|
code: FormControl("", And([Required(), Length(4)])),
|
||||||
|
url: FormControl("", And([Required(), Min(4), Max(255)])),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const used = ref(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the page data.
|
||||||
|
*/
|
||||||
|
const loadData = async () => {
|
||||||
|
if (route.params.id) {
|
||||||
|
try {
|
||||||
|
form.loading(true);
|
||||||
|
const result = await api.get({
|
||||||
|
url: "/shortlinks/{id}",
|
||||||
|
params: {
|
||||||
|
id: route.params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = result.data as ShortlinkResponse;
|
||||||
|
|
||||||
|
if (data && data.shortlink) {
|
||||||
|
form.controls.code.value = data.shortlink.code;
|
||||||
|
form.controls.url.value = data.shortlink.url;
|
||||||
|
used.value = data.shortlink.used;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
form.apiErrors(err);
|
||||||
|
} finally {
|
||||||
|
form.loading(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let foundCode = false;
|
||||||
|
|
||||||
|
while (foundCode == false) {
|
||||||
|
const randomCode = Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substring(2, 6)
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.get({
|
||||||
|
url: "/shortlinks",
|
||||||
|
params: {
|
||||||
|
code: randomCode,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
foundCode = true;
|
||||||
|
if (err.status == 404) {
|
||||||
|
form.controls.code.value = randomCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the user submitting the form.
|
||||||
|
*/
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
form.loading(true);
|
||||||
|
if (isCreating == false) {
|
||||||
|
await api.put({
|
||||||
|
url: "/shortlinks/{id}",
|
||||||
|
params: {
|
||||||
|
id: route.params.id,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
code: form.controls.code.value,
|
||||||
|
url: form.controls.url.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useToastStore().addToast({
|
||||||
|
title: "Shortlink Updated",
|
||||||
|
content: "The shortlink has been updated.",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await api.post({
|
||||||
|
url: "/shortlinks",
|
||||||
|
body: {
|
||||||
|
code: form.controls.code.value,
|
||||||
|
url: form.controls.url.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useToastStore().addToast({
|
||||||
|
title: "Shortlink Created",
|
||||||
|
content: "The shortlink has been created.",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({ name: "dashboard" });
|
||||||
|
} catch (err) {
|
||||||
|
form.apiErrors(err);
|
||||||
|
} finally {
|
||||||
|
form.loading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pageHeading = computed(() => {
|
||||||
|
return route.params.id == null ? "Create Shortlink" : "Edit Shortlink";
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveButtonLabel = computed(() => {
|
||||||
|
return route.params.id == null ? "Create" : "Update";
|
||||||
|
});
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.page-dashboard-account-details {
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
218
resources/js/views/dashboard/ShortlinkList.vue
Normal file
218
resources/js/views/dashboard/ShortlinkList.vue
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
<template>
|
||||||
|
<SMPage permission="admin/users">
|
||||||
|
<SMMastHead
|
||||||
|
title="Shortlinks"
|
||||||
|
:back-link="{ name: 'dashboard' }"
|
||||||
|
back-title="Return to Dashboard" />
|
||||||
|
<SMContainer class="flex-grow-1">
|
||||||
|
<SMToolbar>
|
||||||
|
<SMButton
|
||||||
|
:to="{ name: 'dashboard-shortlink-create' }"
|
||||||
|
type="primary"
|
||||||
|
label="Create Link" />
|
||||||
|
<SMInput
|
||||||
|
v-model="itemSearch"
|
||||||
|
label="Search"
|
||||||
|
class="toolbar-search"
|
||||||
|
@keyup.enter="handleSearch">
|
||||||
|
<template #append>
|
||||||
|
<SMButton
|
||||||
|
type="primary"
|
||||||
|
label="Search"
|
||||||
|
icon="search-outline"
|
||||||
|
@click="handleSearch" />
|
||||||
|
</template>
|
||||||
|
</SMInput>
|
||||||
|
</SMToolbar>
|
||||||
|
<SMLoading large v-if="itemsLoading" />
|
||||||
|
<template v-else>
|
||||||
|
<SMPagination
|
||||||
|
v-if="items.length < itemsTotal"
|
||||||
|
v-model="itemsPage"
|
||||||
|
:total="itemsTotal"
|
||||||
|
:per-page="itemsPerPage" />
|
||||||
|
<SMNoItems v-if="items.length == 0" text="No Links Found" />
|
||||||
|
<SMTable
|
||||||
|
v-if="items.length > 0"
|
||||||
|
:headers="headers"
|
||||||
|
:items="items"
|
||||||
|
@row-click="handleEdit">
|
||||||
|
<template #item-actions="item">
|
||||||
|
<SMButton
|
||||||
|
label="Edit"
|
||||||
|
:dropdown="{
|
||||||
|
delete: 'Delete',
|
||||||
|
}"
|
||||||
|
size="medium"
|
||||||
|
@click="handleActionButton(item, $event)" />
|
||||||
|
</template>
|
||||||
|
</SMTable>
|
||||||
|
</template>
|
||||||
|
</SMContainer>
|
||||||
|
</SMPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { openDialog } from "../../components/SMDialog";
|
||||||
|
import DialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
|
||||||
|
import { api, getApiResultData } from "../../helpers/api";
|
||||||
|
import SMTable from "../../components/SMTable.vue";
|
||||||
|
import SMMastHead from "../../components/SMMastHead.vue";
|
||||||
|
import { useToastStore } from "../../store/ToastStore";
|
||||||
|
import SMNoItems from "../../components/SMNoItems.vue";
|
||||||
|
import SMButton from "../../components/SMButton.vue";
|
||||||
|
import SMInput from "../../components/SMInput.vue";
|
||||||
|
import SMToolbar from "../../components/SMToolbar.vue";
|
||||||
|
import { updateRouterParams } from "../../helpers/url";
|
||||||
|
import { Shortlink, ShortlinkCollection } from "../../helpers/api.types";
|
||||||
|
import SMLoading from "../../components/SMLoading.vue";
|
||||||
|
import SMPagination from "../../components/SMPagination.vue";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const items = ref([]);
|
||||||
|
const itemsLoading = ref(false);
|
||||||
|
const itemSearch = ref((route.query.search as string) || "");
|
||||||
|
const itemsTotal = ref(0);
|
||||||
|
const itemsPerPage = 25;
|
||||||
|
const itemsPage = ref(parseInt((route.query.page as string) || "1"));
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{ text: "Code", value: "code", sortable: true },
|
||||||
|
{ text: "URL", value: "url", sortable: true },
|
||||||
|
{ text: "Used", value: "used", sortable: true },
|
||||||
|
{ text: "Actions", value: "actions" },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch if page number changes.
|
||||||
|
*/
|
||||||
|
watch(itemsPage, () => {
|
||||||
|
handleLoad();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle searching for item.
|
||||||
|
*/
|
||||||
|
const handleSearch = () => {
|
||||||
|
itemsPage.value = 1;
|
||||||
|
handleLoad();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle user selecting option in action button.
|
||||||
|
*
|
||||||
|
* @param {Shortlink} item The item.
|
||||||
|
* @param option
|
||||||
|
*/
|
||||||
|
const handleActionButton = (item: Shortlink, option: string): void => {
|
||||||
|
if (option.length == 0) {
|
||||||
|
handleEdit(item);
|
||||||
|
} else if (option.toLowerCase() == "delete") {
|
||||||
|
handleDelete(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle loading the page and list
|
||||||
|
*/
|
||||||
|
const handleLoad = async () => {
|
||||||
|
itemsLoading.value = true;
|
||||||
|
items.value = [];
|
||||||
|
itemsTotal.value = 0;
|
||||||
|
|
||||||
|
updateRouterParams(router, {
|
||||||
|
search: itemSearch.value,
|
||||||
|
page: itemsPage.value == 1 ? "" : itemsPage.value.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let params = {
|
||||||
|
page: itemsPage.value,
|
||||||
|
limit: itemsPerPage,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (itemSearch.value.length > 0) {
|
||||||
|
params[
|
||||||
|
"filter"
|
||||||
|
] = `code:${itemSearch.value},OR,url:${itemSearch.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = await api.get({
|
||||||
|
url: "/shortlinks",
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
|
||||||
|
const collection = getApiResultData<ShortlinkCollection>(result);
|
||||||
|
items.value = collection.shortlinks;
|
||||||
|
itemsTotal.value = collection.total;
|
||||||
|
} catch (err) {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsLoading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (shortlink: Shortlink) => {
|
||||||
|
router.push({
|
||||||
|
name: "dashboard-shortlink-edit",
|
||||||
|
params: { id: shortlink.id },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (shortlink: Shortlink) => {
|
||||||
|
let result = await openDialog(DialogConfirm, {
|
||||||
|
title: "Delete User?",
|
||||||
|
text: `Are you sure you want to delete the user <strong>${shortlink.code}</strong>?`,
|
||||||
|
cancel: {
|
||||||
|
type: "secondary",
|
||||||
|
label: "Cancel",
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
type: "danger",
|
||||||
|
label: "Delete User",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result == true) {
|
||||||
|
try {
|
||||||
|
await api.delete(`shortlinks${shortlink.id}`);
|
||||||
|
handleLoad();
|
||||||
|
|
||||||
|
useToastStore().addToast({
|
||||||
|
title: "Shortlink Deleted",
|
||||||
|
content: "Shortlink deleted successfully.",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
useToastStore().addToast({
|
||||||
|
title: "Server Error",
|
||||||
|
content:
|
||||||
|
"Shortlink could not be deleted because an error occurred.",
|
||||||
|
type: "danger",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleLoad();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.page-dashboard-shortlink-list {
|
||||||
|
.toolbar-search {
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.page-dashboard-shortlink-list {
|
||||||
|
.toolbar-search {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,7 +9,7 @@ use App\Http\Controllers\Api\LogController;
|
|||||||
use App\Http\Controllers\Api\MediaController;
|
use App\Http\Controllers\Api\MediaController;
|
||||||
use App\Http\Controllers\Api\OCRController;
|
use App\Http\Controllers\Api\OCRController;
|
||||||
use App\Http\Controllers\Api\ArticleController;
|
use App\Http\Controllers\Api\ArticleController;
|
||||||
use App\Http\Controllers\Api\SubscriptionController;
|
use App\Http\Controllers\Api\ShortlinkController;
|
||||||
use App\Http\Controllers\Api\UserController;
|
use App\Http\Controllers\Api\UserController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -46,6 +46,8 @@ Route::apiAttachmentResource('events', EventController::class);
|
|||||||
|
|
||||||
Route::post('/contact', [ContactController::class, 'send']);
|
Route::post('/contact', [ContactController::class, 'send']);
|
||||||
|
|
||||||
|
Route::apiResource('/shortlinks', ShortlinkController::class);
|
||||||
|
|
||||||
Route::get('/logs/{name}', [LogController::class, 'show']);
|
Route::get('/logs/{name}', [LogController::class, 'show']);
|
||||||
Route::get('/ocr', [OCRController::class, 'show']);
|
Route::get('/ocr', [OCRController::class, 'show']);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user