added created view and confirm show
CI/CD / test_and_check_app (push) Failing after 48s
CI/CD / Publish immutable image (push) Skipped

This commit is contained in:
2026-10-10 23:29:45 +02:00
parent 095ce60d8f
commit 8fadceabed
8 changed files with 151 additions and 33 deletions
+29 -5
View File
@@ -4,30 +4,54 @@ namespace App\Http\Controllers;
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\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
class OTSController extends Controller class OTSController extends Controller
{ {
public function created(Request $request)
{
$link = $request->session()->get('otsLink');
$request->session()->remove('otsLink');
if (! $link) {
return redirect()->route('ots.create');
}
return Inertia::render('ots/Created', [
'otsLink' => $link,
]);
}
public function store(StoreOTSRequest $request): RedirectResponse public function store(StoreOTSRequest $request): RedirectResponse
{ {
$validated = $request->validated(); $validated = $request->validated();
OneTimeSecret::create(($validated + ['expires_at' => now()->addMinutes(30)])); $ots = OneTimeSecret::create(($validated + ['expires_at' => now()->addMinutes(30)]));
$otsLink = route('ots.show', ['oneTimeSecret' => $ots->id]);
return back(); return redirect()->route('ots.created')->with(['otsLink' => $otsLink]);
} }
public function create(): Response public function create(): Response
{ {
return Inertia::render('ots/Create'); return Inertia::render('ots/Create', []);
} }
public function show(OneTimeSecret $oneTimeSecret): Response public function show(OneTimeSecret $oneTimeSecret): Response
{ {
$oneTimeSecret->delete(); return Inertia::render('ots/Show', ['oneTimeSecretId' => $oneTimeSecret->id]);
}
return Inertia::render('ots/Show', ['oneTimeSecret' => $oneTimeSecret]); public function reveal(OneTimeSecret $oneTimeSecret): JsonResponse
{
$oneTimeSecret->delete();
if ($oneTimeSecret->expires_at->isPast()) {
abort(404);
}
return response()->json(['secret' => $oneTimeSecret->secret]);
} }
} }
+1 -1
View File
@@ -80,7 +80,7 @@ class TeamController extends Controller
public function create(): Response public function create(): Response
{ {
return Inertia::render('team/Create'); return Inertia::render('team/Create', []);
} }
/** /**
+27 -19
View File
@@ -203,6 +203,14 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
brace-expansion@>=2.0.0 <2.1.5: ^2.1.5
brace-expansion@>=2.0.0 <2.1.6: ^2.1.6
shell-quote@>=1.8.4 <1.11.0: ^1.11.0
source-map-js@>=1.0.0 <1.2.2: ^1.2.2
tinypool@<2.1.2: ^2.1.2
tinypool@<=2.1.0: ^2.1.1
importers: importers:
.: .:
@@ -1359,8 +1367,8 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.1.4: brace-expansion@2.1.7:
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
chai@6.2.2: chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
@@ -1846,8 +1854,8 @@ packages:
scheduler@0.27.0: scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
shell-quote@1.9.0: shell-quote@1.12.0:
resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} resolution: {integrity: sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
siginfo@2.0.0: siginfo@2.0.0:
@@ -1864,8 +1872,8 @@ packages:
resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==} resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==}
engines: {node: '>=22'} engines: {node: '>=22'}
source-map-js@1.2.1: source-map-js@1.2.2:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
stack-utils@2.0.6: stack-utils@2.0.6:
@@ -1923,8 +1931,8 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
tinypool@2.1.0: tinypool@2.2.0:
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==}
engines: {node: ^20.0.0 || >=22.0.0} engines: {node: ^20.0.0 || >=22.0.0}
tinyrainbow@3.1.1: tinyrainbow@3.1.1:
@@ -2493,7 +2501,7 @@ snapshots:
jiti: 2.7.0 jiti: 2.7.0
lightningcss: 1.32.0 lightningcss: 1.32.0
magic-string: 0.30.21 magic-string: 0.30.21
source-map-js: 1.2.1 source-map-js: 1.2.2
tailwindcss: 4.3.3 tailwindcss: 4.3.3
'@tailwindcss/oxide-android-arm64@4.3.3': '@tailwindcss/oxide-android-arm64@4.3.3':
@@ -2735,7 +2743,7 @@ snapshots:
'@vue/shared': 3.5.42 '@vue/shared': 3.5.42
entities: 7.0.1 entities: 7.0.1
estree-walker: 2.0.2 estree-walker: 2.0.2
source-map-js: 1.2.1 source-map-js: 1.2.2
'@vue/compiler-dom@3.5.42': '@vue/compiler-dom@3.5.42':
dependencies: dependencies:
@@ -2752,7 +2760,7 @@ snapshots:
estree-walker: 2.0.2 estree-walker: 2.0.2
magic-string: 0.30.21 magic-string: 0.30.21
postcss: 8.5.28 postcss: 8.5.28
source-map-js: 1.2.1 source-map-js: 1.2.2
'@vue/compiler-ssr@3.5.42': '@vue/compiler-ssr@3.5.42':
dependencies: dependencies:
@@ -2929,7 +2937,7 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
brace-expansion@2.1.4: brace-expansion@2.1.7:
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
@@ -2977,7 +2985,7 @@ snapshots:
dependencies: dependencies:
chalk: 5.6.2 chalk: 5.6.2
rxjs: 7.8.2 rxjs: 7.8.2
shell-quote: 1.9.0 shell-quote: 1.12.0
supports-color: 10.2.2 supports-color: 10.2.2
tree-kill: 1.2.2 tree-kill: 1.2.2
yargs: 18.0.0 yargs: 18.0.0
@@ -3215,7 +3223,7 @@ snapshots:
minimatch@9.0.9: minimatch@9.0.9:
dependencies: dependencies:
brace-expansion: 2.1.4 brace-expansion: 2.1.7
mrmime@2.0.1: {} mrmime@2.0.1: {}
@@ -3234,7 +3242,7 @@ snapshots:
oxfmt@0.64.0(vite-plus@0.3.0(@types/node@22.20.2)(jiti@2.7.0)(typescript@5.9.3)(vite@8.2.2(@types/node@22.20.2)(jiti@2.7.0))): oxfmt@0.64.0(vite-plus@0.3.0(@types/node@22.20.2)(jiti@2.7.0)(typescript@5.9.3)(vite@8.2.2(@types/node@22.20.2)(jiti@2.7.0))):
dependencies: dependencies:
tinypool: 2.1.0 tinypool: 2.2.0
optionalDependencies: optionalDependencies:
'@oxfmt/binding-android-arm-eabi': 0.64.0 '@oxfmt/binding-android-arm-eabi': 0.64.0
'@oxfmt/binding-android-arm64': 0.64.0 '@oxfmt/binding-android-arm64': 0.64.0
@@ -3309,7 +3317,7 @@ snapshots:
dependencies: dependencies:
nanoid: 3.3.18 nanoid: 3.3.18
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.2
pretty-format@27.5.1: pretty-format@27.5.1:
dependencies: dependencies:
@@ -3378,7 +3386,7 @@ snapshots:
scheduler@0.27.0: scheduler@0.27.0:
optional: true optional: true
shell-quote@1.9.0: {} shell-quote@1.12.0: {}
siginfo@2.0.0: {} siginfo@2.0.0: {}
@@ -3397,7 +3405,7 @@ snapshots:
is-fullwidth-code-point: 5.1.0 is-fullwidth-code-point: 5.1.0
optional: true optional: true
source-map-js@1.2.1: {} source-map-js@1.2.2: {}
stack-utils@2.0.6: stack-utils@2.0.6:
dependencies: dependencies:
@@ -3447,7 +3455,7 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.7) fdir: 6.5.0(picomatch@4.0.7)
picomatch: 4.0.7 picomatch: 4.0.7
tinypool@2.1.0: {} tinypool@2.2.0: {}
tinyrainbow@3.1.1: {} tinyrainbow@3.1.1: {}
+8
View File
@@ -3,5 +3,13 @@ packages:
ignoreScripts: true ignoreScripts: true
overrides:
brace-expansion@>=2.0.0 <2.1.5: ^2.1.5
brace-expansion@>=2.0.0 <2.1.6: ^2.1.6
shell-quote@>=1.8.4 <1.11.0: ^1.11.0
source-map-js@>=1.0.0 <1.2.2: ^1.2.2
tinypool@<2.1.2: ^2.1.2
tinypool@<=2.1.0: ^2.1.1
publicHoistPattern: publicHoistPattern:
- '@inertiajs/core' - '@inertiajs/core'
+31
View File
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { Head } from '@inertiajs/vue3';
import { Card, CardContent, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
const props = defineProps<{
otsLink: string;
}>();
const copyLink = async () => {
await navigator.clipboard.writeText(props.otsLink);
};
</script>
<template>
<div class="flex flex-col justify-center p-16">
<Head title="OTS" />
<Card class="w-full">
<CardContent>
<CardTitle class="mb-8 text-center text-2xl">
<h1>Erstellt!</h1>
</CardTitle>
<div class="flex gap-4">
<Input :model-value="otsLink" name="ots_link" readonly />
<Button variant="secondary" @click="copyLink">Kopieren</Button>
</div>
</CardContent>
</Card>
</div>
</template>
+51 -5
View File
@@ -1,19 +1,46 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Head } from '@inertiajs/vue3'; import otsRoutes from '@/routes/ots';
import { Head, useHttp } from '@inertiajs/vue3';
import type { OTS } from '@/types/models'; import type { OTS } from '@/types/models';
import { Card, CardContent } from '@/components/ui/card'; import {
Card,
CardAction,
CardContent,
CardDescription,
CardTitle,
} from '@/components/ui/card';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { ref } from 'vue';
import { Button } from '@/components/ui/button';
defineProps<{ const props = defineProps<{
oneTimeSecret: OTS; oneTimeSecretId: string;
}>(); }>();
const oneTimeSecret = ref<OTS | null>(null);
const loadingSecret = ref<boolean>(false);
const confirmShow = async () => {
loadingSecret.value = true;
try {
oneTimeSecret.value = await useHttp<{}, { secret: string }>().submit(
otsRoutes.reveal({
oneTimeSecret: props.oneTimeSecretId,
}),
);
} catch (error) {
console.error(error);
} finally {
loadingSecret.value = false;
}
};
</script> </script>
<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 v-if="oneTimeSecret">
<Textarea <Textarea
v-model="oneTimeSecret.secret" v-model="oneTimeSecret.secret"
:rows="10" :rows="10"
@@ -21,6 +48,25 @@ defineProps<{
readonly readonly
/> />
</CardContent> </CardContent>
<CardContent v-else>
<CardTitle class="text-center text-xl">
Möchtest du das Secret Anzeigen?
</CardTitle>
<CardDescription class="text-md mb-8 text-center">
Nach dem Anzeigen des Secrets wird es unwiderruflich gelöscht und kann
nicht noch einmal angezeigt werden!
</CardDescription>
<CardAction class="justify-self-center">
<Button
:disabled="loadingSecret"
class="cursor-pointer"
variant="destructive"
@click="confirmShow"
>
Anzeigen
</Button>
</CardAction>
</CardContent>
</Card> </Card>
</div> </div>
</template> </template>
-1
View File
@@ -8,7 +8,6 @@ export interface Team {
export interface OTS { export interface OTS {
secret: string; secret: string;
expires_at: string;
} }
export type TeamMemberRole = 'owner' | 'admin' | 'member'; export type TeamMemberRole = 'owner' | 'admin' | 'member';
+2
View File
@@ -34,8 +34,10 @@ Route::middleware(['auth'])->group(function () {
Route::name('ots.')->prefix('ots')->group(function () { Route::name('ots.')->prefix('ots')->group(function () {
Route::get('/', [OTSController::class, 'create'])->name('create'); Route::get('/', [OTSController::class, 'create'])->name('create');
Route::get('created', [OTSController::class, 'created'])->name('created');
Route::post('/', [OTSController::class, 'store'])->name('store'); Route::post('/', [OTSController::class, 'store'])->name('store');
Route::get('/{oneTimeSecret}', [OTSController::class, 'show'])->name('show'); Route::get('/{oneTimeSecret}', [OTSController::class, 'show'])->name('show');
Route::post('/{oneTimeSecret}', [OTSController::class, 'reveal'])->name('reveal');
}); });
require __DIR__.'/settings.php'; require __DIR__.'/settings.php';