Delete expires ots & add duration to create
CI/CD / test_and_check_app (push) Failing after 53s
CI/CD / Publish immutable image (push) Skipped

This commit is contained in:
2026-10-13 09:56:53 +02:00
parent 8fadceabed
commit ec93510f4b
7 changed files with 106 additions and 24 deletions
@@ -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();
}
}
+25
View File
@@ -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',
};
}
}
+21 -5
View File
@@ -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]);
+3
View File
@@ -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)],
]; ];
} }
} }
+1 -4
View File
@@ -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
{ {
+26 -1
View File
@@ -7,9 +7,18 @@ 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 {
@@ -23,7 +32,7 @@ function submit(): void {
<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
@@ -35,6 +44,22 @@ function submit(): void {
/> />
<InputError :message="form.errors.secret"/> <InputError :message="form.errors.secret"/>
</div> </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 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">
+2 -5
View File
@@ -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');