Compare commits

..

2 commits

Author SHA1 Message Date
b6db17e7d8
wip: Remove incompatible react package
Some checks failed
Build / build (pull_request) Successful in 57s
Check formatting / check-formatting (pull_request) Failing after 17s
2025-06-21 14:07:27 +02:00
e91bf96e74
wip: Create some frontend components to test API generation 2025-06-21 14:06:44 +02:00
12 changed files with 140 additions and 44 deletions

3
frontend/.gitignore vendored
View file

@ -20,3 +20,6 @@ pnpm-debug.log*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Generated source files
src/generated/

View file

@ -8,6 +8,7 @@
"build": "run-p type-check \"build-only {@}\" --", "build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview", "preview": "vite preview",
"build-only": "vite build", "build-only": "vite build",
"update-types": "openapi-typescript http://localhost:8080/v3/api-docs.yaml -o src/generated/api-spec.ts",
"type-check": "vue-tsc --build --force", "type-check": "vue-tsc --build --force",
"lint": "eslint . --fix" "lint": "eslint . --fix"
}, },
@ -17,8 +18,7 @@
"roboto-fontface": "*", "roboto-fontface": "*",
"vue": "^3.5.13", "vue": "^3.5.13",
"vuetify": "^3.8.1", "vuetify": "^3.8.1",
"openapi-fetch": "^0.14.0", "openapi-fetch": "^0.14.0"
"openapi-react-query": "^0.5.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.24.0", "@eslint/js": "^9.24.0",

View file

@ -17,9 +17,6 @@ importers:
openapi-fetch: openapi-fetch:
specifier: ^0.14.0 specifier: ^0.14.0
version: 0.14.0 version: 0.14.0
openapi-react-query:
specifier: ^0.5.0
version: 0.5.0(@tanstack/react-query@5.80.10(react@19.1.0))(openapi-fetch@0.14.0)
roboto-fontface: roboto-fontface:
specifier: '*' specifier: '*'
version: 0.10.0 version: 0.10.0
@ -627,14 +624,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@tanstack/query-core@5.80.10':
resolution: {integrity: sha512-mUNQOtzxkjL6jLbyChZoSBP6A5gQDVRUiPvW+/zw/9ftOAz+H754zCj3D8PwnzPKyHzGkQ9JbH48ukhym9LK1Q==}
'@tanstack/react-query@5.80.10':
resolution: {integrity: sha512-6zM098J8sLy9oU60XAdzUlAH4wVzoMVsWUWiiE/Iz4fd67PplxeyL4sw/MPcVJJVhbwGGXCsHn9GrQt2mlAzig==}
peerDependencies:
react: ^18 || ^19
'@tsconfig/node22@22.0.1': '@tsconfig/node22@22.0.1':
resolution: {integrity: sha512-VkgOa3n6jvs1p+r3DiwBqeEwGAwEvnVCg/hIjiANl5IEcqP3G0u5m8cBJspe1t9qjZRlZ7WFgqq5bJrGdgAKMg==} resolution: {integrity: sha512-VkgOa3n6jvs1p+r3DiwBqeEwGAwEvnVCg/hIjiANl5IEcqP3G0u5m8cBJspe1t9qjZRlZ7WFgqq5bJrGdgAKMg==}
@ -1458,12 +1447,6 @@ packages:
openapi-fetch@0.14.0: openapi-fetch@0.14.0:
resolution: {integrity: sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==} resolution: {integrity: sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==}
openapi-react-query@0.5.0:
resolution: {integrity: sha512-VtyqiamsbWsdSWtXmj/fAR+m9nNxztsof6h8ZIsjRj8c8UR/x9AIwHwd60IqwgymmFwo7qfSJQ1ZzMJrtqjQVg==}
peerDependencies:
'@tanstack/react-query': ^5.25.0
openapi-fetch: ^0.14.0
openapi-typescript-helpers@0.0.15: openapi-typescript-helpers@0.0.15:
resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==}
@ -1583,10 +1566,6 @@ packages:
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
read-package-json-fast@4.0.0: read-package-json-fast@4.0.0:
resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
engines: {node: ^18.17.0 || >=20.5.0} engines: {node: ^18.17.0 || >=20.5.0}
@ -2606,13 +2585,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.40.0': '@rollup/rollup-win32-x64-msvc@4.40.0':
optional: true optional: true
'@tanstack/query-core@5.80.10': {}
'@tanstack/react-query@5.80.10(react@19.1.0)':
dependencies:
'@tanstack/query-core': 5.80.10
react: 19.1.0
'@tsconfig/node22@22.0.1': {} '@tsconfig/node22@22.0.1': {}
'@types/cookie@0.6.0': {} '@types/cookie@0.6.0': {}
@ -3543,12 +3515,6 @@ snapshots:
dependencies: dependencies:
openapi-typescript-helpers: 0.0.15 openapi-typescript-helpers: 0.0.15
openapi-react-query@0.5.0(@tanstack/react-query@5.80.10(react@19.1.0))(openapi-fetch@0.14.0):
dependencies:
'@tanstack/react-query': 5.80.10(react@19.1.0)
openapi-fetch: 0.14.0
openapi-typescript-helpers: 0.0.15
openapi-typescript-helpers@0.0.15: {} openapi-typescript-helpers@0.0.15: {}
openapi-typescript@7.8.0(typescript@5.8.3): openapi-typescript@7.8.0(typescript@5.8.3):
@ -3668,8 +3634,6 @@ snapshots:
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
react@19.1.0: {}
read-package-json-fast@4.0.0: read-package-json-fast@4.0.0:
dependencies: dependencies:
json-parse-even-better-errors: 4.0.0 json-parse-even-better-errors: 4.0.0

