diff --git a/.dockerignore b/.dockerignore
index e6a5744f..004fb3ff 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -9,4 +9,5 @@ Dockerfile
.dockerignore
.eslint.config.msj
.README.md
-.env.example
\ No newline at end of file
+.env.example
+.env
\ No newline at end of file
diff --git a/.env.prod b/.env.prod
index 491dbeaa..38414e75 100644
--- a/.env.prod
+++ b/.env.prod
@@ -1,37 +1,73 @@
-# Allgemein / NextAuth
-NEXTAUTH_URL=http://localhost:3000
-NEXTAUTH_COOKIE_PREFIX=HUB
-NEXTAUTH_SECRET=var
-NEXTAUTH_HUB_SECRET=var-hub-secret
+# ───────────────────────────────────────────────
+# 🔐 Authentifizierung & Cookies
+# ───────────────────────────────────────────────
+AUTH_DISPATCH_SECRET=dispatch
+AUTH_HUB_SECRET=var
-# Datenbank
+AUTH_DISPATCH_COOKIE_PREFIX=DISPATCH
+AUTH_HUB_COOKIE_PREFIX=HUB
+
+AUTH_DISPATCH_URL=http://localhost:3001
+AUTH_HUB_URL=http://localhost:3000
+NEXT_PUBLIC_DISPATCH_SERVICE_ID=1
+
+
+# ───────────────────────────────────────────────
+# 🌐 Öffentliche URLs
+# ───────────────────────────────────────────────
+NEXT_PUBLIC_HUB_URL=http://localhost:3000
+NEXT_PUBLIC_HUB_SERVER_URL=http://localhost:3003
+NEXT_PUBLIC_DISPATCH_URL=http://localhost:3001
+NEXT_PUBLIC_DISPATCH_SERVER_URL=http://localhost:3002
+
+# ───────────────────────────────────────────────
+# 🗄️ Datenbank
+# ───────────────────────────────────────────────
DATABASE_URL=postgresql://persistant-data:persistant-data-pw@postgres:5432/var
-# Discord
+# ───────────────────────────────────────────────
+# 📡 LiveKit Konfiguration
+# ───────────────────────────────────────────────
+NEXT_PUBLIC_LIVEKIT_URL=ws://localhost:7880
+LIVEKIT_API_KEY=APIAnsGdtdYp2Ho
+LIVEKIT_API_SECRET=tdPjVsYUx8ddC7K9NvdmVAeLRF9GeADD6Fedm1x63fWC
+
+# ───────────────────────────────────────────────
+# 🚦 Dispatch Server (Backend)
+# ───────────────────────────────────────────────
+DISPATCH_SERVER_PORT=3000
+DISPATCH_APP_TOKEN=dispatch
+
+REDIS_HOST=redis
+REDIS_PORT=6379
+
+# ───────────────────────────────────────────────
+# 🧠 HUB Server (Backend)
+# ───────────────────────────────────────────────
+HUB_SERVER_PORT=3000
+HUB_URL=
+
+# ───────────────────────────────────────────────
+# 📚 Moodle
+# ───────────────────────────────────────────────
+MOODLE_URL=http://localhost:8081
+MOODLE_API_TOKEN=ac346f0324647b68488d13fd52a9bbe8
+MOODLE_USER_PASSWORD=var-api-user-P1
+NEXT_PUBLIC_MOODLE_URL=http://localhost:8081
+
+# ───────────────────────────────────────────────
+# 📧 E-Mail Einstellungen (nur HUB Server)
+# ───────────────────────────────────────────────
+MAIL_SERVER=asmtp.mail.hostpoint.ch
+MAIL_PORT=465
+MAIL_USER=noreply@virtualairrescue.com
+MAIL_PASSWORD=b7316PB8aDPCC%-&
+
+# ───────────────────────────────────────────────
+# 🕹️ Discord OAuth (optional)
+# ───────────────────────────────────────────────
DISCORD_OAUTH_CLIENT_ID=
DISCORD_OAUTH_SECRET=
DISCORD_BOT_TOKEN=
-NEXT_PUBLIC_DISCORD_URL=
-DISCORD_REDIRECT=
-
-# Moodle
-MOODLE_PW=var-api-user-P1
-MOODLE_TOKEN=ac346f0324647b68488d13fd52a9bbe8
-NEXT_PUBLIC_MOODLE_URL=http://localhost:8081
-
-# Hub Server
-NEXT_PUBLIC_HUB_SERVER_URL=http://localhost:3003
-
-# Dispatch Server
-NEXT_PUBLIC_DISPATCH_SERVER_URL=http://localhost:3001
-
-# Livekit
-NEXT_PUBLIC_LIVEKIT_URL=http://localhost:7880
-LIVEKIT_API_KEY=
-LIVEKIT_API_SECRET=
-
-# HUB Server
-HUB_API_PORT=3000
-
-# Redis (Beispiel)
-REDIS_URL=redis://localhost:6379
\ No newline at end of file
+DISCORD_REDIRECT_URL=
+NEXT_PUBLIC_DISCORD_URL=
\ No newline at end of file
diff --git a/README.md b/README.md
index 47f603fb..ff540a6f 100644
--- a/README.md
+++ b/README.md
@@ -1,103 +1,9 @@
-# Turborepo starter
+# Turborepo Monorepo für LST V2
-This is an official starter Turborepo.
+## Docker Dev
-## Using this example
-
-Run the following command:
+Um lokal Docker-Images zu bauen, gib die `.env`-Datei mit folgendem Befehl an `docker compose` weiter:
```sh
-npx create-turbo@latest
+docker compose --env-file .env.prod -f 'docker-compose.prod.yml' up -d
```
-
-## What's inside?
-
-This Turborepo includes the following packages/apps:
-
-### Apps and Packages
-
-- `dispatch`: a dispatching platform for web based unit/mission dispatching
-- `docs`: a [Next.js](https://nextjs.org/) app
-- `@repo/ui`: a stub React component library shared by both `web` and `docs` applications
-- `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
-- `@repo/typescript-config`: `tsconfig.json`s used throughout the monorepo
-
-Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
-
-### Utilities
-
-This Turborepo has some additional tools already setup for you:
-
-- [TypeScript](https://www.typescriptlang.org/) for static type checking
-- [ESLint](https://eslint.org/) for code linting
-- [Prettier](https://prettier.io) for code formatting
-
-### Build
-
-To build all apps and packages, run the following command:
-
-```
-cd my-turborepo
-pnpm build
-```
-
-### Develop
-
-To develop all apps and packages, run the following command:
-
-```
-cd my-turborepo
-pnpm dev
-```
-
-### Remote Caching
-
-> [!TIP]
-> Vercel Remote Cache is free for all plans. Get started today at [vercel.com](https://vercel.com/signup?/signup?utm_source=remote-cache-sdk&utm_campaign=free_remote_cache).
-
-Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
-
-By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup?utm_source=turborepo-examples), then enter the following commands:
-
-```
-cd my-turborepo
-npx turbo login
-```
-
-This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
-
-Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
-
-```
-npx turbo link
-```
-
-## Useful Links
-
-Learn more about the power of Turborepo:
-
-- [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks)
-- [Caching](https://turbo.build/repo/docs/core-concepts/caching)
-- [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching)
-- [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
-- [Configuration Options](https://turbo.build/repo/docs/reference/configuration)
-- [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference)
-
-## Execution policy
-
-MachinePolicy Undefined
-UserPolicy Undefined
-Process Undefined
-CurrentUser RemoteSigned
-LocalMachine RemoteSigned
-
-## Moodle:
-
-1. Im docker volume gehe in lib -> Classes -> OAuth2 -> Endpoint.php
-2. überspringe die https enforcement rule am Ende der Datei (true in if abfrage)
-3. Moodle Admin -> General -> HTTP Security -> Curl einschränkungen löschen
-4. http://localhost:8081/admin/category.php?category=authsettings -> Guest login button -> Hide
-5. http://localhost:8081/admin/settings.php?section=sitepolicies -> emailchangeconfirmation -> False
-6. Beim anlegen des Auth-Services Require Email verification deaktivieren
-7. Beim erstellen der Role für API user: permission moodle/site:viewuseridentity geben
-8. API user zu moodle kursen hinzufügen, um andere Nutzer hinzuzufügen
diff --git a/apps/dispatch-server/index.ts b/apps/dispatch-server/index.ts
index 09581c3e..bbb08eba 100644
--- a/apps/dispatch-server/index.ts
+++ b/apps/dispatch-server/index.ts
@@ -47,6 +47,6 @@ app.use(cookieParser());
app.use(authMiddleware as any);
app.use(router);
-server.listen(process.env.PORT, () => {
- console.log(`Server running on port ${process.env.PORT}`);
+server.listen(process.env.DISPATCH_SERVER_PORT, () => {
+ console.log(`Server running on port ${process.env.DISPATCH_SERVER_PORT}`);
});
diff --git a/apps/dispatch-server/modules/redis.ts b/apps/dispatch-server/modules/redis.ts
index e31cab05..ff7d7415 100644
--- a/apps/dispatch-server/modules/redis.ts
+++ b/apps/dispatch-server/modules/redis.ts
@@ -1,6 +1,8 @@
import { createClient, RedisClientType } from "redis";
-export const pubClient: RedisClientType = createClient();
+export const pubClient: RedisClientType = createClient({
+ url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
+});
export const subClient: RedisClientType = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
diff --git a/apps/dispatch-server/package.json b/apps/dispatch-server/package.json
index 24189b39..6c0edf9c 100644
--- a/apps/dispatch-server/package.json
+++ b/apps/dispatch-server/package.json
@@ -5,7 +5,7 @@
},
"scripts": {
"dev": "nodemon --signal SIGINT",
- "start": "node index.js",
+ "start": "tsx index.ts --transpile-only",
"build": "tsc"
},
"devDependencies": {
@@ -34,6 +34,7 @@
"nodemailer": "^6.10.0",
"react": "^19.0.0",
"redis": "^4.7.0",
- "socket.io": "^4.8.1"
+ "socket.io": "^4.8.1",
+ "tsx": "^4.19.4"
}
}
diff --git a/apps/dispatch/.dockerignore b/apps/dispatch/.dockerignore
index 3f569ce2..325bc2bd 100644
--- a/apps/dispatch/.dockerignore
+++ b/apps/dispatch/.dockerignore
@@ -3,4 +3,5 @@ Dockerfile
.dockerignore
.eslint.config.msj
.README.md
-.env.example
\ No newline at end of file
+.env.example
+.env
\ No newline at end of file
diff --git a/apps/dispatch/.env.example b/apps/dispatch/.env.example
index ae8f55ea..1ec06832 100644
--- a/apps/dispatch/.env.example
+++ b/apps/dispatch/.env.example
@@ -1,10 +1,10 @@
-NEXTAUTH_SECRET=dispatch
-NEXTAUTH_COOKIE_PREFIX=DISPATCH
+AUTH_DISPATCH_SECRET=dispatch
+AUTH_DISPATCH_COOKIE_PREFIX=DISPATCH
NEXT_PUBLIC_DISPATCH_SERVER_URL=http://localhost:3002
NEXTAUTH_URL=http://localhost:3001
NEXT_PUBLIC_HUB_URL=http://localhost:3000
-NEXT_PUBLIC_PUBLIC_URL=http://localhost:3001
-NEXT_PUBLIC_SERVICE_ID=1
+NEXT_PUBLIC_DISPATCH_URL=http://localhost:3001
+NEXT_PUBLIC_DISPATCH_SERVICE_ID=1
DATABASE_URL=postgresql://persistant-data:persistant-data-pw@localhost:5432/var
NEXT_PUBLIC_LIVEKIT_URL=ws://localhost:7880
LIVEKIT_API_KEY=APIAnsGdtdYp2Ho
diff --git a/apps/dispatch/Dockerfile b/apps/dispatch/Dockerfile
index a6cc8923..52967528 100644
--- a/apps/dispatch/Dockerfile
+++ b/apps/dispatch/Dockerfile
@@ -1,5 +1,17 @@
FROM node:22-alpine AS base
+ARG NEXT_PUBLIC_DISPATCH_URL
+ARG NEXT_PUBLIC_DISPATCH_SERVER_URL
+ARG NEXT_PUBLIC_HUB_URL
+ARG NEXT_PUBLIC_DISPATCH_SERVICE_ID
+ARG NEXT_PUBLIC_LIVEKIT_URL
+
+ENV NEXT_PUBLIC_DISPATCH_SERVER_URL=$NEXT_PUBLIC_DISPATCH_SERVER_URL
+ENV NEXT_PUBLIC_DISPATCH_URL=$NEXT_PUBLIC_DISPATCH_URL
+ENV NEXT_PUBLIC_HUB_URL=$NEXT_PUBLIC_HUB_URL
+ENV NEXT_PUBLIC_DISPATCH_SERVICE_ID=$NEXT_PUBLIC_DISPATCH_SERVICE_ID
+ENV NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL
+
ENV PNPM_HOME="/usr/local/pnpm"
ENV PATH="${PNPM_HOME}:${PATH}"
RUN corepack enable && corepack prepare pnpm@latest --activate
@@ -12,6 +24,10 @@ RUN apk add --no-cache libc6-compat
WORKDIR /usr/app
+RUN echo "NEXT_PUBLIC_HUB_URL is: $NEXT_PUBLIC_HUB_URL"
+RUN echo "NEXT_PUBLIC_DISPATCH_SERVICE_ID is: $NEXT_PUBLIC_DISPATCH_SERVICE_ID"
+RUN echo "NEXT_PUBLIC_DISPATCH_SERVER_URL is: $NEXT_PUBLIC_DISPATCH_SERVER_URL"
+
COPY . .
RUN turbo prune dispatch --docker
diff --git a/apps/dispatch/app/(auth)/login/_components/Login.tsx b/apps/dispatch/app/(auth)/login/_components/Login.tsx
index 0e9be153..93440f59 100644
--- a/apps/dispatch/app/(auth)/login/_components/Login.tsx
+++ b/apps/dispatch/app/(auth)/login/_components/Login.tsx
@@ -49,16 +49,10 @@ export const Login = () => {
-
diff --git a/apps/dispatch/app/_components/Audio.tsx b/apps/dispatch/app/_components/Audio.tsx
index c765ed56..d16dfbbc 100644
--- a/apps/dispatch/app/_components/Audio.tsx
+++ b/apps/dispatch/app/_components/Audio.tsx
@@ -15,7 +15,7 @@ import {
ZapOff,
} from "lucide-react";
import { useAudioStore } from "_store/audioStore";
-import { cn } from "helpers/cn";
+import { cn } from "_helpers/cn";
import { ConnectionQuality } from "livekit-client";
import { ROOMS } from "_data/livekitRooms";
diff --git a/apps/dispatch/app/_components/MicVolumeIndication.tsx b/apps/dispatch/app/_components/MicVolumeIndication.tsx
index c1dde18b..f8683f57 100644
--- a/apps/dispatch/app/_components/MicVolumeIndication.tsx
+++ b/apps/dispatch/app/_components/MicVolumeIndication.tsx
@@ -1,6 +1,6 @@
"use client";
-import { cn } from "helpers/cn";
+import { cn } from "_helpers/cn";
import { useEffect, useState } from "react";
type MicrophoneLevelProps = {
diff --git a/apps/dispatch/app/_components/Select.tsx b/apps/dispatch/app/_components/Select.tsx
index 25311197..d59fb594 100644
--- a/apps/dispatch/app/_components/Select.tsx
+++ b/apps/dispatch/app/_components/Select.tsx
@@ -1,20 +1,11 @@
"use client";
-import {
- FieldValues,
- Path,
- RegisterOptions,
- UseFormReturn,
-} from "react-hook-form";
-import SelectTemplate, {
- Props as SelectTemplateProps,
- StylesConfig,
-} from "react-select";
-import { cn } from "helpers/cn";
+import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form";
+import SelectTemplate, { Props as SelectTemplateProps, StylesConfig } from "react-select";
+import { cn } from "_helpers/cn";
import dynamic from "next/dynamic";
import { CSSProperties } from "react";
-interface SelectProps
- extends Omit {
+interface SelectProps extends Omit {
label?: any;
name: Path;
form: UseFormReturn | any;
@@ -69,9 +60,7 @@ const SelectCom = ({
}: SelectProps) => {
return (
-
- {label}
-
+
{label}
{
if (Array.isArray(newValue)) {
@@ -88,12 +77,8 @@ const SelectCom = ({
}}
value={
(inputProps as any)?.isMulti
- ? (inputProps as any).options?.filter((o: any) =>
- form.watch(name)?.includes(o.value),
- )
- : (inputProps as any).options?.find(
- (o: any) => o.value === form.watch(name),
- )
+ ? (inputProps as any).options?.filter((o: any) => form.watch(name)?.includes(o.value))
+ : (inputProps as any).options?.find((o: any) => o.value === form.watch(name))
}
styles={customStyles as any}
className={cn("w-full placeholder:text-neutral-600", className)}
@@ -101,17 +86,13 @@ const SelectCom = ({
{...inputProps}
/>
{form.formState.errors[name]?.message && (
-
- {form.formState.errors[name].message as string}
-
+ {form.formState.errors[name].message as string}
)}
);
};
-const SelectWrapper = (props: SelectProps) => (
-
-);
+const SelectWrapper = (props: SelectProps) => ;
export const Select = dynamic(() => Promise.resolve(SelectWrapper), {
ssr: false,
diff --git a/apps/dispatch/app/_components/Settings.tsx b/apps/dispatch/app/_components/Settings.tsx
index ece681de..ad9198be 100644
--- a/apps/dispatch/app/_components/Settings.tsx
+++ b/apps/dispatch/app/_components/Settings.tsx
@@ -4,7 +4,7 @@ import { GearIcon } from "@radix-ui/react-icons";
import { SettingsIcon, Volume2 } from "lucide-react";
import MicVolumeBar from "_components/MicVolumeIndication";
import { useMutation, useQuery } from "@tanstack/react-query";
-import { editUserAPI, getUserAPI } from "querys/user";
+import { editUserAPI, getUserAPI } from "_querys/user";
import { Prisma } from "@repo/db";
import { useSession } from "next-auth/react";
import { useAudioStore } from "_store/audioStore";
diff --git a/apps/dispatch/app/_components/SmartPopup.tsx b/apps/dispatch/app/_components/SmartPopup.tsx
index 975211d7..a19a4a60 100644
--- a/apps/dispatch/app/_components/SmartPopup.tsx
+++ b/apps/dispatch/app/_components/SmartPopup.tsx
@@ -1,10 +1,5 @@
-import { cn } from "helpers/cn";
-import {
- RefAttributes,
- useCallback,
- useEffect,
- useImperativeHandle,
-} from "react";
+import { cn } from "_helpers/cn";
+import { RefAttributes, useCallback, useEffect, useImperativeHandle } from "react";
import { createContext, Ref, useContext, useState } from "react";
import { Popup, PopupProps, useMap } from "react-leaflet";
import { Popup as LPopup } from "leaflet";
@@ -113,9 +108,9 @@ export const SmartPopup = (
const [showContent, setShowContent] = useState(false);
const { smartPopupRef, id, className, wrapperClassName, options } = props;
- const [anchor, setAnchor] = useState<
- "topleft" | "topright" | "bottomleft" | "bottomright"
- >("topleft");
+ const [anchor, setAnchor] = useState<"topleft" | "topright" | "bottomleft" | "bottomright">(
+ "topleft",
+ );
const handleConflict = useCallback(() => {
const newAnchor = calculateAnchor(id, "popup", options);
@@ -160,9 +155,7 @@ export const SmartPopup = (
anchor.includes("top") && "-translate-y-1/2",
)}
/>
-
- {props.children}
-
+ {props.children}
);
diff --git a/apps/dispatch/app/_components/customToasts/BaseNotification.tsx b/apps/dispatch/app/_components/customToasts/BaseNotification.tsx
index d51475fa..815d2979 100644
--- a/apps/dispatch/app/_components/customToasts/BaseNotification.tsx
+++ b/apps/dispatch/app/_components/customToasts/BaseNotification.tsx
@@ -1,4 +1,4 @@
-import { cn } from "helpers/cn";
+import { cn } from "_helpers/cn";
export const BaseNotification = ({
children,
diff --git a/apps/dispatch/app/_components/left/Chat.tsx b/apps/dispatch/app/_components/left/Chat.tsx
index 505561cb..579a5e17 100644
--- a/apps/dispatch/app/_components/left/Chat.tsx
+++ b/apps/dispatch/app/_components/left/Chat.tsx
@@ -3,10 +3,10 @@ import { ChatBubbleIcon, PaperPlaneIcon } from "@radix-ui/react-icons";
import { useLeftMenuStore } from "_store/leftMenuStore";
import { useSession } from "next-auth/react";
import { Fragment, useEffect, useState } from "react";
-import { cn } from "helpers/cn";
+import { cn } from "_helpers/cn";
import { asPublicUser } from "@repo/db";
import { useQuery } from "@tanstack/react-query";
-import { getConnectedUserAPI } from "querys/connected-user";
+import { getConnectedUserAPI } from "_querys/connected-user";
export const Chat = () => {
const {
@@ -88,8 +88,7 @@ export const Chat = () => {
{[
...(connectedUser?.filter(
- (user, idx, arr) =>
- arr.findIndex((u) => u.userId === user.userId) === idx,
+ (user, idx, arr) => arr.findIndex((u) => u.userId === user.userId) === idx,
) || []),
].map((user) => (