mirror of
https://github.com/LukeHagar/OpenAPI.gg.git
synced 2025-12-06 12:37:48 +00:00
refactor path handling
This commit is contained in:
6
TODO.md
6
TODO.md
@@ -1,6 +1,6 @@
|
|||||||
- [x] Add path renaming
|
- [x] Add path renaming
|
||||||
- [x] Add path sub-page for editing a specific route
|
- [x] Add path sub-page for editing a specific route
|
||||||
- [ ] Check that a path variable can only be included once
|
- [x] Check that a path variable can only be included once
|
||||||
- [ ] Add schema editor component
|
- [ ] Add schema editor component
|
||||||
- [ ] Add route-parameters
|
- [ ] Add route-parameters
|
||||||
- [ ] path
|
- [ ] path
|
||||||
@@ -55,6 +55,10 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
- [ ] Replace prompt, alert and confirms with skeleton-modals
|
- [ ] Replace prompt, alert and confirms with skeleton-modals
|
||||||
|
- [x] Create path prompt
|
||||||
|
- [x] Edit path prompt
|
||||||
|
- [x] Rename path prompt
|
||||||
|
- [x] Delete path confirm
|
||||||
- [ ] Add extensions
|
- [ ] Add extensions
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,106 +1,50 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { pathTemplate } from '$lib/pathTemplate';
|
import { pathTemplate } from '$lib/pathTemplate';
|
||||||
import { openApiStore, pathRegex, sortPathsAlphabetically } from '$lib';
|
import {
|
||||||
|
addPath,
|
||||||
|
deletePath,
|
||||||
|
openApiStore,
|
||||||
|
pathRegex,
|
||||||
|
renamePath,
|
||||||
|
sortPathsAlphabetically
|
||||||
|
} from '$lib';
|
||||||
|
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||||
|
const modalStore = getModalStore();
|
||||||
|
|
||||||
/** The actual pathname */
|
/** The actual pathname */
|
||||||
export let pathName: string;
|
export let pathName: string;
|
||||||
/** The index of the pathname in the paths object */
|
/** The index of the pathname in the paths object */
|
||||||
export let id: number;
|
export let id: number;
|
||||||
|
|
||||||
const removeRoute = () => {
|
|
||||||
if (!confirm('Are you sure you want to delete this route?')) return;
|
|
||||||
let tempPathObject = {
|
|
||||||
...$openApiStore.paths
|
|
||||||
};
|
|
||||||
delete tempPathObject[pathName];
|
|
||||||
$openApiStore.paths = tempPathObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addSubRoute = () => {
|
|
||||||
// prompt user to enter path
|
|
||||||
const path = prompt(
|
|
||||||
`Enter path. Wrap path parameters in curly braces.\nIs directly appended to "${pathName}".`
|
|
||||||
);
|
|
||||||
if (!path) return;
|
|
||||||
let newPath = pathName + path;
|
|
||||||
|
|
||||||
// check that path is valid
|
|
||||||
if (!newPath.match(pathRegex)) {
|
|
||||||
alert('Invalid path name');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if path already exists
|
|
||||||
// @ts-expect-error - we are creating a new key
|
|
||||||
if ($openApiStore.paths[newPath]) {
|
|
||||||
alert('Path already exists');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: check if variables are duplicated
|
|
||||||
// add path to paths object
|
|
||||||
$openApiStore.paths = {
|
|
||||||
...$openApiStore.paths,
|
|
||||||
[newPath]: pathTemplate
|
|
||||||
};
|
|
||||||
|
|
||||||
// sort paths object
|
|
||||||
sortPathsAlphabetically();
|
|
||||||
};
|
|
||||||
|
|
||||||
const renamePath = () => {
|
|
||||||
const newName = prompt('Edit the path name', pathName);
|
|
||||||
if (!newName) return;
|
|
||||||
// check that path is valid
|
|
||||||
if (!newName.match(pathRegex)) {
|
|
||||||
alert('Invalid path name');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if path already exists
|
|
||||||
// @ts-expect-error - we are creating a new key
|
|
||||||
if ($openApiStore.paths[newName]) {
|
|
||||||
alert('Path already exists');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if pathName is empty, show error
|
|
||||||
// @ts-expect-error - we are checking if the value is defined
|
|
||||||
if (!$openApiStore.paths[pathName]) {
|
|
||||||
alert('Previous value cannot be empty');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new object with new key and old value
|
|
||||||
$openApiStore.paths = {
|
|
||||||
...$openApiStore.paths,
|
|
||||||
[newName]: $openApiStore.paths![pathName]
|
|
||||||
};
|
|
||||||
|
|
||||||
// delete old key
|
|
||||||
delete $openApiStore.paths[pathName];
|
|
||||||
$openApiStore.paths = {
|
|
||||||
...$openApiStore.paths
|
|
||||||
};
|
|
||||||
|
|
||||||
// sort paths object
|
|
||||||
sortPathsAlphabetically();
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<h3 class="h3">{pathName}</h3>
|
<h3 class="h3">{pathName}</h3>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<a href="/path-{id}" class="btn btn-sm variant-filled-primary">Edit</a>
|
<a href="/path-{id}" class="btn btn-sm variant-filled-primary">Edit</a>
|
||||||
<button type="button" class="btn btn-sm variant-filled-warning" on:click={renamePath}
|
<button
|
||||||
>Rename</button
|
type="button"
|
||||||
|
class="btn btn-sm variant-filled-warning"
|
||||||
|
on:click={() => {
|
||||||
|
renamePath(modalStore, pathName);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Rename
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm variant-filled-secondary"
|
||||||
|
on:click={() => {
|
||||||
|
addPath(modalStore, pathName);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<button type="button" class="btn btn-sm variant-filled-secondary" on:click={addSubRoute}>
|
|
||||||
Add Sub-Route
|
Add Sub-Route
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm variant-ringed-error hover:variant-filled-error"
|
class="btn btn-sm variant-ringed-error hover:variant-filled-error"
|
||||||
on:click={removeRoute}
|
on:click={() => {
|
||||||
|
deletePath(modalStore, pathName);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,40 +1,44 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { addPath } from '$lib';
|
||||||
import { openApiStore, pathRegex, sortPathsAlphabetically } from '$lib';
|
import { openApiStore, pathRegex, sortPathsAlphabetically } from '$lib';
|
||||||
import { pathTemplate } from '$lib/pathTemplate';
|
import { pathTemplate } from '$lib/pathTemplate';
|
||||||
import PathListItem from '../atoms/PathListItem.svelte';
|
import PathListItem from '../atoms/PathListItem.svelte';
|
||||||
|
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||||
|
|
||||||
|
const modalStore = getModalStore();
|
||||||
|
|
||||||
// match path with parameters
|
// match path with parameters
|
||||||
|
|
||||||
// add path
|
// add path
|
||||||
const addPath = () => {
|
// const addPath = () => {
|
||||||
// prompt user to enter path
|
// // prompt user to enter path
|
||||||
const path = prompt(
|
// const path = prompt(
|
||||||
'Enter path. Wrap path parameters in curly braces. Example: /users/{userId}'
|
// 'Enter path. Wrap path parameters in curly braces. Example: /users/{userId}'
|
||||||
);
|
// );
|
||||||
if (!path) return;
|
// if (!path) return;
|
||||||
// check if path is valid
|
// // check if path is valid
|
||||||
if (!pathRegex.test(path)) {
|
// if (!pathRegex.test(path)) {
|
||||||
alert('Invalid path');
|
// alert('Invalid path');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// check if path already exists
|
// // check if path already exists
|
||||||
// @ts-expect-error - we are working with an initially empty object
|
// // @ts-expect-error - we are working with an initially empty object
|
||||||
if ($openApiStore.paths[path]) {
|
// if ($openApiStore.paths[path]) {
|
||||||
alert('Path already exists');
|
// alert('Path already exists');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// create a temporary object to store paths
|
// // create a temporary object to store paths
|
||||||
// add path to paths object
|
// // add path to paths object
|
||||||
$openApiStore.paths = {
|
// $openApiStore.paths = {
|
||||||
...$openApiStore.paths,
|
// ...$openApiStore.paths,
|
||||||
[path]: pathTemplate
|
// [path]: pathTemplate
|
||||||
};
|
// };
|
||||||
|
|
||||||
// sort paths alphabetically
|
// // sort paths alphabetically
|
||||||
sortPathsAlphabetically();
|
// sortPathsAlphabetically();
|
||||||
};
|
// };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -44,7 +48,13 @@
|
|||||||
<PathListItem {pathName} id={index} />
|
<PathListItem {pathName} id={index} />
|
||||||
{/each}
|
{/each}
|
||||||
<span class="w-full flex justify-center">
|
<span class="w-full flex justify-center">
|
||||||
<button type="button" class="btn variant-filled-primary" on:click={addPath}>Add Path</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn variant-filled-primary"
|
||||||
|
on:click={() => {
|
||||||
|
addPath(modalStore);
|
||||||
|
}}>Add Path</button
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
<span class="w-full flex justify-center">
|
<span class="w-full flex justify-center">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { persisted } from 'svelte-persisted-store';
|
import { persisted } from 'svelte-persisted-store';
|
||||||
import type { OpenAPIV3_1 } from './openAPITypes';
|
import type { OpenAPIV3_1 } from './openAPITypes';
|
||||||
import { get } from 'svelte/store';
|
|
||||||
|
|
||||||
export const localStoragePrefix = 'openapigen-';
|
export const localStoragePrefix = 'openapigen-';
|
||||||
|
|
||||||
@@ -40,22 +39,5 @@ export const openApiStore = persisted<OpenAPIV3_1.Document>(`${localStoragePrefi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const pathRegex = /\/([/]*[{]?[a-zA-Z]+[}]?)*/gm;
|
// export path handling functions
|
||||||
|
export * from './pathHandling';
|
||||||
export const sortPathsAlphabetically = () => {
|
|
||||||
const tempPathObject = {};
|
|
||||||
const store = get(openApiStore);
|
|
||||||
// @ts-expect-error - we are working with an initially empty object
|
|
||||||
Object.keys(store.paths)
|
|
||||||
.sort()
|
|
||||||
.forEach((key) => {
|
|
||||||
// @ts-expect-error - we are working with initially empty objects
|
|
||||||
tempPathObject[key] = store.paths[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
// update path object
|
|
||||||
openApiStore.update((data) => {
|
|
||||||
data.paths = tempPathObject;
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
163
src/lib/pathHandling.ts
Normal file
163
src/lib/pathHandling.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { get } from 'svelte/store';
|
||||||
|
import type { ModalSettings, ModalStore } from '@skeletonlabs/skeleton';
|
||||||
|
import { pathTemplate } from './pathTemplate';
|
||||||
|
import { openApiStore } from '$lib';
|
||||||
|
|
||||||
|
export const pathRegex = /\/([/]*[{]?[a-zA-Z]+[}]?)*/gm;
|
||||||
|
|
||||||
|
/// Adds a new path to the OpenAPI document
|
||||||
|
export const addPath = (modalStore: ModalStore, startingPoint: string = '/') => {
|
||||||
|
let userPath: string = startingPoint.trim();
|
||||||
|
|
||||||
|
const modal: ModalSettings = {
|
||||||
|
type: 'prompt',
|
||||||
|
// Data
|
||||||
|
title: 'Enter Pathname',
|
||||||
|
body: 'Wrap path parameters in curly braces. Example: /users/{userId}',
|
||||||
|
// Populates the input value and attributes
|
||||||
|
value: userPath,
|
||||||
|
valueAttr: { type: 'text', minlength: 1, required: true },
|
||||||
|
// Returns the updated response value
|
||||||
|
response: (r: string | boolean | undefined) => {
|
||||||
|
if (r === false) return;
|
||||||
|
if (typeof r !== 'string') return;
|
||||||
|
|
||||||
|
userPath = r;
|
||||||
|
|
||||||
|
// Check if path already exists
|
||||||
|
if (!pathExists(userPath)) {
|
||||||
|
alert('Path already exists');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if path is valid
|
||||||
|
if (!isValidPath(userPath)) {
|
||||||
|
alert('Invalid path');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create path object
|
||||||
|
const store = get(openApiStore);
|
||||||
|
if (!store.paths) store.paths = {};
|
||||||
|
store.paths[userPath] = pathTemplate;
|
||||||
|
// add path to store
|
||||||
|
openApiStore.set(store);
|
||||||
|
|
||||||
|
// sort paths alphabetically
|
||||||
|
sortPathsAlphabetically();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
modalStore.trigger(modal);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renamePath = (modalStore: ModalStore, oldPath: string) => {
|
||||||
|
let userPath: string = oldPath;
|
||||||
|
const modal: ModalSettings = {
|
||||||
|
type: 'prompt',
|
||||||
|
// Data
|
||||||
|
title: 'Rename path',
|
||||||
|
body: 'Wrap path parameters in curly braces. Example: /users/{userId}',
|
||||||
|
// Populates the input value and attributes
|
||||||
|
value: userPath,
|
||||||
|
valueAttr: { type: 'text', minlength: 1, required: true },
|
||||||
|
// Returns the updated response value
|
||||||
|
response: (r: string | boolean | undefined) => {
|
||||||
|
if (r === false) return;
|
||||||
|
if (typeof r !== 'string') return;
|
||||||
|
|
||||||
|
userPath = r;
|
||||||
|
|
||||||
|
// Check if path already exists
|
||||||
|
if (!pathExists(userPath)) {
|
||||||
|
alert('Path already exists');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if path is valid
|
||||||
|
if (!isValidPath(userPath)) {
|
||||||
|
alert('Invalid path');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create path object
|
||||||
|
const store = get(openApiStore);
|
||||||
|
if (!store.paths) store.paths = {};
|
||||||
|
|
||||||
|
// copy old path object to new path object
|
||||||
|
store.paths[userPath] = store.paths[oldPath];
|
||||||
|
// delete old path object
|
||||||
|
delete store.paths[oldPath];
|
||||||
|
|
||||||
|
// add path to store
|
||||||
|
openApiStore.set(store);
|
||||||
|
|
||||||
|
// sort paths alphabetically
|
||||||
|
sortPathsAlphabetically();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
modalStore.trigger(modal);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Removes a path from the OpenAPI document
|
||||||
|
export const deletePath = (modalStore: ModalStore, path: string) => {
|
||||||
|
const modal: ModalSettings = {
|
||||||
|
type: 'confirm',
|
||||||
|
// Data
|
||||||
|
title: `Delete ${path}?`,
|
||||||
|
body: 'Are you sure you wish to delete the path?',
|
||||||
|
// TRUE if confirm pressed, FALSE if cancel pressed
|
||||||
|
response: (r: boolean) => {
|
||||||
|
if (r === false) return;
|
||||||
|
const store = get(openApiStore);
|
||||||
|
// check if path exists
|
||||||
|
if (!store.paths) return;
|
||||||
|
if (!(path in store.paths)) return;
|
||||||
|
delete store.paths[path];
|
||||||
|
openApiStore.set(store);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
modalStore.trigger(modal);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// checks if a given path already exists
|
||||||
|
export const pathExists = (path: string) => {
|
||||||
|
const store = get(openApiStore);
|
||||||
|
return !(path in store.paths!);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// checks if a given path is valid
|
||||||
|
export const isValidPath = (path: string) => {
|
||||||
|
// check if path variables aren't duplicated
|
||||||
|
const variableRegex = /\{([^}]+)\}/gm;
|
||||||
|
const variables = path.match(variableRegex);
|
||||||
|
if (variables) {
|
||||||
|
const uniqueVariables = new Set(variables);
|
||||||
|
if (variables.length !== uniqueVariables.size) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if path is a valid path
|
||||||
|
const pathWithoutVariables = path.replace('{', '').replace('}', '');
|
||||||
|
const pathRegex = /(\/[a-zA-Z-]+)+|\//gm;
|
||||||
|
const pathParts = pathWithoutVariables.match(pathRegex);
|
||||||
|
// the fallback is to return false if the pathParts array is null
|
||||||
|
return pathParts?.length === 1 ?? false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// sorts the paths in the OpenAPI document alphabetically
|
||||||
|
export const sortPathsAlphabetically = () => {
|
||||||
|
const tempPathObject = {};
|
||||||
|
const store = get(openApiStore);
|
||||||
|
// @ts-expect-error - we are working with an initially empty object
|
||||||
|
Object.keys(store.paths)
|
||||||
|
.sort()
|
||||||
|
.forEach((key) => {
|
||||||
|
// @ts-expect-error - we are working with initially empty objects
|
||||||
|
tempPathObject[key] = store.paths[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
// update path object
|
||||||
|
openApiStore.update((data) => {
|
||||||
|
data.paths = tempPathObject;
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -7,8 +7,14 @@
|
|||||||
import { storePopup } from '@skeletonlabs/skeleton';
|
import { storePopup } from '@skeletonlabs/skeleton';
|
||||||
import { localStoragePrefix } from '$lib';
|
import { localStoragePrefix } from '$lib';
|
||||||
storePopup.set({ computePosition, autoUpdate, flip, shift, offset, arrow });
|
storePopup.set({ computePosition, autoUpdate, flip, shift, offset, arrow });
|
||||||
|
|
||||||
|
// Modal
|
||||||
|
import { initializeStores, Modal } from '@skeletonlabs/skeleton';
|
||||||
|
initializeStores();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Modal />
|
||||||
|
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<AppBar>
|
<AppBar>
|
||||||
<svelte:fragment slot="lead">
|
<svelte:fragment slot="lead">
|
||||||
|
|||||||
Reference in New Issue
Block a user