added Station Add, Delete Update

This commit is contained in:
PxlLoewe
2025-02-16 17:53:55 +01:00
parent 883c47bdce
commit ac492b934f
20 changed files with 615 additions and 33 deletions

View File

@@ -0,0 +1,13 @@
import { prisma } from '@repo/db';
import { StationForm } from '../_components/Form';
export default async ({ params }: { params: Promise<{ id: string }> }) => {
const { id } = await params;
const station = await prisma.station.findUnique({
where: {
id: parseInt(id),
},
});
if (!station) return <div>Station not found</div>;
return <StationForm station={station} />;
};

View File

@@ -0,0 +1,246 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { StationOptionalDefaultsSchema } from '@repo/db/zod';
import { set, useForm } from 'react-hook-form';
import { z } from 'zod';
import { BosUse, Country, Station } from '@repo/db';
import { FileText, LocateIcon, PlaneIcon } from 'lucide-react';
import { Input } from '../../../../_components/ui/Input';
import { useState } from 'react';
import { deleteStation, upsertStation } from '../action';
import { Button } from '../../../../_components/ui/Button';
import { redirect } from 'next/navigation';
export const StationForm = ({ station }: { station?: Station }) => {
const form = useForm<z.infer<typeof StationOptionalDefaultsSchema>>({
resolver: zodResolver(StationOptionalDefaultsSchema),
defaultValues: station,
});
const [loading, setLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
console.log(form.formState.errors);
return (
<>
<form
onSubmit={form.handleSubmit(async (values) => {
setLoading(true);
const createdStation = await upsertStation(values, station?.id);
setLoading(false);
if (!station) redirect(`/admin/station`);
})}
className="grid grid-cols-6 gap-3"
>
<div className="card bg-base-200 shadow-xl col-span-2 max-xl:col-span-6">
<div className="card-body">
<h2 className="card-title">
<FileText className="w-5 h-5" /> Allgemeines
</h2>
<Input
form={form}
label="BOS Rufname"
name="bosCallsign"
className="input-sm"
/>
<Input
form={form}
label="BOS Rufname (kurz)"
name="bosCallsignShort"
className="input-sm"
/>
<Input
form={form}
label="Betreiber"
name="operator"
className="input-sm"
/>
<Input
form={form}
label="ATC Rufname"
name="atcCallsign"
className="input-sm"
/>
<Input
form={form}
label="FIR (Flight Information Region)"
name="fir"
className="input-sm"
/>
<Input
form={form}
label="Leitstelle Rufname"
name="bosRadioArea"
className="input-sm"
/>
<label className="form-control w-full">
<span className="label-text text-lg flex items-center gap-2">
BOS Nutzung
</span>
<select
className="input-sm select select-bordered select-sm"
{...form.register('bosUse')}
>
{Object.keys(BosUse).map((use) => (
<option key={use} value={use}>
{use}
</option>
))}
</select>
</label>
</div>
</div>
<div className="card bg-base-200 shadow-xl col-span-2 max-xl:col-span-6">
<div className="card-body">
<h2 className="card-title">
<LocateIcon className="w-5 h-5" /> Standort + Ausrüstung
</h2>
<label className="form-control w-full">
<span className="label-text text-lg flex items-center gap-2">
Land
</span>
<select
className="input-sm select select-bordered select-sm"
{...form.register('country', {})}
>
{Object.keys(Country).map((use) => (
<option key={use} value={use}>
{use}
</option>
))}
</select>
</label>
<Input
form={form}
label="Bundesland"
name="locationState"
className="input-sm"
/>
<Input
form={form}
label="Bundesland (kurz)"
name="locationStateShort"
className="input-sm"
/>
<span className="label-text text-lg flex items-center gap-2">
Ausgerüstet mit:
</span>
<div className="form-control">
<label className="label cursor-pointer">
<span>Winde</span>
<input
type="checkbox"
className="toggle"
{...form.register('hasWinch')}
/>
</label>
<label className="label cursor-pointer">
<span>Nachtsicht Gerät</span>
<input
type="checkbox"
className="toggle"
{...form.register('hasNvg')}
/>
</label>
<label className="label cursor-pointer">
<span>24-Stunden Einsatzfähig</span>
<input
type="checkbox"
className="toggle"
{...form.register('is24h')}
/>
</label>
<label className="label cursor-pointer">
<span>Bergetau</span>
<input
type="checkbox"
className="toggle"
{...form.register('hasRope')}
/>
</label>
</div>
<Input
form={form}
label="Breitengrad"
name="latitude"
className="input-sm"
formOptions={{ valueAsNumber: true }}
type="number"
step="any"
/>
<Input
form={form}
label="Längengrad"
name="longitude"
className="input-sm"
formOptions={{ valueAsNumber: true }}
type="number"
step="any"
/>
<label className="label cursor-pointer">
<span className="text-lg">Reichweiten ausblenden</span>
<input
type="checkbox"
className="toggle"
{...form.register('hideRangeRings')}
/>
</label>
</div>
</div>
<div className="card bg-base-200 shadow-xl col-span-2 max-xl:col-span-6">
<div className="card-body">
<h2 className="card-title">
<PlaneIcon className="w-5 h-5" /> Hubschrauber
</h2>
<Input
form={form}
label="Hubschrauber Typ"
name="aircraft"
className="input-sm"
/>
<Input
form={form}
formOptions={{ valueAsNumber: true }}
type="number"
label="Hubschrauber Geschwindigkeit"
className="input-sm"
name="aircraftSpeed"
/>
<Input
form={form}
label="Hubschrauber Registrierung"
name="aircraftRegistration"
className="input-sm"
/>
</div>
</div>
<div className="card bg-base-200 shadow-xl col-span-6">
<div className="card-body ">
<div className="flex w-full gap-4">
<Button
isLoading={loading}
type="submit"
className="btn btn-primary flex-1"
>
Speichern
</Button>
{station && (
<Button
isLoading={deleteLoading}
onClick={async () => {
setDeleteLoading(true);
await deleteStation(station.id);
redirect('/admin/station');
}}
className="btn btn-error"
>
Löschen
</Button>
)}
</div>
</div>
</div>
</form>
</>
);
};

View File

@@ -0,0 +1,20 @@
'use server';
import { prisma, Prisma, Station } from '@repo/db';
export const upsertStation = async (
station: Prisma.StationCreateInput,
id?: Station['id']
) => {
const newStation = id
? await prisma.station.update({
where: { id: id },
data: station,
})
: await prisma.station.create({ data: station });
return newStation;
};
export const deleteStation = async (id: Station['id']) => {
await prisma.station.delete({ where: { id: id } });
};

View File

@@ -0,0 +1,5 @@
import { StationForm } from '../_components/Form';
export default () => {
return <StationForm />;
};

View File

@@ -0,0 +1,42 @@
import { DatabaseBackupIcon } from 'lucide-react';
import { PaginatedTable } from '../../../_components/PaginatedTable';
import Link from 'next/link';
export default () => {
return (
<>
<p className="text-2xl font-semibold text-left flex items-center gap-2 justify-between">
<span className="flex items-center gap-2">
<DatabaseBackupIcon className="w-5 h-5" /> Stationen
</span>
<Link href={'/admin/station/new'}>
<button className="btn btn-sm btn-outline btn-primary">
Erstellen
</button>
</Link>
</p>
<PaginatedTable
showEditButton
prismaModel="station"
columns={[
{
header: 'BOS Name',
accessorKey: 'bosCallsign',
},
{
header: 'Bos Use',
accessorKey: 'bosUse',
},
{
header: 'Country',
accessorKey: 'country',
},
{
header: 'operator',
accessorKey: 'operator',
},
]}
/>
</>
);
};

View File

@@ -0,0 +1,5 @@
import { Loading } from '../../../_components/ui/Loading';
export default () => {
<Loading />;
};

View File

@@ -1,8 +1,12 @@
import Link from 'next/link'; import { User2 } from 'lucide-react';
import { PaginatedTable } from '../../../_components/PaginatedTable'; import { PaginatedTable } from '../../../_components/PaginatedTable';
export default async () => { export default async () => {
return ( return (
<>
<p className="text-2xl font-semibold text-left flex items-center gap-2">
<User2 className="w-5 h-5" /> Benutzer
</p>
<PaginatedTable <PaginatedTable
showEditButton showEditButton
prismaModel="user" prismaModel="user"
@@ -25,5 +29,6 @@ export default async () => {
}, },
]} ]}
/> />
</>
); );
}; };

