meteostanice.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. import { Database } from "bun:sqlite";
  2. import { nanoid } from 'nanoid'
  3. import generateSecureRandomString from "./generateSecureRandomString";
  4. const meteostaniceDB = new Database("./data/meteostanice.sqlite");
  5. meteostaniceDB.run(`create table if not exists list (
  6. id text not null primary key,
  7. owner text not null,
  8. name text not null,
  9. description text,
  10. websocketKey text not null,
  11. subowners text,
  12. timestamp datetime default current_timestamp
  13. );`)
  14. meteostaniceDB.run(`create table if not exists data (
  15. id text not null primary key,
  16. meteostanica text not null,
  17. timestamp datetime default current_timestamp,
  18. indoorTemp text not null,
  19. indoorPressure text not null,
  20. indoorHumidity text not null,
  21. indoorAltitude text not null,
  22. outdoorConnected integer not null,
  23. outdoorTemp text not null,
  24. outdoorPressure text not null,
  25. outdoorHumidity text not null,
  26. outdoorAltitude text not null
  27. );`)
  28. meteostaniceDB.run(`create table if not exists public (
  29. id text not null primary key,
  30. name text not null,
  31. description text,
  32. timestamp datetime default current_timestamp,
  33. showOwner integer default 1,
  34. showIndoorTemp integer default 1,
  35. showIndoorPressure integer default 1,
  36. showIndoorHumidity integer default 1,
  37. showIndoorAltitude integer default 1,
  38. showOutdoorConnected integer default 1,
  39. showOutdoorTemp integer default 1,
  40. showOutdoorPressure integer default 1,
  41. showOutdoorHumidity integer default 1,
  42. showOutdoorAltitude integer default 1
  43. );`)
  44. export default class Meteostanice {
  45. static add(owner, name, description) {
  46. const id = nanoid()
  47. const websocketKey = generateSecureRandomString()
  48. meteostaniceDB.prepare(`
  49. INSERT INTO list (id, owner, name, description, websocketKey)
  50. VALUES (?, ?, ?, ?, ?);
  51. `).run(id, owner, name, description, websocketKey)
  52. }
  53. static get(owner, id) {
  54. const statement = meteostaniceDB.prepare(`
  55. SELECT *
  56. FROM list
  57. WHERE id = $id
  58. AND (
  59. owner = $owner
  60. OR EXISTS (
  61. SELECT 1
  62. FROM json_each(list.subowners)
  63. WHERE value = $owner
  64. )
  65. );
  66. `);
  67. const result = statement.get({
  68. $owner: owner,
  69. $id: id
  70. });
  71. return result
  72. }
  73. static getWebsocket(key) {
  74. const statement = meteostaniceDB.prepare(`
  75. SELECT *
  76. FROM list
  77. WHERE websocketKey = $key;
  78. `)
  79. const result = statement.get({
  80. $key: key
  81. });
  82. return result
  83. }
  84. static getOwned(owner) {
  85. const statement = meteostaniceDB.prepare(`
  86. SELECT *
  87. FROM list
  88. WHERE owner = $owner
  89. OR EXISTS (
  90. SELECT *
  91. FROM json_each(list.subowners)
  92. WHERE value = $owner
  93. )
  94. ORDER BY timestamp DESC;
  95. `)
  96. const result = statement.all({
  97. $owner: owner
  98. });
  99. return result
  100. }
  101. static edit(id, newName, newDescription, newOwner, subowners) {
  102. let newSubowners = subowners ? subowners.split(',').map(s => s.trim()).filter(Boolean) : [];
  103. meteostaniceDB.prepare(`
  104. update list
  105. set name = ?,
  106. description = ?,
  107. owner = ?,
  108. subowners = json(?)
  109. where id = ?;
  110. `).run(newName, newDescription, newOwner, JSON.stringify(newSubowners), id)
  111. }
  112. static editOwnerOnOwned(owner, newOwner) {
  113. meteostaniceDB.prepare(`
  114. UPDATE list
  115. SET
  116. -- Update the primary owner if it matches
  117. owner = CASE WHEN owner = $owner THEN $newOwner ELSE owner END,
  118. -- Update the subowners array if it contains the owner
  119. subowners = CASE
  120. WHEN EXISTS (SELECT 1 FROM json_each(subowners) WHERE value = $owner)
  121. THEN (
  122. SELECT json_group_array(
  123. CASE WHEN value = $owner THEN $newOwner ELSE value END
  124. )
  125. FROM json_each(subowners)
  126. )
  127. ELSE subowners
  128. END
  129. WHERE owner = $owner
  130. OR EXISTS (SELECT 1 FROM json_each(subowners) WHERE value = $owner);
  131. `).run({
  132. $owner: owner,
  133. $newOwner: newOwner
  134. });
  135. }
  136. static delete(id) {
  137. meteostaniceDB.prepare(`
  138. DELETE
  139. FROM data
  140. WHERE meteostanica = $id;
  141. `).run({
  142. $id: id
  143. });
  144. meteostaniceDB.prepare(`
  145. DELETE
  146. FROM public
  147. WHERE id = $id;
  148. `).run({
  149. $id: id
  150. });
  151. meteostaniceDB.prepare(`
  152. DELETE
  153. FROM list
  154. WHERE id = $id;
  155. `).run({
  156. $id: id
  157. });
  158. }
  159. static deleteOwned(owner) {
  160. const meteostanice = this.getOwned(owner)
  161. for (const meteostanica of meteostanice) {
  162. this.delete(meteostanica.owner, meteostanica.id)
  163. }
  164. }
  165. static postData(meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude, timestamp) {
  166. const id = nanoid()
  167. meteostaniceDB.prepare(`
  168. INSERT INTO data (id, meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude${timestamp ? `, timestamp` : ``})
  169. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?${timestamp ? `, ?` : ``});
  170. `).run(id, meteostanica, indoorTemp, indoorPressure, indoorHumidity, indoorAltitude, outdoorConnected, outdoorTemp, outdoorPressure, outdoorHumidity, outdoorAltitude, timestamp)
  171. }
  172. static getData(meteostanica) {
  173. const statement = meteostaniceDB.prepare(`
  174. SELECT *
  175. FROM data
  176. WHERE meteostanica = $meteostanica
  177. ORDER BY timestamp DESC;
  178. `)
  179. const result = statement.all({
  180. $meteostanica: meteostanica
  181. });
  182. return result
  183. }
  184. static getDataProperty(meteostanica, property) {
  185. const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
  186. if (!tableNames.find(i => i.name === property)) return null
  187. const statement = meteostaniceDB.prepare(`
  188. SELECT ${property}
  189. FROM data
  190. WHERE meteostanica = $meteostanica
  191. ORDER BY timestamp DESC
  192. LIMIT 10;
  193. `)
  194. const result = statement.all({
  195. $meteostanica: meteostanica
  196. });
  197. return result
  198. }
  199. static getDataPropertyDaily(meteostanica, property, date) {
  200. const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
  201. if (!tableNames.find(i => i.name === property)) return null
  202. const statement = meteostaniceDB.prepare(`
  203. SELECT strftime('%Y-%m-%d %H:00:00', timestamp) AS timeMark,
  204. AVG(${property}) AS value
  205. FROM data
  206. WHERE meteostanica = ? AND date(timestamp) = ? -- Pass 'YYYY-MM-DD' here
  207. GROUP BY timeMark
  208. ORDER BY timeMark;
  209. `)
  210. const result = statement.all(meteostanica, date);
  211. return result
  212. }
  213. static getDataPropertyMonthly(meteostanica, property, yearMonth) {
  214. const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
  215. if (!tableNames.find(i => i.name === property)) return null
  216. const statement = meteostaniceDB.prepare(`
  217. SELECT date(timestamp) AS timeMark,
  218. AVG(${property}) AS value
  219. FROM data
  220. WHERE meteostanica = ? AND strftime('%Y-%m', timestamp) = ? -- Pass 'YYYY-MM' here
  221. GROUP BY timeMark
  222. ORDER BY timeMark;
  223. `)
  224. const result = statement.all(meteostanica, yearMonth);
  225. return result
  226. }
  227. static getDataPropertyYearly(meteostanica, property, year) {
  228. const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
  229. if (!tableNames.find(i => i.name === property)) return null
  230. const statement = meteostaniceDB.prepare(`
  231. SELECT strftime('%Y-%m', timestamp) AS timeMark,
  232. AVG(${property}) AS value
  233. FROM data
  234. WHERE meteostanica = ? AND strftime('%Y', timestamp) = ? -- Pass 'YYYY' here
  235. GROUP BY timeMark
  236. ORDER BY timeMark;
  237. `)
  238. const result = statement.all(meteostanica, year);
  239. return result
  240. }
  241. static getDataPropertyAllTime(meteostanica, property) {
  242. const tableNames = meteostaniceDB.prepare(`PRAGMA table_info('data');`).all()
  243. if (!tableNames.find(i => i.name === property)) return null
  244. const statement = meteostaniceDB.prepare(`
  245. SELECT strftime('%Y', timestamp) AS timeMark,
  246. AVG(${property}) AS value
  247. FROM data
  248. WHERE meteostanica = ?
  249. GROUP BY timeMark
  250. ORDER BY timeMark;
  251. `)
  252. const result = statement.all(meteostanica);
  253. return result
  254. }
  255. static getDateMap(meteostanica) {
  256. const statement = meteostaniceDB.query("SELECT DISTINCT date(timestamp) as d FROM data WHERE meteostanica = ? ORDER BY d ASC");
  257. const rows = statement.all(meteostanica);
  258. return rows.reduce((acc, row) => {
  259. const [year, month, day] = row.d.split("-");
  260. if (!acc[year]) acc[year] = {};
  261. if (!acc[year][month]) acc[year][month] = [];
  262. acc[year][month].push(day);
  263. return acc;
  264. }, {});
  265. }
  266. static resetWebsocketKey(id) {
  267. const websocketKey = generateSecureRandomString()
  268. meteostaniceDB.prepare(`
  269. update list
  270. set websocketKey = ?
  271. where id = ?;
  272. `).run(websocketKey, id)
  273. }
  274. }