ソースを参照

refactor i18n

marek 1 ヶ月 前
コミット
8579190a21

+ 3 - 0
.gitignore

@@ -2,6 +2,9 @@
 node_modules
 node_modules
 bun.lock
 bun.lock
 
 
+# vs code
+.vscode
+
 # secrets
 # secrets
 .env
 .env
 
 

+ 77 - 62
lang/en.js

@@ -1,91 +1,106 @@
-export default {
-  "timeFormats": {
-    "days": {
-      "1": "day",
-      "2": "days"
+import formatTimeToString from '../utils/formatTimeToString'
+
+const general = {
+  timeFormats: {
+    days: {
+      1: () => `day`,
+      2: () => `days`,
+    },
+    hours: {
+      1: () => `hour`,
+      2: () => `hours`,
     },
     },
-    "hours": {
-      "1": "hour",
-      "2": "hours"
+    minutes: {
+      1: () => `minute`,
+      2: () => `minutes`,
     },
     },
-    "minutes": {
-      "1": "minute",
-      "2": "minutes"
+    seconds: {
+      1: () => `second`,
+      2: () => `seconds`,
     },
     },
-    "seconds": {
-      "1": "second",
-      "2": "seconds"
-    }
   },
   },
 
 
-  "functionWords": {
-    "and": "and"
+  functionWords: {
+    and: () => `and`,
   },
   },
 
 
-  "emails": {
-    "auth": {
-      "subject": "login link",
-      "text": (code, link) => `
+  errors: {
+    turnstile: {
+      unavailable: () => `cannot connect to Turnstile. please try again.`,
+      noToken: () => `Turnstile token was not provided. please try again.`,
+      invalidResponse: () => `invalid Turnstile response. please try again.`,
+      keyUsedOrExpired: () => `Turnstile key already used or expired. please try again.`
+    },
+  },
+}
+
+export default {
+  emails: {
+    auth: {
+      subject: () => `login link`,
+      text: (details) => `
         hi!
         hi!
 
 
-        you can login using the following code: ${code}
-        or the following link: ${link}
+        you can login using the following code: ${details?.code}
+        or the following link: ${details?.link}
 
 
         if you did not request this email, feel free to ignore it.
         if you did not request this email, feel free to ignore it.
 
 
         meteostanica
         meteostanica
-      `
-    }
+      `,
+    },
   },
   },
 
 
-  "auth": {
-    "errors": {
-      "invalidEmail": "you need to provide a valid email.",
-      "noVerificationToken": "no verification token provided. please try again.",
-      "verificationTokenUsedOrExpired": "verification token already used or expired. please try again.",
-      "invalidVerificationCode": "invalid verification code. please try again.",
-      "loginNeeded": "please log in first.",
-
-      "turnstile": {
-        "unavailable": "cannot connect to Turnstile. please try again.",
-        "noToken": "Turnstile token was not provided. please try again.",
-        "invalidResponse": "invalid Turnstile response. please try again.",
-        "keyUsedOrExpired": "Turnstile key already used or expired. please try again."
+  auth: {
+    errors: {
+      invalidEmail: () => `you need to provide a valid email.`,
+      noVerificationToken: () => `verification token was not provided. please try again.`,
+      verificationTokenUsedOrExpired: () => `verification token already used or expired. please try again.`,
+      invalidVerificationCode: () => `invalid verification code. please try again.`,
+      loginNeeded: () => `please log in first.`,
+      
+      ratelimits: {
+        email: (details) => {
+          if (!Number.parseInt(details?.duration)) return `too many requests for this email. try again later.`
+          return `too many requests for this email. try again in ${formatTimeToString(general.timeFormats, general.functionWords.and(), details?.duration * 1000)}.`
+        },
+        ip: (details) => {
+          if (!Number.parseInt(details?.duration)) return `you made too many requests. try again later.`
+          return `you made too many requests. try again in ${formatTimeToString(general.timeFormats, general.functionWords.and(), details?.duration * 1000)}.`
+        },
+        code: () => `you entered too many wrong codes. you need to request a new verification.`,
       },
       },
 
 
-      "ratelimits": {
-        "email": "too many requests for this email. try again later.",
-        "ip": "you made too many requests. try again later.",
-        "code": "you entered too many wrong codes. try again later."
-      }
-    }
+      turnstile: general.errors.turnstile,
+    },
   },
   },
 
 
   settings: {
   settings: {
     errors: {
     errors: {
-      invalidEmail: "you need to provide a valid email.",
+      invalidEmail: () => `you need to provide a valid email.`,
+      emailTaken: (details) => `a user with the provided email (${details?.newEmail}) already exists.`,
 
 
-      turnstile: {
-        "unavailable": "cannot connect to Turnstile. please try again.",
-        "noToken": "Turnstile token was not provided. please try again.",
-        "invalidResponse": "invalid Turnstile response. please try again.",
-        "keyUsedOrExpired": "Turnstile key already used or expired. please try again."
-      }
-    }
+      turnstile: general.errors.turnstile,
+    },
   },
   },
 
 
   stations: {
   stations: {
     errors: {
     errors: {
-      noName: "you need to provide a name.",
-      invalidOwner: "you need to provide a valid owner email.",
-      ownerUserNotFound: "no user with the provided email exists.",
+      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.`,
 
 
-      turnstile: {
-        "unavailable": "cannot connect to Turnstile. please try again.",
-        "noToken": "Turnstile token was not provided. please try again.",
-        "invalidResponse": "invalid Turnstile response. please try again.",
-        "keyUsedOrExpired": "Turnstile key already used or expired. please try again."
-      }
-    }
+      turnstile: general.errors.turnstile,
+    },
+  },
+
+  websocket: {
+    keepalive: () => `free ferris`,
+    dataSaved: (details) => `successfully saved data for ${details?.meteostanica?.name}`,
+
+    errors: {
+      missingFields: () => `missing required fields: indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude`,
+      invalidKey: (details) => `invalid station websocket key (${details.key}) provided`,
+    },
   }
   }
 }
 }

+ 81 - 66
lang/sk.js

@@ -1,95 +1,110 @@
-export default {
-  "timeFormats": {
-    "days": {
-      "1": "deň",
-      "2": "dni",
-      "5": "dní"
+import formatTimeToString from '../utils/formatTimeToString'
+
+const general = {
+  timeFormats: {
+    days: {
+      1: () => `deň`,
+      2: () => `dni`,
+      5: () => `dní`,
+    },
+    hours: {
+      1: () => `hodina`,
+      2: () => `hodiny`,
+      5: () => `hodín`,
     },
     },
-    "hours": {
-      "1": "hodina",
-      "2": "hodiny",
-      "5": "hodín"
+    minutes: {
+      1: () => `minúta`,
+      2: () => `minúty`,
+      5: () => `minút`,
     },
     },
-    "minutes": {
-      "1": "minúta",
-      "2": "minúty",
-      "5": "minút"
+    seconds: {
+      1: () => `sekunda`,
+      2: () => `sekundy`,
+      5: () => `sekúnd`,
     },
     },
-    "seconds": {
-      "1": "sekunda",
-      "2": "sekundy",
-      "5": "sekúnd"
-    }
   },
   },
 
 
-  "functionWords": {
-    "and": "a"
+  functionWords: {
+    and: () => `a`,
   },
   },
 
 
-  "emails": {
-    "auth": {
-      "subject": "prihlasovací link",
-      "text": (code, link) => `
+  errors: {
+    turnstile: {
+      unavailable: () => `nemožno kontaktovať Turnstile. prosím skúste to znova.`,
+      noToken: () => `Turnstile token nebol poskytnutý. prosím skúste to znova.`,
+      invalidResponse: () => `neplatná Turnstile odpoveď. prosím skúste to znova.`,
+      keyUsedOrExpired: () => `Turnstile kľúč už bol použitý alebo vypršal. prosím skúste to znova.`,
+    },
+  },
+}
+
+export default {
+  emails: {
+    auth: {
+      subject: () => `prihlasovací link`,
+      text: (details) => `
         dobrý deň,
         dobrý deň,
 
 
-        môžete sa prihlásiť nasledujúcim kódom: ${code}
-        alebo nasledujúcim linkom: ${link}
+        môžete sa prihlásiť nasledujúcim kódom: ${details?.code}
+        alebo nasledujúcim linkom: ${details?.link}
 
 
         ak ste tento email nevyžiadali, môžete ho kľudne ignorovať.
         ak ste tento email nevyžiadali, môžete ho kľudne ignorovať.
 
 
         meteostanica
         meteostanica
-      `
-    }
+      `,
+    },
   },
   },
 
 
-  "auth": {
-    "errors": {
-      "invalidEmail": "musíte zadať platný email.",
-      "noVerificationToken": "nebol poskytnutý žiadny verifikačný token. prosím skúste to znova.",
-      "verificationTokenUsedOrExpired": "verifikačný token už bol použitý alebo vypršal. prosím skúste to znova.",
-      "invalidVerificationCode": "neplatný verifikačný kód. prosím skúste to znova.",
-      "loginNeeded": "najprv sa prihláste prosím.",
-
-      "turnstile": {
-        "unavailable": "nemožno kontaktovať Turnstile. prosím skúste to znova.",
-        "noToken": "Turnstile token nebol poskytnutý. prosím skúste to znova.",
-        "invalidResponse": "neplatná Turnstile odpoveď. prosím skúste to znova.",
-        "keyUsedOrExpired": "Turnstile kľúč už bol použitý alebo vypršal. prosím skúste to znova."
+  auth: {
+    errors: {
+      invalidEmail: () => `musíte zadať platný email.`,
+      noVerificationToken: () => `nebol poskytnutý žiadny verifikačný token. prosím skúste to znova.`,
+      verificationTokenUsedOrExpired: () => `verifikačný token už bol použitý alebo vypršal. prosím skúste to znova.`,
+      invalidVerificationCode: () => `neplatný verifikačný kód. prosím skúste to znova.`,
+      loginNeeded: () => `najprv sa prihláste prosím.`,
+      
+      ratelimits: {
+        email: (details) => {
+          if (!Number.parseInt(details?.duration)) return `príliš veľa žiadostí pre tento email. skúste to znova neskôr.`
+          return `príliš veľa žiadostí pre tento email. skúste to znova o ${formatTimeToString(general.timeFormats, general.functionWords.and(), details?.duration * 1000)}.`
+        },
+        ip: (details) => {
+          if (!Number.parseInt(details?.duration)) return `poslali ste príliš veľa žiadostí. skúste to znova neskôr.`
+          return `poslali ste príliš veľa žiadostí. skúste to znova o ${formatTimeToString(general.timeFormats, general.functionWords.and(), details?.duration * 1000)}.`
+        },
+        code: () => `zadali ste príliš veľa zlých kódov. musíte požiadať o novú verifikáciu.`,
       },
       },
 
 
-      "ratelimits": {
-        "email": "príliš veľa žiadostí pre tento email. skúste to znova neskôr.",
-        "ip": "poslali ste príliš veľa žiadostí. skúste to znova neskôr.",
-        "code": "zadali ste príliš veľa zlých kódov. skúste to znova neskôr."
-      }
-    }
+      turnstile: general.errors.turnstile,
+    },
   },
   },
 
 
   settings: {
   settings: {
     errors: {
     errors: {
-      invalidEmail: "musíte zadať platný email.",
+      invalidEmail: () => `musíte zadať platný email.`,
+      emailTaken: (details) => `používateľ so zadaným emailom (${details?.newEmail}) už existuje.`,
 
 
-      turnstile: {
-        "unavailable": "nemožno kontaktovať Turnstile. prosím skúste to znova.",
-        "noToken": "Turnstile token nebol poskytnutý. prosím skúste to znova.",
-        "invalidResponse": "neplatná Turnstile odpoveď. prosím skúste to znova.",
-        "keyUsedOrExpired": "Turnstile kľúč už bol použitý alebo vypršal. prosím skúste to znova."
-      }
-    }
+      turnstile: general.errors.turnstile,
+    },
   },
   },
 
 
   stations: {
   stations: {
     errors: {
     errors: {
-      noName: "musíte zadať meno.",
-      invalidOwner: "musíte zadať platný email vlastníka.",
-      ownerUserNotFound: "používateľ so zadaným emailom neexistuje.",
+      noName: () => `musíte zadať meno.`,
+      invalidOwner: () => `musíte zadať platný email vlastníka.`,
+      ownerUserNotFound: (details) => `používateľ so zadaným emailom (${details?.newOwnerEmail}) neexistuje.`,
 
 
-      turnstile: {
-        "unavailable": "nemožno kontaktovať Turnstile. prosím skúste to znova.",
-        "noToken": "Turnstile token nebol poskytnutý. prosím skúste to znova.",
-        "invalidResponse": "neplatná Turnstile odpoveď. prosím skúste to znova.",
-        "keyUsedOrExpired": "Turnstile kľúč už bol použitý alebo vypršal. prosím skúste to znova."
-      }
-    }
+      turnstile: general.errors.turnstile,
+    },
+  },
+
+  websocket: {
+    keepalive: () => `beep`,
+    dataSaved: (details) => `úspešne uložené dáta pre ${details?.meteostanica?.name}`,
+
+    errors: {
+      missingFields: () => `chýbajú dôležité polia: indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude`,
+      invalidKey: (details) => `zadali ste neplatný websocket kľúč stanice (${details.key})`,
+    },
   }
   }
 }
 }

+ 6 - 5
routes/include/auth.js

@@ -19,9 +19,10 @@ export default (langName, lang) => new Elysia({ prefix: "/auth" })
     }
     }
 
 
     const error = query?.error
     const error = query?.error
+    const duration = query?.duration
 
 
     set.headers['content-type'] = 'text/html; charset=utf8'
     set.headers['content-type'] = 'text/html; charset=utf8'
-    return eta.render(`${langName}/auth/index`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, error })
+    return eta.render(`${langName}/auth/index`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, error, errorDetails: { duration } })
   })
   })
   .post("/", async ({ request, server, body, redirect }) => {
   .post("/", async ({ request, server, body, redirect }) => {
     const clientIP = request.headers.get('x-forwarded-for') ?? server.requestIP(request).address
     const clientIP = request.headers.get('x-forwarded-for') ?? server.requestIP(request).address
@@ -30,13 +31,13 @@ export default (langName, lang) => new Elysia({ prefix: "/auth" })
     if (!normalizeEmail(email)) return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=invalidEmail`)
     if (!normalizeEmail(email)) return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=invalidEmail`)
 
 
     const hourlyEmailRatelimit = ratelimits("authEmailHourly", email, process.env.AUTH_EMAIL_HOURLY_RATELIMIT, 3600)
     const hourlyEmailRatelimit = ratelimits("authEmailHourly", email, process.env.AUTH_EMAIL_HOURLY_RATELIMIT, 3600)
-    if (hourlyEmailRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.email`)
+    if (hourlyEmailRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.email&duration=${hourlyEmailRatelimit.duration}`)
 
 
     const hourlyIPRatelimit = ratelimits("authIPHourly", clientIP, process.env.AUTH_IP_HOURLY_RATELIMIT, 3600)
     const hourlyIPRatelimit = ratelimits("authIPHourly", clientIP, process.env.AUTH_IP_HOURLY_RATELIMIT, 3600)
-    if (hourlyIPRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.ip`)
+    if (hourlyIPRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.ip&duration=${hourlyIPRatelimit.duration}`)
 
 
     const dailyIPRatelimit = ratelimits("authIPDaily", clientIP, process.env.AUTH_IP_DAILY_RATELIMIT, 86400)
     const dailyIPRatelimit = ratelimits("authIPDaily", clientIP, process.env.AUTH_IP_DAILY_RATELIMIT, 86400)
-    if (dailyIPRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.ip`)
+    if (dailyIPRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.ip&duration=${dailyIPRatelimit.duration}`)
 
 
     const turnstileResponse = body?.["cf-turnstile-response"]
     const turnstileResponse = body?.["cf-turnstile-response"]
     if (!turnstileResponse) return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=turnstile.noToken`)
     if (!turnstileResponse) return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=turnstile.noToken`)
@@ -59,7 +60,7 @@ export default (langName, lang) => new Elysia({ prefix: "/auth" })
 
 
     const emailLink = `${process.env.BASE_URL}/${langName === "sk" ? `` : `${langName}/`}auth/verify?token=${verification.token}&code=${verification.code}`
     const emailLink = `${process.env.BASE_URL}/${langName === "sk" ? `` : `${langName}/`}auth/verify?token=${verification.token}&code=${verification.code}`
 
 
-    await Auth.sendVerification(email, lang.emails.auth.subject, lang.emails.auth.text(verification.code, emailLink), eta.render(`${langName}/email/auth`, { code: verification.code, link: emailLink }))
+    await Auth.sendVerification(email, lang.emails.auth.subject(), lang.emails.auth.text({ code: verification.code, link: emailLink }), eta.render(`${langName}/email/auth`, { code: verification.code, link: emailLink }))
 
 
     return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth/verify?token=${verification.token}`)
     return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth/verify?token=${verification.token}`)
   })
   })

+ 15 - 8
routes/include/panel.js

@@ -50,14 +50,6 @@ export default (langName, lang) => new Elysia({ prefix: "/panel" })
 
 
     const user = Auth.getUser(session.email)
     const user = Auth.getUser(session.email)
 
 
-    const newName = body?.name
-    const newEmail = body?.email
-
-    if (!normalizeEmail(newEmail)) {
-      set.headers['content-type'] = 'text/html; charset=utf8'
-      return eta.render(`${langName}/panel/settings`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, error: "invalidEmail" })
-    }
-
     const turnstileResponse = body?.["cf-turnstile-response"]
     const turnstileResponse = body?.["cf-turnstile-response"]
         
         
     if (!turnstileResponse) {
     if (!turnstileResponse) {
@@ -80,6 +72,21 @@ export default (langName, lang) => new Elysia({ prefix: "/panel" })
       return eta.render(`${langName}/panel/settings`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, error: errorMessage })
       return eta.render(`${langName}/panel/settings`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, error: errorMessage })
     }
     }
 
 
+    const newName = body?.name
+    const newEmail = body?.email
+
+    if (!normalizeEmail(newEmail)) {
+      set.headers['content-type'] = 'text/html; charset=utf8'
+      return eta.render(`${langName}/panel/settings`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, error: "invalidEmail" })
+    }
+
+    const newEmailUser = Auth.getUser(newEmail)
+
+    if (newEmailUser && session.email !== newEmail) {
+      set.headers['content-type'] = 'text/html; charset=utf8'
+      return eta.render(`${langName}/panel/settings`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, error: "emailTaken", errorDetails: { newEmail } })
+    }
+
     Auth.editUser(session.email, newName, newEmail)
     Auth.editUser(session.email, newName, newEmail)
 
 
     set.headers['content-type'] = 'text/html; charset=utf8'
     set.headers['content-type'] = 'text/html; charset=utf8'

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

@@ -196,7 +196,7 @@ export default (langName, lang) => new Elysia({ prefix: "/stations" })
 
 
     if (!newOwner) {
     if (!newOwner) {
       set.headers['content-type'] = 'text/html; charset=utf8'
       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: "ownerUserNotFound" })
+      return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "ownerUserNotFound", errorDetails: { newOwnerEmail } })
     }
     }
 
 
     const newDescription = body?.description
     const newDescription = body?.description

+ 21 - 11
routes/include/websocket.js

@@ -1,11 +1,27 @@
 import { Elysia } from 'elysia'
 import { Elysia } from 'elysia'
 import Meteostanice from '../../utils/meteostanice'
 import Meteostanice from '../../utils/meteostanice'
 
 
-export default new Elysia()
+export default (langName, lang) => new Elysia({ prefix: "/ws" })
   .ws("/sendData/:key", {
   .ws("/sendData/:key", {
+    open({ send, data: { params: { key } } }) {
+        const meteostanica = Meteostanice.getWebsocket(key)
+
+        if (!meteostanica) {
+            return send(lang.websocket.errors.invalidKey({ key }))
+        }
+
+        return send(lang.websocket.keepalive())
+    },
+
     message({ send, data: { params: { key } } }, message) {
     message({ send, data: { params: { key } } }, message) {
-        if (message === "meow :3") {
-            return send("meow :3")
+        if (message === lang.websocket.keepalive()) {
+            return send(lang.websocket.keepalive())
+        }
+
+        const meteostanica = Meteostanice.getWebsocket(key)
+
+        if (!meteostanica) {
+            return send(lang.websocket.errors.invalidKey({ key }))
         }
         }
 
 
         message = Bun.JSON5.parse(message.toString())
         message = Bun.JSON5.parse(message.toString())
@@ -21,13 +37,7 @@ export default new Elysia()
             !message?.outdoorHumidity ||
             !message?.outdoorHumidity ||
             !message?.outdoorAltitude
             !message?.outdoorAltitude
         ) {
         ) {
-            return send("missing required fields: indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude")
-        }
-
-        const meteostanica = Meteostanice.getWebsocket(key)
-
-        if (!meteostanica) {
-            return send("invalid station websocket key")
+            return send(lang.websocket.errors.missingFields())
         }
         }
 
 
         const {
         const {
@@ -55,6 +65,6 @@ export default new Elysia()
             outdoorAltitude
             outdoorAltitude
         )
         )
 
 
-        send(`posted data for ${meteostanica.name}`)
+        return send(lang.websocket.dataSaved({ meteostanica }))
     }
     }
   })
   })

+ 1 - 3
routes/index.js

@@ -2,9 +2,7 @@ import { Elysia } from 'elysia'
 
 
 import enRoutes from './lang/en'
 import enRoutes from './lang/en'
 import skRoutes from './lang/sk'
 import skRoutes from './lang/sk'
-import websocket from './include/websocket'
 
 
 export default new Elysia()
 export default new Elysia()
   .use(skRoutes)
   .use(skRoutes)
-  .use(enRoutes)
-  .use(websocket)
+  .use(enRoutes)

+ 3 - 1
routes/lang/en.js

@@ -3,10 +3,12 @@ import { Elysia } from 'elysia'
 import mainRoutes from '../include/main'
 import mainRoutes from '../include/main'
 import authRoutes from '../include/auth'
 import authRoutes from '../include/auth'
 import panelRoutes from '../include/panel'
 import panelRoutes from '../include/panel'
+import websocketRoutes from '../include/websocket'
 
 
 import lang from '../../lang/en'
 import lang from '../../lang/en'
 
 
 export default new Elysia({ prefix: "/en" })
 export default new Elysia({ prefix: "/en" })
   .use(mainRoutes("en"))
   .use(mainRoutes("en"))
   .use(authRoutes("en", lang))
   .use(authRoutes("en", lang))
-  .use(panelRoutes("en", lang))
+  .use(panelRoutes("en", lang))
+  .use(websocketRoutes("en", lang))

+ 3 - 1
routes/lang/sk.js

@@ -3,10 +3,12 @@ import { Elysia } from 'elysia'
 import mainRoutes from '../include/main'
 import mainRoutes from '../include/main'
 import authRoutes from '../include/auth'
 import authRoutes from '../include/auth'
 import panelRoutes from '../include/panel'
 import panelRoutes from '../include/panel'
+import websocketRoutes from '../include/websocket'
 
 
 import lang from '../../lang/sk'
 import lang from '../../lang/sk'
 
 
 export default new Elysia()
 export default new Elysia()
   .use(mainRoutes("sk", lang))
   .use(mainRoutes("sk", lang))
   .use(authRoutes("sk", lang))
   .use(authRoutes("sk", lang))
-  .use(panelRoutes("sk", lang))
+  .use(panelRoutes("sk", lang))
+  .use(websocketRoutes("sk", lang))

+ 1 - 1
templates/en/auth/index.eta

@@ -3,7 +3,7 @@
 <h2>auth</h2>
 <h2>auth</h2>
 <p>enter your email and we will send you a code.</p>
 <p>enter your email and we will send you a code.</p>
 
 
-<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.auth?.errors) %>
+<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.auth?.errors)?.(it?.errorDetails) %>
 
 
 <% if (typeof errorValue === "string") { %>
 <% if (typeof errorValue === "string") { %>
   <p class="error" style="color: red;"><%= errorValue %></p>
   <p class="error" style="color: red;"><%= errorValue %></p>

+ 1 - 1
templates/en/auth/verify.eta

@@ -3,7 +3,7 @@
 <h2>verification</h2>
 <h2>verification</h2>
 <p>enter the code from your email or click on the link</p>
 <p>enter the code from your email or click on the link</p>
 
 
-<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.auth?.errors) %>
+<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.auth?.errors)?.(it?.errorDetails) %>
 
 
 <% if (errorValue) { %>
 <% if (errorValue) { %>
   <p class="error" style="color: red;"><%= errorValue %></p>
   <p class="error" style="color: red;"><%= errorValue %></p>

+ 1 - 1
templates/en/panel/settings.eta

@@ -5,7 +5,7 @@
 <div>
 <div>
   <h2>settings</h2>
   <h2>settings</h2>
 
 
-  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.settings?.errors) %>
+  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.settings?.errors)?.(it?.errorDetails) %>
 
 
   <% if (typeof errorValue === "string") { %>
   <% if (typeof errorValue === "string") { %>
     <span class="error"><%= errorValue %></span>
     <span class="error"><%= errorValue %></span>

+ 1 - 1
templates/en/panel/stations/add.eta

@@ -5,7 +5,7 @@
 <div>
 <div>
   <h2>add a station</h2>
   <h2>add a station</h2>
 
 
-  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.stations?.errors) %>
+  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.stations?.errors)?.(it?.errorDetails) %>
 
 
   <% if (typeof errorValue === "string") { %>
   <% if (typeof errorValue === "string") { %>
     <span class="error"><%= errorValue %></span>
     <span class="error"><%= errorValue %></span>

+ 1 - 1
templates/en/panel/stations/edit.eta

@@ -5,7 +5,7 @@
 <div>
 <div>
   <h2><%= it.meteostanica.name %> - edit</h2>
   <h2><%= it.meteostanica.name %> - edit</h2>
 
 
-  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.stations?.errors) %>
+  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.stations?.errors)?.(it?.errorDetails) %>
 
 
   <% if (typeof errorValue === "string") { %>
   <% if (typeof errorValue === "string") { %>
     <span class="error"><%= errorValue %></span>
     <span class="error"><%= errorValue %></span>

+ 1 - 1
templates/sk/auth/index.eta

@@ -3,7 +3,7 @@
 <h1>prihlásenie</h1>
 <h1>prihlásenie</h1>
 <p>zadajte svoj email a kliknite na link, ktorý vám príde.</p>
 <p>zadajte svoj email a kliknite na link, ktorý vám príde.</p>
 
 
-<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.auth?.errors) %>
+<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.auth?.errors)?.(it?.errorDetails) %>
 
 
 <% if (typeof errorValue === "string") { %>
 <% if (typeof errorValue === "string") { %>
   <p class="error" style="color: red;"><%= errorValue %></p>
   <p class="error" style="color: red;"><%= errorValue %></p>

+ 1 - 1
templates/sk/auth/verify.eta

@@ -3,7 +3,7 @@
 <h1>overenie</h1>
 <h1>overenie</h1>
 <p>zadajte kód z emailu alebo kliknite na link v emaili</p>
 <p>zadajte kód z emailu alebo kliknite na link v emaili</p>
 
 
-<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.auth?.errors) %>
+<% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.auth?.errors)?.(it?.errorDetails) %>
 
 
 <% if (typeof errorValue === "string") { %>
 <% if (typeof errorValue === "string") { %>
   <p class="error" style="color: red;"><%= errorValue %></p>
   <p class="error" style="color: red;"><%= errorValue %></p>

+ 2 - 2
templates/sk/panel/settings.eta

@@ -5,7 +5,7 @@
 <div>
 <div>
   <h2>nastavenia</h2>
   <h2>nastavenia</h2>
 
 
-  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.settings?.errors) %>
+  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.settings?.errors)?.(it?.errorDetails) %>
 
 
   <% if (typeof errorValue === "string") { %>
   <% if (typeof errorValue === "string") { %>
     <span class="error"><%= errorValue %></span>
     <span class="error"><%= errorValue %></span>
@@ -14,7 +14,7 @@
   <form action="/panel/settings" method="post">
   <form action="/panel/settings" method="post">
     <div>
     <div>
       <label for="name">meno</label>
       <label for="name">meno</label>
-      <input type="text" id="name" name="name" placeholder="ferris" value="<%= it.user.name ?? "" %>">
+      <input type="text" id="name" name="name" placeholder="vaše meno" value="<%= it.user.name ?? "" %>">
     </div>
     </div>
 
 
     <br>
     <br>

+ 1 - 1
templates/sk/panel/stations/add.eta

@@ -5,7 +5,7 @@
 <div>
 <div>
   <h2>pridať stanicu</h2>
   <h2>pridať stanicu</h2>
 
 
-  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.stations?.errors) %>
+  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.stations?.errors)?.(it?.errorDetails) %>
 
 
   <% if (typeof errorValue === "string") { %>
   <% if (typeof errorValue === "string") { %>
     <span class="error"><%= errorValue %></span>
     <span class="error"><%= errorValue %></span>

+ 1 - 1
templates/sk/panel/stations/edit.eta

@@ -5,7 +5,7 @@
 <div>
 <div>
   <h2><%= it.meteostanica.name %> - upraviť</h2>
   <h2><%= it.meteostanica.name %> - upraviť</h2>
 
 
-  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.stations?.errors) %>
+  <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.stations?.errors)?.(it?.errorDetails) %>
 
 
   <% if (typeof errorValue === "string") { %>
   <% if (typeof errorValue === "string") { %>
     <span class="error"><%= errorValue %></span>
     <span class="error"><%= errorValue %></span>

+ 1 - 1
utils/getFormatBasedOnCount.js

@@ -11,5 +11,5 @@ export default (formats, count) => {
     }
     }
   }
   }
 
 
-  return formats[formatNumber];
+  return formats[formatNumber]?.();
 }
 }

+ 5 - 6
utils/ratelimits.js

@@ -13,14 +13,14 @@ export default (type, value, limit, seconds) => {
         VALUES ($value)
         VALUES ($value)
         ON CONFLICT(subject) DO UPDATE SET
         ON CONFLICT(subject) DO UPDATE SET
             countLeft = CASE 
             countLeft = CASE 
-                WHEN (strftime('%s', 'now') - strftime('%s', timestamp)) > $seconds THEN $limit
+                WHEN unixepoch('now') > unixepoch(timestamp) + $seconds THEN $limit
                 ELSE MAX(0, countLeft - 1)
                 ELSE MAX(0, countLeft - 1)
             END,
             END,
             timestamp = CASE 
             timestamp = CASE 
-                WHEN (strftime('%s', 'now') - strftime('%s', timestamp)) > $seconds THEN CURRENT_TIMESTAMP
+                WHEN unixepoch('now') > unixepoch(timestamp) + $seconds THEN CURRENT_TIMESTAMP
                 ELSE timestamp
                 ELSE timestamp
             END
             END
-        RETURNING countLeft, timestamp;
+        RETURNING countLeft, timestamp, (unixepoch(timestamp) + $seconds - unixepoch('now')) AS duration;
     `);
     `);
 
 
     const result = statement.get({
     const result = statement.get({
@@ -30,9 +30,8 @@ export default (type, value, limit, seconds) => {
     });
     });
     
     
     if (result.countLeft <= 0) {
     if (result.countLeft <= 0) {
-        const resetTime = new Date(new Date(result.timestamp).getTime() + seconds * 1000);
-        return { status: true, until: resetTime };
+        return { status: true, ...result };
     }
     }
 
 
-    return { status: false, countLeft: result.countLeft };
+    return { status: false, ...result };
 }
 }