Fix P2 issues
This commit is contained in:
@@ -2,8 +2,8 @@ import { useState, useEffect, useMemo, type FormEvent } from 'react';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Modal } from '../ui/Modal';
|
import { Modal } from '../ui/Modal';
|
||||||
import { Button } from '../ui/Button';
|
import { Button } from '../ui/Button';
|
||||||
|
import { ConfirmDialog } from '../ui/ConfirmDialog';
|
||||||
import { useRackStore } from '../../store/useRackStore';
|
import { useRackStore } from '../../store/useRackStore';
|
||||||
import type { Connection } from '../../types';
|
|
||||||
|
|
||||||
interface ConnectionConfigModalProps {
|
interface ConnectionConfigModalProps {
|
||||||
connectionId: string | null;
|
connectionId: string | null;
|
||||||
@@ -29,8 +29,9 @@ const PRESET_COLORS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function ConnectionConfigModal({ connectionId, open, onClose }: ConnectionConfigModalProps) {
|
export function ConnectionConfigModal({ connectionId, open, onClose }: ConnectionConfigModalProps) {
|
||||||
const { racks, setCablingFromPortId, updateConnection, deleteConnection } = useRackStore();
|
const { racks, updateConnection, deleteConnection } = useRackStore();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||||
|
|
||||||
// Synchronously find the connection from the global store
|
// Synchronously find the connection from the global store
|
||||||
const connection = useMemo(() => {
|
const connection = useMemo(() => {
|
||||||
@@ -77,11 +78,11 @@ export function ConnectionConfigModal({ connectionId, open, onClose }: Connectio
|
|||||||
|
|
||||||
async function handleDelete() {
|
async function handleDelete() {
|
||||||
if (!connectionId) return;
|
if (!connectionId) return;
|
||||||
if (!confirm('Are you sure you want to remove this connection?')) return;
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await deleteConnection(connectionId);
|
await deleteConnection(connectionId);
|
||||||
toast.success('Connection removed');
|
toast.success('Connection removed');
|
||||||
|
setConfirmDeleteOpen(false);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err instanceof Error ? err.message : 'Delete failed');
|
toast.error(err instanceof Error ? err.message : 'Delete failed');
|
||||||
@@ -93,89 +94,107 @@ export function ConnectionConfigModal({ connectionId, open, onClose }: Connectio
|
|||||||
if (!open || !connection) return null;
|
if (!open || !connection) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={() => !loading && onClose()} title="Edit Connection">
|
<>
|
||||||
<div className="flex flex-col gap-6">
|
<Modal open={open} onClose={() => !loading && onClose()} title="Edit Connection">
|
||||||
<p className="text-sm text-slate-400">
|
<div className="flex flex-col gap-6">
|
||||||
Customize the cable style and color.
|
<p className="text-sm text-slate-400">
|
||||||
</p>
|
Customize the cable style and color.
|
||||||
|
</p>
|
||||||
|
|
||||||
<form id="connection-form" onSubmit={handleSubmit} className="flex flex-col gap-5">
|
<form id="connection-form" onSubmit={handleSubmit} className="flex flex-col gap-5">
|
||||||
{/* Label Name */}
|
<div className="flex flex-col gap-1.5">
|
||||||
<div className="flex flex-col gap-1.5">
|
<label htmlFor="connection-label" className="text-sm font-semibold text-slate-300">Cable Label (Optional)</label>
|
||||||
<label className="text-sm font-semibold text-slate-300">Cable Label (Optional)</label>
|
<input
|
||||||
<input
|
id="connection-label"
|
||||||
type="text"
|
type="text"
|
||||||
value={label}
|
value={label}
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
placeholder="e.g. Uplink to Core"
|
placeholder="e.g. Uplink to Core"
|
||||||
className="px-3 py-2 rounded-md bg-slate-900 border border-slate-700 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors"
|
className="px-3 py-2 rounded-md bg-slate-900 border border-slate-700 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Color Picker */}
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-col gap-2">
|
<label className="text-sm font-semibold text-slate-300">Cable Color</label>
|
||||||
<label className="text-sm font-semibold text-slate-300">Cable Color</label>
|
<div className="flex flex-wrap gap-2">
|
||||||
<div className="flex flex-wrap gap-2">
|
{PRESET_COLORS.map((c) => (
|
||||||
{PRESET_COLORS.map((c) => (
|
<button
|
||||||
<button
|
key={c}
|
||||||
key={c}
|
type="button"
|
||||||
type="button"
|
onClick={() => setColor(c)}
|
||||||
onClick={() => setColor(c)}
|
style={{ backgroundColor: c }}
|
||||||
style={{ backgroundColor: c }}
|
className={`w-8 h-8 rounded-full border-2 transition-all hover:scale-110 ${
|
||||||
className={`w-8 h-8 rounded-full border-2 transition-all hover:scale-110 ${
|
color === c ? 'border-white scale-110 shadow-lg' : 'border-transparent'
|
||||||
color === c ? 'border-white scale-110 shadow-lg' : 'border-transparent'
|
}`}
|
||||||
}`}
|
aria-label={`Select color ${c}`}
|
||||||
aria-label={`Select color ${c}`}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
<div className="relative w-8 h-8 rounded-full overflow-hidden border-2 border-slate-700">
|
||||||
<div className="relative w-8 h-8 rounded-full overflow-hidden border-2 border-slate-700">
|
<input
|
||||||
<input
|
type="color"
|
||||||
type="color"
|
value={color}
|
||||||
value={color}
|
onChange={(e) => setColor(e.target.value)}
|
||||||
onChange={(e) => setColor(e.target.value)}
|
className="absolute -top-2 -left-2 w-12 h-12 cursor-pointer"
|
||||||
className="absolute -top-2 -left-2 w-12 h-12 cursor-pointer"
|
title="Custom color"
|
||||||
title="Custom color"
|
aria-label="Select custom cable color"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Edge Style */}
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-col gap-2">
|
<label className="text-sm font-semibold text-slate-300">Curve Style</label>
|
||||||
<label className="text-sm font-semibold text-slate-300">Curve Style</label>
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<div className="grid grid-cols-3 gap-2">
|
{EDGE_STYLES.map((style) => (
|
||||||
{EDGE_STYLES.map((style) => (
|
<button
|
||||||
<button
|
key={style.value}
|
||||||
key={style.value}
|
type="button"
|
||||||
type="button"
|
onClick={() => setEdgeType(style.value)}
|
||||||
onClick={() => setEdgeType(style.value)}
|
className={`px-3 py-2 rounded-md border text-xs font-medium transition-all ${
|
||||||
className={`px-3 py-2 rounded-md border text-xs font-medium transition-all ${
|
edgeType === style.value
|
||||||
edgeType === style.value
|
? 'bg-blue-500/10 border-blue-500 text-blue-400'
|
||||||
? 'bg-blue-500/10 border-blue-500 text-blue-400'
|
: 'bg-slate-900 border-slate-700 text-slate-400 hover:border-slate-500 hover:text-slate-200'
|
||||||
: 'bg-slate-900 border-slate-700 text-slate-400 hover:border-slate-500 hover:text-slate-200'
|
}`}
|
||||||
}`}
|
aria-pressed={edgeType === style.value}
|
||||||
>
|
>
|
||||||
{style.label}
|
{style.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between pt-4 border-t border-slate-800">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
className="text-red-400 hover:text-red-300"
|
||||||
|
onClick={() => setConfirmDeleteOpen(true)}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Delete Connection
|
||||||
|
</Button>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button type="button" variant="ghost" onClick={onClose} disabled={loading}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" form="connection-form" variant="primary" loading={loading}>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between pt-4 border-t border-slate-800">
|
|
||||||
<Button type="button" variant="ghost" className="text-red-400 hover:text-red-300" onClick={handleDelete} disabled={loading}>
|
|
||||||
Delete Connection
|
|
||||||
</Button>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button type="button" variant="ghost" onClick={onClose} disabled={loading}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" form="connection-form" variant="primary" loading={loading}>
|
|
||||||
Save Changes
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Modal>
|
||||||
</Modal>
|
|
||||||
|
<ConfirmDialog
|
||||||
|
open={confirmDeleteOpen}
|
||||||
|
onClose={() => !loading && setConfirmDeleteOpen(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
title="Delete Connection"
|
||||||
|
message="Are you sure you want to remove this connection?"
|
||||||
|
confirmLabel="Delete Connection"
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useEffect, useState, useMemo, useCallback } from 'react';
|
import { useEffect, useState, useMemo, useCallback } from 'react';
|
||||||
import { useRackStore } from '../../store/useRackStore';
|
import { useRackStore } from '../../store/useRackStore';
|
||||||
import { cn } from '../../lib/utils';
|
|
||||||
|
|
||||||
export function ConnectionLayer() {
|
export function ConnectionLayer() {
|
||||||
const { racks, cablingFromPortId, setActiveConfigConnectionId } = useRackStore();
|
const { racks, cablingFromPortId, setActiveConfigConnectionId } = useRackStore();
|
||||||
@@ -171,12 +170,21 @@ export function ConnectionLayer() {
|
|||||||
strokeWidth="10"
|
strokeWidth="10"
|
||||||
fill="none"
|
fill="none"
|
||||||
className={isShiftPressed ? 'pointer-events-auto cursor-pointer' : 'pointer-events-none'}
|
className={isShiftPressed ? 'pointer-events-auto cursor-pointer' : 'pointer-events-none'}
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
aria-label="Edit connection"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setActiveConfigConnectionId(conn.id);
|
setActiveConfigConnectionId(conn.id);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
setActiveConfigConnectionId(conn.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|||||||
43
eslint.config.js
Normal file
43
eslint.config.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const js = require('@eslint/js');
|
||||||
|
const globals = require('globals');
|
||||||
|
const tseslint = require('typescript-eslint');
|
||||||
|
|
||||||
|
module.exports = tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: ['dist/**', 'client/**', 'node_modules/**'],
|
||||||
|
},
|
||||||
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
|
{
|
||||||
|
files: ['server/**/*.ts', 'scripts/**/*.ts', 'prisma/seed.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-misused-promises': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
checksVoidReturn: {
|
||||||
|
arguments: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
389
package-lock.json
generated
389
package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"jsonwebtoken": "^9.0.2"
|
"jsonwebtoken": "^9.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.4",
|
||||||
"@types/better-sqlite3": "^7.6.12",
|
"@types/better-sqlite3": "^7.6.12",
|
||||||
"@types/cookie-parser": "^1.4.8",
|
"@types/cookie-parser": "^1.4.8",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
@@ -25,11 +26,13 @@
|
|||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
"concurrently": "^9.1.0",
|
"concurrently": "^9.1.0",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
|
"globals": "^17.4.0",
|
||||||
"nodemon": "^3.1.7",
|
"nodemon": "^3.1.7",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prisma": "^5.22.0",
|
"prisma": "^5.22.0",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
|
"typescript-eslint": "^8.57.2",
|
||||||
"vitest": "^2.1.5"
|
"vitest": "^2.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -582,6 +585,19 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
||||||
|
"version": "14.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
||||||
|
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.39.4",
|
"version": "9.39.4",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
|
||||||
@@ -1276,6 +1292,288 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@eslint-community/regexpp": "^4.12.2",
|
||||||
|
"@typescript-eslint/scope-manager": "8.57.2",
|
||||||
|
"@typescript-eslint/type-utils": "8.57.2",
|
||||||
|
"@typescript-eslint/utils": "8.57.2",
|
||||||
|
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||||
|
"ignore": "^7.0.5",
|
||||||
|
"natural-compare": "^1.4.0",
|
||||||
|
"ts-api-utils": "^2.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@typescript-eslint/parser": "^8.57.2",
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
|
||||||
|
"version": "7.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
|
||||||
|
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/parser": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/scope-manager": "8.57.2",
|
||||||
|
"@typescript-eslint/types": "8.57.2",
|
||||||
|
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||||
|
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||||
|
"debug": "^4.4.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/tsconfig-utils": "^8.57.2",
|
||||||
|
"@typescript-eslint/types": "^8.57.2",
|
||||||
|
"debug": "^4.4.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/types": "8.57.2",
|
||||||
|
"@typescript-eslint/visitor-keys": "8.57.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/types": "8.57.2",
|
||||||
|
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||||
|
"@typescript-eslint/utils": "8.57.2",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"ts-api-utils": "^2.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/types": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/project-service": "8.57.2",
|
||||||
|
"@typescript-eslint/tsconfig-utils": "8.57.2",
|
||||||
|
"@typescript-eslint/types": "8.57.2",
|
||||||
|
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"minimatch": "^10.2.2",
|
||||||
|
"semver": "^7.7.3",
|
||||||
|
"tinyglobby": "^0.2.15",
|
||||||
|
"ts-api-utils": "^2.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||||
|
"version": "10.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||||
|
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^5.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/utils": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@eslint-community/eslint-utils": "^4.9.1",
|
||||||
|
"@typescript-eslint/scope-manager": "8.57.2",
|
||||||
|
"@typescript-eslint/types": "8.57.2",
|
||||||
|
"@typescript-eslint/typescript-estree": "8.57.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/types": "8.57.2",
|
||||||
|
"eslint-visitor-keys": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "2.1.9",
|
"version": "2.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
|
||||||
@@ -2741,9 +3039,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/globals": {
|
"node_modules/globals": {
|
||||||
"version": "14.0.0",
|
"version": "17.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
|
||||||
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
|
"integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -4337,6 +4635,54 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tinyglobby": {
|
||||||
|
"version": "0.2.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
|
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fdir": "^6.5.0",
|
||||||
|
"picomatch": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tinyglobby/node_modules/fdir": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"picomatch": "^3 || ^4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"picomatch": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinypool": {
|
"node_modules/tinypool": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
|
||||||
@@ -4409,6 +4755,19 @@
|
|||||||
"tree-kill": "cli.js"
|
"tree-kill": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-api-utils": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.8.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
@@ -4488,6 +4847,30 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript-eslint": {
|
||||||
|
"version": "8.57.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz",
|
||||||
|
"integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||||
|
"@typescript-eslint/parser": "8.57.2",
|
||||||
|
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||||
|
"@typescript-eslint/utils": "8.57.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undefsafe": {
|
"node_modules/undefsafe": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"jsonwebtoken": "^9.0.2"
|
"jsonwebtoken": "^9.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.4",
|
||||||
"@types/better-sqlite3": "^7.6.12",
|
"@types/better-sqlite3": "^7.6.12",
|
||||||
"@types/cookie-parser": "^1.4.8",
|
"@types/cookie-parser": "^1.4.8",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
@@ -35,11 +36,13 @@
|
|||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
"concurrently": "^9.1.0",
|
"concurrently": "^9.1.0",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
|
"globals": "^17.4.0",
|
||||||
"nodemon": "^3.1.7",
|
"nodemon": "^3.1.7",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prisma": "^5.22.0",
|
"prisma": "^5.22.0",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
|
"typescript-eslint": "^8.57.2",
|
||||||
"vitest": "^2.1.5"
|
"vitest": "^2.1.5"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import * as connService from '../services/connectionService';
|
import * as connService from '../services/connectionService';
|
||||||
|
import { ok } from '../types/index';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ router.post('/', async (req, res, next) => {
|
|||||||
try {
|
try {
|
||||||
const { fromPortId, toPortId, color, label, edgeType } = req.body;
|
const { fromPortId, toPortId, color, label, edgeType } = req.body;
|
||||||
const conn = await connService.createConnection({ fromPortId, toPortId, color, label, edgeType });
|
const conn = await connService.createConnection({ fromPortId, toPortId, color, label, edgeType });
|
||||||
res.status(201).json(conn);
|
res.status(201).json(ok(conn));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
@@ -19,7 +20,7 @@ router.put('/:id', async (req, res, next) => {
|
|||||||
try {
|
try {
|
||||||
const { color, label, edgeType } = req.body;
|
const { color, label, edgeType } = req.body;
|
||||||
const conn = await connService.updateConnection(req.params.id, { color, label, edgeType });
|
const conn = await connService.updateConnection(req.params.id, { color, label, edgeType });
|
||||||
res.json(conn);
|
res.json(ok(conn));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
@@ -29,7 +30,7 @@ router.put('/:id', async (req, res, next) => {
|
|||||||
router.delete('/:id', async (req, res, next) => {
|
router.delete('/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
await connService.deleteConnection(req.params.id);
|
await connService.deleteConnection(req.params.id);
|
||||||
res.json({ success: true });
|
res.json(ok(null));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
@@ -39,7 +40,7 @@ router.delete('/:id', async (req, res, next) => {
|
|||||||
router.delete('/ports/:p1/:p2', async (req, res, next) => {
|
router.delete('/ports/:p1/:p2', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
await connService.deleteByPorts(req.params.p1, req.params.p2);
|
await connService.deleteByPorts(req.params.p1, req.params.p2);
|
||||||
res.json({ success: true });
|
res.json(ok(null));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user