View File

@@ -1,7 +1,6 @@
import Link from 'next/link'; import Link from 'next/link';
import { PaginatedTable } from '../_components/PaginatedTable'; import { PaginatedTable } from '../_components/PaginatedTable';
import { Header } from '../_components/ui/Header'; import { Header } from '../_components/ui/Header';
import { PrismaClient } from '@repo/db';
export default async function Home() { export default async function Home() {
return ( return (

View File

@@ -227,6 +227,7 @@ export const SocialForm = ({
type="number" type="number"
className="input input-bordered w-full" className="input input-bordered w-full"
placeholder="1445241" placeholder="1445241"
defaultValue={user.vatsimCid as number | undefined}
{...form.register('vatsimCid', { {...form.register('vatsimCid', {
valueAsNumber: true, valueAsNumber: true,
})} })}

View File

@@ -39,7 +39,7 @@ export default function SortableTable<TData>({
<Link <Link
href={`/admin/${prismaModel as string}/${(row.original as any).id}`} href={`/admin/${prismaModel as string}/${(row.original as any).id}`}
> >
<button className="btn">Edit</button> <button className="btn btn-sm">Edit</button>
</Link> </Link>
</div> </div>
), ),

View File

@@ -0,0 +1,52 @@
import React from 'react';
import {
FieldValues,
Path,
RegisterOptions,
UseFormReturn,
} from 'react-hook-form';
import { cn } from '../../../helper/cn';
interface InputProps<T extends FieldValues>
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'form'> {
name: Path<T>;
form: UseFormReturn<T>;
formOptions?: RegisterOptions<T>;
label?: string;
placeholder?: string;
}
export const Input = <T extends FieldValues>({
name,
label = name,
placeholder = label,
form,
formOptions,
className,
...inputProps
}: InputProps<T>) => {
return (
<label className="form-control w-full">
<div className="label">
<span className="label-text text-lg flex items-center gap-2">
{label}
</span>
</div>
<input
{...form.register(name, formOptions)}
type="text"
className={cn(
'input input-bordered w-full placeholder:text-neutral-600',
className
)}
placeholder={placeholder}
{...inputProps}
/>
{form.formState.errors[name] && (
<p className="text-error">
{form.formState.errors[name].message as string}
</p>
)}
</label>
);
};

View File

@@ -0,0 +1,8 @@
export const Loading = () => {
return (
<div className="flex items-center justify-center h-full">
<div className="spinner"></div>
<p>LAAAADEN</p>
</div>
);
};

View File

@@ -29,7 +29,10 @@ export const VerticalNav = () => {
</summary> </summary>
<ul> <ul>
<li> <li>
<Link href="/admin/user">User</Link> <Link href="/admin/user">Benutzer</Link>
</li>
<li>
<Link href="/admin/station">Stationen</Link>
</li> </li>
</ul> </ul>
</details> </details>

109
package-lock.json generated
View File

@@ -107,6 +107,7 @@
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@next-auth/prisma-adapter": "^1.0.7", "@next-auth/prisma-adapter": "^1.0.7",
"@repo/db": "*",
"@repo/ui": "*", "@repo/ui": "*",
"@tanstack/react-table": "^8.20.6", "@tanstack/react-table": "^8.20.6",
"axios": "^1.7.9", "axios": "^1.7.9",
@@ -1175,6 +1176,58 @@
} }
} }
}, },
"node_modules/@prisma/debug": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.3.1.tgz",
"integrity": "sha512-RrEBkd+HLZx+ydfmYT0jUj7wjLiS95wfTOSQ+8FQbvb6vHh5AeKfEPt/XUQ5+Buljj8hltEfOslEW57/wQIVeA=="
},
"node_modules/@prisma/engines": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.3.1.tgz",
"integrity": "sha512-sXdqEVLyGAJ5/iUoG/Ea5AdHMN71m6PzMBWRQnLmhhOejzqAaEr8rUd623ql6OJpED4s/U4vIn4dg1qkF7vGag==",
"hasInstallScript": true,
"peer": true,
"dependencies": {
"@prisma/debug": "6.3.1",
"@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0",
"@prisma/fetch-engine": "6.3.1",
"@prisma/get-platform": "6.3.1"
}
},
"node_modules/@prisma/engines-version": {
"version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0.tgz",
"integrity": "sha512-R/ZcMuaWZT2UBmgX3Ko6PAV3f8//ZzsjRIG1eKqp3f2rqEqVtCv+mtzuH2rBPUC9ujJ5kCb9wwpxeyCkLcHVyA==",
"peer": true
},
"node_modules/@prisma/fetch-engine": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.3.1.tgz",
"integrity": "sha512-HOf/0umOgt+/S2xtZze+FHKoxpVg4YpVxROr6g2YG09VsI3Ipyb+rGvD6QGbCqkq5NTWAAZoOGNL+oy7t+IhaQ==",
"peer": true,
"dependencies": {
"@prisma/debug": "6.3.1",
"@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0",
"@prisma/get-platform": "6.3.1"
}
},
"node_modules/@prisma/generator-helper": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-6.3.1.tgz",
"integrity": "sha512-hX2fxjMksyAWAS0OcDi7GVmRUqsZ35ZY3Zla1EfO+uDYW6BY+om8kuKHyKkIvvRcUlTmL+xccl+nJwNToqP/aA==",
"dependencies": {
"@prisma/debug": "6.3.1"
}
},
"node_modules/@prisma/get-platform": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.3.1.tgz",
"integrity": "sha512-AYLq6Hk9xG73JdLWJ3Ip9Wg/vlP7xPvftGBalsPzKDOHr/ImhwJ09eS8xC2vNT12DlzGxhfk8BkL0ve2OriNhQ==",
"peer": true,
"dependencies": {
"@prisma/debug": "6.3.1"
}
},
"node_modules/@radix-ui/react-icons": { "node_modules/@radix-ui/react-icons": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz",
@@ -2666,6 +2719,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/code-block-writer": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
"integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w=="
},
"node_modules/color": { "node_modules/color": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -4274,7 +4332,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@@ -7314,6 +7371,33 @@
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
}, },
"node_modules/prisma": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.3.1.tgz",
"integrity": "sha512-JKCZWvBC3enxk51tY4TWzS4b5iRt4sSU1uHn2I183giZTvonXaQonzVtjLzpOHE7qu9MxY510kAtFGJwryKe3Q==",
"hasInstallScript": true,
"peer": true,
"dependencies": {
"@prisma/engines": "6.3.1"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": ">=18.18"
},
"optionalDependencies": {
"fsevents": "2.3.3"
},
"peerDependencies": {
"typescript": ">=5.1.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/prop-types": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -8869,7 +8953,7 @@
"version": "5.5.4", "version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true, "devOptional": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -9282,6 +9366,24 @@
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
}, },
"node_modules/zod-prisma-types": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/zod-prisma-types/-/zod-prisma-types-3.2.4.tgz",
"integrity": "sha512-S4spVBMJAmecLv+aLyRhXK26qW9nWwcsOf1H1fRcEmiI8DPbftZ99u0fhqHlymuTpmMcUpuKxcNNOIqNY0ScSQ==",
"dependencies": {
"@prisma/generator-helper": "^6.3.0",
"code-block-writer": "^12.0.0",
"lodash": "^4.17.21",
"zod": "^3.24.1"
},
"bin": {
"zod-prisma-types": "dist/bin.js"
},
"peerDependencies": {
"@prisma/client": "^4.x.x || ^5.x.x || ^6.x.x",
"prisma": "^4.x.x || ^5.x.x || ^6.x.x"
}
},
"node_modules/zustand": { "node_modules/zustand": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
@@ -9315,7 +9417,8 @@
"version": "0.0.0", "version": "0.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@prisma/client": "^6.2.1" "@prisma/client": "^6.2.1",
"zod-prisma-types": "^3.2.4"
} }
}, },
"packages/eslint-config": { "packages/eslint-config": {

View File

@@ -1,2 +1,6 @@
export { prisma } from './prisma/client'; // exports instance of prisma export { prisma } from './prisma/client'; // exports instance of prisma
export * from './generated/client'; // exports generated types from prisma export * from './generated/client'; // exports generated types from prisma
import * as zodTypes from './generated/zod';
export const zod = zodTypes;

View File

@@ -5,16 +5,18 @@
"main": "generated/client/index.js", "main": "generated/client/index.js",
"types": "generated/client/index.d.ts", "types": "generated/client/index.d.ts",
"scripts": { "scripts": {
"db:generate": "npx prisma generate", "db:generate": "npx prisma generate && npx prisma generate zod",
"db:migrate": "npx prisma migrate dev", "db:migrate": "npx prisma migrate dev",
"db:deploy": "npx prisma migrate deploy" "db:deploy": "npx prisma migrate deploy"
}, },
"exports": { "exports": {
".": "./index.ts" ".": "./index.ts",
"./zod": "./zod.ts"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@prisma/client": "^6.2.1" "@prisma/client": "^6.2.1",
"zod-prisma-types": "^3.2.4"
} }
} }

