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;
use App\Enums\OneTimeSecretDuration;
use App\Http\Requests\StoreOTSRequest;
use App\Models\OneTimeSecret;
use Illuminate\Http\JsonResponse;
@@ -29,7 +30,10 @@ class OTSController extends Controller
{
$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]);
return redirect()->route('ots.created')->with(['otsLink' => $otsLink]);
@@ -37,19 +41,31 @@ class OTSController extends Controller
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]);
}
public function reveal(OneTimeSecret $oneTimeSecret): JsonResponse
public function reveal(OneTimeSecret $oneTimeSecret): JsonResponse|RedirectResponse
{
$oneTimeSecret->delete();
if ($oneTimeSecret->expires_at->isPast()) {
abort(404);
return redirect()->route('ots.create');
}
return response()->json(['secret' => $oneTimeSecret->secret]);
+3
View File
@@ -2,8 +2,10 @@
namespace App\Http\Requests;
use App\Enums\OneTimeSecretDuration;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreOTSRequest extends FormRequest
{
@@ -16,6 +18,7 @@ class StoreOTSRequest extends FormRequest
{
return [
'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 Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
@@ -23,7 +22,6 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
* @property-read User|Team|null $recipient
* @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 newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|OneTimeSecret query()
@@ -41,8 +39,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
#[Fillable(['secret', 'expires_at'])]
class OneTimeSecret extends Model
{
/** @use HasFactory<OneTimeSecretFactory> */
use HasFactory, HasUuids;
use HasUuids;
public function team(): BelongsTo
{
+35 -10
View File
@@ -1,15 +1,24 @@
<script lang="ts" setup>
import otsRoutes from '@/routes/ots';
import { Save } from '@lucide/vue';
import { Head, useForm } from '@inertiajs/vue3';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import {Save} from '@lucide/vue';
import {Head, useForm} from '@inertiajs/vue3';
import {Button} from '@/components/ui/button';
import {Label} from '@/components/ui/label';
import InputError from '@/components/InputError.vue';
import { Card, CardContent } from '@/components/ui/card';
import { Textarea } from '@/components/ui/textarea';
import {Card, CardContent} from '@/components/ui/card';
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({
secret: '',
duration: props.durations[props.durations.length - 1].value
});
function submit(): void {
@@ -19,11 +28,11 @@ function submit(): void {
<template>
<div class="flex flex-col justify-center p-16">
<Head title="OTS" />
<Head title="OTS"/>
<Card class="w-full">
<CardContent>
<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">
<Label for="secret">Secret</Label>
<Textarea
@@ -33,12 +42,28 @@ function submit(): void {
autofocus
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 class="flex w-full justify-end">
<Button :disabled="form.processing" class="w-fit" type="submit">
<component :is="Save" />
<component :is="Save"/>
Erstellen
</Button>
</div>
+2 -5
View File
@@ -1,8 +1,5 @@
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use App\Console\Commands\DeleteExpiredOneTimeSecrets;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
Schedule::command(DeleteExpiredOneTimeSecrets::class)->hourly();