Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7321759baf | ||
|
|
383e15f38a | ||
|
|
afe32306d3 | ||
|
|
1b23aede89 | ||
|
|
54e0bc0b12 | ||
|
|
2671571bfb | ||
|
|
b602a5836c | ||
|
|
4eb46cb783 |
@@ -14,113 +14,118 @@ export const sendAlert = async (
|
|||||||
connectedAircrafts: ConnectedAircraft[];
|
connectedAircrafts: ConnectedAircraft[];
|
||||||
mission: Mission;
|
mission: Mission;
|
||||||
}> => {
|
}> => {
|
||||||
const mission = await prisma.mission.findUnique({
|
try {
|
||||||
where: { id: id },
|
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,
|
|
||||||
});
|
});
|
||||||
io.to(`desktop:${aircraft.userId}`).emit("mission-alert", {
|
const Stations = await prisma.station.findMany({
|
||||||
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: {
|
where: {
|
||||||
missionId: mission.id,
|
id: {
|
||||||
userId: aircraft.userId,
|
in: mission?.missionStationIds,
|
||||||
stationId: aircraft.stationId,
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingMissionOnStationUser)
|
if (!mission) {
|
||||||
await prisma.missionOnStationUsers.create({
|
throw new Error("Mission not found");
|
||||||
data: {
|
}
|
||||||
|
|
||||||
|
// 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,
|
missionId: mission.id,
|
||||||
userId: aircraft.userId,
|
userId: aircraft.userId,
|
||||||
stationId: aircraft.stationId,
|
stationId: aircraft.stationId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// for statistics only
|
if (!existingMissionOnStationUser)
|
||||||
await prisma.missionsOnStations
|
await prisma.missionOnStationUsers.create({
|
||||||
.createMany({
|
data: {
|
||||||
data: mission.missionStationIds.map((stationId) => ({
|
missionId: mission.id,
|
||||||
missionId: mission.id,
|
userId: aircraft.userId,
|
||||||
stationId,
|
stationId: aircraft.stationId,
|
||||||
})),
|
},
|
||||||
})
|
});
|
||||||
.catch((err) => {
|
}
|
||||||
// Ignore if the entry already exists
|
|
||||||
});
|
// for statistics only
|
||||||
if (user === "HPG") {
|
await prisma.missionsOnStations
|
||||||
await prisma.mission.update({
|
.createMany({
|
||||||
where: { id: Number(id) },
|
data: mission.missionStationIds.map((stationId) => ({
|
||||||
data: {
|
missionId: mission.id,
|
||||||
state: "running",
|
stationId,
|
||||||
missionLog: {
|
})),
|
||||||
push: {
|
})
|
||||||
type: "alert-log",
|
.catch((err) => {
|
||||||
auto: true,
|
// Ignore if the entry already exists
|
||||||
timeStamp: new Date().toISOString(),
|
});
|
||||||
} as any,
|
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 {
|
||||||
} else {
|
await prisma.mission.update({
|
||||||
await prisma.mission.update({
|
where: { id: Number(id) },
|
||||||
where: { id: Number(id) },
|
data: {
|
||||||
data: {
|
state: "running",
|
||||||
state: "running",
|
missionLog: {
|
||||||
missionLog: {
|
push: {
|
||||||
push: {
|
type: "alert-log",
|
||||||
type: "alert-log",
|
auto: false,
|
||||||
auto: false,
|
timeStamp: new Date().toISOString(),
|
||||||
timeStamp: new Date().toISOString(),
|
data: {
|
||||||
data: {
|
stationId: stationId,
|
||||||
stationId: stationId,
|
user: getPublicUser(user, { ignorePrivacy: true }),
|
||||||
user: getPublicUser(user, { ignorePrivacy: true }),
|
},
|
||||||
},
|
} as any,
|
||||||
} 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 };
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,10 +50,7 @@ const getRthCallsigns = (mission: Mission, stations: Station[]) => {
|
|||||||
return `🚁 RTH${callsigns.length > 1 ? "s" : ""}: ${callsigns.join(" / ")} `;
|
return `🚁 RTH${callsigns.length > 1 ? "s" : ""}: ${callsigns.join(" / ")} `;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNtfyHeader = (
|
const getNtfyHeader = (mission: Mission, clientStation: Station): NtfyHeader => ({
|
||||||
mission: Mission,
|
|
||||||
clientStation: Station,
|
|
||||||
): NtfyHeader => ({
|
|
||||||
headers: {
|
headers: {
|
||||||
Title: `${clientStation.bosCallsignShort} / ${mission.missionKeywordAbbreviation} / ${mission.missionKeywordCategory}`,
|
Title: `${clientStation.bosCallsignShort} / ${mission.missionKeywordAbbreviation} / ${mission.missionKeywordCategory}`,
|
||||||
Tags: "pager",
|
Tags: "pager",
|
||||||
@@ -76,9 +73,13 @@ export const sendNtfyMission = async (
|
|||||||
clientStation: Station,
|
clientStation: Station,
|
||||||
ntfyRoom: string,
|
ntfyRoom: string,
|
||||||
) => {
|
) => {
|
||||||
axios.post(
|
try {
|
||||||
`https://ntfy.sh/${ntfyRoom}`,
|
await axios.post(
|
||||||
getNtfyData(mission, stations),
|
`https://ntfy.sh/${ntfyRoom}`,
|
||||||
getNtfyHeader(mission, clientStation),
|
getNtfyData(mission, stations),
|
||||||
);
|
getNtfyHeader(mission, clientStation),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending Ntfy mission:", error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ router.patch("/:id", async (req, res) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (discordAccount?.id) {
|
if (discordAccount?.id && !disaptcherUpdate.ghostMode) {
|
||||||
await renameMember(
|
await renameMember(
|
||||||
discordAccount.discordId.toString(),
|
discordAccount.discordId.toString(),
|
||||||
`${getPublicUser(newDispatcher.user).fullName} • ${newDispatcher.zone}`,
|
`${getPublicUser(newDispatcher.user).fullName} • ${newDispatcher.zone}`,
|
||||||
|
|||||||
@@ -189,7 +189,11 @@ router.post("/:id/send-alert", async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(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;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export const handleConnectDispatch =
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (discordAccount?.id) {
|
if (discordAccount?.id && !ghostMode) {
|
||||||
await renameMember(
|
await renameMember(
|
||||||
discordAccount.discordId.toString(),
|
discordAccount.discordId.toString(),
|
||||||
`${getPublicUser(user).fullName} • ${selectedZone}`,
|
`${getPublicUser(user).fullName} • ${selectedZone}`,
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ const MissionMarker = ({
|
|||||||
tab: "home",
|
tab: "home",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
close: openMissionMarker?.map((m) => m.id) || [],
|
close: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ const PopupContent = ({
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{aircraft.Station.bosCallsign.length > 15
|
{aircraft.Station.bosCallsign.length > 15
|
||||||
? aircraft.Station.locationStateShort
|
? aircraft.Station.bosCallsignShort
|
||||||
: aircraft.Station.bosCallsign}
|
: aircraft.Station.bosCallsign}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
|||||||
if (dispatchState.status === "connected" && dispatchState.connectedDispatcher?.id) {
|
if (dispatchState.status === "connected" && dispatchState.connectedDispatcher?.id) {
|
||||||
changeDispatcherAPI(dispatchState.connectedDispatcher?.id, {
|
changeDispatcherAPI(dispatchState.connectedDispatcher?.id, {
|
||||||
zone: roomName,
|
zone: roomName,
|
||||||
|
ghostMode: dispatchState.ghostMode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,55 +7,59 @@ export const handleParticipantFinished = async (
|
|||||||
participant: Participant,
|
participant: Participant,
|
||||||
user: User,
|
user: User,
|
||||||
) => {
|
) => {
|
||||||
const discordAccount = await prisma.discordAccount.findFirst({
|
try {
|
||||||
where: {
|
const discordAccount = await prisma.discordAccount.findFirst({
|
||||||
userId: user.id,
|
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,
|
|
||||||
},
|
},
|
||||||
permissions: {
|
|
||||||
push: permissionsToAdd,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (discordAccount) {
|
|
||||||
await setStandardName({
|
|
||||||
memberId: discordAccount.discordId,
|
|
||||||
userId: user.id,
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
await sendCourseCompletedEmail(user.email, user, event);
|
|
||||||
|
|
||||||
await prisma.participant.update({
|
const badgedToAdd = event.finishedBadges.filter((badge) => {
|
||||||
where: {
|
return !user.badges.includes(badge);
|
||||||
id: participant.id,
|
});
|
||||||
},
|
const permissionsToAdd = event.finishedPermissions.filter((permission) => {
|
||||||
data: {
|
return !user.permissions.includes(permission);
|
||||||
statusLog: {
|
});
|
||||||
push: {
|
|
||||||
event: "Berechtigungen und Badges vergeben",
|
await prisma.user.update({
|
||||||
timestamp: new Date(),
|
where: {
|
||||||
user: "system",
|
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 (
|
export const handleParticipantEnrolled = async (
|
||||||
|
|||||||
@@ -34,43 +34,61 @@ const initTransporter = () => {
|
|||||||
initTransporter();
|
initTransporter();
|
||||||
|
|
||||||
export const sendCourseCompletedEmail = async (to: string, user: User, event: Event) => {
|
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) {
|
if (!transporter) {
|
||||||
console.error("Transporter is not initialized");
|
console.error("Transporter is not initialized");
|
||||||
return;
|
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) => {
|
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) => {
|
export const sendEmailVerification = async (to: string, user: User, code: string) => {
|
||||||
const emailHtml = await renderVerificationCode({
|
try {
|
||||||
user,
|
const emailHtml = await renderVerificationCode({
|
||||||
code,
|
user,
|
||||||
});
|
code,
|
||||||
await sendMail(to, "Bestätige deine E-Mail-Adresse", emailHtml);
|
});
|
||||||
|
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) => {
|
export const sendBannEmail = async (to: string, user: User, staffName: string) => {
|
||||||
const emailHtml = await renderBannNotice({
|
try {
|
||||||
user,
|
const emailHtml = await renderBannNotice({
|
||||||
staffName,
|
user,
|
||||||
});
|
staffName,
|
||||||
await sendMail(to, "Deine Sperrung bei Virtual Air Rescue", emailHtml);
|
});
|
||||||
|
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) => {
|
export const sendTimebannEmail = async (to: string, user: User, staffName: string) => {
|
||||||
const emailHtml = await renderTimeBanNotice({
|
try {
|
||||||
user,
|
const emailHtml = await renderTimeBanNotice({
|
||||||
staffName,
|
user,
|
||||||
});
|
staffName,
|
||||||
await sendMail(to, "Deine vorrübergehende Sperrung bei Virtual Air Rescue", emailHtml);
|
});
|
||||||
|
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) =>
|
export const sendMail = async (to: string, subject: string, html: string) =>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 552 KiB After Width: | Height: | Size: 38 KiB |
@@ -22,13 +22,16 @@ export const getPublicUser = (
|
|||||||
},
|
},
|
||||||
): PublicUser => {
|
): PublicUser => {
|
||||||
const lastName = user.lastname
|
const lastName = user.lastname
|
||||||
|
.trim()
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((part) => `${part[0]}.`)
|
.map((part) => `${part[0] || ""}.`)
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
firstname: user.firstname,
|
firstname: user.firstname,
|
||||||
lastname: user.settingsHideLastname && !options.ignorePrivacy ? "" : lastName, // Only take the first letter of each section of the last name
|
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}`,
|
fullName:
|
||||||
|
`${user.firstname} ${user.settingsHideLastname && !options.ignorePrivacy ? "" : lastName}`.trim(),
|
||||||
publicId: user.publicId,
|
publicId: user.publicId,
|
||||||
badges: user.badges,
|
badges: user.badges,
|
||||||
};
|
};
|
||||||
|
|||||||