Browse Source

subowners

marek 1 month ago
parent
commit
c0ce016c39

+ 3 - 1
lang/en.js

@@ -233,7 +233,9 @@ export default {
     errors: {
       noName: () => `you need to provide a name.`,
       invalidOwner: () => `you need to provide a valid owner email.`,
-      ownerUserNotFound: (details) => `a user with the provided email (${details?.newOwnerEmail}) does not exist.`,
+      ownerUserNotFound: (details) => `no user with the provided email (${details?.newOwnerEmail}) exists.`,
+      invalidSubowner: () => `one (or more) of the subowner emails is invalid.`,
+      subownerUserNotFound: (details) => `no user with the provided email (${details?.subownerEmail}) exists.`,
 
       turnstile: general.errors.turnstile,
     },

+ 3 - 1
lang/sk.js

@@ -251,7 +251,9 @@ export default {
     errors: {
       noName: () => `musíte zadať meno.`,
       invalidOwner: () => `musíte zadať platný email vlastníka.`,
-      ownerUserNotFound: (details) => `používateľ so zadaným emailom (${details?.newOwnerEmail}) neexistuje.`,
+      ownerUserNotFound: (details) => `žiadny používateľ so zadaným emailom (${details?.newOwnerEmail}) neexistuje.`,
+      invalidSubowner: () => `musíte zadať platný email podvlastníka.`,
+      subownerUserNotFound: (details) => `žiadny používateľ so zadaným emailom (${details?.subownerEmail}) neexistuje.`,
 
       turnstile: general.errors.turnstile,
     },

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

@@ -201,9 +201,29 @@ export default (langName, lang) => new Elysia({ prefix: "/stations" })
       return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "ownerUserNotFound", errorDetails: { newOwnerEmail } })
     }
 
+    const subowners = body?.subowners
+
+    if (subowners) {
+      const subownersSplit = subowners.split(',')
+
+      for (const subownerEmail of subownersSplit) {
+        if (!normalizeEmail(subownerEmail)) {
+          set.headers['content-type'] = 'text/html; charset=utf8'
+          return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "invalidSubowner" })
+        }
+
+        const subowner = Auth.getUser(subownerEmail)
+
+        if (!subowner) {
+          set.headers['content-type'] = 'text/html; charset=utf8'
+          return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "subownerUserNotFound", errorDetails: { subownerEmail } })
+        }
+      }
+    }
+
     const newDescription = body?.description
 
-    Meteostanice.edit(meteostanica.id, newName, newDescription, newOwnerEmail)
+    Meteostanice.edit(meteostanica.id, newName, newDescription, newOwnerEmail, subowners)
 
     return redirect(`/${langName === "sk" ? `` : `${langName}/`}panel/stations/${meteostanica.id}`)
   })

+ 4 - 2
routes/include/websocket.js

@@ -33,7 +33,8 @@ export default (langName, lang) => new Elysia({ prefix: "/ws" })
             outdoorTemp,
             outdoorPressure,
             outdoorHumidity,
-            outdoorAltitude
+            outdoorAltitude,
+            timestamp
         } = message
 
         if (
@@ -60,7 +61,8 @@ export default (langName, lang) => new Elysia({ prefix: "/ws" })
             outdoorTemp,
             outdoorPressure,
             outdoorHumidity,
-            outdoorAltitude
+            outdoorAltitude,
+            timestamp
         )
 
         return lang.websocket.dataSaved({ meteostanica })

+ 6 - 0
templates/en/panel/stations/edit.eta

@@ -61,6 +61,12 @@
     <input type="text" id="owner" name="owner" placeholder="your@email.com" value="<%= it.meteostanica.owner %>">
   </div>
 
+  <div>
+    <label for="subowners">subowners</label>
+    <small>emails separated by a comma</small>
+    <textarea id="subowners" name="subowners" placeholder="subowner@email.com[,subowner2@email.com]"><%= JSON.parse(it.meteostanica?.subowners)?.length ? JSON.parse(it.meteostanica?.subowners).join(',') : "" %></textarea>
+  </div>
+
   <div class="cf-turnstile" data-sitekey="<%= it.siteKey %>"></div>
 
   <button type="submit" class="primary"><%~ saveIcon %> save</button>

+ 2 - 0
templates/en/panel/stations/index.eta

@@ -28,12 +28,14 @@
             <tr>
                 <th>name</th>
                 <th>description</th>
+                <th>owner</th>
                 <th>created</th>
             </tr>
             <% for (const meteostanica of it.meteostanice) { %>
                 <tr>
                     <td><a href="/en/panel/stations/<%= meteostanica.id %>"><%= meteostanica.name %></a></td>
                     <td><%= meteostanica?.description ?? `` %></td>
+                    <td><%= meteostanica.owner %></td>
                     <td><%= meteostanica.timestamp %></td>
                 </tr>
             <% } %>

+ 2 - 0
templates/en/panel/stations/partials/details.eta

@@ -58,6 +58,8 @@
 
 <p><strong>created:</strong> <%= it.meteostanica.timestamp %></p>
 
+<p><strong>owner:</strong> <%= it.meteostanica.owner %></p>
+
 <div class="container-row">
     <p><strong>websocket key:</strong> <%= it.meteostanica.websocketKey %></p>
     <a role="button" href="/en/panel/stations/<%= it.meteostanica.id %>/resetWebsocketKey"><%~ resetIcon %> reset</a>

