8 Commits

Author SHA1 Message Date
PxlLoewe
7321759baf added other badges 2025-07-25 10:39:25 -07:00
PxlLoewe
383e15f38a Merge branch 'staging' of https://github.com/VAR-Virtual-Air-Rescue/var-monorepo into staging 2025-07-25 10:32:45 -07:00
PxlLoewe
afe32306d3 fixed #106 2025-07-25 10:32:35 -07:00
PxlLoewe
1b23aede89 fixed #105 2025-07-25 10:04:44 -07:00
PxlLoewe
54e0bc0b12 fixed #104 2025-07-25 09:53:58 -07:00
nocnico
2671571bfb maybe ghostmode fix now? 2025-07-25 17:43:05 +02:00
nocnico
b602a5836c Ghostmode fix now? 2025-07-25 17:19:58 +02:00
nocnico
4eb46cb783 fixed #100 // fix Marker Cluster naming, fix Marker Popup settings not used 2025-07-25 16:39:21 +02:00
19 changed files with 214 additions and 178 deletions

View File

@@ -14,113 +14,118 @@ export const sendAlert = async (
connectedAircrafts: ConnectedAircraft[];
mission: Mission;
}> => {
const mission = await prisma.mission.findUnique({
where: { id: id },
});
const Stations = await prisma.station.findMany({
where: {
id: {
in: mission?.missionStationIds,
},
},
});
if (!mission) {
throw new Error("Mission not found");
}
// connectedAircrafts the alert is sent to
const connectedAircrafts = await prisma.connectedAircraft.findMany({
where: {
stationId: stationId
? stationId
: {
in: mission.missionStationIds,
},
logoutTime: null,
},
include: {
Station: true,
},
});
for (const aircraft of connectedAircrafts) {
io.to(`station:${aircraft.stationId}`).emit("mission-alert", {
...mission,
Stations,
try {
const mission = await prisma.mission.findUnique({
where: { id: id },
});
io.to(`desktop:${aircraft.userId}`).emit("mission-alert", {
missionId: mission.id,
});
const user = await prisma.user.findUnique({
where: { id: aircraft.userId },
});
if (!user) continue;
if (user.settingsNtfyRoom) {
await sendNtfyMission(mission, Stations, aircraft.Station, user.settingsNtfyRoom);
}
const existingMissionOnStationUser = await prisma.missionOnStationUsers.findFirst({
const Stations = await prisma.station.findMany({
where: {
missionId: mission.id,
userId: aircraft.userId,
stationId: aircraft.stationId,
id: {
in: mission?.missionStationIds,
},
},
});
if (!existingMissionOnStationUser)
await prisma.missionOnStationUsers.create({
data: {
if (!mission) {
throw new Error("Mission not found");
}
// connectedAircrafts the alert is sent to
const connectedAircrafts = await prisma.connectedAircraft.findMany({
where: {
stationId: stationId
? stationId
: {
in: mission.missionStationIds,
},
logoutTime: null,
},
include: {
Station: true,
},
});
for (const aircraft of connectedAircrafts) {
io.to(`station:${aircraft.stationId}`).emit("mission-alert", {
...mission,
Stations,
});
io.to(`desktop:${aircraft.userId}`).emit("mission-alert", {
missionId: mission.id,
});
const user = await prisma.user.findUnique({
where: { id: aircraft.userId },
});
if (!user) continue;
if (user.settingsNtfyRoom) {
await sendNtfyMission(mission, Stations, aircraft.Station, user.settingsNtfyRoom);
}
const existingMissionOnStationUser = await prisma.missionOnStationUsers.findFirst({
where: {
missionId: mission.id,
userId: aircraft.userId,
stationId: aircraft.stationId,
},
});
}
// for statistics only
await prisma.missionsOnStations
.createMany({
data: mission.missionStationIds.map((stationId) => ({
missionId: mission.id,
stationId,
})),
})
.catch((err) => {
// Ignore if the entry already exists
});
if (user === "HPG") {
await prisma.mission.update({
where: { id: Number(id) },
data: {
state: "running",
missionLog: {
push: {
type: "alert-log",
auto: true,
timeStamp: new Date().toISOString(),
} as any,
if (!existingMissionOnStationUser)
await prisma.missionOnStationUsers.create({
data: {
missionId: mission.id,
userId: aircraft.userId,
stationId: aircraft.stationId,
},
});
}
// for statistics only
await prisma.missionsOnStations
.createMany({
data: mission.missionStationIds.map((stationId) => ({
missionId: mission.id,
stationId,
})),
})
.catch((err) => {
// Ignore if the entry already exists
});
if (user === "HPG") {
await prisma.mission.update({
where: { id: Number(id) },
data: {
state: "running",
missionLog: {
push: {
type: "alert-log",
auto: true,
timeStamp: new Date().toISOString(),
} as any,
},
},
},
});
} else {
await prisma.mission.update({
where: { id: Number(id) },
data: {
state: "running",
missionLog: {
push: {
type: "alert-log",
auto: false,
timeStamp: new Date().toISOString(),
data: {
stationId: stationId,
user: getPublicUser(user, { ignorePrivacy: true }),
},
} as any,
});
} else {
await prisma.mission.update({
where: { id: Number(id) },
data: {
state: "running",
missionLog: {
push: {
type: "alert-log",
auto: false,
timeStamp: new Date().toISOString(),
data: {
stationId: stationId,
user: getPublicUser(user, { ignorePrivacy: true }),
},
} as any,
},
},
},
});
});
}
return { connectedAircrafts, mission };
} catch (error) {
console.error("Error sending mission alert:", error);
throw new Error("Ein Fehler ist aufgetreten. Bitte melde den Fehler als Bug");
}
return { connectedAircrafts, mission };
};

View File

@@ -50,10 +50,7 @@ const getRthCallsigns = (mission: Mission, stations: Station[]) => {
return `🚁 RTH${callsigns.length > 1 ? "s" : ""}: ${callsigns.join(" / ")} `;
};
const getNtfyHeader = (
mission: Mission,
clientStation: Station,
): NtfyHeader => ({
const getNtfyHeader = (mission: Mission, clientStation: Station): NtfyHeader => ({
headers: {
Title: `${clientStation.bosCallsignShort} / ${mission.missionKeywordAbbreviation} / ${mission.missionKeywordCategory}`,
Tags: "pager",
@@ -76,9 +73,13 @@ export const sendNtfyMission = async (
clientStation: Station,
ntfyRoom: string,
) => {
axios.post(
`https://ntfy.sh/${ntfyRoom}`,
getNtfyData(mission, stations),
getNtfyHeader(mission, clientStation),
);
try {
await axios.post(
`https://ntfy.sh/${ntfyRoom}`,
getNtfyData(mission, stations),
getNtfyHeader(mission, clientStation),
);
} catch (error) {
console.error("Error sending Ntfy mission:", error);
}
};

View File

@@ -34,7 +34,7 @@ router.patch("/:id", async (req, res) => {
},
});
if (discordAccount?.id) {
if (discordAccount?.id && !disaptcherUpdate.ghostMode) {
await renameMember(
discordAccount.discordId.toString(),
`${getPublicUser(newDispatcher.user).fullName}${newDispatcher.zone}`,

View File

@@ -189,7 +189,11 @@ router.post("/:id/send-alert", async (req, res) => {
return;
} catch (error) {
console.error(error);
res.status(500).json({ error: "Failed to send mission" });
res
.status(500)
.json({
error: `Ein Fehler ist aufgetreten. Bitte melde den Fehler als Bug (${(error as Error).message})`,
});
return;
}
});

View File

@@ -70,7 +70,7 @@ export const handleConnectDispatch =
userId: user.id,
},
});
if (discordAccount?.id) {
if (discordAccount?.id && !ghostMode) {
await renameMember(
discordAccount.discordId.toString(),
`${getPublicUser(user).fullName}${selectedZone}`,

View File

@@ -253,7 +253,7 @@ const MissionMarker = ({
tab: "home",
},
],
close: openMissionMarker?.map((m) => m.id) || [],
close: [],
});
}
};

View File

@@ -158,7 +158,7 @@ const PopupContent = ({
</span>
<span>
{aircraft.Station.bosCallsign.length > 15
? aircraft.Station.locationStateShort
? aircraft.Station.bosCallsignShort
: aircraft.Station.bosCallsign}
</span>
</div>

View File

@@ -177,6 +177,7 @@ export const useAudioStore = create<TalkState>((set, get) => ({
if (dispatchState.status === "connected" && dispatchState.connectedDispatcher?.id) {
changeDispatcherAPI(dispatchState.connectedDispatcher?.id, {
zone: roomName,
ghostMode: dispatchState.ghostMode,
});
}

View File

@@ -7,55 +7,59 @@ export const handleParticipantFinished = async (
participant: Participant,
user: User,
) => {
const discordAccount = await prisma.discordAccount.findFirst({
where: {
userId: user.id,
},
});
const badgedToAdd = event.finishedBadges.filter((badge) => {
return !user.badges.includes(badge);
});
const permissionsToAdd = event.finishedPermissions.filter((permission) => {
return !user.permissions.includes(permission);
});
await prisma.user.update({
where: {
id: user.id,
},
data: {
badges: {
push: badgedToAdd,
try {
const discordAccount = await prisma.discordAccount.findFirst({
where: {
userId: user.id,
},
permissions: {
push: permissionsToAdd,
},
},
});
if (discordAccount) {
await setStandardName({
memberId: discordAccount.discordId,
userId: user.id,
});
}
await sendCourseCompletedEmail(user.email, user, event);
await prisma.participant.update({
where: {
id: participant.id,
},
data: {
statusLog: {
push: {
event: "Berechtigungen und Badges vergeben",
timestamp: new Date(),
user: "system",
const badgedToAdd = event.finishedBadges.filter((badge) => {
return !user.badges.includes(badge);
});
const permissionsToAdd = event.finishedPermissions.filter((permission) => {
return !user.permissions.includes(permission);
});
await prisma.user.update({
where: {
id: user.id,
},
data: {
badges: {
push: badgedToAdd,
},
permissions: {
push: permissionsToAdd,
},
},
},
});
});
if (discordAccount) {
await setStandardName({
memberId: discordAccount.discordId,
userId: user.id,
});
}
await sendCourseCompletedEmail(user.email, user, event);
await prisma.participant.update({
where: {
id: participant.id,
},
data: {
statusLog: {
push: {
event: "Berechtigungen und Badges vergeben",
timestamp: new Date(),
user: "system",
},
},
},
});
} catch (error) {
console.error("Error handling participant finished:", error);
}
};
export const handleParticipantEnrolled = async (

View File

@@ -34,43 +34,61 @@ const initTransporter = () => {
initTransporter();
export const sendCourseCompletedEmail = async (to: string, user: User, event: Event) => {
const emailHtml = await renderCourseCompleted({ user, event });
try {
const emailHtml = await renderCourseCompleted({ user, event });
if (!transporter) {
console.error("Transporter is not initialized");
return;
if (!transporter) {
console.error("Transporter is not initialized");
return;
}
await sendMail(to, `Kurs ${event.name} erfolgreich abgeschlossen`, emailHtml);
} catch (error) {
console.error("Error sending course completed email:", error);
}
sendMail(to, `Kurs ${event.name} erfolgreich abgeschlossen`, emailHtml);
};
export const sendPasswordChanged = async (to: string, user: User, password: string) => {
const emailHtml = await renderPasswordChanged({ user, password });
try {
const emailHtml = await renderPasswordChanged({ user, password });
await sendMail(to, `Dein Passwort wurde geändert`, emailHtml);
await sendMail(to, `Dein Passwort wurde geändert`, emailHtml);
} catch (error) {}
};
export const sendEmailVerification = async (to: string, user: User, code: string) => {
const emailHtml = await renderVerificationCode({
user,
code,
});
await sendMail(to, "Bestätige deine E-Mail-Adresse", emailHtml);
try {
const emailHtml = await renderVerificationCode({
user,
code,
});
await sendMail(to, "Bestätige deine E-Mail-Adresse", emailHtml);
} catch (error) {
console.error("Error sending email verification:", error);
}
};
export const sendBannEmail = async (to: string, user: User, staffName: string) => {
const emailHtml = await renderBannNotice({
user,
staffName,
});
await sendMail(to, "Deine Sperrung bei Virtual Air Rescue", emailHtml);
try {
const emailHtml = await renderBannNotice({
user,
staffName,
});
await sendMail(to, "Deine Sperrung bei Virtual Air Rescue", emailHtml);
} catch (error) {
console.error("Error sending ban email:", error);
}
};
export const sendTimebannEmail = async (to: string, user: User, staffName: string) => {
const emailHtml = await renderTimeBanNotice({
user,
staffName,
});
await sendMail(to, "Deine vorrübergehende Sperrung bei Virtual Air Rescue", emailHtml);
try {
const emailHtml = await renderTimeBanNotice({
user,
staffName,
});
await sendMail(to, "Deine vorrübergehende Sperrung bei Virtual Air Rescue", emailHtml);
} catch (error) {
console.error("Error sending time ban email:", error);
}
};
export const sendMail = async (to: string, subject: string, html: string) =>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -22,13 +22,16 @@ export const getPublicUser = (
},
): PublicUser => {
const lastName = user.lastname
.trim()
.split(" ")
.map((part) => `${part[0]}.`)
.map((part) => `${part[0] || ""}.`)
.join(" ");
return {
firstname: user.firstname,
lastname: user.settingsHideLastname && !options.ignorePrivacy ? "" : lastName, // Only take the first letter of each section of the last name
fullName: `${user.firstname} ${user.settingsHideLastname && !options.ignorePrivacy ? "" : lastName}`,
lastname: user.settingsHideLastname && !options.ignorePrivacy ? "" : lastName.trim(), // Only take the first letter of each section of the last name
fullName:
`${user.firstname} ${user.settingsHideLastname && !options.ignorePrivacy ? "" : lastName}`.trim(),
publicId: user.publicId,
badges: user.badges,
};