Delete expires ots & add duration to create
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\OneTimeSecret;
|
||||||
|
use Illuminate\Console\Attributes\Signature;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
#[Signature('app:delete-expired-one-time-secrets')]
|
||||||
|
class DeleteExpiredOneTimeSecrets extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
OneTimeSecret::whereDate('expires_at', '<', now())->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum OneTimeSecretDuration: int
|
||||||
|
{
|
||||||
|
case ThirtyMinutes = 1800;
|
||||||
|
case OneHour = 3600;
|
||||||
|
case SixHours = 21_600;
|
||||||
|
case OneDay = 86_400;
|
||||||
|
case ThreeDays = 259_200;
|
||||||
|
case OneWeek = 604_800;
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::ThirtyMinutes => '30 Minutes',
|
||||||
|
self::OneHour => '1 Hour',
|
||||||
|
self::SixHours => '6 Hours',
|
||||||
|
self::OneDay => '1 Day',
|
||||||
|
self::ThreeDays => '3 Days',
|
||||||
|
self::OneWeek => '1 Week',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Enums\OneTimeSecretDuration;
|
||||||
use App\Http\Requests\StoreOTSRequest;
|
use App\Http\Requests\StoreOTSRequest;
|
||||||
use App\Models\OneTimeSecret;
|
use App\Models\OneTimeSecret;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@@ -29,7 +30,10 @@ class OTSController extends Controller
|
|||||||
{
|
{
|
||||||
$validated = $request->validated();
|
$validated = $request->validated();
|
||||||
|
|
||||||
$ots = OneTimeSecret::create(($validated + ['expires_at' => now()->addMinutes(30)]));
|
$ots = OneTimeSecret::create([
|
||||||
|
'secret' => $validated['secret'],
|
||||||
|
'expires_at' => now()->addSeconds($validated['duration']),
|
||||||
|
]);
|
||||||
$otsLink = route('ots.show', ['oneTimeSecret' => $ots->id]);
|
$otsLink = route('ots.show', ['oneTimeSecret' => $ots->id]);
|
||||||
|
|
||||||
return redirect()->route('ots.created')->with(['otsLink' => $otsLink]);
|
return redirect()->route('ots.created')->with(['otsLink' => $otsLink]);
|
||||||
@@ -37,19 +41,31 @@ class OTSController extends Controller
|
|||||||
|
|
||||||
public function create(): Response
|
public function create(): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('ots/Create', []);
|
return Inertia::render('ots/Create', [
|
||||||
|
'durations' => array_map(
|
||||||
|
fn (OneTimeSecretDuration $duration): array => [
|
||||||
|
'value' => $duration->value,
|
||||||
|
'label' => $duration->label(),
|
||||||
|
],
|
||||||
|
OneTimeSecretDuration::cases(),
|
||||||
|
),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(OneTimeSecret $oneTimeSecret): Response
|
public function show(OneTimeSecret $oneTimeSecret): Response|RedirectResponse
|
||||||
{
|
{
|
||||||
|
if ($oneTimeSecret->expires_at->isPast()) {
|
||||||
|
$oneTimeSecret->delete();
|
||||||
|
return redirect()->route('ots.create');
|
||||||
|
}
|
||||||
return Inertia::render('ots/Show', ['oneTimeSecretId' => $oneTimeSecret->id]);
|
return Inertia::render('ots/Show', ['oneTimeSecretId' => $oneTimeSecret->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reveal(OneTimeSecret $oneTimeSecret): JsonResponse
|
public function reveal(OneTimeSecret $oneTimeSecret): JsonResponse|RedirectResponse
|
||||||
{
|
{
|
||||||
$oneTimeSecret->delete();
|
$oneTimeSecret->delete();
|
||||||
if ($oneTimeSecret->expires_at->isPast()) {
|
if ($oneTimeSecret->expires_at->isPast()) {
|
||||||
abort(404);
|
return redirect()->route('ots.create');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['secret' => $oneTimeSecret->secret]);
|
return response()->json(['secret' => $oneTimeSecret->secret]);
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Enums\OneTimeSecretDuration;
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class StoreOTSRequest extends FormRequest
|
class StoreOTSRequest extends FormRequest
|
||||||
{
|
{
|
||||||
@@ -16,6 +18,7 @@ class StoreOTSRequest extends FormRequest
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'secret' => 'required|string',
|
'secret' => 'required|string',
|
||||||
|
'duration' => ['required', 'integer', Rule::enum(OneTimeSecretDuration::class)],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use Carbon\CarbonImmutable;
|
|||||||
use Database\Factories\OneTimeSecretFactory;
|
use Database\Factories\OneTimeSecretFactory;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
@@ -23,7 +22,6 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|||||||
* @property-read User|Team|null $recipient
|
* @property-read User|Team|null $recipient
|
||||||
* @property-read Team|null $team
|
* @property-read Team|null $team
|
||||||
*
|
*
|
||||||
* @method static \Database\Factories\OneTimeSecretFactory factory($count = null, $state = [])
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|OneTimeSecret newModelQuery()
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|OneTimeSecret newModelQuery()
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|OneTimeSecret newQuery()
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|OneTimeSecret newQuery()
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|OneTimeSecret query()
|
* @method static \Illuminate\Database\Eloquent\Builder<static>|OneTimeSecret query()
|
||||||
@@ -41,8 +39,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|||||||
#[Fillable(['secret', 'expires_at'])]
|
#[Fillable(['secret', 'expires_at'])]
|
||||||
class OneTimeSecret extends Model
|
class OneTimeSecret extends Model
|
||||||
{
|
{
|
||||||
/** @use HasFactory<OneTimeSecretFactory> */
|
use HasUuids;
|
||||||
use HasFactory, HasUuids;
|
|
||||||
|
|
||||||
public function team(): BelongsTo
|
public function team(): BelongsTo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import otsRoutes from '@/routes/ots';
|
import otsRoutes from '@/routes/ots';
|
||||||
import { Save } from '@lucide/vue';
|
import {Save} from '@lucide/vue';
|
||||||
import { Head, useForm } from '@inertiajs/vue3';
|
import {Head, useForm} from '@inertiajs/vue3';
|
||||||
import { Button } from '@/components/ui/button';
|
import {Button} from '@/components/ui/button';
|
||||||
import { Label } from '@/components/ui/label';
|
import {Label} from '@/components/ui/label';
|
||||||
import InputError from '@/components/InputError.vue';
|
import InputError from '@/components/InputError.vue';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import {Card, CardContent} from '@/components/ui/card';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import {Textarea} from '@/components/ui/textarea';
|
||||||
|
import {Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
durations: {
|
||||||
|
value: number,
|
||||||
|
label: string
|
||||||
|
}[],
|
||||||
|
}>();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
secret: '',
|
secret: '',
|
||||||
|
duration: props.durations[props.durations.length - 1].value
|
||||||
});
|
});
|
||||||
|
|
||||||
function submit(): void {
|
function submit(): void {
|
||||||
@@ -19,11 +28,11 @@ function submit(): void {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col justify-center p-16">
|
<div class="flex flex-col justify-center p-16">
|
||||||
<Head title="OTS" />
|
<Head title="OTS"/>
|
||||||
<Card class="w-full">
|
<Card class="w-full">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form class="grid w-full gap-5 py-4" @submit.prevent="submit">
|
<form class="grid w-full gap-5 py-4" @submit.prevent="submit">
|
||||||
<div class="grid grid-cols-2 items-start">
|
<div class="grid grid-cols-2 items-start gap-4">
|
||||||
<div class="col-span-2 grid items-start gap-3">
|
<div class="col-span-2 grid items-start gap-3">
|
||||||
<Label for="secret">Secret</Label>
|
<Label for="secret">Secret</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@@ -33,12 +42,28 @@ function submit(): void {
|
|||||||
autofocus
|
autofocus
|
||||||
name="secret"
|
name="secret"
|
||||||
/>
|
/>
|
||||||
<InputError :message="form.errors.secret" />
|
<InputError :message="form.errors.secret"/>
|
||||||
|
</div>
|
||||||
|
<div class="grid items-start gap-3">
|
||||||
|
<Label for="duration">Duration</Label>
|
||||||
|
<Select v-model="form.duration">
|
||||||
|
<SelectTrigger class="w-full">
|
||||||
|
<SelectValue placeholder="Select expires duration" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem v-for="duration in durations" :value="duration.value">
|
||||||
|
{{ duration.label }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<InputError :message="form.errors.duration"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full justify-end">
|
<div class="flex w-full justify-end">
|
||||||
<Button :disabled="form.processing" class="w-fit" type="submit">
|
<Button :disabled="form.processing" class="w-fit" type="submit">
|
||||||
<component :is="Save" />
|
<component :is="Save"/>
|
||||||
Erstellen
|
Erstellen
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-5
@@ -1,8 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Foundation\Inspiring;
|
use App\Console\Commands\DeleteExpiredOneTimeSecrets;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
|
|
||||||
Artisan::command('inspire', function () {
|
Schedule::command(DeleteExpiredOneTimeSecrets::class)->hourly();
|
||||||
$this->comment(Inspiring::quote());
|
|
||||||
})->purpose('Display an inspiring quote');
|
|
||||||
|
|||||||
Reference in New Issue
Block a user