stations.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import { Elysia } from 'elysia'
  2. import { Eta } from "eta"
  3. const eta = new Eta({ views: "./templates" })
  4. import Auth from '../../../utils/auth';
  5. import Meteostanice from '../../../utils/meteostanice';
  6. import validateTurnstile from '../../../utils/validateTurnstile';
  7. import normalizeEmail from '../../../utils/normalizeEmail';
  8. import stationsHistory from './stationsHistory';
  9. export default (langName, lang) => new Elysia({ prefix: "/stations" })
  10. .use(stationsHistory(langName, lang))
  11. .get("/", async ({ cookie, redirect, set }) => {
  12. const token = cookie.session.value
  13. const session = await Auth.getSession(token)
  14. if (!session) {
  15. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  16. }
  17. const user = Auth.getUser(session.email)
  18. const meteostanice = Meteostanice.getOwned(session.email)
  19. set.headers['content-type'] = 'text/html; charset=utf8'
  20. return eta.render(`${langName}/panel/stations/index`, { user, meteostanice })
  21. })
  22. .get("/add", async ({ cookie, redirect, set }) => {
  23. const token = cookie.session.value
  24. const session = await Auth.getSession(token)
  25. if (!session) {
  26. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  27. }
  28. const user = Auth.getUser(session.email)
  29. set.headers['content-type'] = 'text/html; charset=utf8'
  30. return eta.render(`${langName}/panel/stations/add`, { siteKey: process.env.TURNSTILE_SITE_KEY, user })
  31. })
  32. .post("/add", async ({ request, server, cookie, redirect, body, set }) => {
  33. const clientIP = request.headers.get('x-forwarded-for') ?? server.requestIP(request).address
  34. const token = cookie.session.value
  35. const session = await Auth.getSession(token)
  36. if (!session) {
  37. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  38. }
  39. const user = Auth.getUser(session.email)
  40. const turnstileResponse = body?.["cf-turnstile-response"]
  41. if (!turnstileResponse) {
  42. set.headers['content-type'] = 'text/html; charset=utf8'
  43. return eta.render(`${langName}/panel/stations/add`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, error: "turnstile.noToken" })
  44. }
  45. const turnstileValid = await validateTurnstile(turnstileResponse, clientIP)
  46. if (!turnstileValid.success) {
  47. let errorMessage = `turnstile.unavailable`;
  48. if (turnstileValid["error-codes"]?.includes("invalid-input-response"))
  49. errorMessage = `turnstile.invalidResponse`
  50. if (turnstileValid["error-codes"]?.includes("timeout-or-duplicate"))
  51. errorMessage = `turnstile.keyUsedOrExpired`
  52. set.headers['content-type'] = 'text/html; charset=utf8'
  53. return eta.render(`${langName}/panel/stations/add`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, error: errorMessage })
  54. }
  55. let name = body?.name
  56. if (!name) name = lang.general.generateName()
  57. const description = body?.description
  58. Meteostanice.add(session.email, name, description)
  59. return redirect(`/${langName === "sk" ? `` : `${langName}/`}panel/stations`)
  60. })
  61. .get("/:station", async ({ cookie, redirect, set, params: { station } }) => {
  62. const token = cookie.session.value
  63. const session = await Auth.getSession(token)
  64. if (!session) {
  65. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  66. }
  67. const user = Auth.getUser(session.email)
  68. if (!station) {
  69. set.headers['content-type'] = 'text/html; charset=utf8'
  70. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  71. }
  72. const meteostanica = Meteostanice.get(session.email, station)
  73. if (!meteostanica) {
  74. set.headers['content-type'] = 'text/html; charset=utf8'
  75. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  76. }
  77. const data = Meteostanice.getData(meteostanica.id)
  78. set.headers['content-type'] = 'text/html; charset=utf8'
  79. return eta.render(`${langName}/panel/stations/station`, { user, lang, meteostanica, data })
  80. })
  81. .get("/:station/edit", async ({ cookie, redirect, set, params: { station } }) => {
  82. const token = cookie.session.value
  83. const session = await Auth.getSession(token)
  84. if (!session) {
  85. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  86. }
  87. const user = Auth.getUser(session.email)
  88. if (!station) {
  89. set.headers['content-type'] = 'text/html; charset=utf8'
  90. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  91. }
  92. const meteostanica = Meteostanice.get(session.email, station)
  93. if (!meteostanica) {
  94. set.headers['content-type'] = 'text/html; charset=utf8'
  95. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  96. }
  97. set.headers['content-type'] = 'text/html; charset=utf8'
  98. return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, user, meteostanica })
  99. })
  100. .post("/:station/edit", async ({ request, server, cookie, redirect, params: { station }, body, set }) => {
  101. const clientIP = request.headers.get('x-forwarded-for') ?? server.requestIP(request).address
  102. const token = cookie.session.value
  103. const session = await Auth.getSession(token)
  104. if (!session) {
  105. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  106. }
  107. const user = Auth.getUser(session.email)
  108. if (!station) {
  109. set.headers['content-type'] = 'text/html; charset=utf8'
  110. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  111. }
  112. const meteostanica = Meteostanice.get(session.email, station)
  113. if (!meteostanica) {
  114. set.headers['content-type'] = 'text/html; charset=utf8'
  115. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  116. }
  117. const turnstileResponse = body?.["cf-turnstile-response"]
  118. if (!turnstileResponse) {
  119. set.headers['content-type'] = 'text/html; charset=utf8'
  120. return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "turnstile.noToken" })
  121. }
  122. const turnstileValid = await validateTurnstile(turnstileResponse, clientIP)
  123. if (!turnstileValid.success) {
  124. let errorMessage = `turnstile.unavailable`;
  125. if (turnstileValid["error-codes"]?.includes("invalid-input-response"))
  126. errorMessage = `turnstile.invalidResponse`
  127. if (turnstileValid["error-codes"]?.includes("timeout-or-duplicate"))
  128. errorMessage = `turnstile.keyUsedOrExpired`
  129. set.headers['content-type'] = 'text/html; charset=utf8'
  130. return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: errorMessage })
  131. }
  132. const newName = body?.name
  133. if (!newName) {
  134. set.headers['content-type'] = 'text/html; charset=utf8'
  135. return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "noName" })
  136. }
  137. const newOwnerEmail = body?.owner
  138. if (!normalizeEmail(newOwnerEmail)) {
  139. set.headers['content-type'] = 'text/html; charset=utf8'
  140. return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "invalidOwner" })
  141. }
  142. const newOwner = Auth.getUser(newOwnerEmail)
  143. if (!newOwner) {
  144. set.headers['content-type'] = 'text/html; charset=utf8'
  145. return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "ownerUserNotFound", errorDetails: { newOwnerEmail } })
  146. }
  147. const subowners = body?.subowners
  148. if (subowners) {
  149. const subownersSplit = subowners.split(',')
  150. for (const subownerEmail of subownersSplit) {
  151. if (!normalizeEmail(subownerEmail)) {
  152. set.headers['content-type'] = 'text/html; charset=utf8'
  153. return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "invalidSubowner" })
  154. }
  155. const subowner = Auth.getUser(subownerEmail)
  156. if (!subowner) {
  157. set.headers['content-type'] = 'text/html; charset=utf8'
  158. return eta.render(`${langName}/panel/stations/edit`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user, meteostanica, error: "subownerUserNotFound", errorDetails: { subownerEmail } })
  159. }
  160. }
  161. }
  162. const newDescription = body?.description
  163. Meteostanice.edit(meteostanica.id, newName, newDescription, newOwnerEmail, subowners)
  164. return redirect(`/${langName === "sk" ? `` : `${langName}/`}panel/stations/${meteostanica.id}`)
  165. })
  166. .get("/:station/resetWebsocketKey", async ({ cookie, redirect, set, params: { station } }) => {
  167. const token = cookie.session.value
  168. const session = await Auth.getSession(token)
  169. if (!session) {
  170. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  171. }
  172. const user = Auth.getUser(session.email)
  173. if (!station) {
  174. set.headers['content-type'] = 'text/html; charset=utf8'
  175. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  176. }
  177. const meteostanica = Meteostanice.get(session.email, station)
  178. if (!meteostanica) {
  179. set.headers['content-type'] = 'text/html; charset=utf8'
  180. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  181. }
  182. Meteostanice.resetWebsocketKey(meteostanica.id)
  183. return redirect(`/${langName === "sk" ? `` : `${langName}/`}panel/stations/${meteostanica.id}`)
  184. })
  185. .get("/:station/delete", async ({ cookie, redirect, set, params: { station } }) => {
  186. const token = cookie.session.value
  187. const session = await Auth.getSession(token)
  188. if (!session) {
  189. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  190. }
  191. const user = Auth.getUser(session.email)
  192. if (!station) {
  193. set.headers['content-type'] = 'text/html; charset=utf8'
  194. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  195. }
  196. const meteostanica = Meteostanice.get(session.email, station)
  197. if (!meteostanica) {
  198. set.headers['content-type'] = 'text/html; charset=utf8'
  199. return eta.render(`${langName}/panel/stations/notFound`, { lang, user })
  200. }
  201. set.headers['content-type'] = 'text/html; charset=utf8'
  202. return eta.render(`${langName}/panel/stations/delete`, { user, meteostanica })
  203. })
  204. .get("/:station/deleteConfirm", async ({ cookie, redirect, set, params: { station } }) => {
  205. const token = cookie.session.value
  206. const session = await Auth.getSession(token)
  207. if (!session) {
  208. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  209. }
  210. const user = Auth.getUser(session.email)
  211. if (!station) {
  212. set.headers['content-type'] = 'text/html; charset=utf8'
  213. return eta.render(`${langName}/panel/stations/notFound`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user })
  214. }
  215. const meteostanica = Meteostanice.get(session.email, station)
  216. if (!meteostanica) {
  217. set.headers['content-type'] = 'text/html; charset=utf8'
  218. return eta.render(`${langName}/panel/stations/notFound`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user })
  219. }
  220. Meteostanice.delete(meteostanica.id)
  221. return redirect(`/${langName === "sk" ? `` : `${langName}/`}panel/stations`)
  222. })
  223. .get('/:station/currentData', async ({ cookie, redirect, set, params: { station } }) => {
  224. const token = cookie.session.value
  225. const session = await Auth.getSession(token)
  226. if (!session) {
  227. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=loginNeeded`)
  228. }
  229. const user = Auth.getUser(session.email)
  230. if (!station) {
  231. set.headers['content-type'] = 'text/html; charset=utf8'
  232. return eta.render(`${langName}/panel/stations/notFound`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user })
  233. }
  234. const meteostanica = Meteostanice.get(session.email, station)
  235. if (!meteostanica) {
  236. set.headers['content-type'] = 'text/html; charset=utf8'
  237. return eta.render(`${langName}/panel/stations/notFound`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, user })
  238. }
  239. const data = Meteostanice.getData(station)
  240. return data?.[0]
  241. })