+ 6 - 0
templates/sk/panel/stations/edit.eta

@@ -61,6 +61,12 @@
     <input type="text" id="owner" name="owner" placeholder="váš@email.sk" value="<%= it.meteostanica.owner %>">
   </div>
 
+  <div>
+    <label for="subowners">podvlastníci</label>
+    <small>emaily oddelené čiarkou</small>
+    <textarea id="subowners" name="subowners" placeholder="podvlastník@email.sk[,podvlastník2@email.sk]"><%= JSON.parse(it.meteostanica?.subowners)?.length ? JSON.parse(it.meteostanica?.subowners).join(',') : "" %></textarea>
+  </div>
+
   <div class="cf-turnstile" data-sitekey="<%= it.siteKey %>"></div>
 
   <button type="submit" class="primary"><%~ saveIcon %> uložiť</button>

+ 2 - 0
templates/sk/panel/stations/index.eta

@@ -27,12 +27,14 @@
         <tr>
             <th>meno</th>
             <th>popis</th>
+            <th>vlastník</th>
             <th>vytvorená</th>
         </tr>
         <% for (const meteostanica of it.meteostanice) { %>
             <tr>
                 <td><a href="/panel/stations/<%= meteostanica.id %>"><%= meteostanica.name %></a></td>
                 <td><%= meteostanica?.description ?? `` %></td>
+                <td><%= meteostanica.owner %></td>
                 <td><%= meteostanica.timestamp %></td>
             </tr>
         <% } %>

+ 2 - 0
templates/sk/panel/stations/partials/details.eta

@@ -58,6 +58,8 @@
 
 <p><strong>vytvorená:</strong> <%= it.meteostanica.timestamp %></p>
 
+<p><strong>vlastník:</strong> <%= it.meteostanica.owner %></p>
+
 <div class="container-row">
     <p><strong>websocket kľúč:</strong> <%= it.meteostanica.websocketKey %></p>
     <a role="button" href="/panel/stations/<%= it.meteostanica.id %>/resetWebsocketKey"><%~ resetIcon %> reset</a>

+ 50 - 15
utils/meteostanice.js

@@ -10,6 +10,7 @@ meteostaniceDB.run(`create table if not exists list (
     name text not null,
     description text,
     websocketKey text not null,
+    subowners text,
     timestamp datetime default current_timestamp
 );`)
 
@@ -65,8 +66,16 @@ export default class Meteostanice {
         const statement = meteostaniceDB.prepare(`
             SELECT *
             FROM list 
-            WHERE owner = $owner AND id = $id;
-        `)
+            WHERE id = $id 
+            AND (
+                owner = $owner 
+                OR EXISTS (
+                    SELECT 1 
+                    FROM json_each(list.subowners) 
+                    WHERE value = $owner
+                )
+            );
+        `);
 
         const result = statement.get({
             $owner: owner,
@@ -93,8 +102,13 @@ export default class Meteostanice {
     static getOwned(owner) {
         const statement = meteostaniceDB.prepare(`
             SELECT *
-            FROM list 
-            WHERE owner = $owner
+            FROM list
+            WHERE owner = $owner 
+            OR EXISTS (
+                SELECT * 
+                FROM json_each(list.subowners) 
+                WHERE value = $owner
+            )
             ORDER BY timestamp DESC;
         `)
 
@@ -105,22 +119,43 @@ export default class Meteostanice {
         return result
     }
 
-    static edit(id, newName, newDescription, newOwner) {
+    static edit(id, newName, newDescription, newOwner, subowners) {
+        let newSubowners = subowners ? subowners.split(',').map(s => s.trim()).filter(Boolean) : [];
+
         meteostaniceDB.prepare(`
             update list
             set name = ?,
             description = ?,
-            owner = ?
+            owner = ?,
+            subowners = json(?)
             where id = ?;
-        `).run(newName, newDescription, newOwner, id)
+        `).run(newName, newDescription, newOwner, JSON.stringify(newSubowners), id)
     }
 
     static editOwnerOnOwned(owner, newOwner) {
         meteostaniceDB.prepare(`
-            update list
-            set owner = ?
-            where owner = ?;
-        `).run(newOwner, owner)
+            UPDATE list
+            SET 
+                -- Update the primary owner if it matches
+                owner = CASE WHEN owner = $owner THEN $newOwner ELSE owner END,
+                
+                -- Update the subowners array if it contains the owner
+                subowners = CASE 
+                    WHEN EXISTS (SELECT 1 FROM json_each(subowners) WHERE value = $owner)
+                    THEN (
+                        SELECT json_group_array(
+                            CASE WHEN value = $owner THEN $newOwner ELSE value END
+                        )
+                        FROM json_each(subowners)
+                    )
+                    ELSE subowners 
+                END
+            WHERE owner = $owner 
+            OR EXISTS (SELECT 1 FROM json_each(subowners) WHERE value = $owner);
+        `).run({
+            $owner: owner,
+            $newOwner: newOwner
+        });
     }
 
     static delete(id) {
@@ -157,13 +192,13 @@ export default class Meteostanice {
         }
     }
 
-    static postData(meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude) {
+    static postData(meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude, timestamp) {
         const id = nanoid()
 
         meteostaniceDB.prepare(`
-            INSERT INTO data (id, meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude) 
-            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
-        `).run(id, meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude)
+            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)
     }
 
     static getData(meteostanica) {