浏览代码

added warnings for critical values

marek 1 月之前
父节点
当前提交
f8f072a71b

+ 1 - 1
assets/css/style.css

@@ -35,7 +35,7 @@ body {
     color: rgb(50, 50, 50);
 }
 
-.message.error {
+.message.warning {
     background-color: rgba(255, 50, 50, 0.3);
     border: 2px solid rgb(255, 50, 50);
     color: rgb(255, 50, 50);

+ 50 - 0
lang/en.js

@@ -194,6 +194,38 @@ export default {
         ${process.env.APP_NAME}
       `,
     },
+
+    stations: {
+      warningAdded: {
+        subject: () => `new warnings!`,
+        text: (details) => `
+          hi,
+
+          new warnings have been activated on your station ${details.stationName}:
+
+          ${details.warnings.join('\n')}
+
+          you can monitor them using this link: ${details.stationLink}
+
+          ${process.env.APP_NAME} ${new Date().getFullYear()}
+        `,
+      },
+
+      warningRemoved: {
+        subject: () => `warnings removed`,
+        text: (details) => `
+          hi,
+
+          the following warnings have been removed from your station ${details.stationName}:
+
+          ${details.warnings.join('\n')}
+
+          you can monitor them using this link: ${details.stationLink}
+
+          ${process.env.APP_NAME} ${new Date().getFullYear()}
+        `,
+      }
+    }
   },
 
   auth: {
@@ -240,6 +272,24 @@ export default {
       turnstile: general.errors.turnstile,
     },
 
+    warnings: {
+      highIndoorTemp: () => `High indoor temperature detected!`,
+      highIndoorPressure: () => `High indoor pressure detected!`,
+      highIndoorHumidity: () => `High indoor humidity detected!`,
+
+      lowIndoorTemp: () => `Low indoor temperature detected!`,
+      lowIndoorPressure: () => `Low indoor pressure detected!`,
+      lowIndoorHumidity: () => `Low indoor humidity detected!`,
+
+      highOutdoorTemp: () => `High outdoor temperature detected!`,
+      highOutdoorPressure: () => `High outdoor pressure detected!`,
+      highOutdoorHumidity: () => `High outdoor humidity detected!`,
+
+      lowOutdoorTemp: () => `Low outdoor temperature detected!`,
+      lowOutdoorPressure: () => `Low outdoor pressure detected!`,
+      lowOutdoorHumidity: () => `Low outdoor humidity detected!`,
+    },
+
     history: {
       properties: {
         indoorTemp: () => `${icons.tempIcon()} indoor temperature`,

+ 50 - 0
lang/sk.js

@@ -212,6 +212,38 @@ export default {
         ${process.env.APP_NAME}
       `,
     },
+
+    stations: {
+      warningAdded: {
+        subject: () => `nové varovania!`,
+        text: (details) => `
+          dobrý deň,
+
+          na vašej stanici ${details.stationName} boli aktivované nasledujúce varovania:
+
+          ${details.warnings.join('\n')}
+
+          môžete ich sledovať na nasledujúcom linku: ${details.stationLink}
+
+          ${process.env.APP_NAME} ${new Date().getFullYear()}
+        `,
+      },
+
+      warningRemoved: {
+        subject: () => `varovania zrušené`,
+        text: (details) => `
+          dobrý deň,
+
+          na vašej stanici ${details.stationName} boli deaktivované nasledujúce varovania:
+
+          ${details.warnings.join('\n')}
+
+          môžete ich sledovať na nasledujúcom linku: ${details.stationLink}
+
+          ${process.env.APP_NAME} ${new Date().getFullYear()}
+        `,
+      }
+    }
   },
 
   auth: {
@@ -258,6 +290,24 @@ export default {
       turnstile: general.errors.turnstile,
     },
 
+    warnings: {
+      highIndoorTemp: () => `Zistená vysoká vnútorná teplota!`,
+      highIndoorPressure: () => `Zistený vysoký vnútorný tlak!`,
+      highIndoorHumidity: () => `Zistená vysoká vnútorná vlhkosť!`,
+
+      lowIndoorTemp: () => `Zistená nízka vnútorná teplota!`,
+      lowIndoorPressure: () => `Zistená nízky vnútorný tlak!`,
+      lowIndoorHumidity: () => `Zistená nízka vnútorná vlhkosť!`,
+
+      highOutdoorTemp: () => `Zistená vysoká vonkajšia teplota!`,
+      highOutdoorPressure: () => `Zistená vysoký vonkajší tlak!`,
+      highOutdoorHumidity: () => `Zistená vysoká vonkajšia vlhkosť!`,
+
+      lowOutdoorTemp: () => `Zistená nízka vonkajšia teplota!`,
+      lowOutdoorPressure: () => `Zistená nízky vonkajší tlak!`,
+      lowOutdoorHumidity: () => `Zistená nízka vonkajšia vlhkosť!`,
+    },
+
     history: {
       properties: {
         indoorTemp: () => `${icons.tempIcon()} vnutorná teplota`,

+ 1 - 1
routes/include/panel/stations.js

@@ -107,7 +107,7 @@ export default (langName, lang) => new Elysia({ prefix: "/stations" })
     const data = Meteostanice.getData(meteostanica.id)
 
     set.headers['content-type'] = 'text/html; charset=utf8'
-    return eta.render(`${langName}/panel/stations/station`, { user, meteostanica, data })
+    return eta.render(`${langName}/panel/stations/station`, { user, lang, meteostanica, data })
   })
   .get("/:station/edit",  async ({ cookie, redirect, set, params: { station } }) => {
     const token = cookie.session.value

+ 5 - 8
routes/include/websocket.js

@@ -1,5 +1,6 @@
 import { Elysia } from 'elysia'
 import Meteostanice from '../../utils/meteostanice'
+import warningCheck from '../../utils/warningCheck'
 
 export default (langName, lang) => new Elysia({ prefix: "/ws" })
   .ws("/sendData/:key", {
@@ -13,7 +14,7 @@ export default (langName, lang) => new Elysia({ prefix: "/ws" })
         return lang.websocket.keepalive()
     },
 
-    message({ data: { params: { key } } }, message) {
+    async message({ data: { params: { key } } }, message) {
         if (message === lang.websocket.keepalive()) {
             return lang.websocket.keepalive()
         }
@@ -28,12 +29,10 @@ export default (langName, lang) => new Elysia({ prefix: "/ws" })
             indoorTemp,
             indoorPressure,
             indoorHumidity,
-            indoorAltitude,
             outdoorConnected,
             outdoorTemp,
             outdoorPressure,
             outdoorHumidity,
-            outdoorAltitude,
             timestamp
         } = message
 
@@ -41,12 +40,10 @@ export default (langName, lang) => new Elysia({ prefix: "/ws" })
             !indoorTemp?.length ||
             !indoorPressure?.length ||
             !indoorHumidity?.length ||
-            !indoorAltitude?.length ||
             !outdoorConnected?.toString()?.length ||
             !outdoorTemp?.length ||
             !outdoorPressure?.length ||
-            !outdoorHumidity?.length ||
-            !outdoorAltitude?.length
+            !outdoorHumidity?.length
         ) {
             return lang.websocket.errors.missingFields()
         }
@@ -56,15 +53,15 @@ export default (langName, lang) => new Elysia({ prefix: "/ws" })
             indoorTemp,
             indoorPressure,
             indoorHumidity,
-            indoorAltitude,
             outdoorConnected,
             outdoorTemp,
             outdoorPressure,
             outdoorHumidity,
-            outdoorAltitude,
             timestamp
         )
 
+        await warningCheck(langName, lang, meteostanica)
+
         return lang.websocket.dataSaved({ meteostanica })
     }
   })

+ 90 - 0
templates/en/email/stations/warningsAdded.eta

@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="sk">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="color-scheme" content="light dark">
+    <meta name="supported-color-schemes" content="light dark">
+    
+    <title>new warnings!</title>
+</head>
+<body style="margin: 0; padding: 0; background-color: #eeeeee; font-family: sans-serif;">
+    <div style="display: none; max-height: 0px; overflow: hidden; font-size: 1px; line-height: 1px; color: #eeeeee;">
+        new warnings have been activated on your station <%= it.station.name %>.
+        &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
+        &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
+    </div>
+    
+    <table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#eeeeee" style="background-color: #eeeeee;">
+        <tr>
+            <td align="center" style="padding: 20px 10%;">
+                <table width="100%" border="0" cellspacing="0" cellpadding="0">
+                    
+                    <tr>
+                        <td style="padding-top: 10px; padding-bottom: 10px;">
+                            <h2 style="margin: 0; font-size: 24px; color: #000000;"><%= process.env.APP_NAME %></h2>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">hi,</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">the following warnings have been activated on your station <strong><%= it.station.name %></strong>:</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <ul style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">
+                                <% for (const warning of it.warnings) { %>
+                                    <li><%= warning %></li>
+                                <% } %>
+                            </ul>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">you can monitor them by clicking on the button below.</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 15px;">
+                            <table border="0" cellspacing="0" cellpadding="0">
+                                <tr>
+                                    <td align="center" bgcolor="#3232ff" style="border-radius: 8px; background-color: #3232ff;">
+                                        <a href="<%= it.stationLink %>" target="_blank" style="padding: 10px 20px; font-size: 16px; color: #eeeeee; text-decoration: none; display: inline-block; font-weight: normal;">
+                                            monitor warnings
+                                        </a>
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 30px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">
+                                cannot click on the link? copy it: <span style="word-break: break-all;"><%= it.stationLink %></span>
+                            </p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="border-top: 1px solid #cccccc; padding-top: 20px;">
+                            <p style="margin: 0;"><small style="font-size: 12px; color: #666666;"><%= process.env.APP_NAME %> <%= new Date().getFullYear() %></small></p>
+                        </td>
+                    </tr>
+
+                </table>
+            </td>
+        </tr>
+    </table>
+</body>
+</html>

+ 90 - 0
templates/en/email/stations/warningsRemoved.eta

@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="sk">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="color-scheme" content="light dark">
+    <meta name="supported-color-schemes" content="light dark">
+    
+    <title>warnings removed</title>
+</head>
+<body style="margin: 0; padding: 0; background-color: #eeeeee; font-family: sans-serif;">
+    <div style="display: none; max-height: 0px; overflow: hidden; font-size: 1px; line-height: 1px; color: #eeeeee;">
+        warnings have been removed from your station <%= it.station.name %>.
+        &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
+        &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
+    </div>
+    
+    <table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#eeeeee" style="background-color: #eeeeee;">
+        <tr>
+            <td align="center" style="padding: 20px 10%;">
+                <table width="100%" border="0" cellspacing="0" cellpadding="0">
+                    
+                    <tr>
+                        <td style="padding-top: 10px; padding-bottom: 10px;">
+                            <h2 style="margin: 0; font-size: 24px; color: #000000;"><%= process.env.APP_NAME %></h2>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">hi,</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">the following warnings have been removed from your station <strong><%= it.station.name %></strong>:</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <ul style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">
+                                <% for (const warning of it.warnings) { %>
+                                    <li><%= warning %></li>
+                                <% } %>
+                            </ul>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">you can monitor them by clicking on the button below.</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 15px;">
+                            <table border="0" cellspacing="0" cellpadding="0">
+                                <tr>
+                                    <td align="center" bgcolor="#3232ff" style="border-radius: 8px; background-color: #3232ff;">
+                                        <a href="<%= it.stationLink %>" target="_blank" style="padding: 10px 20px; font-size: 16px; color: #eeeeee; text-decoration: none; display: inline-block; font-weight: normal;">
+                                            monitor warnings
+                                        </a>
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 30px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">
+                                cannot click on the link? copy it: <span style="word-break: break-all;"><%= it.stationLink %></span>
+                            </p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="border-top: 1px solid #cccccc; padding-top: 20px;">
+                            <p style="margin: 0;"><small style="font-size: 12px; color: #666666;"><%= process.env.APP_NAME %> <%= new Date().getFullYear() %></small></p>
+                        </td>
+                    </tr>
+
+                </table>
+            </td>
+        </tr>
+    </table>
+</body>
+</html>

+ 90 - 0
templates/sk/email/stations/warningsAdded.eta

@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="sk">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="color-scheme" content="light dark">
+    <meta name="supported-color-schemes" content="light dark">
+    
+    <title>nové varovania!</title>
+</head>
+<body style="margin: 0; padding: 0; background-color: #eeeeee; font-family: sans-serif;">
+    <div style="display: none; max-height: 0px; overflow: hidden; font-size: 1px; line-height: 1px; color: #eeeeee;">
+        na vašej stanici <%= it.station.name %> boli aktivované varovania.
+        &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
+        &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
+    </div>
+    
+    <table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#eeeeee" style="background-color: #eeeeee;">
+        <tr>
+            <td align="center" style="padding: 20px 10%;">
+                <table width="100%" border="0" cellspacing="0" cellpadding="0">
+                    
+                    <tr>
+                        <td style="padding-top: 10px; padding-bottom: 10px;">
+                            <h2 style="margin: 0; font-size: 24px; color: #000000;"><%= process.env.APP_NAME %></h2>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">dobrý deň,</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">na vašej stanici <strong><%= it.station.name %></strong> boli aktivované nasledujúce varovania:</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <ul style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">
+                                <% for (const warning of it.warnings) { %>
+                                    <li><%= warning %></li>
+                                <% } %>
+                            </ul>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">sledovať ich môžete kliknutím na tlačidlo nižšie.</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 15px;">
+                            <table border="0" cellspacing="0" cellpadding="0">
+                                <tr>
+                                    <td align="center" bgcolor="#3232ff" style="border-radius: 8px; background-color: #3232ff;">
+                                        <a href="<%= it.stationLink %>" target="_blank" style="padding: 10px 20px; font-size: 16px; color: #eeeeee; text-decoration: none; display: inline-block; font-weight: normal;">
+                                            sledovať varovania
+                                        </a>
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 30px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">
+                                nemôžete kliknúť na link? skopírujte si ho: <span style="word-break: break-all;"><%= it.stationLink %></span>
+                            </p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="border-top: 1px solid #cccccc; padding-top: 20px;">
+                            <p style="margin: 0;"><small style="font-size: 12px; color: #666666;"><%= process.env.APP_NAME %> <%= new Date().getFullYear() %></small></p>
+                        </td>
+                    </tr>
+
+                </table>
+            </td>
+        </tr>
+    </table>
+</body>
+</html>

+ 90 - 0
templates/sk/email/stations/warningsRemoved.eta

@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="sk">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="color-scheme" content="light dark">
+    <meta name="supported-color-schemes" content="light dark">
+    
+    <title>varovania zrušené</title>
+</head>
+<body style="margin: 0; padding: 0; background-color: #eeeeee; font-family: sans-serif;">
+    <div style="display: none; max-height: 0px; overflow: hidden; font-size: 1px; line-height: 1px; color: #eeeeee;">
+        na vašej stanici <%= it.station.name %> boli zrušené varovania.
+        &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
+        &nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;
+    </div>
+    
+    <table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#eeeeee" style="background-color: #eeeeee;">
+        <tr>
+            <td align="center" style="padding: 20px 10%;">
+                <table width="100%" border="0" cellspacing="0" cellpadding="0">
+                    
+                    <tr>
+                        <td style="padding-top: 10px; padding-bottom: 10px;">
+                            <h2 style="margin: 0; font-size: 24px; color: #000000;"><%= process.env.APP_NAME %></h2>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">dobrý deň,</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">na vašej stanici <strong><%= it.station.name %></strong> boli zrušené nasledujúce varovania:</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <ul style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">
+                                <% for (const warning of it.warnings) { %>
+                                    <li><%= warning %></li>
+                                <% } %>
+                            </ul>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 10px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">sledovať ich môžete kliknutím na tlačidlo nižšie.</p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 15px;">
+                            <table border="0" cellspacing="0" cellpadding="0">
+                                <tr>
+                                    <td align="center" bgcolor="#3232ff" style="border-radius: 8px; background-color: #3232ff;">
+                                        <a href="<%= it.stationLink %>" target="_blank" style="padding: 10px 20px; font-size: 16px; color: #eeeeee; text-decoration: none; display: inline-block; font-weight: normal;">
+                                            sledovať varovania
+                                        </a>
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="padding-bottom: 30px;">
+                            <p style="margin: 0; font-size: 16px; line-height: 1.3; color: #000000;">
+                                nemôžete kliknúť na link? skopírujte si ho: <span style="word-break: break-all;"><%= it.stationLink %></span>
+                            </p>
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td style="border-top: 1px solid #cccccc; padding-top: 20px;">
+                            <p style="margin: 0;"><small style="font-size: 12px; color: #666666;"><%= process.env.APP_NAME %> <%= new Date().getFullYear() %></small></p>
+                        </td>
+                    </tr>
+
+                </table>
+            </td>
+        </tr>
+    </table>
+</body>
+</html>

+ 7 - 5
templates/sk/panel/stations/station.eta

@@ -42,11 +42,13 @@
 
 <% if (it.data?.[0]) { %>
     <div id="mainStats">
-        <% /* <div class="message error">
-            <p>High temperature was detcted!</p>
-            <p>High pressure was detcted!</p>
-            <p>High humidity was detcted!</p>
-        </div> */ %>
+        <% if (JSON.parse(it.meteostanica?.warnings).length) { %>
+            <div class="message warning">
+                <% for (const warning of JSON.parse(it.meteostanica?.warnings)) { %>
+                    <p><%= it.lang.stations?.warnings?.[warning]() %></p>
+                <% } %>
+            </div>
+        <% } %>
 
         <p><em>naposledy aktualizované: <%= it.data?.[0]?.timestamp %></em></p>
 

+ 109 - 16
utils/meteostanice.js

@@ -11,6 +11,7 @@ meteostaniceDB.run(`create table if not exists list (
     description text,
     websocketKey text not null,
     subowners text,
+    warnings text,
     timestamp datetime default current_timestamp
 );`)
 
@@ -22,13 +23,11 @@ meteostaniceDB.run(`create table if not exists data (
     indoorTemp text not null,
     indoorPressure text not null,
     indoorHumidity text not null,
-    indoorAltitude text not null,
 
     outdoorConnected integer not null,
     outdoorTemp text not null,
     outdoorPressure text not null,
-    outdoorHumidity text not null,
-    outdoorAltitude text not null
+    outdoorHumidity text not null
 );`)
 
 meteostaniceDB.run(`create table if not exists public (
@@ -42,15 +41,25 @@ meteostaniceDB.run(`create table if not exists public (
     showIndoorTemp integer default 1,
     showIndoorPressure integer default 1,
     showIndoorHumidity integer default 1,
-    showIndoorAltitude integer default 1,
 
     showOutdoorConnected integer default 1,
     showOutdoorTemp integer default 1,
     showOutdoorPressure integer default 1,
-    showOutdoorHumidity integer default 1,
-    showOutdoorAltitude integer default 1
+    showOutdoorHumidity integer default 1
 );`)
 
+import nodemailer from "nodemailer"
+
+const transporter = nodemailer.createTransport({
+  host: process.env.WARNING_EMAIL_SMTP_HOSTNAME,
+  port: process.env.WARNING_EMAIL_SMTP_PORT,
+  secure: true, // Use true for port 465, false for port 587
+  auth: {
+    user: process.env.WARNING_EMAIL_SMTP_USERNAME,
+    pass: process.env.WARNING_EMAIL_SMTP_PASSWORD,
+  },
+});
+
 export default class Meteostanice {
     static add(owner, name, description) {
         const id = nanoid()
@@ -85,6 +94,20 @@ export default class Meteostanice {
         return result
     }
 
+    static getById(id) {
+        const statement = meteostaniceDB.prepare(`
+            SELECT *
+            FROM list 
+            WHERE id = $id
+        `);
+
+        const result = statement.get({
+            $id: id
+        });
+
+        return result
+    }
+
     static getWebsocket(key) {
         const statement = meteostaniceDB.prepare(`
             SELECT *
@@ -192,13 +215,27 @@ export default class Meteostanice {
         }
     }
 
-    static postData(meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude, timestamp) {
-        const id = nanoid()
+    static postData(meteostanica, indoorTemp, indoorPressure, indoorHumidity, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, timestamp) {
+        const id = nanoid();
 
-        meteostaniceDB.prepare(`
-            INSERT INTO data (id, meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude${timestamp ? `, timestamp` : ``}) 
-            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?${timestamp ? `, ?` : ``});
-        `).run(id, meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude, timestamp)
+        // 1. Define base columns and values
+        let columns = ['id', 'meteostanica', 'indoorTemp', 'indoorPressure', 'indoorHumidity', 'outdoorConnected', 'outdoorTemp', 'outdoorPressure', 'outdoorHumidity'];
+        let placeholders = ['?', '?', '?', '?', '?', '?', '?', '?', '?'];
+        let args = [id, meteostanica, indoorTemp, indoorPressure, indoorHumidity, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity];
+
+        // 2. Conditionally add timestamp
+        if (timestamp) {
+            columns.push('timestamp');
+            placeholders.push('?');
+            args.push(timestamp);
+        }
+
+        const statement = meteostaniceDB.prepare(`
+            INSERT INTO data (${columns.join(', ')}) 
+            VALUES (${placeholders.join(', ')})
+        `);
+        
+        statement.run(...args);
     }
 
     static getData(meteostanica) {
@@ -247,7 +284,7 @@ export default class Meteostanice {
             GROUP BY timeMark
             ORDER BY timeMark;
         `)
-        
+
         const result = statement.all(meteostanica, date);
 
         return result
@@ -265,7 +302,7 @@ export default class Meteostanice {
             GROUP BY timeMark
             ORDER BY timeMark;
         `)
-        
+
         const result = statement.all(meteostanica, yearMonth);
 
         return result
@@ -283,7 +320,7 @@ export default class Meteostanice {
             GROUP BY timeMark
             ORDER BY timeMark;
         `)
-        
+
         const result = statement.all(meteostanica, year);
 
         return result
@@ -301,7 +338,7 @@ export default class Meteostanice {
             GROUP BY timeMark
             ORDER BY timeMark;
         `)
-        
+
         const result = statement.all(meteostanica);
 
         return result
@@ -332,4 +369,60 @@ export default class Meteostanice {
             where id = ?;
         `).run(websocketKey, id)
     }
+
+    static getEmails(id) {
+        const statement = meteostaniceDB.prepare(`
+            WITH all_emails AS (
+                -- Get the primary owner
+                SELECT owner AS email
+                FROM list
+                WHERE id = $id
+
+                UNION
+
+                -- Get all subowners from the JSON array
+                SELECT json_each.value AS email
+                FROM list, json_each(list.subowners)
+                WHERE list.id = $id
+            )
+            SELECT email FROM all_emails WHERE email IS NOT NULL;
+        `);
+
+        // .all() returns an array of objects: [{email: '...'}, {email: '...'}]
+        const rows = statement.all({ $id: id });
+
+        // Map it to a simple array of strings: ['owner@email.com', 'subowner@email.com']
+        return rows.map(row => row.email);
+    }
+
+    static getStationDataLast5Minutes(id) {
+        const rows = meteostaniceDB.prepare(`
+            SELECT * FROM data 
+            WHERE meteostanica = ? 
+            AND timestamp >= datetime('now', '-6 minutes')
+            ORDER BY timestamp DESC
+            LIMIT 5;
+        `).all(id);
+
+        // Only return the data if we have a full 5-minute window
+        return rows.length >= 5 ? rows : null;
+    }
+
+    static editWarnings(id, warnings) {
+        meteostaniceDB.prepare(`
+            UPDATE list 
+            SET warnings = json(?) 
+            WHERE id = ?;
+        `).run(JSON.stringify(warnings), id)
+    }
+
+    static async sendWarnings(email, subject, text, html) {
+        return await transporter.sendMail({
+            from: process.env.WARNING_EMAIL_SMTP_FROM,
+            to: email,
+            subject,
+            text, // Plain-text version of the message
+            html, // HTML version of the message
+        });
+    }
 }

+ 60 - 0
utils/warningCheck.js

@@ -0,0 +1,60 @@
+import Meteostanice from "./meteostanice"
+
+import { Eta } from "eta"
+const eta = new Eta({ views: "./templates" })
+
+export default async (langName, lang, station) => {
+    const data = Meteostanice.getStationDataLast5Minutes(station.id)
+    if (!data) return
+
+    const thresholds = {
+        indoorTemp: { low: 1800, high: 2800 },  // 18°C - 28°C
+        indoorPressure: { low: 98000, high: 103000 }, // 980hPa - 1030hPa
+        indoorHumidity: { low: 3000, high: 6000 },  // 30% - 60%
+        outdoorTemp: { low: -1000, high: 3500 },  // -10°C - 35°C
+        outdoorPressure: { low: 97000, high: 104000 }, // 970hPa - 1040hPa
+        outdoorHumidity: { low: 2000, high: 9000 }   // 20% - 90%
+    }
+
+    for (const entries of data) {
+        const detectedWarnings = []
+
+        for (const [prop, limit] of Object.entries(thresholds)) {
+            if (entries.every(entry => Number(entry[prop]) > limit.high)) {
+                detectedWarnings.push(`high${prop.charAt(0).toUpperCase() + prop.slice(1)}`)
+            }
+
+            if (entries.every(entry => Number(entry[prop]) < limit.low)) {
+                detectedWarnings.push(`low${prop.charAt(0).toUpperCase() + prop.slice(1)}`)
+            }
+        }
+    }
+
+    const currentWarnings = station?.warnings ?? [];
+
+    const addedWarnings = [
+        ...detectedWarnings.filter(w => !currentWarnings.includes(w))
+    ];
+
+    const removedWarnings = [
+        ...currentWarnings.filter(w => !detectedWarnings.includes(w))
+    ]
+
+    const stationEmails = Meteostanice.getEmails(station.id)
+
+    const emailLink = `${process.env.BASE_URL}/${langName === "sk" ? `` : `${langName}/`}panel/stations/${station.id}`
+
+    if (addedWarnings.length) {
+        for (const email of stationEmails) {
+            await Meteostanice.sendWarnings(email, lang.emails.stations.addedWarnings.subject(), lang.emails.stations.addedWarnings.text({ stationName: station.name, warnings: addedWarnings, stationLink: emailLink }), eta.render(`${langName}/email/stations/addedWarnings`, { stationName: station.name, warnings: addedWarnings, stationLink: emailLink }))
+        }
+    }
+
+    if (removedWarnings.length) {
+        for (const email of stationEmails) {
+            await Meteostanice.sendWarnings(email, lang.emails.stations.removedWarnings.subject(), lang.emails.stations.removedWarnings.text({ stationName: station.name, warnings: addedWarnings, stationLink: emailLink }), eta.render(`${langName}/email/stations/removedWarnings`, { stationName: station.name, warnings: addedWarnings, stationLink: emailLink }))
+        }
+    }
+
+    Meteostanice.editWarnings(station.id, detectedWarnings)
+}