View file

@ -39,7 +39,7 @@
rounded="lg" rounded="lg"
title="Manage your certificates" title="Manage your certificates"
variant="text" variant="text"
to="/cert-request" to="/certificates"
/> />
</v-col> </v-col>

View file

@ -0,0 +1,34 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { fetchClient, type Schemas } from "@/plugins/client.ts";
import { useRoute } from "vue-router";
const route = useRoute();
const certificate = ref<Schemas["Certificate"] | null>(null);
async function getCertificate() {
const fingerprint = route.params.fingerprint as string;
const response = await fetchClient.GET("/api/certificates/{fingerprint}", {
params: {
path: {
fingerprint,
}
}
});
certificate.value = response.data ?? null;
}
onMounted(() => {
getCertificate()
});
</script>
<template>
<main>
<form v-if="certificate">
<h2>Details des Zertifikats: {{ certificate.fingerprint }}</h2>
</form>
</main>
</template>

View file

@ -0,0 +1,49 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { fetchClient, type Schemas } from "@/plugins/client.ts";
const certificates = ref<Schemas["Certificate"][]>([]);
async function getCertificates() {
const response = await fetchClient.GET("/api/certificates");
certificates.value = response.data?.content ?? [];
}
onMounted(() => {
getCertificates();
});
</script>
<template>
<main>
<h2>Currently known certificates</h2>
<section id="no-certs-found" v-if="certificates.length === 0">
<v-row>
<v-col cols="12" class="text-center">
<h3>There seems to be nothing in here.</h3>
<p>Why don't you start with one of the following options.</p>
<v-row>
<v-col cols="12" md="6">
<v-btn primary>Request new certificate</v-btn>
</v-col>
<v-col cols="12" md="6">
<v-btn>Import an existing certificate</v-btn>
</v-col>
</v-row>
</v-col>
</v-row>
</section>
<section aria-label="Bekannte Zertifikate">
<ul v-if="certificates.length > 0">
<li v-for="cert in certificates" :key="cert.fingerprint">
<p>{{ cert.fingerprint }}</p>
</li>
</ul>
</section>
</main>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
<h2>New certificate</h2>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,18 @@
import createFetchClient from "openapi-fetch";
import type { components, paths } from "@/generated/api-spec.ts";
import type { App } from "vue";
export const fetchClient = createFetchClient<paths>({
baseUrl: "http://localhost:8080"
});
export type Schemas = components["schemas"];
// noinspection JSUnusedGlobalSymbols
const $api = {
install: (app: App) => {
app.config.globalProperties.$api = fetchClient;
}
}
export default $api;

View file

@ -1,10 +1,12 @@
import vuetify from './vuetify' import vuetify from "./vuetify";
import router from '../router' import router from "../router";
import client from "./client";
import type { App } from 'vue' import type { App } from "vue";
export function registerPlugins (app: App) { export function registerPlugins(app: App) {
app app
.use(vuetify) .use(vuetify)
.use(router) .use(router)
.use(client);
} }

View file

@ -20,5 +20,8 @@ declare module 'vue-router/auto-routes' {
export interface RouteNamedMap { export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>, '/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/cert-request': RouteRecordInfo<'/cert-request', '/cert-request', Record<never, never>, Record<never, never>>, '/cert-request': RouteRecordInfo<'/cert-request', '/cert-request', Record<never, never>, Record<never, never>>,
'/certificates/': RouteRecordInfo<'/certificates/', '/certificates', Record<never, never>, Record<never, never>>,
'/certificates/[fingerprint]': RouteRecordInfo<'/certificates/[fingerprint]', '/certificates/:fingerprint', { fingerprint: ParamValue<true> }, { fingerprint: ParamValue<false> }>,
'/certificates/new': RouteRecordInfo<'/certificates/new', '/certificates/new', Record<never, never>, Record<never, never>>,
} }
} }

View file

@ -16,7 +16,7 @@ public class SecurityConfiguration {
// Allow unauthenticated access to OpenAPI and swagger documentation. // Allow unauthenticated access to OpenAPI and swagger documentation.
// This should be removed or at least configurable at some point, but for now, this is fine (tm) // This should be removed or at least configurable at some point, but for now, this is fine (tm)
http.authorizeHttpRequests(auth -> auth http.authorizeHttpRequests(auth -> auth
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html") .requestMatchers("/v3/api-docs/**", "/v3/api-docs.yaml", "/swagger-ui/**", "/swagger-ui.html")
.permitAll()); .permitAll());
return http.build(); return http.build();
} }

View file

@ -38,6 +38,18 @@ public class CertificatesEndpoint {
return ResponseEntity.ok(DocumentedPage.of(certificates)); return ResponseEntity.ok(DocumentedPage.of(certificates));
} }
@GetMapping("/certificates/{fingerprint}")
@Operation(
description = "Fetches a single certificate by the provided fingerprint",
responses = {
@ApiResponse(responseCode = "200")
}
)
public ResponseEntity<Certificate> getCertificate(@PathVariable String fingerprint) {
var certificate = certificateRepository.findByFingerprintIs(fingerprint);
return ResponseEntity.ok(certificate);
}
@PostMapping("/certificates") @PostMapping("/certificates")
@Operation(description = "Requests a new certificate", responses = { @Operation(description = "Requests a new certificate", responses = {
@ApiResponse(responseCode = "400", description = "One of the provided parameters is invalid."), @ApiResponse(responseCode = "400", description = "One of the provided parameters is invalid."),