Skip to content

Commit e145e53

Browse files
npm update; multiple methods
1 parent 7ee2f17 commit e145e53

13 files changed

Lines changed: 1476 additions & 995 deletions

File tree

package-lock.json

Lines changed: 1273 additions & 903 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,42 +21,43 @@
2121
"start:prod": "prisma migrate deploy && node server.js"
2222
},
2323
"devDependencies": {
24-
"@eslint/compat": "^2.0.2",
24+
"@eslint/compat": "^2.1.0",
2525
"@eslint/js": "^9.18.0",
26-
"@sveltejs/adapter-node": "^5.5.2",
27-
"@sveltejs/kit": "^2.50.1",
26+
"@sveltejs/adapter-node": "^5.5.4",
27+
"@sveltejs/kit": "^2.60.1",
2828
"@sveltejs/vite-plugin-svelte": "^6.2.4",
2929
"@types/better-sqlite3": "^7.6.13",
3030
"@types/jsdom": "^27.0.0",
31-
"@types/node": "^24.10.9",
31+
"@types/node": "^24.12.4",
3232
"@vitest/browser-playwright": "^4.0.6",
3333
"@vitest/ui": "^4.0.6",
34-
"eslint": "^9.39.2",
34+
"eslint": "^9.39.4",
3535
"eslint-config-prettier": "^10.1.8",
36-
"eslint-plugin-svelte": "^3.14.0",
37-
"globals": "^17.2.0",
38-
"prettier": "^3.8.1",
36+
"eslint-plugin-svelte": "^3.17.1",
37+
"globals": "^17.6.0",
38+
"prettier": "^3.8.3",
3939
"prettier-plugin-prisma": "^5.0.0",
40-
"prettier-plugin-svelte": "^3.4.1",
41-
"svelte": "^5.49.1",
42-
"svelte-check": "^4.3.5",
43-
"typescript": "^5.9.3",
44-
"typescript-eslint": "^8.54.0",
45-
"vite": "^7.3.1",
40+
"prettier-plugin-svelte": "^3.5.2",
41+
"prisma-json-types-generator": "^5.0.0",
42+
"svelte": "^5.55.7",
43+
"svelte-check": "^4.4.8",
44+
"typescript": "^6.0.3",
45+
"typescript-eslint": "^8.59.3",
46+
"vite": "^7.3.3",
4647
"vitest": "^4.0.6",
47-
"vitest-browser-svelte": "^2.0.2"
48+
"vitest-browser-svelte": "^2.1.1"
4849
},
4950
"dependencies": {
50-
"@prisma/adapter-better-sqlite3": "^7.3.0",
51-
"@prisma/client": "^7.3.0",
51+
"@prisma/adapter-better-sqlite3": "^7.8.0",
52+
"@prisma/client": "^7.8.0",
5253
"argon2": "^0.44.0",
53-
"better-sqlite3": "^12.6.2",
54-
"prisma": "^7.3.0",
54+
"better-sqlite3": "^12.10.0",
55+
"prisma": "^7.8.0",
5556
"socket.io": "^4.8.3",
5657
"socket.io-client": "^4.8.3",
5758
"ulid": "^3.0.2",
58-
"valibot": "^1.2.0",
59-
"zod": "^4.3.6"
59+
"valibot": "^1.4.0",
60+
"zod": "^4.4.3"
6061
},
6162
"volta": {
6263
"node": "24.11.1"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `method` on the `webhook_endpoints` table. All the data in the column will be lost.
5+
6+
*/
7+
-- RedefineTables
8+
PRAGMA defer_foreign_keys=ON;
9+
PRAGMA foreign_keys=OFF;
10+
CREATE TABLE "new_webhook_endpoints" (
11+
"id" TEXT NOT NULL PRIMARY KEY,
12+
"user_id" TEXT NOT NULL,
13+
"target" TEXT NOT NULL,
14+
"methods" JSONB NOT NULL DEFAULT [],
15+
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
16+
CONSTRAINT "webhook_endpoints_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
17+
);
18+
INSERT INTO "new_webhook_endpoints" ("created_at", "id", "target", "user_id") SELECT "created_at", "id", "target", "user_id" FROM "webhook_endpoints";
19+
DROP TABLE "webhook_endpoints";
20+
ALTER TABLE "new_webhook_endpoints" RENAME TO "webhook_endpoints";
21+
PRAGMA foreign_keys=ON;
22+
PRAGMA defer_foreign_keys=OFF;

prisma/schema.prisma

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ generator client {
33
output = "../generated/prisma/"
44
}
55

6+
generator json {
7+
provider = "prisma-json-types-generator"
8+
}
9+
610
datasource db {
711
provider = "sqlite"
812
}
@@ -59,7 +63,8 @@ model WebhookEndpoint {
5963
id String @id
6064
userId String @map("user_id")
6165
target String
62-
method String
66+
/// [WebhookEndpointMethods]
67+
methods Json @default("[]")
6368
createdAt DateTime @default(now()) @map("created_at")
6469
6570
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

src/lib/components/EndpointEditModal.svelte

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import CloseIcon from '../icons/CloseIcon.svelte';
44
import { updateEndpoint } from '../../routes/webhooks.remote.js';
55
import type { Endpoint } from '../shared/types.js';
6-
import { HTTP_METHODS } from '../constants';
6+
import { HTTP_METHODS, type HttpMethod } from '../constants';
77
88
let { onendpointUpdated = () => {} } = $props<{
99
onendpointUpdated?: (endpoint: Endpoint) => void;
@@ -12,7 +12,7 @@
1212
let dialog: HTMLDialogElement;
1313
let loading = $state(false);
1414
let editTarget = $state('');
15-
let editMethod = $state('');
15+
let editMethods = $state<HttpMethod[]>([]);
1616
let currentEndpoint = $state<Endpoint | null>(null);
1717
1818
$effect(() => {
@@ -26,15 +26,23 @@
2626
export function openModal(endpoint: Endpoint) {
2727
currentEndpoint = endpoint;
2828
editTarget = endpoint.target;
29-
editMethod = endpoint.method;
29+
editMethods = [...endpoint.methods];
3030
view.set(VIEW.EDIT_WEBHOOK);
3131
}
3232
3333
function closeModal() {
3434
view.set(VIEW.LANDING);
3535
currentEndpoint = null;
3636
editTarget = '';
37-
editMethod = '';
37+
editMethods = [];
38+
}
39+
40+
function toggleMethod(method: HttpMethod) {
41+
if (editMethods.includes(method)) {
42+
editMethods = editMethods.filter((m) => m !== method);
43+
} else {
44+
editMethods = [...editMethods, method];
45+
}
3846
}
3947
4048
async function handleSave() {
@@ -44,7 +52,7 @@
4452
const updated = await updateEndpoint({
4553
id: currentEndpoint.id,
4654
target: editTarget,
47-
method: editMethod
55+
methods: editMethods
4856
});
4957
onendpointUpdated(updated);
5058
closeModal();
@@ -64,29 +72,32 @@
6472
<h1>webhook edit</h1>
6573
{#if currentEndpoint}
6674
<label>
67-
target url
75+
<span>target url</span>
6876
<input
6977
type="text"
7078
bind:value={editTarget}
7179
placeholder="http://localhost:3000/api/webhook"
72-
/>
73-
</label>
80+
/></label
81+
>
82+
7483
<div class="method-label">
75-
<p>http method</p>
84+
<p>http method(s)</p>
7685
<div class="method-buttons">
7786
{#each HTTP_METHODS as method (method)}
78-
{#if method !== editMethod}
79-
<button type="button" class="btn-secondary" onclick={() => (editMethod = method)}>
80-
{method}
81-
</button>
82-
{:else}
83-
<p class="btn">{editMethod}</p>
84-
{/if}
87+
{@const selected = editMethods.includes(method)}
88+
<button
89+
type="button"
90+
class:btn-secondary={!selected}
91+
class:btn={selected}
92+
onclick={() => toggleMethod(method)}
93+
>
94+
{method}
95+
</button>
8596
{/each}
8697
</div>
8798
</div>
8899
<div class="modal-buttons">
89-
<button onclick={handleSave} disabled={loading}>Save</button>
100+
<button onclick={handleSave} disabled={loading || editMethods.length === 0}>Save</button>
90101
<button class="btn-secondary" onclick={closeModal}>Cancel</button>
91102
</div>
92103
{/if}
@@ -124,13 +135,17 @@
124135
font-size: 14px;
125136
}
126137
138+
label span,
139+
.method-label p {
140+
margin-bottom: 0.25rem;
141+
}
142+
127143
.method-buttons {
128144
display: flex;
129145
justify-content: space-between;
130146
gap: 0.5rem;
131147
}
132148
133-
.method-buttons p.btn,
134149
button {
135150
box-sizing: border-box;
136151
font-size: 14px;

src/lib/components/EndpointListItem.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<li>
1414
<span>{endpoint.url}</span>
15-
<span class="method">{endpoint.method}</span>
15+
<span class="method">{endpoint.methods.join(', ')}</span>
1616
<span class="target">→ {endpoint.target}</span>
1717
<button class="btn-secondary" onclick={() => navigator.clipboard.writeText(endpoint.url)}
1818
>Copy URL</button
@@ -38,7 +38,7 @@
3838
& .method {
3939
font-weight: bold;
4040
color: var(--turqoise);
41-
width: 4.5ch;
41+
white-space: nowrap;
4242
}
4343
4444
& .target {

src/lib/components/MethodDropdown.svelte

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<script lang="ts">
2-
import { HTTP_METHODS } from '../constants';
2+
import { HTTP_METHODS, type HttpMethod } from '../constants';
33
44
interface Props {
5-
value: string;
6-
onchange: (value: string) => void;
5+
value: HttpMethod[];
6+
onchange: (value: HttpMethod[]) => void;
77
}
88
99
let { value = $bindable(), onchange }: Props = $props();
@@ -14,10 +14,13 @@
1414
isOpen = !isOpen;
1515
}
1616
17-
function selectMethod(method: string) {
18-
value = method;
19-
isOpen = false;
20-
onchange(method);
17+
function toggleMethod(method: HttpMethod) {
18+
if (value.includes(method)) {
19+
value = value.filter((m) => m !== method);
20+
} else {
21+
value = [...value, method];
22+
}
23+
onchange(value);
2124
}
2225
2326
function handleClickOutside(event: MouseEvent) {
@@ -26,26 +29,39 @@
2629
isOpen = false;
2730
}
2831
}
32+
33+
let triggerLabel = $derived.by(() => {
34+
if (value.length === 0) return 'Select methods';
35+
if (value.length === HTTP_METHODS.length) return 'All methods';
36+
return value.join(', ');
37+
});
2938
</script>
3039

3140
<svelte:window onclick={handleClickOutside} />
3241

3342
<div class="dropdown">
3443
<button type="button" class="dropdown-trigger" onclick={toggleDropdown}>
35-
{value}
44+
<span class="label">{triggerLabel}</span>
3645
<span class="arrow" class:open={isOpen}>▼</span>
3746
</button>
3847

3948
{#if isOpen}
4049
<ul class="dropdown-menu">
4150
{#each HTTP_METHODS as method (method)}
51+
{@const selected = value.includes(method)}
4252
<li>
4353
<button
4454
type="button"
4555
class="dropdown-item"
46-
class:selected={method === value}
47-
onclick={() => selectMethod(method)}
56+
class:selected
57+
onclick={() => toggleMethod(method)}
4858
>
59+
<input
60+
type="checkbox"
61+
checked={selected}
62+
tabindex="-1"
63+
onclick={(event) => event.preventDefault()}
64+
/>
4965
{method}
5066
</button>
5167
</li>
@@ -71,7 +87,7 @@
7187
display: flex;
7288
align-items: center;
7389
gap: 0.5rem;
74-
min-width: 120px;
90+
min-width: 160px;
7591
justify-content: space-between;
7692
transition: var(--color-transition);
7793
}
@@ -81,6 +97,12 @@
8197
color: var(--offwhite);
8298
}
8399
100+
.label {
101+
overflow: hidden;
102+
text-overflow: ellipsis;
103+
white-space: nowrap;
104+
}
105+
84106
.arrow {
85107
font-size: 0.7em;
86108
transition: transform 0.2s ease-out;
@@ -114,6 +136,9 @@
114136
width: 100%;
115137
text-align: left;
116138
cursor: pointer;
139+
display: flex;
140+
align-items: center;
141+
gap: 0.5rem;
117142
transition: var(--color-transition);
118143
}
119144
@@ -126,4 +151,9 @@
126151
background-color: var(--blue);
127152
color: var(--offwhite);
128153
}
154+
155+
.dropdown-item input[type='checkbox'] {
156+
pointer-events: none;
157+
accent-color: var(--blue);
158+
}
129159
</style>

src/lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as const;
2+
export type HttpMethod = (typeof HTTP_METHODS)[number];

0 commit comments

Comments
 (0)