View File

@@ -0,0 +1,32 @@
-- CreateEnum
CREATE TYPE "BosUse" AS ENUM ('PRIMARY', 'SECONDARY', 'DUAL_USE');
-- CreateEnum
CREATE TYPE "Country" AS ENUM ('DE', 'AT', 'CH');
-- CreateTable
CREATE TABLE "Station" (
"id" SERIAL NOT NULL,
"bosUse" "BosUse" NOT NULL,
"bosCallsign" TEXT NOT NULL,
"bosCallsignShort" TEXT NOT NULL,
"bosRadioArea" TEXT NOT NULL,
"country" "Country" NOT NULL,
"operator" TEXT NOT NULL,
"aircraft" TEXT NOT NULL,
"aircraftRegistration" TEXT NOT NULL,
"aircraftSpeed" INTEGER NOT NULL,
"hasWinch" BOOLEAN NOT NULL,
"is24h" BOOLEAN NOT NULL,
"hasNvg" BOOLEAN NOT NULL,
"locationState" TEXT NOT NULL,
"locationStateShort" TEXT NOT NULL,
"hasRope" BOOLEAN NOT NULL,
"fir" TEXT NOT NULL,
"latitude" DOUBLE PRECISION NOT NULL,
"longitude" DOUBLE PRECISION NOT NULL,
"atcCallsign" TEXT NOT NULL,
"hideRangeRings" BOOLEAN NOT NULL,
CONSTRAINT "Station_pkey" PRIMARY KEY ("id")
);

View File

@@ -10,6 +10,12 @@ generator client {
output = "../../generated/client" output = "../../generated/client"
} }
generator zod {
provider = "zod-prisma-types"
output = "../../generated/zod"
createOptionalDefaultValuesTypes = true
}
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")

View File

@@ -0,0 +1,35 @@
enum BosUse {
PRIMARY
SECONDARY
DUAL_USE
}
enum Country {
DE
AT
CH
}
model Station {
id Int @id @default(autoincrement())
bosUse BosUse
bosCallsign String
bosCallsignShort String
bosRadioArea String
country Country
operator String
aircraft String
aircraftRegistration String
aircraftSpeed Int
hasWinch Boolean
is24h Boolean
hasNvg Boolean
locationState String
locationStateShort String
hasRope Boolean
fir String
latitude Float
longitude Float
atcCallsign String
hideRangeRings Boolean
}

1
packages/database/zod.ts Normal file
View File

@@ -0,0 +1 @@
export * from './generated/zod';