diff --git a/.gitignore b/.gitignore index f4867d4a..916a9f13 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ lib-cov *.gz .vscode .yarn-cache +.DS_Store pids logs diff --git a/.scripts/run-tests.sh b/.scripts/run-tests.sh index 1e92befe..3d8fa9a2 100755 --- a/.scripts/run-tests.sh +++ b/.scripts/run-tests.sh @@ -44,19 +44,32 @@ trap cleanup EXIT function executeTests() { runComposeCommand down -v --remove-orphans + # Start database + runComposeCommand up --quiet-pull -d --remove-orphans db + + # Allow the dust and database to settle + sleep 3 + + # Run database migrations from the models package + runComposeCommand run -T --rm osem-api yarn models:db:migrate + + # Allow the dust to settle + sleep 3 + + # Run API tests if [[ -z $only_models_tests ]]; then - runComposeCommand up --quiet-pull -d --force-recreate --remove-orphans + runComposeCommand up --quiet-pull -d --force-recreate --remove-orphans osem-api mailhog mosquitto redis-stack - # Allow the dust to settle + #Allow the dust to settle sleep 3 runComposeCommand exec -T osem-api yarn mocha --exit tests/waitForHttp.js tests/tests.js runComposeCommand stop osem-api fi - runComposeCommand up --quiet-pull -d --remove-orphans db mailer + # Run Models tests # use ./node_modules/.bin/mocha because the workspace does not have the devDependency mocha - runComposeCommand run -T --workdir=/usr/src/app/packages/models osem-api ../../node_modules/.bin/mocha --exit test/waitForDatabase test/index + runComposeCommand run -T --workdir=/usr/src/app/packages/models osem-api ../../node_modules/.bin/mocha --exit test/index } case "$cmd" in diff --git a/docker-compose.yml b/docker-compose.yml index fcba270a..dd35b5e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,17 @@ -version: "3.9" - volumes: - mongo-data: + opensensemap_backend: services: db: - image: mongo:5 - container_name: osem-dev-mongo + image: timescale/timescaledb-ha:pg15.8-ts2.17.1 + command: + - -cshared_preload_libraries=timescaledb,pg_cron + restart: always + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=opensensemap ports: - - "27017:27017" + - 5432:5432 volumes: - - mongo-data:/data/db - # - ./dumps/boxes:/exports/boxes - # - ./dumps/measurements:/exports/measurements - - ./.scripts/mongodb/osem_admin.sh:/docker-entrypoint-initdb.d/osem_admin.sh - # - ./.scripts/mongodb/osem_seed_boxes.sh:/docker-entrypoint-initdb.d/osem_seed_boxes.sh - # - ./.scripts/mongodb/osem_seed_measurements.sh:/docker-entrypoint-initdb.d/osem_seed_measurements.sh + - opensensemap_backend:/home/postgres/pgdata \ No newline at end of file diff --git a/package.json b/package.json index bc7ad789..eff3e800 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,14 @@ "packages/*" ], "scripts": { + "dev": "node packages/api/app.js", + "db:up": "docker compose up -d db", + "db:down": "docker compose down db", + "models:db:migrate": "yarn workspace @sensebox/opensensemap-api-models db:migrate", "start": "node packages/api/app.js", - "start-dev-db": "sudo docker-compose up -d db", - "stop-dev-db": "sudo docker-compose down db", "build-test-env": "./.scripts/run-tests.sh build", "test": "./.scripts/run-tests.sh", - "test-models": "./.scripts/run-tests.sh only_models", + "test:models": "./.scripts/run-tests.sh only_models", "NOTpretest": "node tests/waitForHttp", "tag-container": "./.scripts/npm_tag-container.sh", "lint:ci": "eslint --ignore-pattern node_modules \"{tests,packages}/**/*.js\"", diff --git a/packages/api/app.js b/packages/api/app.js index 762e5580..9cb900cf 100644 --- a/packages/api/app.js +++ b/packages/api/app.js @@ -10,7 +10,7 @@ 'use strict'; -const { db } = require('@sensebox/opensensemap-api-models'), +const { isReady } = require('@sensebox/opensensemap-api-models'), restify = require('restify'), { fullResponse, @@ -53,9 +53,12 @@ if (config.get('logLevel') === 'debug') { server.use(debugLogger); } -db.connect() - .then(function () { - // attach Routes +const run = async function () { + try { + // Check if the database is ready + await isReady(); + + // Load routes routes(server); // start the server @@ -63,11 +66,14 @@ db.connect() stdLogger.logger.info(`${server.name} listening at ${server.url}`); postToMattermost(`openSenseMap API started. Version: ${getVersion}`); }); - }) - .catch(function (err) { - stdLogger.logger.fatal(err, 'Couldn\'t connect to MongoDB. Exiting...'); + } catch (error) { + stdLogger.logger.fatal(error, 'Couldn\'t connect to PostgreSQL. Exiting...'); process.exit(1); - }); + } +}; + +// 🔥 Fire up API +run(); // InternalServerError is the only error we want to report to Honeybadger.. server.on('InternalServer', function (req, res, err, callback) { diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index 029546b2..9631655c 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -70,6 +70,11 @@ const } = require('../helpers/userParamHelpers'), handleError = require('../helpers/errorHandler'), jsonstringify = require('stringify-stream'); +const { findDeviceById } = require('@sensebox/opensensemap-api-models/src/box/box'); +const { createDevice, findDevices, findTags, updateDevice, findById, generateSketch } = require('@sensebox/opensensemap-api-models/src/device'); +const { findByUserId } = require('@sensebox/opensensemap-api-models/src/password'); +const { getSensorsWithLastMeasurement } = require('@sensebox/opensensemap-api-models/src/sensor'); +const { removeDevice, checkPassword } = require('@sensebox/opensensemap-api-models/src/user/user'); /** * @apiDefine Addons @@ -154,13 +159,14 @@ const */ const updateBox = async function updateBox (req, res) { try { - let box = await Box.findBoxById(req._userParams.boxId, { lean: false, populate: false }); - box = await box.updateBox(req._userParams); - if (box._sensorsChanged === true) { - req.user.mail('newSketch', box); - } - - res.send({ code: 'Ok', data: box.toJSON({ includeSecrets: true }) }); + let device = await findDeviceById(req._userParams.boxId); + device = await updateDevice(device.id, req._userParams); + // if (box._sensorsChanged === true) { + // req.user.mail('newSketch', box); + // } + + // res.send({ code: 'Ok', data: box.toJSON({ includeSecrets: true }) }); + res.send({ code: 'Ok', data: device }); clearCache(['getBoxes']); } catch (err) { return handleError(err); @@ -220,7 +226,7 @@ const geoJsonStringifyReplacer = function geoJsonStringifyReplacer (key, box) { * @apiParam {String=json,geojson} [format=json] the format the sensor data is returned in. * @apiParam {String} [grouptag] only return boxes with this grouptag, allows to specify multiple separated with a comma * @apiParam {String="homeEthernet","homeWifi","homeEthernetFeinstaub","homeWifiFeinstaub","luftdaten_sds011","luftdaten_sds011_dht11","luftdaten_sds011_dht22","luftdaten_sds011_bmp180","luftdaten_sds011_bme280"} [model] only return boxes with this model, allows to specify multiple separated with a comma - * @apiParam {Boolean="true","false"} [classify=false] if specified, the api will classify the boxes accordingly to their last measurements. + * @apiParam @deprecated {Boolean="true","false"} [classify=false] if specified, the api will classify the boxes accordingly to their last measurements. * @apiParam {Boolean="true","false"} [minimal=false] if specified, the api will only return a minimal set of box metadata consisting of [_id, updatedAt, currentLocation, exposure, name] for a fast response. * @apiParam {Boolean="true","false"} [full=false] if true the API will return populated lastMeasurements (use this with caution for now, expensive on the database) * @apiParam {Number} [near] A comma separated coordinate, if specified, the api will only return senseBoxes within maxDistance (in m) of this location @@ -309,34 +315,54 @@ const getBoxes = async function getBoxes (req, res) { } try { - let stream; + // let devices; // Search boxes by name // Directly return results and do nothing else - if (req._userParams.name) { - stream = await Box.findBoxes(req._userParams); - } else { - if (req._userParams.minimal === 'true') { - stream = await Box.findBoxesMinimal(req._userParams); - } else { - stream = await Box.findBoxesLastMeasurements(req._userParams); - } - - if (req._userParams.classify === 'true') { - stream = stream - .pipe(new classifyTransformer()) - .on('error', function (err) { - res.end(`Error: ${err.message}`); - }); - } - } - - stream - .pipe(stringifier) - .on('error', function (err) { - res.end(`Error: ${err.message}`); - }) - .pipe(res); + // if (req._userParams.name) { + // // stream = await Box.findBoxes(req._userParams); + // devices = await findDevices(req._userParams, { id: true, name: true, location: true }); + // } else if (req._userParams.minimal === 'true') { + // devices = await findDevicesMinimal(req._userParams, { id: true, name: true, exposure: true, location: true, status: true }); + // // stream = await Box.findBoxesMinimal(req._userParams); + // } else { + // // stream = await Box.findBoxesLastMeasurements(req._userParams); + // devices = await findDevicesMinimal(req._userParams); + // } + + // if (req._userParams.minimal === 'true') { + // devices = await findDevices(req._userParams, { + // id: true, + // name: true, + // exposure: true, + // location: true, + // status: true + // }); + // // stream = await Box.findBoxesMinimal(req._userParams); + // } else { + // // stream = await Box.findBoxesLastMeasurements(req._userParams); + // devices = await findDevices(req._userParams); + // } + + const devices = await findDevices(req._userParams, {}, { sensors: { columns: { deviceId: false, sensorWikiType: false, sensorWikiPhenomenon: false, sensorWikiUnit: false } } }); + + // Deprecated: classify is performed by database + // if (req._userParams.classify === 'true') { + // stream = stream + // .pipe(new classifyTransformer()) + // .on('error', function (err) { + // res.end(`Error: ${err.message}`); + // }); + // } + // } + + // stream + // .pipe(stringifier) + // .on('error', function (err) { + // res.end(`Error: ${err.message}`); + // }) + // .pipe(res); + res.send(devices); } catch (err) { return handleError(err); } @@ -451,16 +477,17 @@ const getBox = async function getBox (req, res) { const { format, boxId } = req._userParams; try { - const box = await Box.findBoxById(boxId); + const device = await findDeviceById(boxId); + const sensorsWithMeasurements = await getSensorsWithLastMeasurement(boxId); + + device.sensors = sensorsWithMeasurements; - if (format === 'geojson') { - const coordinates = box.currentLocation.coordinates; - box.currentLocation = undefined; - box.loc = undefined; + if (format === 'geojson') { // Handle with PostGIS Extension + const coordinates = [device.longitude, device.latitude]; - return res.send(point(coordinates, box)); + return res.send(point(coordinates, device)); } - res.send(box); + res.send(device); } catch (err) { return handleError(err); } @@ -499,18 +526,19 @@ const getBox = async function getBox (req, res) { */ const postNewBox = async function postNewBox (req, res) { try { - let newBox = await req.user.addBox(req._userParams); - newBox = await Box.populate(newBox, Box.BOX_SUB_PROPS_FOR_POPULATION); - res.send(201, { message: 'Box successfully created', data: newBox }); + const newDevice = await createDevice(req.user.id, req._userParams); + // TODO: only return specific fields newBox = await Box.populate(newBox, Box.BOX_SUB_PROPS_FOR_POPULATION); + + res.send(201, { message: 'Box successfully created', data: newDevice }); clearCache(['getBoxes', 'getStats']); postToMattermost( `New Box: ${req.user.name} (${redactEmail( req.user.email - )}) just registered "${newBox.name}" (${ - newBox.model + )}) just registered "${newDevice.name}" (${ + newDevice.model }): [https://opensensemap.org/explore/${ - newBox._id - }](https://opensensemap.org/explore/${newBox._id})` + newDevice.id + }](https://opensensemap.org/explore/${newDevice.id})` ); } catch (err) { return handleError(err); @@ -537,7 +565,7 @@ const postNewBox = async function postNewBox (req, res) { const getSketch = async function getSketch (req, res) { res.header('Content-Type', 'text/plain; charset=utf-8'); try { - const box = await Box.findBoxById(req._userParams.boxId, { populate: false, lean: false }); + const device = await findById(req._userParams.boxId, { accessToken: true, sensors: true }); const params = { serialPort: req._userParams.serialPort, @@ -553,11 +581,11 @@ const getSketch = async function getSketch (req, res) { }; // pass access token only if useAuth is true and access_token is available - if (box.access_token) { - params.access_token = box.access_token; + if (device.useAuth && device.accessToken) { + params.access_token = device.accessToken.token; } - res.send(box.getSketch(params)); + res.send(generateSketch(device, params)); } catch (err) { return handleError(err); } @@ -577,11 +605,14 @@ const deleteBox = async function deleteBox (req, res) { const { password, boxId } = req._userParams; try { - await req.user.checkPassword(password); - const box = await req.user.removeBox(boxId); - res.send({ code: 'Ok', message: 'box and all associated measurements marked for deletion' }); + const hashedPassword = await findByUserId(req.user.id); + + await checkPassword(password, hashedPassword); + const device = await removeDevice(boxId); + + res.send({ code: 'Ok', message: 'device and all associated measurements marked for deletion' }); clearCache(['getBoxes', 'getStats']); - postToMattermost(`Box deleted: ${req.user.name} (${redactEmail(req.user.email)}) just deleted "${box.name}" (${boxId})`); + postToMattermost(`Device deleted: ${req.user.name} (${redactEmail(req.user.email)}) just deleted "${device.name}" (${boxId})`); } catch (err) { return handleError(err); @@ -698,10 +729,11 @@ const claimBox = async function claimBox (req, res) { const getAllTags = async function getAllTags (req, res) { try { - const grouptags = await Box.find().distinct('grouptag') - .exec(); + const tags = await findTags(); + // const grouptags = await Box.find().distinct('grouptag') + // .exec(); - res.send({ code: 'Ok', data: grouptags }); + res.send({ code: 'Ok', data: tags }); } catch (err) { return handleError(err); } @@ -925,7 +957,7 @@ module.exports = { }, { name: 'full', defaultValue: 'false', allowedValues: ['true', 'false'] }, { predef: 'near' }, - { name: 'maxDistance' }, + { name: 'maxDistance', dataType: Number, defaultValue: 1000 }, { predef: 'bbox' } ]), parseAndValidateTimeParamsForFindAllBoxes, diff --git a/packages/api/lib/controllers/measurementsController.js b/packages/api/lib/controllers/measurementsController.js index c85a6f4b..669f77fd 100644 --- a/packages/api/lib/controllers/measurementsController.js +++ b/packages/api/lib/controllers/measurementsController.js @@ -1,5 +1,8 @@ 'use strict'; +const { findAccessToken, findById, saveMeasurement, saveMeasurements } = require('@sensebox/opensensemap-api-models/src/device'); +const { hasDecoder, decodeMeasurements } = require('@sensebox/opensensemap-api-models/src/measurement'); +const { getSensorsWithLastMeasurement, getSensorWithLastMeasurement, getSensorsWithLastMeasurements } = require('@sensebox/opensensemap-api-models/src/sensor'); const { BadRequestError, UnsupportedMediaTypeError, @@ -62,59 +65,79 @@ const { */ const getLatestMeasurements = async function getLatestMeasurements (req, res) { const { _userParams: params } = req; + const { boxId, sensorId, count, onlyValue } = req._userParams; - let box; + let device; try { - if (req._userParams.count) { - box = await Box.findBoxById(req._userParams.boxId, { - populate: false, - onlyLastMeasurements: false, - count: req._userParams.count, - projection: { - name: 1, - lastMeasurementAt: 1, - sensors: 1, - grouptag: 1 - } - }); - - const measurements = await Measurement.findLatestMeasurementsForSensorsWithCount(box, req._userParams.count); - for (let index = 0; index < box.sensors.length; index++) { - const sensor = box.sensors[index]; - const values = measurements.find(elem => elem._id.equals(sensor._id)); - sensor['lastMeasurements'] = values; - } - } else { - box = await Box.findBoxById(req._userParams.boxId, { - onlyLastMeasurements: true - }); - } - } catch (err) { - return handleError(err); - } - - if (params.sensorId) { - const sensor = box.sensors.find(s => s._id.equals(params.sensorId)); - if (sensor) { - if (params.onlyValue) { - if (!sensor.lastMeasurement) { - res.send(undefined); + // if (req._userParams.count) { + // box = await Box.findBoxById(req._userParams.boxId, { + // populate: false, + // onlyLastMeasurements: false, + // count: req._userParams.count, + // projection: { + // name: 1, + // lastMeasurementAt: 1, + // sensors: 1, + // grouptag: 1 + // } + // }); + + // const measurements = await Measurement.findLatestMeasurementsForSensorsWithCount(box, req._userParams.count); + // for (let index = 0; index < box.sensors.length; index++) { + // const sensor = box.sensors[index]; + // const values = measurements.find(elem => elem._id.equals(sensor._id)); + // sensor['lastMeasurements'] = values; + // } + // } else { + // box = await Box.findBoxById(req._userParams.boxId, { + // onlyLastMeasurements: true + // }); + device = await findById(boxId, { sensors: true }); + + if (sensorId) { + const sensor = device.sensors.find((s) => s.id === sensorId); + if (sensor) { + const sensorWithMeasurements = await getSensorWithLastMeasurement( + boxId, + sensorId, + count + ); + + if (onlyValue) { + if (!sensorWithMeasurements[0].lastMeasurement.value) { + res.send(undefined); + + return; + } + + res.send(sensorWithMeasurements[0].lastMeasurement.value); return; } - res.send(sensor.lastMeasurement.value); + + res.send(sensorWithMeasurements[0]); return; } - res.send(sensor); - return; + res.send( + new NotFoundError(`Sensor with id ${params.sensorId} does not exist`) + ); + + } else { + const sensorsWithMeasurements = await getSensorsWithLastMeasurements( + boxId, + count + ); + + device.sensors = sensorsWithMeasurements; } - res.send(new NotFoundError(`Sensor with id ${params.sensorId} does not exist`)); - } else { - res.send(box); + // } + res.send(device); + } catch (err) { + return handleError(err); } }; @@ -235,7 +258,7 @@ const getData = async function getData (req, res) { * @apiParam {Boolean=true,false} [download=true] Set the `content-disposition` header to force browsers to download instead of displaying. */ const getDataMulti = async function getDataMulti (req, res) { - const { boxId, bbox, exposure, delimiter, columns, fromDate, toDate, phenomenon, download, format } = req._userParams; + const { boxId, bbox, exposure, grouptag, delimiter, columns, fromDate, toDate, phenomenon, download, format } = req._userParams; // build query const queryParams = { @@ -257,6 +280,11 @@ const getDataMulti = async function getDataMulti (req, res) { queryParams['exposure'] = { '$in': exposure }; } + // grouptag parameter + if (grouptag) { + // TODO + } + try { let stream = await Box.findMeasurementsOfBoxesStream({ query: queryParams, @@ -306,37 +334,37 @@ const getDataMulti = async function getDataMulti (req, res) { * @apiParam {String} grouptag The grouptag to search by. * @apiParam {String=json} format=json */ -const getDataByGroupTag = async function getDataByGroupTag (req, res) { - const { grouptag, format } = req._userParams; - const queryTags = grouptag.split(','); - // build query - const queryParams = {}; - if (grouptag) { - queryParams['grouptag'] = { '$all': queryTags }; - } - - try { - let stream = await Box.findMeasurementsOfBoxesByTagStream({ - query: queryParams - }); - stream = stream - .on('error', function (err) { - return handleError(err); - }); - switch (format) { - case 'json': - res.header('Content-Type', 'application/json'); - stream = stream - .pipe(jsonstringify({ open: '[', close: ']' })); - break; - } - - stream - .pipe(res); - } catch (err) { - return handleError(err); - } -}; +// const getDataByGroupTag = async function getDataByGroupTag (req, res) { +// const { grouptag, format } = req._userParams; +// const queryTags = grouptag.split(','); +// // build query +// const queryParams = {}; +// if (grouptag) { +// queryParams['grouptag'] = { '$all': queryTags }; +// } + +// try { +// let stream = await Box.findMeasurementsOfBoxesByTagStream({ +// query: queryParams +// }); +// stream = stream +// .on('error', function (err) { +// return handleError(err); +// }); +// switch (format) { +// case 'json': +// res.header('Content-Type', 'application/json'); +// stream = stream +// .pipe(jsonstringify({ open: '[', close: ']' })); +// break; +// } + +// stream +// .pipe(res); +// } catch (err) { +// return handleError(err); +// } +// }; /** * @api {post} /boxes/:senseBoxId/:sensorId Post new measurement @@ -353,22 +381,24 @@ const getDataByGroupTag = async function getDataByGroupTag (req, res) { * @apiHeader {String} Authorization Box' unique access_token. Will be used as authorization token if box has auth enabled (e.g. useAuth: true) */ const postNewMeasurement = async function postNewMeasurement (req, res) { - const { boxId, sensorId, value, createdAt, location } = req._userParams; + const { boxId: deviceId, sensorId, value, createdAt, location } = req._userParams; try { - const box = await Box.findBoxById(boxId, { populate: false, lean: false }); - if (box.useAuth && box.access_token && box.access_token !== req.headers.authorization) { - return Promise.reject(new UnauthorizedError('Box access token not valid!')); + const device = await findById(deviceId, { sensors: true }); + const deviceAccessToken = await findAccessToken(deviceId); + if (device.useAuth && deviceAccessToken.token && deviceAccessToken.token !== req.headers.authorization) { + return Promise.reject(new UnauthorizedError('Device access token not valid!')); } - const [measurement] = await Measurement.decodeMeasurements([{ + const [measurement] = await decodeMeasurements([{ sensor_id: sensorId, value, createdAt, location }]); - await box.saveMeasurement(measurement); - res.send(201, 'Measurement saved in box'); + // await box.saveMeasurement(measurement); + await saveMeasurement(device, measurement); + res.send(201, 'Measurement saved in device'); } catch (err) { return handleError(err); } @@ -454,7 +484,7 @@ const postNewMeasurement = async function postNewMeasurement (req, res) { * } */ const postNewMeasurements = async function postNewMeasurements (req, res) { - const { boxId, luftdaten, hackair } = req._userParams; + const { boxId: deviceId, luftdaten, hackair } = req._userParams; let contentType = req.getContentType(); if (hackair) { @@ -463,21 +493,24 @@ const postNewMeasurements = async function postNewMeasurements (req, res) { contentType = 'luftdaten'; } - if (Measurement.hasDecoder(contentType)) { + if (hasDecoder(contentType)) { try { - const box = await Box.findBoxById(boxId, { populate: false, lean: false, projection: { sensors: 1, locations: 1, lastMeasurementAt: 1, currentLocation: 1, model: 1, access_token: 1, useAuth: 1 } }); + const device = await findById(deviceId, { sensors: true }); + const deviceAccessToken = await findAccessToken(deviceId); + // const box = await Box.findBoxById(boxId, { populate: false, lean: false, projection: { sensors: 1, locations: 1, lastMeasurementAt: 1, currentLocation: 1, model: 1, access_token: 1, useAuth: 1 } }); // if (contentType === 'hackair' && box.access_token !== req.headers.authorization) { // throw new UnauthorizedError('Box access token not valid!'); // } // authorization for all boxes that have not opt out - if ((box.useAuth || contentType === 'hackair') && box.access_token && box.access_token !== req.headers.authorization) { - return Promise.reject(new UnauthorizedError('Box access token not valid!')); + if ((device.useAuth || contentType === 'hackair') && deviceAccessToken.token && deviceAccessToken.token !== req.headers.authorization) { + return Promise.reject(new UnauthorizedError('Device access token not valid!')); } - const measurements = await Measurement.decodeMeasurements(req.body, { contentType, sensors: box.sensors }); - await box.saveMeasurementsArray(measurements); + const measurements = await decodeMeasurements(req.body, { contentType, sensors: device.sensors }); + // await box.saveMeasurementsArray(measurements); + await saveMeasurements(device, measurements); res.send(201, 'Measurements saved in box'); } catch (err) { return handleError(err); @@ -512,10 +545,21 @@ module.exports = { retrieveParameters([ { predef: 'sensorId', required: true }, { name: 'format', defaultValue: 'json', allowedValues: ['json', 'csv'] }, - { name: 'download', defaultValue: 'false', allowedValues: ['true', 'false'] }, + { + name: 'download', + defaultValue: 'false', + allowedValues: ['true', 'false'] + }, { predef: 'delimiter' }, { name: 'outliers', allowedValues: ['mark', 'replace'] }, - { name: 'outlierWindow', dataType: 'Integer', aliases: ['outlier-window'], defaultValue: 15, min: 1, max: 50 }, + { + name: 'outlierWindow', + dataType: 'Integer', + aliases: ['outlier-window'], + defaultValue: 15, + min: 1, + max: 50 + }, { predef: 'toDate' }, { predef: 'fromDate' } ]), @@ -524,27 +568,33 @@ module.exports = { ], getDataMulti: [ retrieveParameters([ - { name: 'boxId', aliases: ['senseboxid', 'senseboxids', 'boxid', 'boxids'], dataType: ['id'] }, + { + name: 'boxId', + aliases: ['senseboxid', 'senseboxids', 'boxid', 'boxids'], + dataType: ['id'] + }, { name: 'phenomenon', required: true }, + { name: 'grouptag' }, { predef: 'delimiter' }, - { name: 'exposure', allowedValues: Box.BOX_VALID_EXPOSURES, dataType: ['String'] }, + { + name: 'exposure', + allowedValues: Box.BOX_VALID_EXPOSURES, + dataType: ['String'] + }, { predef: 'columnsGetDataMulti' }, { predef: 'bbox' }, { predef: 'toDate' }, { predef: 'fromDate' }, - { name: 'download', defaultValue: 'true', allowedValues: ['true', 'false'] }, + { + name: 'download', + defaultValue: 'true', + allowedValues: ['true', 'false'] + }, { name: 'format', defaultValue: 'csv', allowedValues: ['csv', 'json'] } ]), validateFromToTimeParams, getDataMulti ], - getDataByGroupTag: [ - retrieveParameters([ - { name: 'grouptag', required: true }, - { name: 'format', defaultValue: 'json', allowedValues: ['json'] } - ]), - getDataByGroupTag - ], getLatestMeasurements: [ retrieveParameters([ { predef: 'boxId', required: true }, diff --git a/packages/api/lib/controllers/statisticsController.js b/packages/api/lib/controllers/statisticsController.js index a5e6e81e..270699ba 100644 --- a/packages/api/lib/controllers/statisticsController.js +++ b/packages/api/lib/controllers/statisticsController.js @@ -1,6 +1,9 @@ 'use strict'; -const { Box, Measurement } = require('@sensebox/opensensemap-api-models'), +const { measurementTable } = require('@sensebox/opensensemap-api-models/schema/schema'); +const { rowCount, rowCountTimeBucket } = require('@sensebox/opensensemap-api-models/src/stats'); + +const { Box } = require('@sensebox/opensensemap-api-models'), { UnprocessableEntityError, BadRequestError } = require('restify-errors'), idwTransformer = require('../transformers/idwTransformer'), { addCache, createDownloadFilename, computeTimestampTruncationLength, csvStringifier } = require('../helpers/apiUtils'), @@ -28,14 +31,9 @@ const getStatistics = async function getStatistics (req, res) { const { human } = req._userParams; try { let results = await Promise.all([ - Box.count({}), - Measurement.count({}), - Measurement.count({ - createdAt: { - '$gt': new Date(Date.now() - 60000), - '$lt': new Date() - } - }) + rowCount('device'), + rowCount('sensor'), + rowCountTimeBucket(measurementTable, 'time', 60000) ]); if (human === 'true') { results = results.map(r => millify.default(r).toString()); diff --git a/packages/api/lib/controllers/usersController.js b/packages/api/lib/controllers/usersController.js index 4b8d77e1..2a78528a 100644 --- a/packages/api/lib/controllers/usersController.js +++ b/packages/api/lib/controllers/usersController.js @@ -1,20 +1,40 @@ -'use strict'; +"use strict"; -const { User } = require('@sensebox/opensensemap-api-models'), - { InternalServerError, ForbiddenError } = require('restify-errors'), +const { InternalServerError, ForbiddenError } = require("restify-errors"), { checkContentType, redactEmail, - clearCache, postToMattermost, - } = require('../helpers/apiUtils'), - { retrieveParameters } = require('../helpers/userParamHelpers'), - handleError = require('../helpers/errorHandler'), + } = require("../helpers/apiUtils"), + { retrieveParameters } = require("../helpers/userParamHelpers"), + handleError = require("../helpers/errorHandler"), { createToken, refreshJwt, invalidateToken, - } = require('../helpers/jwtHelpers'); + verifyJwtAndRefreshToken, + } = require("../helpers/jwtHelpers"); +const { + findDeviceById, +} = require("@sensebox/opensensemap-api-models/src/box/box"); +const { + findDevicesByUserId, +} = require("@sensebox/opensensemap-api-models/src/device"); +const { + initPasswordReset, + resetOldPassword, +} = require("@sensebox/opensensemap-api-models/src/password"); +const { + checkPassword, +} = require("@sensebox/opensensemap-api-models/src/password/utils"); +const { + createUser, + findUserByNameOrEmail, + resendEmailConfirmation, + confirmEmail, + destroyUser, + updateUserDetails, +} = require("@sensebox/opensensemap-api-models/src/user"); /** * define for nested user parameter for box creation request @@ -52,11 +72,12 @@ const { User } = require('@sensebox/opensensemap-api-models'), * @apiSuccess (Created 201) {String} refreshToken valid refresh token * @apiSuccess (Created 201) {Object} data `{ "user": {"name":"fullname","email":"test@test.de","role":"user","language":"en_US","boxes":[],"emailIsConfirmed":false} }` */ -const registerUser = async function registerUser (req, res) { - const { email, password, language, name, integrations } = req._userParams; +const registerUser = async function registerUser(req, res) { + const { email, password, language, name } = req._userParams; try { - const newUser = await new User({ name, email, password, language, integrations }).save(); + const newUser = await createUser(name, email, password, language); + postToMattermost( `New User: ${newUser.name} (${redactEmail(newUser.email)})` ); @@ -65,8 +86,8 @@ const registerUser = async function registerUser (req, res) { const { token, refreshToken } = await createToken(newUser); res.send(201, { - code: 'Created', - message: 'Successfully registered new user', + code: "Created", + message: "Successfully registered new user", data: { user: newUser }, token, refreshToken, @@ -97,35 +118,32 @@ const registerUser = async function registerUser (req, res) { * @apiSuccess {Object} data `{ "user": {"name":"fullname","email":"test@test.de","role":"user","language":"en_US","boxes":[],"emailIsConfirmed":false} }` * @apiError {String} 403 Unauthorized */ -const signIn = async function signIn (req, res) { +const signIn = async function signIn(req, res) { const { email: emailOrName, password } = req._userParams; try { - // lowercase for email - const user = await User.findOne({ - $or: [{ email: emailOrName.toLowerCase() }, { name: emailOrName }], - }).exec(); + const user = await findUserByNameOrEmail(emailOrName); if (!user) { return Promise.reject( - new ForbiddenError('User and or password not valid!') + new ForbiddenError("User and or password not valid!") ); } - if (await user.checkPassword(password)) { + if (await checkPassword(password, user.password)) { const { token, refreshToken } = await createToken(user); res.send(200, { - code: 'Authorized', - message: 'Successfully signed in', + code: "Authorized", + message: "Successfully signed in", data: { user }, token, refreshToken, }); } } catch (err) { - if (err.name === 'ModelError' && err.message === 'Password incorrect') { - return handleError(new ForbiddenError('User and or password not valid!')); + if (err.name === "ModelError" && err.message === "Password incorrect") { + return handleError(new ForbiddenError("User and or password not valid!")); } return handleError(err); @@ -145,14 +163,18 @@ const signIn = async function signIn (req, res) { * @apiSuccess {Object} data `{ "user": {"name":"fullname","email":"test@test.de","role":"user","language":"en_US","boxes":[],"emailIsConfirmed":false} }` * @apiError {Object} Forbidden `{"code":"ForbiddenError","message":"Refresh token invalid or too old. Please sign in with your username and password."}` */ -const refreshJWT = async function refreshJWT (req, res) { +const refreshJWT = async function refreshJWT(req, res) { try { + // Check if refreshToken matches JWT Token + await verifyJwtAndRefreshToken(req._userParams.token, req._jwtString); + + // Now it´s time to refresh the JWT and invalidate the old one const { token, refreshToken, user } = await refreshJwt( req._userParams.token ); res.send(200, { - code: 'Authorized', - message: 'Successfully refreshed auth', + code: "Authorized", + message: "Successfully refreshed auth", data: { user }, token, refreshToken, @@ -171,10 +193,10 @@ const refreshJWT = async function refreshJWT (req, res) { * @apiSuccess {String} code `Ok` * @apiSuccess {String} message `Successfully signed out` */ -const signOut = async function signOut (req, res) { +const signOut = async function signOut(req, res) { invalidateToken(req); - return res.send(200, { code: 'Ok', message: 'Successfully signed out' }); + return res.send(200, { code: "Ok", message: "Successfully signed out" }); }; /** @@ -187,10 +209,10 @@ const signOut = async function signOut (req, res) { * @apiSuccess {String} message `Password reset initiated` */ // generate new password reset token and send the token to the user -const requestResetPassword = async function requestResetPassword (req, res) { +const requestResetPassword = async function requestResetPassword(req, res) { try { - await User.initPasswordReset(req._userParams); - res.send(200, { code: 'Ok', message: 'Password reset initiated' }); + await initPasswordReset(req._userParams); + res.send(200, { code: "Ok", message: "Password reset initiated" }); } catch (err) { return handleError(err); } @@ -207,13 +229,14 @@ const requestResetPassword = async function requestResetPassword (req, res) { * @apiSuccess {String} message `Password successfully changed. You can now login with your new password` */ // set new password with reset token as auth -const resetPassword = async function resetPassword (req, res) { +const resetPassword = async function resetPassword(req, res) { try { - await User.resetPassword(req._userParams); + // await User.resetPassword(req._userParams); + await resetOldPassword(req._userParams); res.send(200, { - code: 'Ok', + code: "Ok", message: - 'Password successfully changed. You can now login with your new password', + "Password successfully changed. You can now login with your new password", }); } catch (err) { return handleError(err); @@ -230,12 +253,13 @@ const resetPassword = async function resetPassword (req, res) { * @apiSuccess {String} code `Ok` * @apiSuccess {String} message `E-Mail successfully confirmed. Thank you` */ -const confirmEmailAddress = async function confirmEmailAddress (req, res) { +const confirmEmailAddress = async function confirmEmailAddress(req, res) { try { - await User.confirmEmail(req._userParams); + await confirmEmail(req._userParams); + // await User.confirmEmail(req._userParams); res.send(200, { - code: 'Ok', - message: 'E-Mail successfully confirmed. Thank you', + code: "Ok", + message: "E-Mail successfully confirmed. Thank you", }); } catch (err) { return handleError(err); @@ -251,14 +275,18 @@ const confirmEmailAddress = async function confirmEmailAddress (req, res) { * @apiSuccess {String} code `Ok` * @apiSuccess {String} data A json object with a single `boxes` array field */ -const getUserBoxes = async function getUserBoxes (req, res) { +const getUserBoxes = async function getUserBoxes(req, res) { const { page } = req._userParams; try { - const boxes = await req.user.getBoxes(page); - const sharedBoxes = await req.user.getSharedBoxes(); + const devices = await findDevicesByUserId(req.user.id, { page }); + // const sharedBoxes = await req.user.getSharedBoxes(); res.send(200, { - code: 'Ok', - data: { boxes: boxes, boxes_count: req.user.boxes.length, sharedBoxes: sharedBoxes }, + code: "Ok", + data: { + boxes: devices, + boxes_count: devices.length, + sharedBoxes: [], + }, }); } catch (err) { return handleError(err); @@ -274,14 +302,14 @@ const getUserBoxes = async function getUserBoxes (req, res) { * @apiSuccess {String} code `Ok` * @apiSuccess {String} data A json object with a single `box` object field */ -const getUserBox = async function getUserBox (req, res) { +const getUserBox = async function getUserBox(req, res) { const { boxId } = req._userParams; try { - const box = await req.user.getBox(boxId); + const device = await findDeviceById(boxId); res.send(200, { - code: 'Ok', + code: "Ok", data: { - box + box: device, }, }); } catch (err) { @@ -296,14 +324,14 @@ const getUserBox = async function getUserBox (req, res) { * @apiGroup Users * @apiUse JWTokenAuth */ -const getUser = async function getUser (req, res) { - res.send(200, { code: 'Ok', data: { me: req.user } }); +const getUser = async function getUser(req, res) { + res.send(200, { code: "Ok", data: { me: req.user } }); }; /** * @api {put} /users/me Update user details * @apiName updateUser - * @apiDescription Allows to change name, email, language and password of the currently signed in user. Changing the password triggers a sign out. The user has to log in again with the new password. Changing the mail triggers a Email confirmation process. + * @apiDescription Allows to change name, email, language, and password of the currently signed-in user. Changing the password triggers a sign-out. The user has to log in again with the new password. Changing the email triggers an email confirmation process. * @apiGroup Users * @apiUse JWTokenAuth * @apiParam {String} [email] the new email address for this user. @@ -312,23 +340,34 @@ const getUser = async function getUser (req, res) { * @apiParam {String} [newPassword] the new password for this user. Should be at least 8 characters long. * @apiParam {String} currentPassword the current password for this user. */ -const updateUser = async function updateUser (req, res) { +const updateUser = async function updateUser(req, res) { + const user = req.user; + const { email, language, name, currentPassword, newPassword } = + req._userParams; + try { - const { updated, signOut, messages, updatedUser } = - await req.user.updateUser(req._userParams); + const { updated, signOut, messages, updatedUser } = await updateUserDetails( + user, + { email, language, name, currentPassword, newPassword } + ); + + // Safeguard: Ensure messages is always an array + const messageText = Array.isArray(messages) ? messages.join(".") : ""; + if (updated === false) { return res.send(200, { - code: 'Ok', - message: 'No changed properties supplied. User remains unchanged.', + code: "Ok", + message: "No changed properties supplied. User remains unchanged.", }); } if (signOut === true) { invalidateToken(req); } + res.send(200, { - code: 'Ok', - message: `User successfully saved.${messages.join('.')}`, + code: "Ok", + message: `User successfully saved.${messageText}`, data: { me: updatedUser }, }); } catch (err) { @@ -345,19 +384,18 @@ const updateUser = async function updateUser (req, res) { * @apiParam {String} password the current password for this user. */ -const deleteUser = async function deleteUser (req, res) { +const deleteUser = async function deleteUser(req, res) { const { password } = req._userParams; try { - await req.user.checkPassword(password); - invalidateToken(req); + await checkPassword(password, req.user.password); + await invalidateToken(req); - await req.user.destroyUser(); + const deletedUser = await destroyUser(req.user); res.send(200, { - code: 'Ok', - message: 'User and all boxes of user marked for deletion. Bye Bye!', + code: "Ok", + message: `User and all boxes of user marked for deletion. Bye Bye ${deletedUser[0].name}!`, }); - clearCache(['getBoxes', 'getStats']); postToMattermost( `User deleted: ${req.user.name} (${redactEmail(req.user.email)})` ); @@ -375,18 +413,19 @@ const deleteUser = async function deleteUser (req, res) { * @apiSuccess {String} code `Ok` * @apiSuccess {String} message `Email confirmation has been sent to ` */ -const requestEmailConfirmation = async function requestEmailConfirmation ( +const requestEmailConfirmation = async function requestEmailConfirmation( req, res ) { try { - const result = await req.user.resendEmailConfirmation(); + // const result = await req.user.resendEmailConfirmation(); + const result = await resendEmailConfirmation(req.user); let usedAddress = result.email; if (result.unconfirmedEmail) { usedAddress = result.unconfirmedEmail; } res.send(200, { - code: 'Ok', + code: "Ok", message: `Email confirmation has been sent to ${usedAddress}`, }); } catch (err) { @@ -398,76 +437,76 @@ module.exports = { registerUser: [ checkContentType, retrieveParameters([ - { name: 'email', dataType: 'email', required: true }, - { predef: 'password' }, - { name: 'name', required: true, dataType: 'as-is' }, - { name: 'language', defaultValue: 'en_US' }, - { name: 'integrations', dataType: 'object' } + { name: "email", dataType: "email", required: true }, + { predef: "password" }, + { name: "name", required: true, dataType: "as-is" }, + { name: "language", defaultValue: "en_US" }, + { name: "integrations", dataType: "object" }, ]), - registerUser + registerUser, ], signIn: [ checkContentType, retrieveParameters([ - { name: 'email', required: true }, - { predef: 'password' } + { name: "email", required: true }, + { predef: "password" }, ]), - signIn + signIn, ], signOut, resetPassword: [ checkContentType, retrieveParameters([ - { name: 'token', required: true }, - { predef: 'password' } + { name: "token", required: true }, + { predef: "password" }, ]), - resetPassword + resetPassword, ], requestResetPassword: [ checkContentType, - retrieveParameters([{ name: 'email', dataType: 'email', required: true }]), - requestResetPassword + retrieveParameters([{ name: "email", dataType: "email", required: true }]), + requestResetPassword, ], confirmEmailAddress: [ checkContentType, retrieveParameters([ - { name: 'token', required: true }, - { name: 'email', dataType: 'email', required: true } + { name: "token", required: true }, + { name: "email", dataType: "email", required: true }, ]), - confirmEmailAddress + confirmEmailAddress, ], requestEmailConfirmation, getUserBox: [ - retrieveParameters([{ predef: 'boxId', required: true }]), - getUserBox + retrieveParameters([{ predef: "boxId", required: true }]), + getUserBox, ], getUserBoxes: [ retrieveParameters([ - { name: 'page', dataType: 'Integer', defaultValue: 0, min: 0 } + { name: "page", dataType: "Integer", defaultValue: 0, min: 0 }, ]), - getUserBoxes + getUserBoxes, ], updateUser: [ checkContentType, retrieveParameters([ - { name: 'email', dataType: 'email' }, - { predef: 'password', name: 'currentPassword', required: false }, - { predef: 'password', name: 'newPassword', required: false }, - { name: 'name', dataType: 'as-is' }, - { name: 'language' }, - { name: 'integrations', dataType: 'object' } + { name: "email", dataType: "email" }, + { predef: "password", name: "currentPassword", required: false }, + { predef: "password", name: "newPassword", required: false }, + { name: "name", dataType: "as-is" }, + { name: "language" }, + { name: "integrations", dataType: "object" }, ]), - updateUser + updateUser, ], getUser, refreshJWT: [ checkContentType, - retrieveParameters([{ name: 'token', required: true }]), - refreshJWT + retrieveParameters([{ name: "token", required: true }]), + refreshJWT, ], deleteUser: [ checkContentType, - retrieveParameters([{ predef: 'password' }]), - deleteUser - ] + retrieveParameters([{ predef: "password" }]), + deleteUser, + ], }; diff --git a/packages/api/lib/helpers/errorHandler.js b/packages/api/lib/helpers/errorHandler.js index 41a7ed93..45381953 100644 --- a/packages/api/lib/helpers/errorHandler.js +++ b/packages/api/lib/helpers/errorHandler.js @@ -1,41 +1,51 @@ -'use strict'; +"use strict"; -const restifyErrors = require('restify-errors'); +const restifyErrors = require("restify-errors"); -const restifyErrorNames = Object.keys(restifyErrors).filter(e => e.includes('Error') && e !== 'codeToHttpError'); +const restifyErrorNames = Object.keys(restifyErrors).filter( + (e) => e.includes("Error") && e !== "codeToHttpError" +); const handleError = function (err) { - if (err.name === 'ModelError') { - if (err.data && err.data.type) { - return Promise.reject(new restifyErrors[err.data.type](err.message)); + if (err.name === "ModelError") { + const status = err.status || 400; // Ensure a fallback status + + // Map `status` to a Restify error type + switch (status) { + case 400: + return Promise.reject(new restifyErrors.BadRequestError(err.message)); + case 401: + return Promise.reject(new restifyErrors.UnauthorizedError(err.message)); + case 403: + return Promise.reject(new restifyErrors.ForbiddenError(err.message)); // ✅ Now properly handling 403 + case 404: + return Promise.reject(new restifyErrors.NotFoundError(err.message)); + case 422: + return Promise.reject( + new restifyErrors.UnprocessableEntityError(err.message) + ); + case 500: + default: + return Promise.reject( + new restifyErrors.InternalServerError(err.message) + ); } - - return Promise.reject(new restifyErrors.BadRequestError(err.message)); } - if (err.name === 'ValidationError') { - const msgs = []; - for (const field in err.errors) { - if (!err.errors[field].errors) { - msgs.push(err.errors[field].message); - } - } + if (err.name === "ValidationError") { + const msgs = Object.keys(err.errors) + .map((field) => err.errors[field].message) + .join(", "); - return Promise.reject(new restifyErrors.UnprocessableEntityError(`Validation failed: ${msgs.join(', ')}`)); + return Promise.reject( + new restifyErrors.UnprocessableEntityError(`Validation failed: ${msgs}`) + ); } if (restifyErrorNames.includes(err.name)) { return Promise.reject(err); } - if (err.errors) { - const msg = Object.keys(err.errors) - .map(f => `${err.errors[f].message}`) - .join(', '); - - return Promise.reject(new restifyErrors.UnprocessableEntityError(msg)); - } - return Promise.reject(new restifyErrors.InternalServerError(err.message)); }; diff --git a/packages/api/lib/helpers/jwtHelpers.js b/packages/api/lib/helpers/jwtHelpers.js index 382c28cc..1a797f80 100644 --- a/packages/api/lib/helpers/jwtHelpers.js +++ b/packages/api/lib/helpers/jwtHelpers.js @@ -1,12 +1,13 @@ 'use strict'; +const { addRefreshToken, deleteRefreshToken, findRefreshTokenUser } = require('@sensebox/opensensemap-api-models/src/token/refresh'); +const { findUserByEmailAndRole } = require('@sensebox/opensensemap-api-models/src/user'); const config = require('config'), jwt = require('jsonwebtoken'), hashJWT = require('./jwtRefreshTokenHasher'), - { addTokenToBlacklist, addTokenHashToBlacklist, isTokenBlacklisted } = require('./tokenBlacklist'), + { addTokenToBlacklist, isTokenBlacklisted, addRefreshTokenToBlacklist } = require('./tokenBlacklist'), { v4: uuidv4 } = require('uuid'), moment = require('moment'), - { User } = require('@sensebox/opensensemap-api-models'), { ForbiddenError } = require('restify-errors'); const { algorithm: jwt_algorithm, secret: jwt_secret, issuer: jwt_issuer, validity_ms: jwt_validity_ms } = config.get('jwt'); @@ -38,15 +39,10 @@ const createToken = function createToken (user) { // and set the refreshTokenExpires to 1 week // it is a HMAC of the jwt string const refreshToken = hashJWT(token); + const refreshTokenExpiresAt = moment.utc().add(Number(refresh_token_validity_ms), 'ms') + .toDate(); try { - await user.update({ - $set: { - refreshToken, - refreshTokenExpires: moment.utc() - .add(Number(refresh_token_validity_ms), 'ms') - .toDate() - } - }).exec(); + await addRefreshToken(user.id, refreshToken, refreshTokenExpiresAt); return resolve({ token, refreshToken }); } catch (err) { @@ -56,20 +52,23 @@ const createToken = function createToken (user) { }); }; -const invalidateToken = function invalidateToken ({ user, _jwt, _jwtString } = {}) { - createToken(user); +const invalidateToken = async function invalidateToken ({ user, _jwt, _jwtString } = {}) { + // createToken(user); // TODO: why do we create a new token here?!?! + const hash = hashJWT(_jwtString); + await deleteRefreshToken(hash); addTokenToBlacklist(_jwt, _jwtString); }; const refreshJwt = async function refreshJwt (refreshToken) { - const user = await User.findOne({ refreshToken, refreshTokenExpires: { $gte: moment.utc().toDate() } }); + // const user = await User.findOne({ refreshToken, refreshTokenExpires: { $gte: moment.utc().toDate() } }); + const user = await findRefreshTokenUser(refreshToken); if (!user) { throw new ForbiddenError('Refresh token invalid or too old. Please sign in with your username and password.'); } - // invalidate old token - addTokenHashToBlacklist(refreshToken); + // Add the old refresh token to the blacklist + addRefreshTokenToBlacklist(refreshToken); const { token, refreshToken: newRefreshToken } = await createToken(user); @@ -90,19 +89,21 @@ const verifyJwt = function verifyJwt (req, res, next) { return next(new ForbiddenError(jwtInvalidErrorMessage)); } - jwt.verify(jwtString, jwt_secret, jwtVerifyOptions, function (err, decodedJwt) { + jwt.verify(jwtString, jwt_secret, { + ...jwtVerifyOptions, + ignoreExpiration: req.url === '/users/refresh-auth' ? true : false // ignore expiration for refresh endpoint + }, async function (err, decodedJwt) { if (err) { return next(new ForbiddenError(jwtInvalidErrorMessage)); } // check if the token is blacklisted by performing a hmac digest on the string representation of the jwt. // also checks the existence of the jti claim - if (isTokenBlacklisted(decodedJwt, jwtString)) { + if (await isTokenBlacklisted(decodedJwt, jwtString)) { return next(new ForbiddenError(jwtInvalidErrorMessage)); } - User.findOne({ email: decodedJwt.sub.toLowerCase(), role: decodedJwt.role }) - .exec() + findUserByEmailAndRole({ email: decodedJwt.sub.toLowerCase(), role: decodedJwt.role }) .then(function (user) { if (!user) { throw new Error(); @@ -120,9 +121,20 @@ const verifyJwt = function verifyJwt (req, res, next) { }); }; +const verifyJwtAndRefreshToken = async function verifyJwtAndRefreshToken (refreshToken, jwtString) { + if (refreshToken !== hashJWT(jwtString)) { + return Promise.reject( + new ForbiddenError( + 'Refresh token invalid or too old. Please sign in with your username and password.' + ) + ); + } +}; + module.exports = { createToken, invalidateToken, refreshJwt, - verifyJwt + verifyJwt, + verifyJwtAndRefreshToken }; diff --git a/packages/api/lib/helpers/tokenBlacklist.js b/packages/api/lib/helpers/tokenBlacklist.js index de3c8526..4931bb6e 100644 --- a/packages/api/lib/helpers/tokenBlacklist.js +++ b/packages/api/lib/helpers/tokenBlacklist.js @@ -1,31 +1,19 @@ 'use strict'; -const moment = require('moment'), - hashJWT = require('./jwtRefreshTokenHasher'); - -// our token blacklist is just a js object with -// jtis as keys and all claims as values -const tokenBlacklist = Object.create(null); - -const cleanupExpiredTokens = function cleanupExpiredTokens () { - const now = Date.now() / 1000; - for (const jti of Object.keys(tokenBlacklist)) { - if (tokenBlacklist[jti].exp < now) { - delete tokenBlacklist[jti]; - } - } -}; - -const isTokenBlacklisted = function isTokenBlacklisted (token, tokenString) { - cleanupExpiredTokens(); +const { insertTokenToBlacklist, findToken, insertTokenToBlacklistWithExpiresAt } = require('@sensebox/opensensemap-api-models/src/token'); +const hashJWT = require('./jwtRefreshTokenHasher'); +const moment = require('moment'); +const isTokenBlacklisted = async function isTokenBlacklisted (token, tokenString) { if (!token.jti) { // token has no id.. -> shouldn't be accepted return true; } const hash = hashJWT(tokenString); - if (typeof tokenBlacklist[hash] !== 'undefined') { + const blacklistedToken = await findToken(hash); + + if (blacklistedToken.length > 0) { return true; } @@ -33,30 +21,20 @@ const isTokenBlacklisted = function isTokenBlacklisted (token, tokenString) { }; const addTokenToBlacklist = function addTokenToBlacklist (token, tokenString) { - cleanupExpiredTokens(); - const hash = hashJWT(tokenString); if (token && token.jti) { - tokenBlacklist[hash] = token; + insertTokenToBlacklist(hash, token); } }; -const addTokenHashToBlacklist = function addTokenHashToBlacklist (tokenHash) { - cleanupExpiredTokens(); - - if (typeof tokenHash === 'string') { - // just set the exp claim to now plus one week to be sure - tokenBlacklist[tokenHash] = { - exp: moment.utc() - .add(1, 'week') - .unix() - }; - } +const addRefreshTokenToBlacklist = function addRefreshTokenToBlacklist (refreshToken) { + insertTokenToBlacklistWithExpiresAt(refreshToken, '', moment.utc().add(1, 'week') + .unix()); }; module.exports = { isTokenBlacklisted, addTokenToBlacklist, - addTokenHashToBlacklist + addRefreshTokenToBlacklist }; diff --git a/packages/api/lib/helpers/userParamHelpers.js b/packages/api/lib/helpers/userParamHelpers.js index 918a9b87..eff372fd 100644 --- a/packages/api/lib/helpers/userParamHelpers.js +++ b/packages/api/lib/helpers/userParamHelpers.js @@ -1,7 +1,10 @@ 'use strict'; +const { isCuid } = require('@paralleldrive/cuid2'); +const { checkDeviceOwner } = require('@sensebox/opensensemap-api-models/src/user/user'); + const { BadRequestError, UnprocessableEntityError, InvalidArgumentError, ForbiddenError } = require('restify-errors'), - { utils: { parseAndValidateTimestamp }, db: { mongoose }, decoding: { validators: { transformAndValidateCoords } } } = require('@sensebox/opensensemap-api-models'), + { utils: { parseAndValidateTimestamp }, decoding: { validators: { transformAndValidateCoords } } } = require('@sensebox/opensensemap-api-models'), moment = require('moment'), isemail = require('isemail'), handleModelError = require('./errorHandler'), @@ -65,7 +68,7 @@ const stringParser = function stringParser (s) { */ const idCheck = function idCheck (id) { - if (mongoose.Types.ObjectId.isValid(id) && id !== '00112233445566778899aabb') { + if (isCuid(id) && id !== '00112233445566778899aabb') { return id; } @@ -548,7 +551,8 @@ const checkPrivilege = async function checkPrivilege (req) { if (req._userParams.boxId) { try { - req.user.checkBoxOwner(req._userParams.boxId); + await checkDeviceOwner(req.user.id, req._userParams.boxId); + // req.user.checkBoxOwner(req._userParams.boxId); return; } catch (err) { diff --git a/packages/api/lib/routes.js b/packages/api/lib/routes.js index a8a9e5c7..ffe7a011 100644 --- a/packages/api/lib/routes.js +++ b/packages/api/lib/routes.js @@ -80,7 +80,7 @@ const routes = { { path: `${statisticsPath}/descriptive`, method: 'get', handler: statisticsController.descriptiveStatisticsHandler, reference: 'api-Statistics-descriptive' }, { path: `${boxesPath}`, method: 'get', handler: boxesController.getBoxes, reference: 'api-Boxes-getBoxes' }, { path: `${boxesPath}/data`, method: 'get', handler: measurementsController.getDataMulti, reference: 'api-Measurements-getDataMulti' }, - { path: `${boxesPath}/data/bytag`, method: 'get', handler: measurementsController.getDataByGroupTag, reference: 'api-Measurements-getDataByGroupTag' }, + // { path: `${boxesPath}/data/bytag`, method: 'get', handler: measurementsController.getDataByGroupTag, reference: 'api-Measurements-getDataByGroupTag' }, { path: `${boxesPath}/:boxId`, method: 'get', handler: boxesController.getBox, reference: 'api-Boxes-getBox' }, { path: `${boxesPath}/:boxId/sensors`, method: 'get', handler: measurementsController.getLatestMeasurements, reference: 'api-Measurements-getLatestMeasurements' }, { path: `${boxesPath}/:boxId/sensors/:sensorId`, method: 'get', handler: measurementsController.getLatestMeasurements, reference: 'api-Measurements-getLatestMeasurementOfSensor' }, @@ -93,10 +93,10 @@ const routes = { { path: `${usersPath}/request-password-reset`, method: 'post', handler: usersController.requestResetPassword, reference: 'api-Users-request-password-reset' }, { path: `${usersPath}/password-reset`, method: 'post', handler: usersController.resetPassword, reference: 'api-Users-password-reset' }, { path: `${usersPath}/confirm-email`, method: 'post', handler: usersController.confirmEmailAddress, reference: 'api-Users-confirm-email' }, - { path: `${usersPath}/sign-in`, method: 'post', handler: usersController.signIn, reference: 'api-Users-sign-in' }, - { path: `${usersPath}/refresh-auth`, method: 'post', handler: usersController.refreshJWT, reference: 'api-Users-refresh-auth' } + { path: `${usersPath}/sign-in`, method: 'post', handler: usersController.signIn, reference: 'api-Users-sign-in' } ], 'auth': [ + { path: `${usersPath}/refresh-auth`, method: 'post', handler: usersController.refreshJWT, reference: 'api-Users-refresh-auth' }, { path: `${usersPath}/me`, method: 'get', handler: usersController.getUser, reference: 'api-Users-getUser' }, { path: `${usersPath}/me`, method: 'put', handler: usersController.updateUser, reference: 'api-Users-updateUser' }, { path: `${usersPath}/me/boxes`, method: 'get', handler: usersController.getUserBoxes, reference: 'api-Users-getUserBoxes' }, diff --git a/packages/api/package.json b/packages/api/package.json index e8f6d287..f4d11521 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -9,9 +9,11 @@ "Gerald Pape", "Norwin Roosen", "Umut Tas", - "Felix Erdmann" + "Felix Erdmann", + "Frederick Bruch" ], "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", "@sensebox/opensensemap-api-models": "3.3.0", "@turf/area": "^6.5.0", "@turf/bbox": "^6.5.0", diff --git a/packages/models/migrations/0-3_usermanagement/0-mongoshell-boxes-integrations.js b/packages/models/_migrations/0-3_usermanagement/0-mongoshell-boxes-integrations.js similarity index 100% rename from packages/models/migrations/0-3_usermanagement/0-mongoshell-boxes-integrations.js rename to packages/models/_migrations/0-3_usermanagement/0-mongoshell-boxes-integrations.js diff --git a/packages/models/migrations/0-3_usermanagement/1-mongoshell-users-name-createdAt-apikey.js b/packages/models/_migrations/0-3_usermanagement/1-mongoshell-users-name-createdAt-apikey.js similarity index 100% rename from packages/models/migrations/0-3_usermanagement/1-mongoshell-users-name-createdAt-apikey.js rename to packages/models/_migrations/0-3_usermanagement/1-mongoshell-users-name-createdAt-apikey.js diff --git a/packages/models/migrations/0-3_usermanagement/2-node-make-users-unique.js b/packages/models/_migrations/0-3_usermanagement/2-node-make-users-unique.js similarity index 100% rename from packages/models/migrations/0-3_usermanagement/2-node-make-users-unique.js rename to packages/models/_migrations/0-3_usermanagement/2-node-make-users-unique.js diff --git a/packages/models/migrations/10-11_box_grouptag/10-node-update-grouptag.js b/packages/models/_migrations/10-11_box_grouptag/10-node-update-grouptag.js similarity index 100% rename from packages/models/migrations/10-11_box_grouptag/10-node-update-grouptag.js rename to packages/models/_migrations/10-11_box_grouptag/10-node-update-grouptag.js diff --git a/packages/models/migrations/3-4_normalize_boxes/3-mongoshell-normalize-boxes.js b/packages/models/_migrations/3-4_normalize_boxes/3-mongoshell-normalize-boxes.js similarity index 100% rename from packages/models/migrations/3-4_normalize_boxes/3-mongoshell-normalize-boxes.js rename to packages/models/_migrations/3-4_normalize_boxes/3-mongoshell-normalize-boxes.js diff --git a/packages/models/migrations/4-5_mobile_boxes/4-mongoshell-refactor-boxlocation.js b/packages/models/_migrations/4-5_mobile_boxes/4-mongoshell-refactor-boxlocation.js similarity index 100% rename from packages/models/migrations/4-5_mobile_boxes/4-mongoshell-refactor-boxlocation.js rename to packages/models/_migrations/4-5_mobile_boxes/4-mongoshell-refactor-boxlocation.js diff --git a/packages/models/migrations/5-7_measurements_boxes/5-mongoshell-boxes-add-field.js b/packages/models/_migrations/5-7_measurements_boxes/5-mongoshell-boxes-add-field.js similarity index 100% rename from packages/models/migrations/5-7_measurements_boxes/5-mongoshell-boxes-add-field.js rename to packages/models/_migrations/5-7_measurements_boxes/5-mongoshell-boxes-add-field.js diff --git a/packages/models/migrations/5-7_measurements_boxes/6-node-set-latest-measurement.js b/packages/models/_migrations/5-7_measurements_boxes/6-node-set-latest-measurement.js similarity index 100% rename from packages/models/migrations/5-7_measurements_boxes/6-node-set-latest-measurement.js rename to packages/models/_migrations/5-7_measurements_boxes/6-node-set-latest-measurement.js diff --git a/packages/models/migrations/7-9_box_access_token/7-node-boxes-add-field-access-token.js b/packages/models/_migrations/7-9_box_access_token/7-node-boxes-add-field-access-token.js similarity index 100% rename from packages/models/migrations/7-9_box_access_token/7-node-boxes-add-field-access-token.js rename to packages/models/_migrations/7-9_box_access_token/7-node-boxes-add-field-access-token.js diff --git a/packages/models/migrations/7-9_box_access_token/8-node-boxes-fix-sensors.js b/packages/models/_migrations/7-9_box_access_token/8-node-boxes-fix-sensors.js similarity index 100% rename from packages/models/migrations/7-9_box_access_token/8-node-boxes-fix-sensors.js rename to packages/models/_migrations/7-9_box_access_token/8-node-boxes-fix-sensors.js diff --git a/packages/models/migrations/7-9_box_access_token/9-node-boxes-add-field-useAuth.js b/packages/models/_migrations/7-9_box_access_token/9-node-boxes-add-field-useAuth.js similarity index 100% rename from packages/models/migrations/7-9_box_access_token/9-node-boxes-add-field-useAuth.js rename to packages/models/_migrations/7-9_box_access_token/9-node-boxes-add-field-useAuth.js diff --git a/packages/models/migrations/README.md b/packages/models/_migrations/README.md similarity index 100% rename from packages/models/migrations/README.md rename to packages/models/_migrations/README.md diff --git a/packages/models/drizzle.config.js b/packages/models/drizzle.config.js new file mode 100644 index 00000000..8284c608 --- /dev/null +++ b/packages/models/drizzle.config.js @@ -0,0 +1,37 @@ +'use strict'; + +const config = require('config'); + +config.util.setModuleDefaults('opensensemap-migrations', { + db: { + host: 'localhost', + port: 5432, + user: 'postgres', + userpass: 'postgres', + db: 'opensensemap', + database_url: '' + }, +}); + +const getDBUri = function getDBUri () { + // get uri from config + const uri = config.get('opensensemap-migrations.db.database_url'); + if (uri) { + return uri; + } + + // otherwise build uri from config supplied values + const { user, userpass, host, port, db } = config.get('opensensemap-migrations.db'); + + return `postgresql://${user}:${userpass}@${host}:${port}/${db}`; +}; + +/** @type { import("drizzle-kit").Config } */ +module.exports = { + schema: './schema/*', + out: './migrations', + dialect: 'postgresql', + dbCredentials: { + url: getDBUri() + } +}; diff --git a/packages/models/index.js b/packages/models/index.js index fb34435d..1a3088a2 100644 --- a/packages/models/index.js +++ b/packages/models/index.js @@ -9,12 +9,12 @@ const config = require('config'); config.util.setModuleDefaults('openSenseMap-API-models', { db: { host: 'localhost', - port: 27017, - user: 'admin', - userpass: 'admin', - authsource: 'OSeM-api', - db: 'OSeM-api', - mongo_uri: '', + port: 5432, + user: 'postgres', + userpass: 'postgres', + db: 'opensensemap', + database_url: '', + ssl: false }, integrations: { ca_cert: '', @@ -25,29 +25,29 @@ config.util.setModuleDefaults('openSenseMap-API-models', { port: 6379, username: '', password: '', - db: 0, + db: 0 }, mailer: { url: '', origin: '', - queue: 'mails', + queue: 'mails' }, mqtt: { - url: '', - }, + url: '' + } }, password: { min_length: 8, - salt_factor: 13, + salt_factor: 13 }, claims_ttl: { amount: 1, - unit: 'd', + unit: 'd' }, pagination: { max_boxes: 3 }, - image_folder: './userimages/', + image_folder: './userimages/' }); const { model: Box } = require('./src/box/box'), @@ -57,7 +57,8 @@ const { model: Box } = require('./src/box/box'), { model: Claim } = require('./src/box/claim'), utils = require('./src/utils'), decoding = require('./src/measurement/decoding'), - db = require('./src/db'); + db = require('./src/db'), + isReady = require('./src/drizzle').isReady; module.exports = { Box, @@ -67,5 +68,6 @@ module.exports = { User, utils, decoding, - db + db, + isReady }; diff --git a/packages/models/migrations/0000_init_table.sql b/packages/models/migrations/0000_init_table.sql new file mode 100644 index 00000000..89f136ec --- /dev/null +++ b/packages/models/migrations/0000_init_table.sql @@ -0,0 +1,112 @@ +DO $$ BEGIN + CREATE TYPE "public"."model" AS ENUM('HOME_V2_LORA'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + CREATE TYPE "public"."exposure" AS ENUM('indoor', 'outdoor', 'mobile', 'unknown'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + CREATE TYPE "public"."status" AS ENUM('active', 'inactive', 'old'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "device" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "image" text, + "description" text, + "link" text, + "use_auth" boolean, + "exposure" "exposure", + "status" "status" DEFAULT 'inactive', + "model" "model", + "public" boolean DEFAULT false, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + "latitude" double precision NOT NULL, + "longitude" double precision NOT NULL, + "user_id" text NOT NULL, + "sensor_wiki_model" text +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "measurement" ( + "sensor_id" text NOT NULL, + "time" timestamp (3) with time zone DEFAULT now() NOT NULL, + "value" double precision, + CONSTRAINT "measurement_sensor_id_time_unique" UNIQUE("sensor_id","time") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "password" ( + "hash" text NOT NULL, + "user_id" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "profile_image" ( + "id" text PRIMARY KEY NOT NULL, + "alt_text" text, + "content_type" text NOT NULL, + "blob" "bytea" NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + "profile_id" text +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "profile" ( + "id" text PRIMARY KEY NOT NULL, + "username" text NOT NULL, + "public" boolean DEFAULT false, + "user_id" text, + CONSTRAINT "profile_username_unique" UNIQUE("username") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "sensor" ( + "id" text PRIMARY KEY NOT NULL, + "title" text, + "unit" text, + "sensor_type" text, + "status" "status" DEFAULT 'inactive', + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + "device_id" text NOT NULL, + "sensor_wiki_type" text, + "sensor_wiki_phenomenon" text, + "sensor_wiki_unit" text, + "lastMeasurement" json, + "data" json +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "role" text DEFAULT 'user', + "language" text DEFAULT 'en_US', + "email_is_confirmed" boolean DEFAULT false, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "password" ADD CONSTRAINT "password_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "profile_image" ADD CONSTRAINT "profile_image_profile_id_profile_id_fk" FOREIGN KEY ("profile_id") REFERENCES "public"."profile"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "profile" ADD CONSTRAINT "profile_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/packages/models/migrations/0001_init_tsdb.sql b/packages/models/migrations/0001_init_tsdb.sql new file mode 100644 index 00000000..a00429df --- /dev/null +++ b/packages/models/migrations/0001_init_tsdb.sql @@ -0,0 +1,116 @@ +-- This is a custom SQL migration file! -- + +-- CreateExtension +CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE; + +-- Turn the measurement table into a hypertable +SELECT create_hypertable('measurement', 'time'); + +-- Drop raw data older than 1 year +SELECT add_retention_policy('measurement', INTERVAL '1 year'); + +-- Continuous aggregate (CAGG) of the hypertable +-- https://docs.timescale.com/use-timescale/latest/continuous-aggregates/real-time-aggregates/ +CREATE MATERIALIZED VIEW measurement_10min WITH (timescaledb.continuous) AS +SELECT measurement.sensor_id, + time_bucket('10 minutes', measurement.time) AS time, + COUNT(*) total_values, + AVG(measurement.value) AS avg_value, + percentile_agg(measurement.value) as percentile_10min, + MAX(measurement.value) AS max_value, + MIN(measurement.value) AS min_value +FROM measurement +GROUP BY 1, 2 +WITH NO DATA; + +-- Add a CAGG policy in order to refresh it automatically +-- Automatically keep downsampled data up to date with new data from 20 minutes to 2 days ago. +-- https://docs.timescale.com/use-timescale/latest/continuous-aggregates/drop-data/ +SELECT add_continuous_aggregate_policy('measurement_10min', + start_offset => INTERVAL '2 days', + end_offset => INTERVAL '10 minutes', + schedule_interval => INTERVAL '10 minutes' +); + +-- Continuous aggregate (CAGG) of the hypertable +-- https://docs.timescale.com/use-timescale/latest/continuous-aggregates/real-time-aggregates/ +CREATE MATERIALIZED VIEW measurement_1hour WITH (timescaledb.continuous) AS +SELECT sensor_id, + time_bucket('1 hour', time) AS time, + COUNT(*) total_values, + mean(rollup(percentile_10min)) AS avg_value, + rollup(percentile_10min) as percentile_1hour, + MAX(max_value) AS max_value, + MIN(min_value) AS min_value +FROM measurement_10min +GROUP BY 1, 2 +WITH NO DATA; + +SELECT add_continuous_aggregate_policy('measurement_1hour', + start_offset => INTERVAL '2 days', + end_offset => INTERVAL '3 minutes', + schedule_interval => INTERVAL '1 hour', + initial_start => date_trunc('hours', now() + INTERVAL '1 hour') + INTERVAL '5 minutes' +); + +-- Continuous aggregate (CAGG) on top of another CAGG / Hierarchical Continuous Aggregates , new in Timescale 2.9, issue with TZ as of https://github.com/timescale/timescaledb/pull/5195 +-- https://docs.timescale.com/use-timescale/latest/continuous-aggregates/real-time-aggregates/ +CREATE MATERIALIZED VIEW measurement_1day WITH (timescaledb.continuous) AS +SELECT sensor_id, + time_bucket('1 day', time) AS time, + COUNT(*) total_values, + mean(rollup(percentile_1hour)) AS avg_value, + rollup(percentile_1hour) as percentile_1day, + MAX(max_value) AS max_value, + MIN(min_value) AS min_value +FROM measurement_1hour +GROUP BY 1, 2 +WITH NO DATA; + +-- Add a CAGG policy in order to refresh it automatically +-- Automatically keep downsampled data up to date with new data from 2 days to 4 days ago. +-- https://docs.timescale.com/use-timescale/latest/continuous-aggregates/drop-data/ +SELECT add_continuous_aggregate_policy('measurement_1day', + start_offset => INTERVAL '3 days', + end_offset => INTERVAL '3 minutes', + schedule_interval => INTERVAL '1 day', + initial_start => date_trunc('day', now() + INTERVAL '1 day') + INTERVAL '5 minutes' +); + +CREATE MATERIALIZED VIEW measurement_1month WITH (timescaledb.continuous) AS +SELECT sensor_id, + time_bucket('1 month', time) AS time, + COUNT(*) total_values, + mean(rollup(percentile_1day)) AS avg_value, + rollup(percentile_1day) as percentile_1month, + MAX(max_value) AS max_value, + MIN(min_value) AS min_value +FROM measurement_1day +GROUP BY 1, 2 +WITH NO DATA; + +SELECT add_continuous_aggregate_policy('measurement_1month', + start_offset => INTERVAL '3 months', + end_offset => INTERVAL '3 minutes', + schedule_interval => INTERVAL '1 month', + initial_start => date_trunc('month', now() + INTERVAL '1 month') + INTERVAL '5 minutes' +); + +CREATE MATERIALIZED VIEW measurement_1year WITH (timescaledb.continuous) AS +SELECT sensor_id, + time_bucket('1 year', time) AS time, + COUNT(*) total_values, + mean(rollup(percentile_1day)) AS avg_value, + rollup(percentile_1day) as percentile_1year, + MAX(max_value) AS max_value, + MIN(min_value) AS min_value +FROM measurement_1day +GROUP BY 1, 2 +WITH NO DATA; + +SELECT add_continuous_aggregate_policy('measurement_1year', + start_offset => INTERVAL '3 years', + end_offset => INTERVAL '1 hour', + schedule_interval => INTERVAL '1 year', + initial_start => date_trunc('year', now() + INTERVAL '1 year') + INTERVAL '2 hours' +); \ No newline at end of file diff --git a/packages/models/migrations/0002_add_password_reset_table.sql b/packages/models/migrations/0002_add_password_reset_table.sql new file mode 100644 index 00000000..d4081d04 --- /dev/null +++ b/packages/models/migrations/0002_add_password_reset_table.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS "password_reset" ( + "user_id" text NOT NULL, + "token" text NOT NULL, + "expires_at" timestamp NOT NULL, + CONSTRAINT "password_reset_user_id_unique" UNIQUE("user_id") +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "password_reset" ADD CONSTRAINT "password_reset_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/packages/models/migrations/0003_add_cronjob_reset_password_expires.sql b/packages/models/migrations/0003_add_cronjob_reset_password_expires.sql new file mode 100644 index 00000000..6f702964 --- /dev/null +++ b/packages/models/migrations/0003_add_cronjob_reset_password_expires.sql @@ -0,0 +1 @@ +-- Custom SQL migration file, put you code below! -- \ No newline at end of file diff --git a/packages/models/migrations/0004_add_updateOn.sql b/packages/models/migrations/0004_add_updateOn.sql new file mode 100644 index 00000000..ee8f6c7b --- /dev/null +++ b/packages/models/migrations/0004_add_updateOn.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS "refresh_token" ( + "user_id" text NOT NULL, + "token" text, + "expires_at" timestamp, + CONSTRAINT "refresh_token_user_id_unique" UNIQUE("user_id") +); +--> statement-breakpoint +ALTER TABLE "profile" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint +ALTER TABLE "profile" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "refresh_token" ADD CONSTRAINT "refresh_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/packages/models/migrations/0005_add_device_access_token.sql b/packages/models/migrations/0005_add_device_access_token.sql new file mode 100644 index 00000000..47dec71f --- /dev/null +++ b/packages/models/migrations/0005_add_device_access_token.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS "access_token" ( + "device_id" text NOT NULL, + "token" text +); +--> statement-breakpoint +ALTER TABLE "refresh_token" DROP CONSTRAINT "refresh_token_user_id_unique";--> statement-breakpoint +ALTER TABLE "password" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint +ALTER TABLE "password" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "access_token" ADD CONSTRAINT "access_token_device_id_device_id_fk" FOREIGN KEY ("device_id") REFERENCES "public"."device"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/packages/models/migrations/0006_add_ref_sensor_device.sql b/packages/models/migrations/0006_add_ref_sensor_device.sql new file mode 100644 index 00000000..78463b56 --- /dev/null +++ b/packages/models/migrations/0006_add_ref_sensor_device.sql @@ -0,0 +1,5 @@ +DO $$ BEGIN + ALTER TABLE "sensor" ADD CONSTRAINT "sensor_device_id_device_id_fk" FOREIGN KEY ("device_id") REFERENCES "public"."device"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/packages/models/migrations/0007_add_device_models.sql b/packages/models/migrations/0007_add_device_models.sql new file mode 100644 index 00000000..05f01b6f --- /dev/null +++ b/packages/models/migrations/0007_add_device_models.sql @@ -0,0 +1,26 @@ +ALTER TYPE "model" ADD VALUE 'home_v2_lora';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'home_v2_ethernet';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'home_v2_ethernet_feinstaub';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'home_v2_wifi';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'home_v2_wifi_feinstaub';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'home_ethernet';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'home_wifi';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'home_ethernet_feinstaub';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'home_wifi_feinstaub';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_sds011';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_sds011_dht11';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_sds011_dht22';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_sds011_bmp180';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_sds011_bme280';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_pms1003';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_pms1003_bme280';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_pms3003';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_pms3003_bme280';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_pms5003';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_pms5003_bme280';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_pms7003';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_pms7003_bme280';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_sps30_bme280';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten_sps30_sht3x';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'hackair_home_v2';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'custom'; \ No newline at end of file diff --git a/packages/models/migrations/0008_init_postgis.sql b/packages/models/migrations/0008_init_postgis.sql new file mode 100644 index 00000000..3972441f --- /dev/null +++ b/packages/models/migrations/0008_init_postgis.sql @@ -0,0 +1,2 @@ +-- CreateExtension +CREATE EXTENSION IF NOT EXISTS postgis CASCADE; \ No newline at end of file diff --git a/packages/models/migrations/0009_add_geometry_column.sql b/packages/models/migrations/0009_add_geometry_column.sql new file mode 100644 index 00000000..61af1899 --- /dev/null +++ b/packages/models/migrations/0009_add_geometry_column.sql @@ -0,0 +1,2 @@ +ALTER TABLE "device" ADD COLUMN "location" geometry(point) NOT NULL;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "spatial_index" ON "device" USING gist ("location"); \ No newline at end of file diff --git a/packages/models/migrations/0010_add_device_tags.sql b/packages/models/migrations/0010_add_device_tags.sql new file mode 100644 index 00000000..fbd60138 --- /dev/null +++ b/packages/models/migrations/0010_add_device_tags.sql @@ -0,0 +1 @@ +ALTER TABLE "device" ADD COLUMN "tags" text[] DEFAULT ARRAY[]::text[]; \ No newline at end of file diff --git a/packages/models/migrations/0011_drop_unused_columns_sensor.sql b/packages/models/migrations/0011_drop_unused_columns_sensor.sql new file mode 100644 index 00000000..1f0a35ce --- /dev/null +++ b/packages/models/migrations/0011_drop_unused_columns_sensor.sql @@ -0,0 +1,2 @@ +ALTER TABLE "sensor" DROP COLUMN IF EXISTS "lastMeasurement";--> statement-breakpoint +ALTER TABLE "sensor" DROP COLUMN IF EXISTS "data"; \ No newline at end of file diff --git a/packages/models/migrations/0012_add_token_blacklist.sql b/packages/models/migrations/0012_add_token_blacklist.sql new file mode 100644 index 00000000..841d2c11 --- /dev/null +++ b/packages/models/migrations/0012_add_token_blacklist.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS "token_blacklist" ( + "hash" text NOT NULL, + "token" text NOT NULL, + "expires_at" timestamp NOT NULL +); diff --git a/packages/models/migrations/0013_add_email_confirmation.sql b/packages/models/migrations/0013_add_email_confirmation.sql new file mode 100644 index 00000000..9f12da8a --- /dev/null +++ b/packages/models/migrations/0013_add_email_confirmation.sql @@ -0,0 +1 @@ +ALTER TABLE "user" ADD COLUMN "email_confirmation_token" text; \ No newline at end of file diff --git a/packages/models/migrations/0014_update_tables_and_relations.sql b/packages/models/migrations/0014_update_tables_and_relations.sql new file mode 100644 index 00000000..a5cd98e3 --- /dev/null +++ b/packages/models/migrations/0014_update_tables_and_relations.sql @@ -0,0 +1,38 @@ +CREATE TABLE IF NOT EXISTS "location" ( + "id" serial PRIMARY KEY NOT NULL, + "location" geometry(point) NOT NULL, + CONSTRAINT "location_location_unique" UNIQUE("location") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "device_to_location" ( + "device_id" text NOT NULL, + "location_id" integer NOT NULL, + "time" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "device_to_location_device_id_location_id_time_pk" PRIMARY KEY("device_id","location_id","time"), + CONSTRAINT "device_to_location_device_id_location_id_time_unique" UNIQUE("device_id","location_id","time") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "log_entry" ( + "id" text PRIMARY KEY NOT NULL, + "content" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "public" boolean DEFAULT false NOT NULL, + "device_id" text NOT NULL +); +--> statement-breakpoint +DROP INDEX IF EXISTS "spatial_index";--> statement-breakpoint +ALTER TABLE "device" ADD COLUMN "expires_at" date;--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "device_to_location" ADD CONSTRAINT "device_to_location_device_id_device_id_fk" FOREIGN KEY ("device_id") REFERENCES "public"."device"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "device_to_location" ADD CONSTRAINT "device_to_location_location_id_location_id_fk" FOREIGN KEY ("location_id") REFERENCES "public"."location"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "location_index" ON "location" USING gist ("location");--> statement-breakpoint +ALTER TABLE "device" DROP COLUMN IF EXISTS "location"; \ No newline at end of file diff --git a/packages/models/migrations/0015_cascade_user_device.sql b/packages/models/migrations/0015_cascade_user_device.sql new file mode 100644 index 00000000..18bef63a --- /dev/null +++ b/packages/models/migrations/0015_cascade_user_device.sql @@ -0,0 +1,5 @@ +DO $$ BEGIN + ALTER TABLE "device" ADD CONSTRAINT "device_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/packages/models/migrations/0016_greedy_speedball.sql b/packages/models/migrations/0016_greedy_speedball.sql new file mode 100644 index 00000000..98571b76 --- /dev/null +++ b/packages/models/migrations/0016_greedy_speedball.sql @@ -0,0 +1,9 @@ +ALTER TYPE "model" ADD VALUE 'homeEthernet';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'homeWifi';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'homeLora';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'homeV2Lora';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'homeV2Ethernet';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'homeV2Wifi';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'senseBox:Edu';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'luftdaten.info';--> statement-breakpoint +ALTER TYPE "model" ADD VALUE 'Custom'; \ No newline at end of file diff --git a/packages/models/migrations/0017_nappy_giant_girl.sql b/packages/models/migrations/0017_nappy_giant_girl.sql new file mode 100644 index 00000000..e022743e --- /dev/null +++ b/packages/models/migrations/0017_nappy_giant_girl.sql @@ -0,0 +1,2 @@ +ALTER TABLE "user" ADD COLUMN "unconfirmed_email" text;--> statement-breakpoint +ALTER TABLE "user" ADD CONSTRAINT "user_unconfirmed_email_unique" UNIQUE("unconfirmed_email"); \ No newline at end of file diff --git a/packages/models/migrations/meta/0000_snapshot.json b/packages/models/migrations/meta/0000_snapshot.json new file mode 100644 index 00000000..f7d09aee --- /dev/null +++ b/packages/models/migrations/meta/0000_snapshot.json @@ -0,0 +1,518 @@ +{ + "id": "4f13d236-bd47-4bd5-858f-e17ec12a0e50", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "HOME_V2_LORA" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0001_snapshot.json b/packages/models/migrations/meta/0001_snapshot.json new file mode 100644 index 00000000..67d07f57 --- /dev/null +++ b/packages/models/migrations/meta/0001_snapshot.json @@ -0,0 +1,518 @@ +{ + "id": "6fbcacd9-3875-4f2c-8866-54dd0fa1b476", + "prevId": "4f13d236-bd47-4bd5-858f-e17ec12a0e50", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "columns": [ + "sensor_id", + "time" + ], + "nullsNotDistinct": false + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "columnsFrom": [ + "profile_id" + ], + "tableTo": "profile", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "columns": [ + "username" + ], + "nullsNotDistinct": false + } + } + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "nullsNotDistinct": false + } + } + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "HOME_V2_LORA" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0002_snapshot.json b/packages/models/migrations/meta/0002_snapshot.json new file mode 100644 index 00000000..d342b7a8 --- /dev/null +++ b/packages/models/migrations/meta/0002_snapshot.json @@ -0,0 +1,568 @@ +{ + "id": "ea325078-5292-43a6-be9e-3b499c32eba5", + "prevId": "6fbcacd9-3875-4f2c-8866-54dd0fa1b476", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "HOME_V2_LORA" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0003_snapshot.json b/packages/models/migrations/meta/0003_snapshot.json new file mode 100644 index 00000000..b4cfed0c --- /dev/null +++ b/packages/models/migrations/meta/0003_snapshot.json @@ -0,0 +1,568 @@ +{ + "id": "4bcd5795-2204-487c-89a0-3bf529da937e", + "prevId": "ea325078-5292-43a6-be9e-3b499c32eba5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "columns": [ + "sensor_id", + "time" + ], + "nullsNotDistinct": false + } + } + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "nullsNotDistinct": false + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "columns": [ + "user_id" + ], + "nullsNotDistinct": false + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "columns": [ + "username" + ], + "nullsNotDistinct": false + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "columnsFrom": [ + "profile_id" + ], + "tableTo": "profile", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "HOME_V2_LORA" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0004_snapshot.json b/packages/models/migrations/meta/0004_snapshot.json new file mode 100644 index 00000000..78353311 --- /dev/null +++ b/packages/models/migrations/meta/0004_snapshot.json @@ -0,0 +1,632 @@ +{ + "id": "e6acc6ef-9ec1-49c4-8244-a8525f405f59", + "prevId": "4bcd5795-2204-487c-89a0-3bf529da937e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "refresh_token_user_id_unique": { + "name": "refresh_token_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "HOME_V2_LORA" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0005_snapshot.json b/packages/models/migrations/meta/0005_snapshot.json new file mode 100644 index 00000000..588f119f --- /dev/null +++ b/packages/models/migrations/meta/0005_snapshot.json @@ -0,0 +1,674 @@ +{ + "id": "dc99571c-be0a-48ad-9e11-dc7100fd7dbd", + "prevId": "e6acc6ef-9ec1-49c4-8244-a8525f405f59", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "HOME_V2_LORA" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0006_snapshot.json b/packages/models/migrations/meta/0006_snapshot.json new file mode 100644 index 00000000..64c6059f --- /dev/null +++ b/packages/models/migrations/meta/0006_snapshot.json @@ -0,0 +1,688 @@ +{ + "id": "49122c3d-51f1-441a-b120-70a5b34d32d9", + "prevId": "dc99571c-be0a-48ad-9e11-dc7100fd7dbd", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "HOME_V2_LORA" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0007_snapshot.json b/packages/models/migrations/meta/0007_snapshot.json new file mode 100644 index 00000000..9f3e5e59 --- /dev/null +++ b/packages/models/migrations/meta/0007_snapshot.json @@ -0,0 +1,713 @@ +{ + "id": "a6df02d8-5c4a-4e6b-808a-722ec2e4187c", + "prevId": "49122c3d-51f1-441a-b120-70a5b34d32d9", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0008_snapshot.json b/packages/models/migrations/meta/0008_snapshot.json new file mode 100644 index 00000000..f633b417 --- /dev/null +++ b/packages/models/migrations/meta/0008_snapshot.json @@ -0,0 +1,713 @@ +{ + "id": "bfaceeec-8e34-4365-9a07-4ba86dc195be", + "prevId": "a6df02d8-5c4a-4e6b-808a-722ec2e4187c", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "columns": [ + "sensor_id", + "time" + ], + "nullsNotDistinct": false + } + } + }, + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "columnsFrom": [ + "device_id" + ], + "tableTo": "device", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "columnsFrom": [ + "device_id" + ], + "tableTo": "device", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "nullsNotDistinct": false + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "columns": [ + "user_id" + ], + "nullsNotDistinct": false + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "columns": [ + "username" + ], + "nullsNotDistinct": false + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "columnsFrom": [ + "profile_id" + ], + "tableTo": "profile", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0009_snapshot.json b/packages/models/migrations/meta/0009_snapshot.json new file mode 100644 index 00000000..4db01b49 --- /dev/null +++ b/packages/models/migrations/meta/0009_snapshot.json @@ -0,0 +1,735 @@ +{ + "id": "a35bf829-bf84-4113-97f9-00b189b74561", + "prevId": "bfaceeec-8e34-4365-9a07-4ba86dc195be", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "spatial_index": { + "name": "spatial_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0010_snapshot.json b/packages/models/migrations/meta/0010_snapshot.json new file mode 100644 index 00000000..6b1bc123 --- /dev/null +++ b/packages/models/migrations/meta/0010_snapshot.json @@ -0,0 +1,742 @@ +{ + "id": "f4cdd9e0-a1cf-4a53-af88-646898760135", + "prevId": "a35bf829-bf84-4113-97f9-00b189b74561", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "spatial_index": { + "name": "spatial_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lastMeasurement": { + "name": "lastMeasurement", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0011_snapshot.json b/packages/models/migrations/meta/0011_snapshot.json new file mode 100644 index 00000000..fefc7a8f --- /dev/null +++ b/packages/models/migrations/meta/0011_snapshot.json @@ -0,0 +1,730 @@ +{ + "id": "54fd48ea-dce0-4ff8-b404-e58903008279", + "prevId": "f4cdd9e0-a1cf-4a53-af88-646898760135", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "spatial_index": { + "name": "spatial_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0012_snapshot.json b/packages/models/migrations/meta/0012_snapshot.json new file mode 100644 index 00000000..9bc7c30b --- /dev/null +++ b/packages/models/migrations/meta/0012_snapshot.json @@ -0,0 +1,758 @@ +{ + "id": "74a4430b-2bdc-4c7a-aa13-3f5bed6a6bd9", + "prevId": "54fd48ea-dce0-4ff8-b404-e58903008279", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "spatial_index": { + "name": "spatial_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.token_blacklist": { + "name": "token_blacklist", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0013_snapshot.json b/packages/models/migrations/meta/0013_snapshot.json new file mode 100644 index 00000000..7aafd8e2 --- /dev/null +++ b/packages/models/migrations/meta/0013_snapshot.json @@ -0,0 +1,764 @@ +{ + "id": "0194822c-5bf7-43e5-b024-3691b097874f", + "prevId": "74a4430b-2bdc-4c7a-aa13-3f5bed6a6bd9", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "spatial_index": { + "name": "spatial_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "email_confirmation_token": { + "name": "email_confirmation_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.token_blacklist": { + "name": "token_blacklist", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0014_snapshot.json b/packages/models/migrations/meta/0014_snapshot.json new file mode 100644 index 00000000..bc87fed5 --- /dev/null +++ b/packages/models/migrations/meta/0014_snapshot.json @@ -0,0 +1,911 @@ +{ + "id": "6ba30027-0bcb-421c-8e58-5777d52f3422", + "prevId": "0194822c-5bf7-43e5-b024-3691b097874f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "email_confirmation_token": { + "name": "email_confirmation_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.token_blacklist": { + "name": "token_blacklist", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.location": { + "name": "location", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "location_index": { + "name": "location_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "location_location_unique": { + "name": "location_location_unique", + "nullsNotDistinct": false, + "columns": [ + "location" + ] + } + } + }, + "public.device_to_location": { + "name": "device_to_location", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "device_to_location_device_id_device_id_fk": { + "name": "device_to_location_device_id_device_id_fk", + "tableFrom": "device_to_location", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "device_to_location_location_id_location_id_fk": { + "name": "device_to_location_location_id_location_id_fk", + "tableFrom": "device_to_location", + "tableTo": "location", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "device_to_location_device_id_location_id_time_pk": { + "name": "device_to_location_device_id_location_id_time_pk", + "columns": [ + "device_id", + "location_id", + "time" + ] + } + }, + "uniqueConstraints": { + "device_to_location_device_id_location_id_time_unique": { + "name": "device_to_location_device_id_location_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "device_id", + "location_id", + "time" + ] + } + } + }, + "public.log_entry": { + "name": "log_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0015_snapshot.json b/packages/models/migrations/meta/0015_snapshot.json new file mode 100644 index 00000000..f92cce35 --- /dev/null +++ b/packages/models/migrations/meta/0015_snapshot.json @@ -0,0 +1,925 @@ +{ + "id": "7ab04c61-4f5c-4c1b-b477-e79a02b9ce80", + "prevId": "6ba30027-0bcb-421c-8e58-5777d52f3422", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "device_user_id_user_id_fk": { + "name": "device_user_id_user_id_fk", + "tableFrom": "device", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "email_confirmation_token": { + "name": "email_confirmation_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.token_blacklist": { + "name": "token_blacklist", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.location": { + "name": "location", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "location_index": { + "name": "location_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "location_location_unique": { + "name": "location_location_unique", + "nullsNotDistinct": false, + "columns": [ + "location" + ] + } + } + }, + "public.device_to_location": { + "name": "device_to_location", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "device_to_location_device_id_device_id_fk": { + "name": "device_to_location_device_id_device_id_fk", + "tableFrom": "device_to_location", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "device_to_location_location_id_location_id_fk": { + "name": "device_to_location_location_id_location_id_fk", + "tableFrom": "device_to_location", + "tableTo": "location", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "device_to_location_device_id_location_id_time_pk": { + "name": "device_to_location_device_id_location_id_time_pk", + "columns": [ + "device_id", + "location_id", + "time" + ] + } + }, + "uniqueConstraints": { + "device_to_location_device_id_location_id_time_unique": { + "name": "device_to_location_device_id_location_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "device_id", + "location_id", + "time" + ] + } + } + }, + "public.log_entry": { + "name": "log_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "home_v2_lora", + "home_v2_ethernet", + "home_v2_ethernet_feinstaub", + "home_v2_wifi", + "home_v2_wifi_feinstaub", + "home_ethernet", + "home_wifi", + "home_ethernet_feinstaub", + "home_wifi_feinstaub", + "luftdaten_sds011", + "luftdaten_sds011_dht11", + "luftdaten_sds011_dht22", + "luftdaten_sds011_bmp180", + "luftdaten_sds011_bme280", + "luftdaten_pms1003", + "luftdaten_pms1003_bme280", + "luftdaten_pms3003", + "luftdaten_pms3003_bme280", + "luftdaten_pms5003", + "luftdaten_pms5003_bme280", + "luftdaten_pms7003", + "luftdaten_pms7003_bme280", + "luftdaten_sps30_bme280", + "luftdaten_sps30_sht3x", + "hackair_home_v2", + "custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0016_snapshot.json b/packages/models/migrations/meta/0016_snapshot.json new file mode 100644 index 00000000..c86af560 --- /dev/null +++ b/packages/models/migrations/meta/0016_snapshot.json @@ -0,0 +1,908 @@ +{ + "id": "b22d0eca-7eb4-4e1a-a15d-1dde94487c2f", + "prevId": "7ab04c61-4f5c-4c1b-b477-e79a02b9ce80", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "device_user_id_user_id_fk": { + "name": "device_user_id_user_id_fk", + "tableFrom": "device", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "email_confirmation_token": { + "name": "email_confirmation_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.token_blacklist": { + "name": "token_blacklist", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.location": { + "name": "location", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "location_index": { + "name": "location_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "location_location_unique": { + "name": "location_location_unique", + "nullsNotDistinct": false, + "columns": [ + "location" + ] + } + } + }, + "public.device_to_location": { + "name": "device_to_location", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "device_to_location_device_id_device_id_fk": { + "name": "device_to_location_device_id_device_id_fk", + "tableFrom": "device_to_location", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "device_to_location_location_id_location_id_fk": { + "name": "device_to_location_location_id_location_id_fk", + "tableFrom": "device_to_location", + "tableTo": "location", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "device_to_location_device_id_location_id_time_pk": { + "name": "device_to_location_device_id_location_id_time_pk", + "columns": [ + "device_id", + "location_id", + "time" + ] + } + }, + "uniqueConstraints": { + "device_to_location_device_id_location_id_time_unique": { + "name": "device_to_location_device_id_location_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "device_id", + "location_id", + "time" + ] + } + } + }, + "public.log_entry": { + "name": "log_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "homeEthernet", + "homeWifi", + "homeLora", + "homeV2Lora", + "homeV2Ethernet", + "homeV2Wifi", + "senseBox:Edu", + "luftdaten.info", + "Custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/0017_snapshot.json b/packages/models/migrations/meta/0017_snapshot.json new file mode 100644 index 00000000..2049526d --- /dev/null +++ b/packages/models/migrations/meta/0017_snapshot.json @@ -0,0 +1,921 @@ +{ + "id": "5eede25a-ba75-496f-9c80-95bd1eae9551", + "prevId": "b22d0eca-7eb4-4e1a-a15d-1dde94487c2f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_device_id_device_id_fk": { + "name": "access_token_device_id_device_id_fk", + "tableFrom": "access_token", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.device": { + "name": "device", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "use_auth": { + "name": "use_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "exposure", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "model": { + "name": "model", + "type": "model", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_model": { + "name": "sensor_wiki_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "device_user_id_user_id_fk": { + "name": "device_user_id_user_id_fk", + "tableFrom": "device", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sensor": { + "name": "sensor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_type": { + "name": "sensor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'inactive'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensor_wiki_type": { + "name": "sensor_wiki_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_phenomenon": { + "name": "sensor_wiki_phenomenon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sensor_wiki_unit": { + "name": "sensor_wiki_unit", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sensor_device_id_device_id_fk": { + "name": "sensor_device_id_device_id_fk", + "tableFrom": "sensor", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unconfirmed_email": { + "name": "unconfirmed_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'en_US'" + }, + "email_is_confirmed": { + "name": "email_is_confirmed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "email_confirmation_token": { + "name": "email_confirmation_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_unconfirmed_email_unique": { + "name": "user_unconfirmed_email_unique", + "nullsNotDistinct": false, + "columns": [ + "unconfirmed_email" + ] + } + } + }, + "public.measurement": { + "name": "measurement", + "schema": "", + "columns": { + "sensor_id": { + "name": "sensor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "measurement_sensor_id_time_unique": { + "name": "measurement_sensor_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "sensor_id", + "time" + ] + } + } + }, + "public.password": { + "name": "password", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_user_id_user_id_fk": { + "name": "password_user_id_user_id_fk", + "tableFrom": "password", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.password_reset": { + "name": "password_reset", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_user_id_user_id_fk": { + "name": "password_reset_user_id_user_id_fk", + "tableFrom": "password_reset", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_user_id_unique": { + "name": "password_reset_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "profile_user_id_user_id_fk": { + "name": "profile_user_id_user_id_fk", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_unique": { + "name": "profile_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + }, + "public.profile_image": { + "name": "profile_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blob": { + "name": "blob", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "profile_id": { + "name": "profile_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "profile_image_profile_id_profile_id_fk": { + "name": "profile_image_profile_id_profile_id_fk", + "tableFrom": "profile_image", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_token_user_id_user_id_fk": { + "name": "refresh_token_user_id_user_id_fk", + "tableFrom": "refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.token_blacklist": { + "name": "token_blacklist", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.location": { + "name": "location", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(point)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "location_index": { + "name": "location_index", + "columns": [ + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gist", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "location_location_unique": { + "name": "location_location_unique", + "nullsNotDistinct": false, + "columns": [ + "location" + ] + } + } + }, + "public.device_to_location": { + "name": "device_to_location", + "schema": "", + "columns": { + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "time": { + "name": "time", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "device_to_location_device_id_device_id_fk": { + "name": "device_to_location_device_id_device_id_fk", + "tableFrom": "device_to_location", + "tableTo": "device", + "columnsFrom": [ + "device_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "device_to_location_location_id_location_id_fk": { + "name": "device_to_location_location_id_location_id_fk", + "tableFrom": "device_to_location", + "tableTo": "location", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "device_to_location_device_id_location_id_time_pk": { + "name": "device_to_location_device_id_location_id_time_pk", + "columns": [ + "device_id", + "location_id", + "time" + ] + } + }, + "uniqueConstraints": { + "device_to_location_device_id_location_id_time_unique": { + "name": "device_to_location_device_id_location_id_time_unique", + "nullsNotDistinct": false, + "columns": [ + "device_id", + "location_id", + "time" + ] + } + } + }, + "public.log_entry": { + "name": "log_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.model": { + "name": "model", + "schema": "public", + "values": [ + "homeEthernet", + "homeWifi", + "homeLora", + "homeV2Lora", + "homeV2Ethernet", + "homeV2Wifi", + "senseBox:Edu", + "luftdaten.info", + "Custom" + ] + }, + "public.exposure": { + "name": "exposure", + "schema": "public", + "values": [ + "indoor", + "outdoor", + "mobile", + "unknown" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "active", + "inactive", + "old" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/models/migrations/meta/_journal.json b/packages/models/migrations/meta/_journal.json new file mode 100644 index 00000000..4893e0fe --- /dev/null +++ b/packages/models/migrations/meta/_journal.json @@ -0,0 +1,132 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1727183815665, + "tag": "0000_init_table", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1727196569376, + "tag": "0001_init_tsdb", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1727299848109, + "tag": "0002_add_password_reset_table", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1727300042343, + "tag": "0003_add_cronjob_reset_password_expires", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1727340849500, + "tag": "0004_add_updateOn", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1727429631691, + "tag": "0005_add_device_access_token", + "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1727430508941, + "tag": "0006_add_ref_sensor_device", + "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1727431399829, + "tag": "0007_add_device_models", + "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1727688805009, + "tag": "0008_init_postgis", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1727689770873, + "tag": "0009_add_geometry_column", + "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1727701542050, + "tag": "0010_add_device_tags", + "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1727774100701, + "tag": "0011_drop_unused_columns_sensor", + "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1729843974723, + "tag": "0012_add_token_blacklist", + "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1729860468725, + "tag": "0013_add_email_confirmation", + "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1734606789875, + "tag": "0014_update_tables_and_relations", + "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1734691173637, + "tag": "0015_cascade_user_device", + "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1738572304017, + "tag": "0016_greedy_speedball", + "breakpoints": true + }, + { + "idx": 17, + "version": "7", + "when": 1739437360664, + "tag": "0017_nappy_giant_girl", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/packages/models/package.json b/packages/models/package.json index 461cbb8b..fd5f45bd 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -7,11 +7,13 @@ "dependencies": { "@grpc/grpc-js": "^1.9.4", "@grpc/proto-loader": "^0.7.10", + "@paralleldrive/cuid2": "^2.2.2", "@sensebox/osem-protos": "^1.1.0", "@sensebox/sketch-templater": "1.13.1", "bcrypt": "^5.1.1", "bullmq": "^4.12.3", "config": "^3.3.6", + "drizzle-orm": "^0.33.0", "got": "^11.8.2", "isemail": "^3.0.0", "jsonpath": "^1.1.1", @@ -19,14 +21,24 @@ "moment": "^2.29.4", "mongoose": "^5.13.20", "mongoose-timestamp": "^0.6", + "pg": "^8.13.0", "pino": "^8.8.0", + "tiny-invariant": "^1.3.3", "uuid": "^8.3.2" }, "scripts": { + "db:drop": "drizzle-kit drop", + "db:generate": "drizzle-kit generate", + "db:generate-custom": "drizzle-kit generate --custom", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio", "version": "node .scripts/npm_version-update_changelog.js && git add CHANGELOG.md", "test": "mocha test/waitForDatabase test/index.js" }, "publishConfig": { "access": "public" + }, + "devDependencies": { + "drizzle-kit": "^0.24.2" } } diff --git a/packages/models/schema/enum.js b/packages/models/schema/enum.js new file mode 100644 index 00000000..83e0eaea --- /dev/null +++ b/packages/models/schema/enum.js @@ -0,0 +1,31 @@ +"use strict"; + +const { pgEnum } = require("drizzle-orm/pg-core"); + +const DeviceModelEnum = pgEnum("model", [ + "homeEthernet", + "homeWifi", + "homeLora", + "homeV2Lora", + "homeV2Ethernet", + "homeV2Wifi", + "senseBox:Edu", + "luftdaten.info", + "Custom", +]); + +// Enum for device exposure types +const DeviceExposureEnum = pgEnum("exposure", [ + "indoor", + "outdoor", + "mobile", + "unknown", +]); + +const DeviceStatusEnum = pgEnum("status", ["active", "inactive", "old"]); + +module.exports = { + DeviceModelEnum, + DeviceExposureEnum, + DeviceStatusEnum, +}; diff --git a/packages/models/schema/measurement.js b/packages/models/schema/measurement.js new file mode 100644 index 00000000..4ef5169a --- /dev/null +++ b/packages/models/schema/measurement.js @@ -0,0 +1,62 @@ +'use strict'; + +const { text, timestamp, doublePrecision, pgMaterializedView, integer } = require('drizzle-orm/pg-core'); + +/** + * Views + */ +const measurement10minView = pgMaterializedView('measurement_10min', { + sensorId: text('sensor_id'), + time: timestamp('time', { precision: 3, withTimezone: true }), + value: doublePrecision('avg_value'), + total_values: integer('total_values'), + min_value: doublePrecision('min_value'), + max_value: doublePrecision('max_value') +}).existing(); + +const measurements1hourView = pgMaterializedView('measurement_1hour', { + sensorId: text('sensor_id'), + time: timestamp('time', { precision: 3, withTimezone: true }), + value: doublePrecision('avg_value'), + total_values: integer('total_values'), + min_value: doublePrecision('min_value'), + max_value: doublePrecision('max_value') +}).existing(); + +const measurements1dayView = pgMaterializedView('measurement_1day', { + sensorId: text('sensor_id'), + time: timestamp('time', { precision: 3, withTimezone: true }), + value: doublePrecision('avg_value'), + total_values: integer('total_values'), + min_value: doublePrecision('min_value'), + max_value: doublePrecision('max_value') +}).existing(); + +const measurements1monthView = pgMaterializedView('measurement_1month', { + sensorId: text('sensor_id'), + time: timestamp('time', { precision: 3, withTimezone: true }), + value: doublePrecision('avg_value'), + total_values: integer('total_values'), + min_value: doublePrecision('min_value'), + max_value: doublePrecision('max_value') +}).existing(); + +const measurements1yearView = pgMaterializedView('measurement_1year', { + sensorId: text('sensor_id'), + time: timestamp('time', { precision: 3, withTimezone: true }), + value: doublePrecision('avg_value'), + total_values: integer('total_values'), + min_value: doublePrecision('min_value'), + max_value: doublePrecision('max_value') +}).existing(); + + +module.exports = { + views: { + measurement10minView, + measurements1hourView, + measurements1dayView, + measurements1monthView, + measurements1yearView + } +}; diff --git a/packages/models/schema/schema.js b/packages/models/schema/schema.js new file mode 100644 index 00000000..59ef54a1 --- /dev/null +++ b/packages/models/schema/schema.js @@ -0,0 +1,367 @@ +'use strict'; + +const { pgTable, text, boolean, timestamp, doublePrecision, geometry, index, unique } = require('drizzle-orm/pg-core'); +const { relations, sql } = require('drizzle-orm'); +const { createId } = require('@paralleldrive/cuid2'); +const { v4: uuidv4 } = require('uuid'); +const moment = require('moment'); +const { DeviceExposureEnum, DeviceStatusEnum, DeviceModelEnum } = require('./enum'); +const { bytea } = require('./types'); +const { date } = require('drizzle-orm/pg-core'); +const { integer } = require('drizzle-orm/pg-core'); +const { primaryKey } = require('drizzle-orm/pg-core'); +const { serial } = require('drizzle-orm/pg-core'); + +/** + * Table definition + */ +const device = pgTable('device', { + id: text('id') + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + name: text('name').notNull(), + image: text('image'), + description: text('description'), + tags: text('tags') + .array() + .default(sql`ARRAY[]::text[]`), + link: text('link'), + useAuth: boolean('use_auth'), + exposure: DeviceExposureEnum('exposure'), + status: DeviceStatusEnum('status').default('inactive'), + model: DeviceModelEnum('model'), + public: boolean('public').default(false), + createdAt: timestamp('created_at').defaultNow() + .notNull(), + updatedAt: timestamp('updated_at').defaultNow() + .notNull(), + expiresAt: date('expires_at', { mode: 'date' }), + latitude: doublePrecision('latitude').notNull(), + longitude: doublePrecision('longitude').notNull(), + userId: text('user_id').notNull() + .references(() => user.id, { + onDelete: 'cascade', + onUpdate: 'no action' + }), + sensorWikiModel: text('sensor_wiki_model') +}); + +// Many-to-many relation between device - location +// https://orm.drizzle.team/docs/rqb#many-to-many +const deviceToLocation = pgTable( + 'device_to_location', + { + deviceId: text('device_id') + .notNull() + .references(() => device.id, { + onDelete: 'cascade', + onUpdate: 'cascade', + }), + locationId: integer('location_id') + .notNull() + .references(() => location.id), + time: timestamp('time').defaultNow() + .notNull(), + }, + (t) => ({ + pk: primaryKey({ columns: [t.deviceId, t.locationId, t.time] }), + unique: unique().on(t.deviceId, t.locationId, t.time), // Device can only be at one location at the same time + }), +); + +const sensor = pgTable('sensor', { + id: text('id') + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + title: text('title'), + unit: text('unit'), + sensorType: text('sensor_type'), + status: DeviceStatusEnum('status').default('inactive'), + createdAt: timestamp('created_at').defaultNow() + .notNull(), + updatedAt: timestamp('updated_at') + .defaultNow() + .notNull() + .$onUpdateFn(() => new Date()), + deviceId: text('device_id') + .notNull() + .references(() => device.id, { + onDelete: 'cascade' + }), + sensorWikiType: text('sensor_wiki_type'), + sensorWikiPhenomenon: text('sensor_wiki_phenomenon'), + sensorWikiUnit: text('sensor_wiki_unit') +}); + +const user = pgTable('user', { + id: text('id') + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + name: text('name').notNull(), + email: text('email').unique() + .notNull(), + unconfirmedEmail: text('unconfirmed_email').unique(), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + language: text('language').default('en_US'), + emailIsConfirmed: boolean('email_is_confirmed').default(false), + emailConfirmationToken: text('email_confirmation_token').$defaultFn(() => uuidv4()), + createdAt: timestamp('created_at').defaultNow() + .notNull(), + updatedAt: timestamp('updated_at').defaultNow() + .notNull() + .$onUpdateFn(() => new Date()) +}); + +const password = pgTable('password', { + hash: text('hash').notNull(), + userId: text('user_id') + .references(() => user.id, { + onDelete: 'cascade', + onUpdate: 'cascade' + }) + .notNull(), + createdAt: timestamp('created_at').defaultNow() + .notNull(), + updatedAt: timestamp('updated_at').defaultNow() + .notNull() + .$onUpdateFn(() => new Date()), +}); + +const passwordReset = pgTable('password_reset', { + userId: text('user_id').unique() + .notNull() + .references(() => user.id, { + onDelete: 'cascade', + }), + token: text('token').notNull() + .$defaultFn(() => uuidv4()), + expiresAt: timestamp('expires_at').notNull() + .$defaultFn(() => moment.utc().add(12, 'hours') + .toDate()) +}); + +const profile = pgTable('profile', { + id: text('id') + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + username: text('username').unique() + .notNull(), + public: boolean('public').default(false), + userId: text('user_id').references(() => user.id, { + onDelete: 'cascade', + onUpdate: 'cascade' + }), + createdAt: timestamp('created_at').defaultNow() + .notNull(), + updatedAt: timestamp('updated_at').defaultNow() + .notNull() + .$onUpdateFn(() => new Date()), +}); + +const profileImage = pgTable('profile_image', { + id: text('id') + .notNull() + .primaryKey() + .$defaultFn(() => createId()), + altText: text('alt_text'), + contentType: text('content_type').notNull(), + blob: bytea('blob').notNull(), + createdAt: timestamp('created_at').defaultNow() + .notNull(), + updatedAt: timestamp('updated_at').defaultNow() + .notNull() + .$onUpdateFn(() => new Date()), + profileId: text('profile_id').references(() => profile.id, { + onDelete: 'cascade', + onUpdate: 'cascade' + }) +}); + +const refreshToken = pgTable('refresh_token', { + userId: text('user_id') + .notNull() + .references(() => user.id, { + onDelete: 'cascade', + }), + token: text('token'), + expiresAt: timestamp('expires_at') +}); + +const accessToken = pgTable('access_token', { + deviceId: text('device_id').notNull() + .references(() => device.id, { + onDelete: 'cascade' + }), + token: text('token'), +}); + +const tokenBlacklist = pgTable('token_blacklist', { + hash: text('hash').notNull(), + token: text('token').notNull(), + expiresAt: timestamp('expires_at') + .notNull() + .$defaultFn(() => moment.utc().add(1, 'week') + .toDate()) +}); + +const measurement = pgTable('measurement', { + sensorId: text('sensor_id').notNull(), + time: timestamp('time', { precision: 3, withTimezone: true }).defaultNow() + .notNull(), + value: doublePrecision('value') +}, (t) => ({ + unq: unique().on(t.sensorId, t.time) +})); + +/** + * Relations + */ +const deviceRelations = relations(device, ({ many, one }) => ({ + user: one(user, { + fields: [device.userId], + references: [user.id] + }), + sensors: many(sensor), + locations: many(deviceToLocation), + logEntries: many(logEntry), + accessToken: one(accessToken) +})); + +// Many-to-many +const deviceToLocationRelations = relations( + deviceToLocation, + ({ one }) => ({ + device: one(device, { + fields: [deviceToLocation.deviceId], + references: [device.id], + }), + geometry: one(location, { + fields: [deviceToLocation.locationId], + references: [location.id], + }), + }), +); + +const sensorRelations = relations(sensor, ({ one }) => ({ + device: one(device, { + fields: [sensor.deviceId], + references: [device.id] + }) +})); + +const userRelations = relations(user, ({ one, many }) => ({ + password: one(password, { + fields: [user.id], + references: [password.userId] + }), + passwordReset: one(passwordReset, { + fields: [user.id], + references: [passwordReset.userId] + }), + profile: one(profile, { + fields: [user.id], + references: [profile.userId] + }), + devices: many(device), + // TODO: model shared devices sharedDevices: many(device), + refreshToken: many(refreshToken) +})); + +const profileRelations = relations(profile, ({ one }) => ({ + user: one(user, { + fields: [profile.userId], + references: [user.id] + }), + profileImage: one(profileImage, { + fields: [profile.id], + references: [profileImage.profileId] + }) +})); + +const refreshTokenRelations = relations(refreshToken, ({ one }) => ({ + user: one(user, { + fields: [refreshToken.userId], + references: [user.id] + }) +})); + +const accessTokenRelations = relations(accessToken, ({ one }) => ({ + user: one(device, { + fields: [accessToken.deviceId], + references: [device.id] + }) +})); + +const location = pgTable( + 'location', + { + id: serial('id').primaryKey(), + location: geometry('location', { + type: 'point', + mode: 'xy', + srid: 4326 + }).notNull() + }, + (t) => ({ + locationIndex: index('location_index').using('gist', t.location), + unique_location: unique().on(t.location) + }) +); + +/** + * Relations + * 1. One-to-many: Location - Measurement (One location can have many measurements) + */ +const locationRelations = relations(location, ({ many }) => ({ + measurements: many(measurement) +})); + +// Table definition +const logEntry = pgTable('log_entry', { + id: text('id') + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + content: text('content').notNull(), + createdAt: timestamp('created_at').defaultNow() + .notNull(), + public: boolean('public').default(false) + .notNull(), + deviceId: text('device_id').notNull(), +}); + +// Relations definition +const logEntryRelations = relations(logEntry, ({ one }) => ({ + device: one(device, { + fields: [logEntry.deviceId], + references: [device.id], + }), +})); + +module.exports.accessTokenTable = accessToken; +module.exports.deviceTable = device; +module.exports.sensorTable = sensor; +module.exports.userTable = user; +module.exports.measurementTable = measurement; +module.exports.passwordTable = password; +module.exports.passwordResetTable = passwordReset; +module.exports.profileTable = profile; +module.exports.profileImageTable = profileImage; +module.exports.refreshTokenTable = refreshToken; +module.exports.tokenBlacklistTable = tokenBlacklist; +module.exports.accessTokenRelations = accessTokenRelations; +module.exports.deviceRelations = deviceRelations; +module.exports.sensorRelations = sensorRelations; +module.exports.userRelations = userRelations; +module.exports.profileRelations = profileRelations; +module.exports.refreshTokenRelations = refreshTokenRelations; +module.exports.locationTable = location; +module.exports.locationRelations = locationRelations; +module.exports.deviceToLocationTable = deviceToLocation; +module.exports.deviceToLocationRelations = deviceToLocationRelations; +module.exports.logEntryTable = logEntry; +module.exports.logEntryRelations = logEntryRelations; diff --git a/packages/models/schema/types.js b/packages/models/schema/types.js new file mode 100644 index 00000000..87f64686 --- /dev/null +++ b/packages/models/schema/types.js @@ -0,0 +1,13 @@ +'use strict'; + +const { customType } = require('drizzle-orm/pg-core'); + +const bytea = customType({ + dataType () { + return 'bytea'; + } +}); + +module.exports = { + bytea +}; diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index a29100bd..28efaf55 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -1,5 +1,7 @@ 'use strict'; +const { db } = require('../drizzle'); + const { mongoose } = require('../db'), timestamp = require('mongoose-timestamp'), Schema = mongoose.Schema, @@ -168,6 +170,22 @@ const BOX_PROPS_FOR_POPULATION = { lastMeasurementAt: 1 }; +const DEVICE_COLUMNS_FOR_RETURNING = { + id: true, + name: true, + exposure: true, + model: true, + description: true, + image: true, + link: true, + createdAt: true, + updatedAt: true, + location: true, + latitude: true, + longitude: true, + status: true +}; + const BOX_SUB_PROPS_FOR_POPULATION = [ { path: 'sensors.lastMeasurement', select: { value: 1, createdAt: 1, _id: 0 } @@ -1137,7 +1155,24 @@ boxModel.BOX_VALID_MODELS = sensorLayouts.models; boxModel.BOX_VALID_ADDONS = sensorLayouts.addons; boxModel.BOX_VALID_EXPOSURES = ['unknown', 'indoor', 'outdoor', 'mobile']; +const findDeviceById = async function findDeviceById (deviceId, { populate = true, includeSecrets = false, onlyLastMeasurements = false, onlyLocations = false, projection = {} } = {}) { + const device = await db.query.deviceTable.findFirst({ + columns: DEVICE_COLUMNS_FOR_RETURNING, + where: (device, { eq }) => eq(device.id, deviceId), + with: { + sensors: true + } + }); + + if (!device) { + throw new ModelError('Device not found', { type: 'NotFoundError' }); + } + + return device; +}; + module.exports = { schema: boxSchema, - model: boxModel + model: boxModel, + findDeviceById }; diff --git a/packages/models/src/db.js b/packages/models/src/db.js index cf12a50a..b1db4649 100644 --- a/packages/models/src/db.js +++ b/packages/models/src/db.js @@ -5,10 +5,14 @@ const config = require('config').get('openSenseMap-API-models.db'), // Bring Mongoose into the app const mongoose = require('mongoose'); +const { Client } = require('pg'); +const { drizzle } = require('drizzle-orm/node-postgres'); mongoose.Promise = global.Promise; mongoose.set('debug', process.env.NODE_ENV !== 'production'); +let drizzleClient; + const getDBUri = function getDBUri (uri) { // if available, use user specified db connection uri if (uri) { @@ -16,71 +20,89 @@ const getDBUri = function getDBUri (uri) { } // get uri from config - uri = config.get('mongo_uri'); + uri = config.get('database_url'); if (uri) { return uri; } // otherwise build uri from config supplied values - const { user, userpass, host, port, db, authsource } = config; + const { user, userpass, host, port, db } = config; - return `mongodb://${user}:${userpass}@${host}:${port}/${db}?authSource=${authsource}`; + return `postgresql://${user}:${userpass}@${host}:${port}/${db}`; }; -const connect = function connect (uri) { +const connect = async function connect (uri) { uri = getDBUri(uri); - mongoose.connection.on('connecting', function () { - log.info('trying to connect to MongoDB...'); - }); + try { + // const client = new Client({ + // connectionString: uri + // }); + + // await client.connect(); + // drizzleClient = drizzle(client); + + // return drizzle(client); + + // TODO attach event listener + + } catch (error) { + log.error(`Error ${error.message}`); + + throw new Error(error); + } + + // mongoose.connection.on('connecting', function () { + // log.info('trying to connect to MongoDB...'); + // }); // Create the database connection - return new Promise(function (resolve, reject) { - mongoose - .connect(uri, { - useNewUrlParser: true, - useUnifiedTopology: true, - promiseLibrary: global.Promise - }) - .then(function () { - // CONNECTION EVENTS - - // If the connection throws an error - mongoose.connection.on('error', function (err) { - log.error(err, 'Mongoose connection error'); - throw err; - }); - - // When the connection is disconnected - mongoose.connection.on('disconnected', function () { - log.warn('Mongoose connection disconnected. Retrying with mongo AutoReconnect.'); - }); - - // When the connection is resconnected - mongoose.connection.on('reconnected', function () { - log.info('Mongoose connection reconnected.'); - }); - - log.info('Successfully connected to MongoDB.'); - - return resolve(); - }) - .catch(function (err) { - // only called if the initial mongoose.connect fails on first connect - if (err.message.startsWith('failed to connect to server')) { - log.info(`Error ${err.message} - retrying manually in 1 second.`); - mongoose.connection.removeAllListeners(); - - return new Promise(function () { - setTimeout(function () { - resolve(connect()); - }, 1000); - }); - } - - return reject(err); - }); - }); + // return new Promise(function (resolve, reject) { + // mongoose + // .connect(uri, { + // useNewUrlParser: true, + // useUnifiedTopology: true, + // promiseLibrary: global.Promise + // }) + // .then(function () { + // // CONNECTION EVENTS + + // // If the connection throws an error + // mongoose.connection.on('error', function (err) { + // log.error(err, 'Mongoose connection error'); + // throw err; + // }); + + // // When the connection is disconnected + // mongoose.connection.on('disconnected', function () { + // log.warn('Mongoose connection disconnected. Retrying with mongo AutoReconnect.'); + // }); + + // // When the connection is resconnected + // mongoose.connection.on('reconnected', function () { + // log.info('Mongoose connection reconnected.'); + // }); + + // log.info('Successfully connected to MongoDB.'); + + // return resolve(); + // }) + // .catch(function (err) { + // // only called if the initial mongoose.connect fails on first connect + // if (err.message.startsWith('failed to connect to server')) { + // log.info(`Error ${err.message} - retrying manually in 1 second.`); + // mongoose.connection.removeAllListeners(); + + // return new Promise(function () { + // setTimeout(function () { + // resolve(connect()); + // }, 1000); + // }); + // } + + // return reject(err); + // }); + // }); }; module.exports = { diff --git a/packages/models/src/device/index.js b/packages/models/src/device/index.js new file mode 100644 index 00000000..40e33efe --- /dev/null +++ b/packages/models/src/device/index.js @@ -0,0 +1,499 @@ +'use strict'; + +const crypto = require('crypto'); +const { deviceTable, sensorTable, accessTokenTable, deviceToLocationTable, locationTable } = require('../../schema/schema'); +const sensorLayouts = require('../box/sensorLayouts'); +const { db } = require('../drizzle'); +const ModelError = require('../modelError'); +const { inArray, arrayContains, sql, eq, asc, ilike } = require('drizzle-orm'); +const { insertMeasurement, insertMeasurements } = require('../measurement'); +const SketchTemplater = require('@sensebox/sketch-templater'); +const { utcNow } = require('../utils'); + +const { max_boxes: pagination_max_boxes } = require('config').get('openSenseMap-API-models.pagination'); + +const templateSketcher = new SketchTemplater(); + +const buildWhereClause = function buildWhereClause (opts = {}) { + const { name, phenomenon, fromDate, toDate, bbox, near, maxDistance, grouptag } = opts; + const clause = []; + const columns = {}; + + if (name) { + clause.push(ilike(deviceTable['name'], `%${name}%`)); + } + + if (phenomenon) { + columns['sensors'] = { + where: (sensor, { ilike }) => ilike(sensorTable['title'], `%${phenomenon}%`) + }; + } + + // simple string parameters + for (const param of ['exposure', 'model']) { + if (opts[param]) { + clause.push(inArray(deviceTable[param], opts[param])); + } + } + + if (grouptag) { + clause.push(arrayContains(deviceTable['tags'], opts['grouptag'])); + } + + // https://orm.drizzle.team/learn/guides/postgis-geometry-point + if (bbox) { + const [latSW, lngSW] = bbox.coordinates[0][0]; + const [latNE, lngNE] = bbox.coordinates[0][2]; + clause.push( + sql`st_within(${deviceTable['location']}, st_makeenvelope(${lngSW}, ${latSW}, ${lngNE}, ${latNE}, 4326))` + ); + } + + if (near) { + clause.push(sql`st_dwithin(${deviceTable['location']}, ST_SetSRID(ST_MakePoint(${near[1]}, ${near[0]}), 4326), ${maxDistance})`); + } + + if (fromDate || toDate) { + if (phenomenon) { + // TODO: implement + } + } + + return { + includeColumns: columns, + whereClause: clause + }; +}; + +const createDevice = async function createDevice (userId, params) { + const { name, exposure, description, location, model, grouptag, sensorTemplates } = params; + let { sensors, useAuth } = params; + + // if model is not empty, get sensor definitions from products + // otherwise, sensors should not be empty + if (model && sensors) { + return Promise.reject(new ModelError('Parameters model and sensors cannot be specified at the same time.', { type: 'UnprocessableEntityError' })); + } else if (model && !sensors) { + if (sensorTemplates) { + const layout = sensorLayouts.getSensorsForModel(model); + sensors = []; + for (const sensor of layout) { + if (sensorTemplates.includes(sensor['sensorType'].toLowerCase())) { + sensors.push(sensor); + } + } + } else { + sensors = sensorLayouts.getSensorsForModel(model); + } + } + if (model) { + //activate useAuth only for certain models until all sketches are updated + if (['homeV2Lora', 'homeV2Ethernet', 'homeV2EthernetFeinstaub', 'homeV2Wifi', 'homeV2WifiFeinstaub', 'homeEthernet', 'homeWifi', 'homeEthernetFeinstaub', 'homeWifiFeinstaub', 'hackair_home_v2'].indexOf(model) !== -1) { + useAuth = true; + } else { + useAuth = false; + } + } + + // Handle everything in a transaction to ensure consistency + const device = await db.transaction(async (tx) => { + const [device] = await tx + .insert(deviceTable) + .values({ + userId, + name, + exposure, + description, + useAuth, + model, + latitude: location[1], + longitude: location[0], + tags: grouptag + }) + .returning({ ...deviceTable, _id: deviceTable.id }); + + const [geometry] = await tx + .insert(locationTable) + .values({ + location: sql`ST_SetSRID(ST_MakePoint(${location[1]}, ${location[0]}), 4326)` + }) + .onConflictDoNothing() + .returning({ id: locationTable.id }); + + if (geometry) { + // Create location relation + await tx + .insert(deviceToLocationTable) + .values({ deviceId: device.id, locationId: geometry.id }); + } else { + // Get location id + const geom = await tx.query.locationTable.findFirst({ + columns: { + id: true + }, + where: sql`ST_Equals(${locationTable.location}, ST_SetSRID(ST_MakePoint(${location[1]}, ${location[0]}), 4326))` + }); + + // Create location relation + await tx + .insert(deviceToLocationTable) + .values({ deviceId: device.id, locationId: geom.id }); + } + + const [accessToken] = await tx + .insert(accessTokenTable) + .values({ + deviceId: device.id, + token: crypto.randomBytes(32).toString('hex') + }) + .returning({ token: accessTokenTable.token }); + + // Iterate over sensors and add device id + sensors = sensors.map((sensor) => ({ + deviceId: device.id, + ...sensor + })); + + const deviceSensors = await tx + .insert(sensorTable) + .values(sensors) + .returning(); + + device['accessToken'] = accessToken.token; + device['sensors'] = deviceSensors; + + return device; + }); + + return device; + +}; + +const deleteDevice = async function (filter) { + return await db.delete(deviceTable).where(filter) + .returning(); +}; + +const findById = async function findById (deviceId, relations = {}, columns = {}) { + columns = { ...DEFAULT_COLUMNS, ...columns }; + const device = await db.query.deviceTable.findFirst({ + columns: columns, + where: (device, { eq }) => eq(device.id, deviceId), + ...(Object.keys(relations).length !== 0 && { with: relations }) + }); + + return device; +}; + +const findDevicesByUserId = async function findDevicesByUserId (userId, opts = {}) { + const { page } = opts; + const devices = await db.query.deviceTable.findMany({ + where: (device, { eq }) => eq(device.userId, userId), + orderBy: (asc(deviceTable.createdAt)), + limit: pagination_max_boxes, + offset: pagination_max_boxes * page + }); + + return devices; +}; + +const findDevices = async function findDevices ( + opts = {}, + columns = {}, + relations = {} +) { + const { minimal, limit } = opts; + const { includeColumns, whereClause } = buildWhereClause(opts); + + columns = (minimal === 'true') ? MINIMAL_COLUMNS : { ...DEFAULT_COLUMNS, ...columns }; + + relations = { + ...relations, + ...includeColumns + }; + const devices = await db.query.deviceTable.findMany({ + ...(Object.keys(columns).length !== 0 && { columns }), + ...(Object.keys(relations).length !== 0 && { with: relations }), + ...(Object.keys(whereClause).length !== 0 && { + where: (_, { and }) => and(...whereClause) + }), + limit + }); + + return devices; +}; + +const findTags = async function findTags () { + const tags = await db.execute(sql`SELECT array_agg(DISTINCT u.val) tags FROM device d CROSS JOIN LATERAL unnest(d.tags) AS u(val);`); + + return tags.rows[0].tags; +}; + +const findAccessToken = async function findAccessToken (deviceId) { + const token = await db.query.accessTokenTable.findFirst({ + where: (token, { eq }) => eq(token.deviceId, deviceId) + }); + + return token; +}; + +const saveMeasurement = async function saveMeasurement (device, measurement) { + + const sensor = device.sensors.find(sensor => sensor.id === measurement.sensor_id); + + if (!sensor) { + throw new ModelError(`Sensor not found: Sensor ${measurement.sensor_id} of box ${device.id} not found`, { type: 'NotFoundError' }); + } + + await insertMeasurement(measurement); +}; + +const saveMeasurements = async function saveMeasurements (device, measurements) { + + if (!Array.isArray(measurements)) { + return Promise.reject(new Error('Array expected')); + } + + const sensorIds = device.sensors.map(sensor => sensor.id), + lastMeasurements = {}; + + // TODO: refactor + // find new lastMeasurements + // check if all the measurements belong to this box + for (let i = measurements.length - 1; i >= 0; i--) { + if (!sensorIds.includes(measurements[i].sensor_id)) { + return Promise.reject(new ModelError(`Measurement for sensor with id ${measurements[i].sensor_id} does not belong to box`)); + } + + if (!lastMeasurements[measurements[i].sensor_id]) { + lastMeasurements[measurements[i].sensor_id] = measurements[i]; + } + } + + // TODO: check if we can merge this with `saveMeasurement` + + await insertMeasurements(measurements); +}; + +const updateDevice = async function updateDevice (deviceId, args) { + const { + mqtt: { + enabled, + url, + topic, + decodeOptions: mqttDecodeOptions, + connectionOptions, + messageFormat + } = {}, + ttn: { + app_id, + dev_id, + port, + profile, + decodeOptions: ttnDecodeOptions + } = {}, + location, + sensors, + addons: { add: addonToAdd } = {} + } = args; + + if (args.mqtt) { + args['integrations.mqtt'] = { + enabled, + url, + topic, + decodeOptions: mqttDecodeOptions, + connectionOptions, + messageFormat + }; + } + if (args.ttn) { + args['integrations.ttn'] = { + app_id, + dev_id, + port, + profile, + decodeOptions: ttnDecodeOptions + }; + } + + if (args.mqtt) { + args['integrations.mqtt'] = { + enabled, + url, + topic, + decodeOptions: mqttDecodeOptions, + connectionOptions, + messageFormat + }; + } + if (args.ttn) { + args['integrations.ttn'] = { + app_id, + dev_id, + port, + profile, + decodeOptions: ttnDecodeOptions + }; + } + + const setColumns = {}; + for (const prop of [ + 'name', + 'exposure', + 'grouptag', + 'description', + 'weblink', + 'image', + // 'integrations.mqtt', + // 'integrations.ttn', + 'model', + 'useAuth' + ]) { + if (typeof args[prop] !== 'undefined') { + setColumns[prop] = args[prop]; + + if (prop === 'grouptag') { + setColumns['tags'] = args[prop]; + } + } + } + + // TODO: generate new access token + // if user wants a new access_token + // if (typeof args['generate_access_token'] !== 'undefined') { + // if (args['generate_access_token'] === 'true') { + // // Create new acces token for box + // const access_token = crypto.randomBytes(32).toString('hex'); + // box.set('access_token', access_token); + // } + // } + + // TODO update sensors + // if (sensors) { + // box.updateSensors(sensors); + // } else if (addonToAdd) { + // box.addAddon(addonToAdd); + // } + + // run location update logic, if a location was provided. + // if the provided location already exists, just update the relation + // if the location does not exist, create it and update + if (location) { + const [geometry] = await db + .insert(locationTable) + .values({ + location: sql`ST_SetSRID(ST_MakePoint(${location[1]}, ${location[0]}), 4326)` + }) + .onConflictDoNothing() + .returning({ id: locationTable.id }); + + if (geometry) { + // update location relation (update location id of device) + await db + .update(deviceToLocationTable) + .set({ locationId: geometry.id }) + .where(eq(deviceToLocationTable.deviceId, deviceId)); + } else { + // Get location id + const geom = await db.query.locationTable.findFirst({ + columns: { + id: true + }, + where: sql`ST_Equals(${locationTable.location}, ST_SetSRID(ST_MakePoint(${location[1]}, ${location[0]}), 4326))` + }); + + // update location relation (update location id of device) + await db + .update(deviceToLocationTable) + .set({ locationId: geom.id }) + .where(eq(deviceToLocationTable.deviceId, deviceId)); + } + + // also set latitude and longitude of device + setColumns['longitude'] = location[0]; + setColumns['latitude'] = location[1]; + } + + const device = await db + .update(deviceTable) + .set(setColumns) + .where(eq(deviceTable.id, deviceId)) + .returning(); + + device[0]._id = device[0].id; + + return device[0]; +}; + +const generateSketch = function generateSketch (device, { + encoding, + serialPort, + soilDigitalPort, + soundMeterPort, + windSpeedPort, + ssid, + password, + devEUI, + appEUI, + appKey, + access_token, + display_enabled +} = {}) { + if (serialPort) { + device.serialPort = serialPort; + } + if (soilDigitalPort) { + device.soilDigitalPort = soilDigitalPort; + } + if (soundMeterPort) { + device.soundMeterPort = soundMeterPort; + } + if (windSpeedPort) { + device.windSpeedPort = windSpeedPort; + } + + device.ssid = ssid || ''; + device.password = password || ''; + device.devEUI = devEUI || ''; + device.appEUI = appEUI || ''; + device.appKey = appKey || ''; + device.access_token = access_token || ''; + device.display_enabled = display_enabled || ''; + + return templateSketcher.generateSketch(device, { encoding }); +}; + +const MINIMAL_COLUMNS = { + id: true, + name: true, + exposure: true, + location: true +}; + +const DEFAULT_COLUMNS = { + id: true, + name: true, + model: true, + exposure: true, + grouptag: true, + image: true, + description: true, + link: true, + createdAt: true, + updatedAt: true, +}; + +module.exports = { + createDevice, + updateDevice, + deleteDevice, + findById, + findDevices, + findDevicesByUserId, + findTags, + findAccessToken, + saveMeasurement, + saveMeasurements, + generateSketch +}; diff --git a/packages/models/src/drizzle.js b/packages/models/src/drizzle.js new file mode 100644 index 00000000..c15f4f4a --- /dev/null +++ b/packages/models/src/drizzle.js @@ -0,0 +1,105 @@ +'use strict'; + +const config = require('config').get('openSenseMap-API-models.db'); + +const { drizzle } = require('drizzle-orm/node-postgres'); +const { Pool } = require('pg'); +const { + deviceTable, + sensorTable, + userTable, + passwordTable, + passwordResetTable, + profileTable, + profileImageTable, + deviceRelations, + sensorRelations, + userRelations, + profileRelations, + accessTokenRelations, + accessTokenTable, + refreshTokenRelations, + refreshTokenTable, + measurementTable, + locationTable, + locationRelations, + deviceToLocationTable, + deviceToLocationRelations, + logEntryTable, + logEntryRelations, + tokenBlacklistTable +} = require('../schema/schema'); +const { sql } = require('drizzle-orm'); + +const getDBUri = function getDBUri (uri) { + // if available, use user specified db connection uri + if (uri) { + return uri; + } + + // get uri from config + uri = config.get('database_url'); + if (uri) { + return uri; + } + + // otherwise build uri from config supplied values + const { user, userpass, host, port, db } = config; + + return `postgresql://${user}:${userpass}@${host}:${port}/${db}`; +}; + +const isReady = async function isReady () { + try { + await db.execute(sql`select 1`); + } catch (error) { + throw new Error(error); + } +}; + +const pool = new Pool({ + connectionString: getDBUri(), + ssl: config.get('ssl') === 'true' ? true : false +}); + +// TODO: attach event listener +// pool.on('connect', () => { +// console.log('connected to the db'); +// }); + +const schema = { + accessTokenTable, + refreshTokenTable, + deviceTable, + sensorTable, + userTable, + passwordTable, + passwordResetTable, + measurementTable, + profileTable, + profileImageTable, + deviceRelations, + sensorRelations, + userRelations, + profileRelations, + accessTokenRelations, + refreshTokenRelations, + locationTable, + locationRelations, + deviceToLocationTable, + deviceToLocationRelations, + logEntryTable, + logEntryRelations, + tokenBlacklistTable +}; + +const db = drizzle(pool, { + schema +}); + +module.exports = { + db, + isReady +}; +// module.exports.db = db; +// module.exports.isReady = isReady; diff --git a/packages/models/src/measurement/decoding/luftdatenHandler.js b/packages/models/src/measurement/decoding/luftdatenHandler.js index 4770b112..59567233 100644 --- a/packages/models/src/measurement/decoding/luftdatenHandler.js +++ b/packages/models/src/measurement/decoding/luftdatenHandler.js @@ -123,7 +123,7 @@ const findSensorId = function findSensorId (sensors, value_type) { matchings[vt_phenomenon].some((alias) => title.includes(alias))) && type.startsWith(vt_sensortype) ) { - sensorId = sensor._id.toString(); + sensorId = sensor.id; break; } } @@ -135,14 +135,14 @@ const findSensorId = function findSensorId (sensors, value_type) { const transformLuftdatenJson = function transformLuftdatenJson (json, sensors) { const outArray = []; - + for (const sdv of json.sensordatavalues) { const sensor_id = findSensorId(sensors, sdv.value_type); if (sensor_id) { outArray.push({ sensor_id, value: sdv.value }); } } - + return outArray; }; diff --git a/packages/models/src/measurement/decoding/validators.js b/packages/models/src/measurement/decoding/validators.js index 18053858..7564e664 100644 --- a/packages/models/src/measurement/decoding/validators.js +++ b/packages/models/src/measurement/decoding/validators.js @@ -1,5 +1,6 @@ 'use strict'; +const { isCuid } = require('@paralleldrive/cuid2'); const { parseAndValidateTimestamp, isNonEmptyString, isNumeric, utcNow } = require('../../utils'), { mongoose } = require('../../db'); @@ -12,7 +13,7 @@ const validateMeasurementPrimitives = function validateMeasurementPrimitives ({ throw new Error('Missing sensor id'); } - if (!mongoose.Types.ObjectId.isValid(sensor_id) || sensor_id === '00112233445566778899aabb') { + if (!isCuid(sensor_id) || sensor_id === '00112233445566778899aabb') { throw new Error('Invalid sensor id'); } @@ -69,7 +70,7 @@ const transformAndValidateMeasurements = function transformAndValidateMeasuremen } // finally attach a mongodb objectId - elem._id = mongoose.Types.ObjectId(); + // elem._id = mongoose.Types.ObjectId(); } // sort measurements/locations by date arr.sort((a, b) => a.createdAt.diff(b.createdAt)); diff --git a/packages/models/src/measurement/index.js b/packages/models/src/measurement/index.js new file mode 100644 index 00000000..448547f6 --- /dev/null +++ b/packages/models/src/measurement/index.js @@ -0,0 +1,56 @@ +'use strict'; + +const { desc } = require('drizzle-orm'); +const { measurementTable } = require('../../schema/schema'); +const { db } = require('../drizzle'); +const ModelError = require('../modelError'); +const decodeHandlers = require('./decoding'); + +const hasDecoder = function hasDecoder (contentType) { + if (!decodeHandlers[contentType]) { + return false; + } + + return true; +}; + +const decodeMeasurements = function decodeMeasurements (measurements, { contentType = 'json', sensors } = {}) { + return decodeHandlers[contentType].decodeMessage(measurements, { sensors }) + .catch(function (err) { + throw new ModelError(err.message, { type: 'UnprocessableEntityError' }); + }); +}; + +const insertMeasurement = async function insertMeasurement (measurement) { + return db.insert(measurementTable).values({ + sensorId: measurement.sensor_id, + value: measurement.value, + time: measurement.createAt + }); +}; + +const getMeasurements = async function getMeasurements (sensorId, limit = 1) { + const measurements = await db.query.measurementTable.findMany({ + where: (measurement, { eq }) => eq(measurement.sensorId, sensorId), + orderBy: desc(measurementTable.time), + limit: limit, + }); + + return measurements; +}; + +const insertMeasurements = async function insertMeasurements (measurements) { + const transformedMeasurements = measurements.map(({ sensor_id, ...rest }) => ({ + sensorId: sensor_id, // Rename sensor_id to sensorId + ...rest, // Keep the rest of the object properties unchanged + })); + return db.insert(measurementTable).values(transformedMeasurements); +}; + +module.exports = { + decodeMeasurements, + hasDecoder, + getMeasurements, + insertMeasurement, + insertMeasurements +}; diff --git a/packages/models/src/modelError.js b/packages/models/src/modelError.js index 625844de..c9a60d65 100644 --- a/packages/models/src/modelError.js +++ b/packages/models/src/modelError.js @@ -3,10 +3,11 @@ // taken from https://stackoverflow.com/a/35966534 const { inherits } = require('util'); -const ModelError = function ModelError (message, data) { +const ModelError = function ModelError (message, data = {}) { Error.captureStackTrace(this, ModelError); this.name = ModelError.name; this.data = data; + this.status = data.status || 400; this.message = message; }; diff --git a/packages/models/src/password/index.js b/packages/models/src/password/index.js new file mode 100644 index 00000000..65e49c1a --- /dev/null +++ b/packages/models/src/password/index.js @@ -0,0 +1,90 @@ +'use strict'; + +const moment = require('moment'); +const { v4: uuidv4 } = require('uuid'); +const { eq } = require('drizzle-orm'); + +const { passwordResetTable, passwordTable } = require('../../schema/schema'); +const { db } = require('../drizzle'); +const ModelError = require('../modelError'); +const { validatePassword, passwordHasher } = require('./utils'); + +const { min_length: password_min_length } = require('config').get('openSenseMap-API-models.password'); + +const findByUserId = async function findByUserId (userId) { + const password = await db.query.passwordTable.findFirst({ + where: (password, { eq }) => eq(password.userId, userId) + }); + + return password; +}; + +const initPasswordReset = async function initPasswordReset ({ email }) { + const user = await db.query.userTable.findFirst({ + where: (user, { eq }) => eq(user.email, email.toLowerCase()) + }); + + if (!user) { + throw new ModelError('Password reset for this user not possible', { + type: 'ForbiddenError' + }); + } + + // Create entry with default values + await db + .insert(passwordResetTable) + .values({ userId: user.id }) + .onConflictDoUpdate({ + target: passwordResetTable.userId, + set: { + token: uuidv4(), + expiresAt: moment.utc().add(12, 'hours') + .toDate() + } + }); +}; + +const resetOldPassword = async function resetOldPassword ({ password, token }) { + const passwordReset = await db.query.passwordResetTable.findFirst({ + where: (reset, { eq }) => eq(reset.token, token) + }); + + if (!passwordReset) { + throw new ModelError('Password reset for this user not possible', { + type: 'ForbiddenError' + }); + } + + if (moment.utc().isAfter(moment.utc(passwordReset.expiresAt))) { + throw new ModelError('Password reset token expired', { + type: 'ForbiddenError' + }); + } + + // Validate new Password + if (validatePassword(password) === false) { + throw new ModelError( + `Password must be at least ${password_min_length} characters.` + ); + } + + // Update reset password + const hashedPassword = await passwordHasher(password); + await db + .update(passwordTable) + .set({ hash: hashedPassword }) + .where(eq(passwordTable.userId, passwordReset.userId)); + + // invalidate password reset token + await db + .delete(passwordResetTable) + .where(eq(passwordResetTable.token, token)); + + // TODO: invalidate refreshToken and active accessTokens +}; + +module.exports = { + findByUserId, + initPasswordReset, + resetOldPassword +}; diff --git a/packages/models/src/password/utils.js b/packages/models/src/password/utils.js new file mode 100644 index 00000000..5fbd2762 --- /dev/null +++ b/packages/models/src/password/utils.js @@ -0,0 +1,49 @@ +"use strict"; + +const bcrypt = require("bcrypt"); +const crypto = require("crypto"); + +const ModelError = require("../modelError"); + +const { min_length: password_min_length, salt_factor: password_salt_factor } = + require("config").get("openSenseMap-API-models.password"); + +const preparePasswordHash = function preparePasswordHash(plaintextPassword) { + // first round: hash plaintextPassword with sha512 + const hash = crypto.createHash("sha512"); + hash.update(plaintextPassword.toString(), "utf8"); + const hashed = hash.digest("base64"); // base64 for more entropy than hex + + return hashed; +}; + +const checkPassword = function checkPassword( + plaintextPassword, + hashedPassword +) { + return bcrypt + .compare(preparePasswordHash(plaintextPassword), hashedPassword.hash) + .then(function (passwordIsCorrect) { + if (!passwordIsCorrect) { + throw new ModelError("Password incorrect", { status: 403}); + } + return true; + }); +}; + +const validatePassword = function validatePassword(newPassword) { + return newPassword.length >= Number(password_min_length); +}; + +const passwordHasher = function passwordHasher(plaintextPassword) { + return bcrypt.hash( + preparePasswordHash(plaintextPassword), + Number(password_salt_factor) + ); // signature generates a salt and hashes in one step +}; + +module.exports = { + checkPassword, + validatePassword, + passwordHasher, +}; diff --git a/packages/models/src/profile/profile.js b/packages/models/src/profile/profile.js new file mode 100644 index 00000000..e269281a --- /dev/null +++ b/packages/models/src/profile/profile.js @@ -0,0 +1,19 @@ +'use strict'; + +const { profileTable } = require('../../schema/schema'); +const { db } = require('../drizzle'); + +const createProfile = async function createProfile (user) { + + const { name, id } = user; + + return db.insert(profileTable).values({ + username: name, + public: false, + userId: id + }); +}; + +module.exports = { + createProfile: createProfile +}; diff --git a/packages/models/src/sensor/index.js b/packages/models/src/sensor/index.js new file mode 100644 index 00000000..e2fb3c60 --- /dev/null +++ b/packages/models/src/sensor/index.js @@ -0,0 +1,66 @@ +'use strict'; + +const { sql } = require('drizzle-orm'); +const { db } = require('../drizzle'); + +// LATERAL JOIN to get latest measurement for sensors belonging to a specific device, including device name +const getSensorsWithLastMeasurement = async function getSensorsWithLastMeasurement (deviceId, count = 1) { + const { rows } = await db.execute( + sql`SELECT s.id, s.title, s.unit, s.sensor_type, json_object(ARRAY['value', 'createdAt'], ARRAY[CAST(measure.value AS TEXT),CAST(measure.time AS TEXT)]) AS "lastMeasurement" + FROM sensor s + JOIN device d ON s.device_id = d.id + LEFT JOIN LATERAL ( + SELECT * FROM measurement m + WHERE m.sensor_id = s.id + ORDER BY m.time DESC + LIMIT ${count} + ) AS measure ON true + WHERE s.device_id = ${deviceId};`, + ); + + return rows; +}; + +const getSensorsWithLastMeasurements = + async function getSensorsWithLastMeasurements (deviceId, count = 1) { + const { rows } = await db.execute( + sql`SELECT s.id, s.title, s.unit, s.sensor_type, json_agg(json_build_object('value', measure.value, 'createdAt', measure.time)) AS "lastMeasurements" + FROM sensor s + JOIN device d ON s.device_id = d.id + LEFT JOIN LATERAL ( + SELECT * FROM measurement m + WHERE m.sensor_id = s.id + ORDER BY m.time DESC + LIMIT ${count} + ) AS measure ON true + WHERE s.device_id = ${deviceId} + GROUP BY s.id;` + ); + + return rows; + }; + +// LATERAL JOIN to get latest measurement for sensors belonging to a specific device, including device name +const getSensorWithLastMeasurement = + async function getSensorWithLastMeasurement (deviceId, sensorId, count = 1) { + const { rows } = await db.execute( + sql`SELECT s.id, s.title, s.unit, s.sensor_type, json_object(ARRAY['value', 'createdAt'], ARRAY[CAST(measure.value AS TEXT),CAST(measure.time AS TEXT)]) AS "lastMeasurement" + FROM sensor s + JOIN device d ON s.device_id = d.id + LEFT JOIN LATERAL ( + SELECT * FROM measurement m + WHERE m.sensor_id = s.id + ORDER BY m.time DESC + LIMIT ${count} + ) AS measure ON true + WHERE s.device_id = ${deviceId} AND s.id=${sensorId};` + ); + + return rows; + }; + +module.exports = { + getSensorsWithLastMeasurement, + getSensorsWithLastMeasurements, + getSensorWithLastMeasurement +}; diff --git a/packages/models/src/stats/index.js b/packages/models/src/stats/index.js new file mode 100644 index 00000000..5e50ec59 --- /dev/null +++ b/packages/models/src/stats/index.js @@ -0,0 +1,26 @@ +'use strict'; + +const { sql, and, count, gt, lt } = require('drizzle-orm'); +const { db } = require('../drizzle'); + +const rowCount = async function rowCount (table) { + const { rows } = await db.execute(sql`SELECT * FROM approximate_row_count(${table});`); + + const [count] = rows; + + return Number(count.approximate_row_count); +}; + +const rowCountTimeBucket = async function rowCountTimeBucket (table, timeColumn, interval) { + const result = await db.select({ count: count() }).from(table) + .where(and(gt(table[timeColumn], new Date(Date.now() - interval), lt(table[timeColumn], new Date())))); + + const [rowCount] = result; + + return Number(rowCount.count); +}; + +module.exports = { + rowCount, + rowCountTimeBucket +}; diff --git a/packages/models/src/token/index.js b/packages/models/src/token/index.js new file mode 100644 index 00000000..6117dd07 --- /dev/null +++ b/packages/models/src/token/index.js @@ -0,0 +1,35 @@ +'use strict'; + +const moment = require('moment'); +const { eq } = require('drizzle-orm'); +const { tokenBlacklistTable } = require('../../schema/schema'); +const { db } = require('../drizzle'); + +const insertTokenToBlacklist = async function (hash, token) { + await db.insert(tokenBlacklistTable).values({ + hash, + token, + expiresAt: moment.unix(token.exp) + }); +}; + +const insertTokenToBlacklistWithExpiresAt = async function (hash, token, expiresAt) { + await db.insert(tokenBlacklistTable).values({ + hash, + token, + expiresAt: moment.unix(expiresAt) + }); +}; + +const findToken = async function (hash) { + const blacklistedToken = await db.select().from(tokenBlacklistTable) + .where(eq(tokenBlacklistTable.hash, hash)); + + return blacklistedToken; +}; + +module.exports = { + findToken, + insertTokenToBlacklist, + insertTokenToBlacklistWithExpiresAt +}; diff --git a/packages/models/src/token/refresh.js b/packages/models/src/token/refresh.js new file mode 100644 index 00000000..2b52eacd --- /dev/null +++ b/packages/models/src/token/refresh.js @@ -0,0 +1,34 @@ +'use strict'; + +const { eq } = require('drizzle-orm'); +const { refreshTokenTable } = require('../../schema/schema'); +const { db } = require('../drizzle'); + +const addRefreshToken = async function addRefreshToken (userId, token, expiresAt) { + await db.insert(refreshTokenTable).values({ + userId, + token, + expiresAt + }); +}; + +const deleteRefreshToken = async function deleteRefreshToken (hash) { + await db.delete(refreshTokenTable).where(eq(refreshTokenTable.token, hash)); +}; + +const findRefreshTokenUser = async function findRefreshTokenUser (token) { + const token1 = await db.query.refreshTokenTable.findFirst({ + where: (refreshToken, { eq }) => eq(refreshToken.token, token), + with: { + user: true + } + }); + + return token1 ? token1.user : token1; +}; + +module.exports = { + addRefreshToken, + deleteRefreshToken, + findRefreshTokenUser +}; diff --git a/packages/models/src/user/index.js b/packages/models/src/user/index.js new file mode 100644 index 00000000..2e5d11f9 --- /dev/null +++ b/packages/models/src/user/index.js @@ -0,0 +1,302 @@ +"use strict"; + +const { v4: uuidv4 } = require("uuid"); + +const { + userTable, + passwordTable, + profileTable, +} = require("../../schema/schema"); +const { db } = require("../drizzle"); +const { eq } = require("drizzle-orm"); +const ModelError = require("../modelError"); +const { + checkPassword, + validatePassword, + passwordHasher, +} = require("../password/utils"); +const IsEmail = require("isemail"); +const { validateField } = require("../utils/validation"); +const createToken = require("../../../api/lib/helpers/jwtHelpers"); + +const userNameRequirementsText = + "Parameter name must consist of at least 3 and up to 40 alphanumerics (a-zA-Z0-9), dot (.), dash (-), underscore (_) and spaces."; +const nameValidRegex = + /^[^~`!@#$%^&*()+=£€{}[\]|\\:;"'<>,?/\n\r\t\s][^~`!@#$%^&*()+=£€{}[\]|\\:;"'<>,?/\n\r\t]{1,39}[^~`!@#$%^&*()+=£€{}[\]|\\:;"'<>,?/\n\r\t\s]$/; + +const findUserByNameOrEmail = async function findUserByNameOrEmail( + emailOrName +) { + return db.query.userTable.findFirst({ + where: (user, { eq, or }) => + or(eq(user.email, emailOrName.toLowerCase()), eq(user.name, emailOrName)), + with: { + password: true, + }, + }); +}; + +const findUserByEmailAndRole = async function findUserByEmailAndRole({ + email, + role, +}) { + const user = await db.query.userTable.findFirst({ + with: { + password: true, + }, + where: (user, { eq, and }) => + and(eq(user.email, email.toLowerCase(), eq(user.role, role))), + }); + + return user; +}; + +const createUser = async function createUser(name, email, password, language) { + validateField("name", name.length > 0, "Name is required"); + validateField( + "name", + name.length > 3 && name.length < 40, + userNameRequirementsText + ); + validateField("name", nameValidRegex.test(name), userNameRequirementsText); + validateField("email", IsEmail.validate(email), "Email is required"); + validateField( + "password", + validatePassword(password), + "Password must be at least 8 characters" + ); + + const hashedPassword = await passwordHasher(password); + + try { + const user = await db.transaction(async (tx) => { + const user = await tx + .insert(userTable) + .values({ name, email, language }) + .returning(); + + await tx.insert(passwordTable).values({ + hash: hashedPassword, + userId: user[0].id, + }); + + await tx.insert(profileTable).values({ + username: name, + public: false, + userId: user[0].id, + }); + + return user[0]; + }); + + // Delete not needed properties + delete user.emailConfirmationToken; + + return user; + } catch (error) { + // Catch and transform database errors + console.log(error); + /** + * { + "code": "BadRequest", + "message": "Duplicate user detected" + } + */ + if (error.code === "23505") { + throw new ModelError("Duplicate user detected", { type: "BadRequest" }); + } + } +}; + +const destroyUser = async function destroyUser(user) { + return await db + .delete(userTable) + .where(eq(userTable.id, user.id)) + .returning({ name: userTable.name }); +}; + +const updateUserDetails = async function updateUserDetails( + user, + { email, language, name, currentPassword, newPassword } +) { + let messages = []; // Default to empty array + + // don't allow email and password change in one request + if (email && newPassword) { + return Promise.reject( + new ModelError( + "You cannot change your email address and password in the same request.", + { status: 400 } + ) + ); + } + + // for password and email changes, require parameter currentPassword to be valid + if ((newPassword && newPassword !== "") || (email && email !== "")) { + if (!currentPassword) { + return Promise.reject( + new ModelError( + "To change your password or email address, please supply your current password.", + { status: 400 } + ) + ); + } + + try { + await checkPassword(currentPassword, user.password); + } catch (error) { + return Promise.reject( + new ModelError("Password incorrect", { status: 403 }) + ); + } + + if (newPassword && validatePassword(newPassword) === false) { + return Promise.reject( + new ModelError("New password should have at least 8 characters") + ); + } + } + + const setColumns = {}; + let signOut = false; + let hasChanges = false; + + if (name && user.name !== name) { + user.name = name; + setColumns.name = name; + hasChanges = true; + } + + if (language && user.language !== language) { + user.language = language; + setColumns.language = language; + messages.push("Language changed."); + hasChanges = true; + } + + if (email && user.email !== email) { + user.unconfirmedEmail = email; + setColumns.unconfirmedEmail = email; + messages.push( + " E-Mail changed. Please confirm your new address. Until confirmation, sign in using your old address" + ); + hasChanges = true; + } + + if (newPassword) { + user.password = newPassword; + setColumns.password = newPassword; + messages.push(" Password changed. Please sign in with your new password"); + signOut = true; + hasChanges = true; + } + + if (hasChanges) { + try { + const updatedUser = await db + .update(userTable) + .set(setColumns) + .where(eq(userTable.id, user.id)) + .returning(); + + return { + updated: true, + signOut, + messages, + updatedUser: updatedUser[0], + }; + } catch (err) { + console.error("Error updating user:", err); + throw new ModelError("Error updating user", { type: "DatabaseError" }); + } + } + + return { + updated: false, + messages: ["No changed properties supplied. User remains unchanged."], + updatedUser: user, + }; +}; + + +const confirmEmail = async function confirmEmail({ token, email }) { + const user = await findUserByNameOrEmail(email); + + if (!user || user.emailConfirmationToken !== token) { + throw new ModelError("invalid email confirmation token", { + type: "ForbiddenError", + }); + } + + const updatedUser = await db + .update(userTable) + .set({ + emailIsConfirmed: true, + emailConfirmationToken: null, + }) + .where() + .returning(); + + return updatedUser[0]; + + // return this.findOne({ + // $and: [ + // { $or: [{ email: email }, { unconfirmedEmail: email }] }, + // { emailConfirmationToken: token } + // ] + // }) + // .exec() + // .then(function (user) { + // if (!user) { + // throw new ModelError('invalid email confirmation token', { + // type: 'ForbiddenError' + // }); + // } + + // // set email to email address from request + // user.set('email', email); + + // // mark user as confirmed + // user.set('emailConfirmationToken', undefined); + // user.set('emailIsConfirmed', true); + // user.set('unconfirmedEmail', undefined); + + // return user.save(); + // }); +}; + +const resendEmailConfirmation = async function resendEmailConfirmation(user) { + if (user.emailIsConfirmed === true) { + return Promise.reject( + new ModelError(`Email address ${user.email} is already confirmed.`, { + type: "UnprocessableEntityError", + }) + ); + } + + const savedUser = await db + .update(userTable) + .set({ + emailConfirmationToken: uuidv4(), + }) + .returning(); + + return savedUser[0]; + // user.set('emailConfirmationToken', uuidv4()); + + // return user.save().then(function (savedUser) { + // savedUser.mail('resendEmailConfirmation'); + + // return savedUser; + // }); +}; + +module.exports = { + findUserByNameOrEmail, + findUserByEmailAndRole, + createUser, + destroyUser, + updateUserDetails, + confirmEmail, + resendEmailConfirmation, +}; diff --git a/packages/models/src/user/user.js b/packages/models/src/user/user.js index 4775ee68..745816ad 100644 --- a/packages/models/src/user/user.js +++ b/packages/models/src/user/user.js @@ -1,5 +1,6 @@ 'use strict'; +const { db } = require('../drizzle'); const integrations = require('./integrations'); /** @@ -25,6 +26,9 @@ const { mongoose } = require('../db'), ModelError = require('../modelError'), isemail = require('isemail'); +const { eq } = require('drizzle-orm'); +const { findById, deleteDevice } = require('../device'); + const userNameRequirementsText = 'Parameter name must consist of at least 3 and up to 40 alphanumerics (a-zA-Z0-9), dot (.), dash (-), underscore (_) and spaces.', userEmailRequirementsText = 'Parameter {PATH} is not a valid email address.'; @@ -301,7 +305,7 @@ userSchema.methods.addBox = function addBox (params) { const user = this; const serialPort = params.serialPort; - // initialize new box + // ialize new box return Box.initNew(params) .then(function (savedBox) { // request is valid @@ -716,7 +720,42 @@ integrations.addToSchema(userSchema); const userModel = mongoose.model('User', userSchema); +const checkDeviceOwner = async function checkDeviceOwner (userId, deviceId) { + + const device = await findById(deviceId, {}, { userId: true }); + + if (!device || device.userId !== userId) { + throw new ModelError('User does not own this senseBox', { type: 'ForbiddenError' }); + } + + return true; +}; + +const removeDevice = async function removeDevice (deviceId) { + + const device = await findById(deviceId); + + if (!device) { + return Promise.reject(new ModelError('coudn\'t remove, device not found', { type: 'NotFoundError' })); + } + + // TODO: remove all measurements + // // remove box and measurements + // box.removeSelfAndMeasurements() + // .catch(function (err) { + // throw err; + // }); + + const [deletedDevice] = await deleteDevice(eq(device.id, deviceId)); + + return deletedDevice; +}; + + + module.exports = { schema: userSchema, - model: userModel + model: userModel, + removeDevice, + checkDeviceOwner }; diff --git a/packages/models/src/utils/validation.js b/packages/models/src/utils/validation.js new file mode 100644 index 00000000..6b001915 --- /dev/null +++ b/packages/models/src/utils/validation.js @@ -0,0 +1,20 @@ +'use strict'; + +const invariant = require('tiny-invariant'); + +const validateField = function validateField (field, expr, msg) { + try { + invariant(expr, msg); + } catch (error) { + const err = new Error(msg); + err.name = 'ValidationError'; + err.errors = { + [field]: { message: msg } + }; + throw err; + } +}; + +module.exports = { + validateField +}; diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 13b020fe..5c876ec1 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -1,4 +1,3 @@ -version: "2.1" volumes: userimages: services: @@ -20,7 +19,7 @@ services: "slack_url": "http://127.0.0.1/bla/bli/blu", "openSenseMap-API-models": { "db": { - "mongo_uri": "mongodb://db/api-test" + "database_url": "postgresql://postgres:postgres@db:5432/opensensemap" }, "password": { "salt_factor": 1 @@ -49,30 +48,35 @@ services: }, "sketch-templater": { "ingress_domain": "test.ingress.domain" + }, + "opensensemap-migrations": { + "db": { + "database_url": "postgresql://postgres:postgres@db:5432/opensensemap" + } } } depends_on: - db - redis-stack - mailer: - image: ghcr.io/opensensemap/mailer:main - platform: linux/amd64 - environment: - - SMTP_HOST=mailhog - - SMTP_PORT=1025 - - SMTP_SECURE=false - - SMTP_USERNAME=ignored - - SMTP_PASSWORD=ignored - - REDIS_HOST=redis-stack - - REDIS_PORT=6379 - - REDIS_USERNAME=queue - - REDIS_PASSWORD=somepassword - - REDIS_DB=0 - - BULLMQ_QUEUE_NAME=mails - depends_on: - - mailhog - - redis-stack + # mailer: + # image: ghcr.io/opensensemap/mailer:main + # platform: linux/amd64 + # environment: + # - SMTP_HOST=mailhog + # - SMTP_PORT=1025 + # - SMTP_SECURE=false + # - SMTP_USERNAME=ignored + # - SMTP_PASSWORD=ignored + # - REDIS_HOST=redis-stack + # - REDIS_PORT=6379 + # - REDIS_USERNAME=queue + # - REDIS_PASSWORD=somepassword + # - REDIS_DB=0 + # - BULLMQ_QUEUE_NAME=mails + # depends_on: + # - mailhog + # - redis-stack mailhog: image: mailhog/mailhog:v1.0.1 @@ -80,27 +84,27 @@ services: ports: - "8025:8025" - mqtt-osem-integration: - image: ghcr.io/sensebox/mqtt-osem-integration:v0.1.0 - platform: linux/amd64 - depends_on: - - db - environment: - NODE_CONFIG: |- - { - "grpc": { - "certificates": { - "ca_cert": "-----BEGIN CERTIFICATE-----\nMIIE8jCCAtqgAwIBAgIBATANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDEw5vcGVu\nU2Vuc2VNYXBDQTAeFw0xODAxMjQxMzQ1NDlaFw0yODAxMjQxMzQ1NDlaMBkxFzAV\nBgNVBAMTDm9wZW5TZW5zZU1hcENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\nCgKCAgEAtRv1MmOOMnL1vqaFF1uf6XzHvUUkFoQsktRMQgr4Kq4TpBJdt2yrEWAf\ngBup20//Hb3pu1tGHnINRlrfnRbu/0Twq0iXP+zjzrn1TIppbQb8Hr5gci25vY8e\nfc3ZRhYDrPyf+Z+F3U5Skr2itvBEshSiy3L53YG+JJ6ohPeVW3ePoBOIZvFYEGNV\nDsEbt3DVAjdFOfB4SypZG9UyX8xNUw7aAbjLib9CkdT2hEDiZ6iiKDklxZnNt1fm\nYQ/FGJRMd3ZTMddrGcexPSY1dQOJHrnlf+fVkahfqLD4RmVaL0O4cc/e/YIJQEZi\nblD+cUduKl0itkeXj1PEbYyngGixkJ+9+WuZvOwTqNrCgWU1uXrRH3UKn2XGYQau\nauq4AqnEgvMSev7nxdXEznzey1ugEeNSHXyyvj70KrUEHJ1kL4aL53YD0Xd8Y0hL\n88MHl3GkjkojZ68U0a/0TUSfTuv+JdP19HfZY5qVKst1309tlgaOiQXafAsiVBRr\ncieFHXxvoruQy/6pTwGRtfWbyujib+xXaTX36y08IXK0Jb45WJDoVgPswsHYqF6E\n/AH0NSRZrXckfMQviUea27/lOD1M96cwslDjvOngJdUu1zBGLdfQ8SWcs1HqdG7i\nq+Iuh/UGq4aJgRTENEBrc4kjQQyZszXBJmRxAWKIgN0h+jEzGIsCAwEAAaNFMEMw\nDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFJl6\n7zgZ/9GRVgb1dBglkSyXfGNRMA0GCSqGSIb3DQEBCwUAA4ICAQBYJIxwwt+/C1+6\nyjIDzQ6wkfdDWzBj9kfV90plp4zX7rhT+M2t/en0/lN2BgK3J1fw+PWbku6A19hA\nniZ/mBZrKki0UzzqGO1/ovh3izC2zeRL1lqwxdxj29+2waPk2vH/sy97AuY3q/5G\nE+H96RReOJZxysIAg2UxXpvgdupnTGMrh+fuU7iGm9NYLSLuD2SaZY/ZThSPFXOh\nb59W6gMmNKo6rN09jj994rJQfsxH2JOkqiTHfwXp3Ch4Zg80XC9RvvRk6S4dtnLe\npQy+Bg3YjQ+sSUIaNrvWoP8ut+9aHdfSlQi/7aYD7tYEhdTK7G++4RWZ0C31G9an\n6Yjcg1lJDWkP+ii91danhisdCwP2xteRXzpFM/uJ/dY+xBaX/Kp12bhF8PzbgJHa\nrsDApLlywVSrZtglSJ5eBhDbuLPEGqATWCExvlPO2R4MF1CID/+aybj5o9Zfmok4\nU8Z1QwLZElpTh1OhkYCyIzRJ2eG1Hx6svLh6nx4ai3iumbjzWh+E4xrncdTBA6hk\nRKF+yZKDfpFF8iwemKExthQwCSjqEGi6bYf02Gw6A226FSqdD2Tw0TghnXtT9fQ6\nyBx1uNSDDXdCFWmPzvZIMLe335mP2RKQrcGIbAUi0WgTuqFNyCshnoWmQigP6lqK\nC5m4Hth2wvbwzeqDD2kvRQfpipy9Cw==\n-----END CERTIFICATE-----\n", - "server_cert": "-----BEGIN CERTIFICATE-----\nMIIFajCCA1KgAwIBAgIQBVxnXzVjuOz8ThLtyglyfDANBgkqhkiG9w0BAQsFADAZ\nMRcwFQYDVQQDEw5vcGVuU2Vuc2VNYXBDQTAeFw0xODAxMjQxMzQ2MTFaFw0yODAx\nMjQxMzQ1NDhaMCcxJTAjBgNVBAMMHG1xdHQtb3NlbS1pbnRlZ3JhdGlvbl9zZXJ2\nZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1G/UyY44ycvW+poUX\nW5/pfMe9RSQWhCyS1ExCCvgqrhOkEl23bKsRYB+AG6nbT/8dvem7W0Yecg1GWt+d\nFu7/RPCrSJc/7OPOufVMimltBvwevmByLbm9jx59zdlGFgOs/J/5n4XdTlKSvaK2\n8ESyFKLLcvndgb4knqiE95Vbd4+gE4hm8VgQY1UOwRu3cNUCN0U58HhLKlkb1TJf\nzE1TDtoBuMuJv0KR1PaEQOJnqKIoOSXFmc23V+ZhD8UYlEx3dlMx12sZx7E9JjV1\nA4keueV/59WRqF+osPhGZVovQ7hxz979gglARmJuUP5xR24qXSK2R5ePU8RtjKeA\naLGQn735a5m87BOo2sKBZTW5etEfdQqfZcZhBq5q6rgCqcSC8xJ6/ufF1cTOfN7L\nW6AR41IdfLK+PvQqtQQcnWQvhovndgPRd3xjSEvzwweXcaSOSiNnrxTRr/RNRJ9O\n6/4l0/X0d9ljmpUqy3XfT22WBo6JBdp8CyJUFGtyJ4UdfG+iu5DL/qlPAZG19ZvK\n6OJv7FdpNffrLTwhcrQlvjlYkOhWA+zCwdioXoT8AfQ1JFmtdyR8xC+JR5rbv+U4\nPUz3pzCyUOO86eAl1S7XMEYt19DxJZyzUep0buKr4i6H9QarhomBFMQ0QGtziSNB\nDJmzNcEmZHEBYoiA3SH6MTMYiwIDAQABo4GfMIGcMA4GA1UdDwEB/wQEAwIDuDAd\nBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFJl67zgZ/9GR\nVgb1dBglkSyXfGNRMB8GA1UdIwQYMBaAFJl67zgZ/9GRVgb1dBglkSyXfGNRMCsG\nA1UdEQQkMCKCFW1xdHQtb3NlbS1pbnRlZ3JhdGlvboIJbG9jYWxob3N0MA0GCSqG\nSIb3DQEBCwUAA4ICAQA0AUViYK7DpfljVsmQG6S5S94pXHtUvG3qO8zHZu/NIkVA\nqZS+1DNN0ER9n0WNQuoFwUSgMEPFG++lrCMYFLFfGT1kHEILx8RD5Zk1n3LbInVc\n/fZOpfYqDQo6dw+J9nq1tKAVYajKq267EfmrOmoGnyR7mg+3kA21Nxm6vkzFEEBM\nQKjN35wbpjC5VV71wB7ERy/9WCwkzxOe+Da5xAN0fEW9aNSp1+VBMKUOugMlRAbF\n5SSO1lHFClsPGhUGrTk4Ng/gja77llPr3yMGP5TDyJKUvLU/6LAzLQGWyz8IPl4f\nnHRi0+wHnI7TN8jmBkoDd5Mf9TXIqAEAo3HuiQs0tGaVCPWksj8u8gzL5YcuOaN/\nypQVHqh69m8XRMHyffz3pqKK8s/3zg1jsZQcZlDB8BtBaRmagX5ZxT+hw4Flgi/w\nYNr5greSdRM9mSFzPqy+God23OGvZCcqS/KI1r8fsThf18xavD5ThI2Pb6NXxQ1l\nu1Lz38J0WPH9XUnMQsDw4D+Kycjt+FD67U6VOOQsStm578spNQoDA1dtfMZfUxAx\nFn22CSqwyEpkSpA2b1c0XYMyO0iUwuFPVM2EIpD9cYeqTuo8my4o6ad0nqMRV+oo\nys16TAfoKOlivdSKrHZOVRLdJS/GWn8GAtxhHdv+aNo5EoiFFKtYMsbpKyzQtQ==\n-----END CERTIFICATE-----\n", - "server_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEAtRv1MmOOMnL1vqaFF1uf6XzHvUUkFoQsktRMQgr4Kq4TpBJd\nt2yrEWAfgBup20//Hb3pu1tGHnINRlrfnRbu/0Twq0iXP+zjzrn1TIppbQb8Hr5g\nci25vY8efc3ZRhYDrPyf+Z+F3U5Skr2itvBEshSiy3L53YG+JJ6ohPeVW3ePoBOI\nZvFYEGNVDsEbt3DVAjdFOfB4SypZG9UyX8xNUw7aAbjLib9CkdT2hEDiZ6iiKDkl\nxZnNt1fmYQ/FGJRMd3ZTMddrGcexPSY1dQOJHrnlf+fVkahfqLD4RmVaL0O4cc/e\n/YIJQEZiblD+cUduKl0itkeXj1PEbYyngGixkJ+9+WuZvOwTqNrCgWU1uXrRH3UK\nn2XGYQauauq4AqnEgvMSev7nxdXEznzey1ugEeNSHXyyvj70KrUEHJ1kL4aL53YD\n0Xd8Y0hL88MHl3GkjkojZ68U0a/0TUSfTuv+JdP19HfZY5qVKst1309tlgaOiQXa\nfAsiVBRrcieFHXxvoruQy/6pTwGRtfWbyujib+xXaTX36y08IXK0Jb45WJDoVgPs\nwsHYqF6E/AH0NSRZrXckfMQviUea27/lOD1M96cwslDjvOngJdUu1zBGLdfQ8SWc\ns1HqdG7iq+Iuh/UGq4aJgRTENEBrc4kjQQyZszXBJmRxAWKIgN0h+jEzGIsCAwEA\nAQKCAgBsqjuyYh19k5BzNcKBQ05tb5sAqy19/QwphQvETISeRxgtx39HgQIbSMtd\nuDtwBU2S8NH+wkMOHWxtnDSzMoFv1FN60fE+P8pnzRerNxkOe7RmVd/UYi8h1296\nGDqXXLoT3ve1dMuC/2138iRhE0SEfPE4lOHqz9/gZPnD3jFVUiVw7IdZDNHD83Wj\nhqY0qJSF4de9bdUfdGdG1eKFrDVw8mZHxjMJkSJGEbtfmva9L2csLy3EpAXUTf9C\nmY2us7w1qV89dn0iWLi1cel9LgPl1bAn0FhKLvZGZvhwdHtqBH30e77V6GHYmOKS\nQjKIkU0+Sed76vS64I3pFQ2jdC2lEBmvYrdX4n8Ab/yWBi/KyouJW583KlevLbPk\n0GsDVQiolLLl4z5gcpGCoLH8jm/soGfXWyTBbd5Ei+01Zf/yYHTtYPh2WYLFBZ4z\nnnhbTcquOoqF44wXPRqOBKVX1amiMG4llmIne89DGlZQaP9A+4Q+o/5aTkInupwT\nBI2JRLe/YZb/0S/wOGKGFygvM1p1I/ToCg2FOX0ZWzkRZ9Sr9RAKMX3VHImYRSID\nszU/PSPxSAjy22u1vgO6wAAaOaO1OS4JwpKbhqVyjuvzvIwHMVfTgqyhIRsk39Ev\nuTqHTse8v6S+fJY3RDLR1ereWJgPpzKnDPxg2F5ROXVGaDZb0QKCAQEAzJ5c3mDz\nKsrsf0HpmK+w95gb4pPd529BLNVv/tKBNqUkg1jsYriL7nGLVNSKFDmIyokOb/sI\nqzDQRNg/4LanH8lhrmtmCeH05Tkz3Aj1IwF8I8/fr/57Aj0DgxFSJ3aDgys2q4ld\ngIR3fe4hIDF0rXb8bAYqOt7rfl3qvbRSsLjQCi358fHRQeMGDjyC9FZ6POwdQFAP\n531uXNYnnePnuXfuSgzVBp83pW3V8rW4YkpFoPhT38msVny63EAhRcoot/2X96Cd\njRQNq/5v8G0luBv2DEDSP/CDfTZKhkMaauf8KIACLeYYNTz2gFvDfa5TroqAe5De\nHZHfqh94wyBV9wKCAQEA4pZTGWk1S0WQz01AlMvRXiM7H7jEAmddRgrpQ2w7g+Ss\nkJesYzREZ7j/kIpK3ACuQXjW/Tjlnpkp206ocCIoIwExOVG4vsewDEGaYqOvllro\n+GW7O4DhwgeUWAao/Ge1T/gu+8z/VM9N88udbc0xq8P0jn6xial9rLkhll5rZDS7\niIz1mFAzrADU5c7TvElXs86KX+T6+/sE2nNwsImaZHC1oclIHl9WnJy/qCPYFS04\nY6q9vg+vOlzRc6KgmOp/5Jex2oQsXKx45v0O8+mfLlyT6CBw4rWb0ksHC3aM8zCr\ncGmgbzqVYYIewnZ//yRm98Mty8p/m3p3iNNzHExdDQKCAQBfrel1HtZ1+x9tPi/x\n8q2IiTr4zvXjg3VxdniBKoO7Pqt9M7aNTwg3viZNy3ipjmG1ezMiD7t0+UVZ+9ia\nxi4NwggIHDZBhsQR75adXB7seIRI5qoNTKzOViNvRUkqJNPIIQvWWEw9jTOm0hPx\nTs7lUg8koBldH+H0XAwpGsnT0weMywTmKpIUAglR3N/LSyirlijzaryVHWTeylEK\nFojDhB4LyEZQa2EE3QA/FtQaOeqnI5dsvIv2gSqLVP15+dbiehV2eEdTsb3W4AoN\n3avWlFSQVDs8JMYHZbyhXX1b4hBaC8l5Fu/Y7SHC0aXu/fYpVqBPp2UFZLG2hjLc\n4yDvAoIBACzF84mz5loHVwP/ieFdHPPzFj3AbsrizeWHRmySOHhpeUfhEKlRrKqq\nPaW8DerHH6fETwcedREPxtuVAWeW+ENieu2OnmjkYH8rf2w6V/nn4N0kjQjHANUs\nVj3GoyGtBIDW08Hh0hpaFFc2RtdpkoUUZYC6vC4tla3Jrz9dTO8yFFR5NhZw0qUM\nTQVUBzbPb0sSZvln78hW47Ce2wenSSDLvLhJY7zMrfqoZp685nfYxam8FV43DzMD\nIEgvPHi67aan6vb44yM02XcbThcYdOHeXUOjFWtW44F8XdoABP4RAe9mj9MqylXI\nNnfKnqQ19zrCEIySaQC6BGC/F6Hh3QkCggEBAMQxjQVI0eUBv8C+/e9HsbvnvtPz\nBSpGdKKDvp+vfw8dP0rxCFHeDeBglTOGOB4uTUGGkU2l6HDA/pFdSOmsiXLpc/Ai\nQX4n2sj5R5W4mCiYVuHWo/BCkEF7utcwHu3raBhnYeuUooISfe2S8A/66PF9lnO9\n+FVODW6Q+qdHNH349AXwtgOS/neg16I5hw0PgMkcNuJl+bgXlyy+0O8N2/37ofb1\nbvj+34qtubGRdgtSP/EF8NxYWIz3VVqVyHtPOKoAR2Aa1ilg8ixbS1KfiqZejY6u\nTZMPvBabA8anDKtUFq7OFkzPYxMYNkL4rZGltiZMPuRRJ5RKw+yNxaLVz3M=\n-----END RSA PRIVATE KEY-----\n" - } - }, - "openSenseMap-API-models": { - "db": { - "mongo_uri": "mongodb://db/api-test" - } - } - } + # mqtt-osem-integration: + # image: ghcr.io/sensebox/mqtt-osem-integration:v0.1.0 + # platform: linux/amd64 + # depends_on: + # - db + # environment: + # NODE_CONFIG: |- + # { + # "grpc": { + # "certificates": { + # "ca_cert": "-----BEGIN CERTIFICATE-----\nMIIE8jCCAtqgAwIBAgIBATANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDEw5vcGVu\nU2Vuc2VNYXBDQTAeFw0xODAxMjQxMzQ1NDlaFw0yODAxMjQxMzQ1NDlaMBkxFzAV\nBgNVBAMTDm9wZW5TZW5zZU1hcENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\nCgKCAgEAtRv1MmOOMnL1vqaFF1uf6XzHvUUkFoQsktRMQgr4Kq4TpBJdt2yrEWAf\ngBup20//Hb3pu1tGHnINRlrfnRbu/0Twq0iXP+zjzrn1TIppbQb8Hr5gci25vY8e\nfc3ZRhYDrPyf+Z+F3U5Skr2itvBEshSiy3L53YG+JJ6ohPeVW3ePoBOIZvFYEGNV\nDsEbt3DVAjdFOfB4SypZG9UyX8xNUw7aAbjLib9CkdT2hEDiZ6iiKDklxZnNt1fm\nYQ/FGJRMd3ZTMddrGcexPSY1dQOJHrnlf+fVkahfqLD4RmVaL0O4cc/e/YIJQEZi\nblD+cUduKl0itkeXj1PEbYyngGixkJ+9+WuZvOwTqNrCgWU1uXrRH3UKn2XGYQau\nauq4AqnEgvMSev7nxdXEznzey1ugEeNSHXyyvj70KrUEHJ1kL4aL53YD0Xd8Y0hL\n88MHl3GkjkojZ68U0a/0TUSfTuv+JdP19HfZY5qVKst1309tlgaOiQXafAsiVBRr\ncieFHXxvoruQy/6pTwGRtfWbyujib+xXaTX36y08IXK0Jb45WJDoVgPswsHYqF6E\n/AH0NSRZrXckfMQviUea27/lOD1M96cwslDjvOngJdUu1zBGLdfQ8SWcs1HqdG7i\nq+Iuh/UGq4aJgRTENEBrc4kjQQyZszXBJmRxAWKIgN0h+jEzGIsCAwEAAaNFMEMw\nDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFJl6\n7zgZ/9GRVgb1dBglkSyXfGNRMA0GCSqGSIb3DQEBCwUAA4ICAQBYJIxwwt+/C1+6\nyjIDzQ6wkfdDWzBj9kfV90plp4zX7rhT+M2t/en0/lN2BgK3J1fw+PWbku6A19hA\nniZ/mBZrKki0UzzqGO1/ovh3izC2zeRL1lqwxdxj29+2waPk2vH/sy97AuY3q/5G\nE+H96RReOJZxysIAg2UxXpvgdupnTGMrh+fuU7iGm9NYLSLuD2SaZY/ZThSPFXOh\nb59W6gMmNKo6rN09jj994rJQfsxH2JOkqiTHfwXp3Ch4Zg80XC9RvvRk6S4dtnLe\npQy+Bg3YjQ+sSUIaNrvWoP8ut+9aHdfSlQi/7aYD7tYEhdTK7G++4RWZ0C31G9an\n6Yjcg1lJDWkP+ii91danhisdCwP2xteRXzpFM/uJ/dY+xBaX/Kp12bhF8PzbgJHa\nrsDApLlywVSrZtglSJ5eBhDbuLPEGqATWCExvlPO2R4MF1CID/+aybj5o9Zfmok4\nU8Z1QwLZElpTh1OhkYCyIzRJ2eG1Hx6svLh6nx4ai3iumbjzWh+E4xrncdTBA6hk\nRKF+yZKDfpFF8iwemKExthQwCSjqEGi6bYf02Gw6A226FSqdD2Tw0TghnXtT9fQ6\nyBx1uNSDDXdCFWmPzvZIMLe335mP2RKQrcGIbAUi0WgTuqFNyCshnoWmQigP6lqK\nC5m4Hth2wvbwzeqDD2kvRQfpipy9Cw==\n-----END CERTIFICATE-----\n", + # "server_cert": "-----BEGIN CERTIFICATE-----\nMIIFajCCA1KgAwIBAgIQBVxnXzVjuOz8ThLtyglyfDANBgkqhkiG9w0BAQsFADAZ\nMRcwFQYDVQQDEw5vcGVuU2Vuc2VNYXBDQTAeFw0xODAxMjQxMzQ2MTFaFw0yODAx\nMjQxMzQ1NDhaMCcxJTAjBgNVBAMMHG1xdHQtb3NlbS1pbnRlZ3JhdGlvbl9zZXJ2\nZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1G/UyY44ycvW+poUX\nW5/pfMe9RSQWhCyS1ExCCvgqrhOkEl23bKsRYB+AG6nbT/8dvem7W0Yecg1GWt+d\nFu7/RPCrSJc/7OPOufVMimltBvwevmByLbm9jx59zdlGFgOs/J/5n4XdTlKSvaK2\n8ESyFKLLcvndgb4knqiE95Vbd4+gE4hm8VgQY1UOwRu3cNUCN0U58HhLKlkb1TJf\nzE1TDtoBuMuJv0KR1PaEQOJnqKIoOSXFmc23V+ZhD8UYlEx3dlMx12sZx7E9JjV1\nA4keueV/59WRqF+osPhGZVovQ7hxz979gglARmJuUP5xR24qXSK2R5ePU8RtjKeA\naLGQn735a5m87BOo2sKBZTW5etEfdQqfZcZhBq5q6rgCqcSC8xJ6/ufF1cTOfN7L\nW6AR41IdfLK+PvQqtQQcnWQvhovndgPRd3xjSEvzwweXcaSOSiNnrxTRr/RNRJ9O\n6/4l0/X0d9ljmpUqy3XfT22WBo6JBdp8CyJUFGtyJ4UdfG+iu5DL/qlPAZG19ZvK\n6OJv7FdpNffrLTwhcrQlvjlYkOhWA+zCwdioXoT8AfQ1JFmtdyR8xC+JR5rbv+U4\nPUz3pzCyUOO86eAl1S7XMEYt19DxJZyzUep0buKr4i6H9QarhomBFMQ0QGtziSNB\nDJmzNcEmZHEBYoiA3SH6MTMYiwIDAQABo4GfMIGcMA4GA1UdDwEB/wQEAwIDuDAd\nBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFJl67zgZ/9GR\nVgb1dBglkSyXfGNRMB8GA1UdIwQYMBaAFJl67zgZ/9GRVgb1dBglkSyXfGNRMCsG\nA1UdEQQkMCKCFW1xdHQtb3NlbS1pbnRlZ3JhdGlvboIJbG9jYWxob3N0MA0GCSqG\nSIb3DQEBCwUAA4ICAQA0AUViYK7DpfljVsmQG6S5S94pXHtUvG3qO8zHZu/NIkVA\nqZS+1DNN0ER9n0WNQuoFwUSgMEPFG++lrCMYFLFfGT1kHEILx8RD5Zk1n3LbInVc\n/fZOpfYqDQo6dw+J9nq1tKAVYajKq267EfmrOmoGnyR7mg+3kA21Nxm6vkzFEEBM\nQKjN35wbpjC5VV71wB7ERy/9WCwkzxOe+Da5xAN0fEW9aNSp1+VBMKUOugMlRAbF\n5SSO1lHFClsPGhUGrTk4Ng/gja77llPr3yMGP5TDyJKUvLU/6LAzLQGWyz8IPl4f\nnHRi0+wHnI7TN8jmBkoDd5Mf9TXIqAEAo3HuiQs0tGaVCPWksj8u8gzL5YcuOaN/\nypQVHqh69m8XRMHyffz3pqKK8s/3zg1jsZQcZlDB8BtBaRmagX5ZxT+hw4Flgi/w\nYNr5greSdRM9mSFzPqy+God23OGvZCcqS/KI1r8fsThf18xavD5ThI2Pb6NXxQ1l\nu1Lz38J0WPH9XUnMQsDw4D+Kycjt+FD67U6VOOQsStm578spNQoDA1dtfMZfUxAx\nFn22CSqwyEpkSpA2b1c0XYMyO0iUwuFPVM2EIpD9cYeqTuo8my4o6ad0nqMRV+oo\nys16TAfoKOlivdSKrHZOVRLdJS/GWn8GAtxhHdv+aNo5EoiFFKtYMsbpKyzQtQ==\n-----END CERTIFICATE-----\n", + # "server_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEAtRv1MmOOMnL1vqaFF1uf6XzHvUUkFoQsktRMQgr4Kq4TpBJd\nt2yrEWAfgBup20//Hb3pu1tGHnINRlrfnRbu/0Twq0iXP+zjzrn1TIppbQb8Hr5g\nci25vY8efc3ZRhYDrPyf+Z+F3U5Skr2itvBEshSiy3L53YG+JJ6ohPeVW3ePoBOI\nZvFYEGNVDsEbt3DVAjdFOfB4SypZG9UyX8xNUw7aAbjLib9CkdT2hEDiZ6iiKDkl\nxZnNt1fmYQ/FGJRMd3ZTMddrGcexPSY1dQOJHrnlf+fVkahfqLD4RmVaL0O4cc/e\n/YIJQEZiblD+cUduKl0itkeXj1PEbYyngGixkJ+9+WuZvOwTqNrCgWU1uXrRH3UK\nn2XGYQauauq4AqnEgvMSev7nxdXEznzey1ugEeNSHXyyvj70KrUEHJ1kL4aL53YD\n0Xd8Y0hL88MHl3GkjkojZ68U0a/0TUSfTuv+JdP19HfZY5qVKst1309tlgaOiQXa\nfAsiVBRrcieFHXxvoruQy/6pTwGRtfWbyujib+xXaTX36y08IXK0Jb45WJDoVgPs\nwsHYqF6E/AH0NSRZrXckfMQviUea27/lOD1M96cwslDjvOngJdUu1zBGLdfQ8SWc\ns1HqdG7iq+Iuh/UGq4aJgRTENEBrc4kjQQyZszXBJmRxAWKIgN0h+jEzGIsCAwEA\nAQKCAgBsqjuyYh19k5BzNcKBQ05tb5sAqy19/QwphQvETISeRxgtx39HgQIbSMtd\nuDtwBU2S8NH+wkMOHWxtnDSzMoFv1FN60fE+P8pnzRerNxkOe7RmVd/UYi8h1296\nGDqXXLoT3ve1dMuC/2138iRhE0SEfPE4lOHqz9/gZPnD3jFVUiVw7IdZDNHD83Wj\nhqY0qJSF4de9bdUfdGdG1eKFrDVw8mZHxjMJkSJGEbtfmva9L2csLy3EpAXUTf9C\nmY2us7w1qV89dn0iWLi1cel9LgPl1bAn0FhKLvZGZvhwdHtqBH30e77V6GHYmOKS\nQjKIkU0+Sed76vS64I3pFQ2jdC2lEBmvYrdX4n8Ab/yWBi/KyouJW583KlevLbPk\n0GsDVQiolLLl4z5gcpGCoLH8jm/soGfXWyTBbd5Ei+01Zf/yYHTtYPh2WYLFBZ4z\nnnhbTcquOoqF44wXPRqOBKVX1amiMG4llmIne89DGlZQaP9A+4Q+o/5aTkInupwT\nBI2JRLe/YZb/0S/wOGKGFygvM1p1I/ToCg2FOX0ZWzkRZ9Sr9RAKMX3VHImYRSID\nszU/PSPxSAjy22u1vgO6wAAaOaO1OS4JwpKbhqVyjuvzvIwHMVfTgqyhIRsk39Ev\nuTqHTse8v6S+fJY3RDLR1ereWJgPpzKnDPxg2F5ROXVGaDZb0QKCAQEAzJ5c3mDz\nKsrsf0HpmK+w95gb4pPd529BLNVv/tKBNqUkg1jsYriL7nGLVNSKFDmIyokOb/sI\nqzDQRNg/4LanH8lhrmtmCeH05Tkz3Aj1IwF8I8/fr/57Aj0DgxFSJ3aDgys2q4ld\ngIR3fe4hIDF0rXb8bAYqOt7rfl3qvbRSsLjQCi358fHRQeMGDjyC9FZ6POwdQFAP\n531uXNYnnePnuXfuSgzVBp83pW3V8rW4YkpFoPhT38msVny63EAhRcoot/2X96Cd\njRQNq/5v8G0luBv2DEDSP/CDfTZKhkMaauf8KIACLeYYNTz2gFvDfa5TroqAe5De\nHZHfqh94wyBV9wKCAQEA4pZTGWk1S0WQz01AlMvRXiM7H7jEAmddRgrpQ2w7g+Ss\nkJesYzREZ7j/kIpK3ACuQXjW/Tjlnpkp206ocCIoIwExOVG4vsewDEGaYqOvllro\n+GW7O4DhwgeUWAao/Ge1T/gu+8z/VM9N88udbc0xq8P0jn6xial9rLkhll5rZDS7\niIz1mFAzrADU5c7TvElXs86KX+T6+/sE2nNwsImaZHC1oclIHl9WnJy/qCPYFS04\nY6q9vg+vOlzRc6KgmOp/5Jex2oQsXKx45v0O8+mfLlyT6CBw4rWb0ksHC3aM8zCr\ncGmgbzqVYYIewnZ//yRm98Mty8p/m3p3iNNzHExdDQKCAQBfrel1HtZ1+x9tPi/x\n8q2IiTr4zvXjg3VxdniBKoO7Pqt9M7aNTwg3viZNy3ipjmG1ezMiD7t0+UVZ+9ia\nxi4NwggIHDZBhsQR75adXB7seIRI5qoNTKzOViNvRUkqJNPIIQvWWEw9jTOm0hPx\nTs7lUg8koBldH+H0XAwpGsnT0weMywTmKpIUAglR3N/LSyirlijzaryVHWTeylEK\nFojDhB4LyEZQa2EE3QA/FtQaOeqnI5dsvIv2gSqLVP15+dbiehV2eEdTsb3W4AoN\n3avWlFSQVDs8JMYHZbyhXX1b4hBaC8l5Fu/Y7SHC0aXu/fYpVqBPp2UFZLG2hjLc\n4yDvAoIBACzF84mz5loHVwP/ieFdHPPzFj3AbsrizeWHRmySOHhpeUfhEKlRrKqq\nPaW8DerHH6fETwcedREPxtuVAWeW+ENieu2OnmjkYH8rf2w6V/nn4N0kjQjHANUs\nVj3GoyGtBIDW08Hh0hpaFFc2RtdpkoUUZYC6vC4tla3Jrz9dTO8yFFR5NhZw0qUM\nTQVUBzbPb0sSZvln78hW47Ce2wenSSDLvLhJY7zMrfqoZp685nfYxam8FV43DzMD\nIEgvPHi67aan6vb44yM02XcbThcYdOHeXUOjFWtW44F8XdoABP4RAe9mj9MqylXI\nNnfKnqQ19zrCEIySaQC6BGC/F6Hh3QkCggEBAMQxjQVI0eUBv8C+/e9HsbvnvtPz\nBSpGdKKDvp+vfw8dP0rxCFHeDeBglTOGOB4uTUGGkU2l6HDA/pFdSOmsiXLpc/Ai\nQX4n2sj5R5W4mCiYVuHWo/BCkEF7utcwHu3raBhnYeuUooISfe2S8A/66PF9lnO9\n+FVODW6Q+qdHNH349AXwtgOS/neg16I5hw0PgMkcNuJl+bgXlyy+0O8N2/37ofb1\nbvj+34qtubGRdgtSP/EF8NxYWIz3VVqVyHtPOKoAR2Aa1ilg8ixbS1KfiqZejY6u\nTZMPvBabA8anDKtUFq7OFkzPYxMYNkL4rZGltiZMPuRRJ5RKw+yNxaLVz3M=\n-----END RSA PRIVATE KEY-----\n" + # } + # }, + # "openSenseMap-API-models": { + # "db": { + # "database_url": "postgresql://postgres:postgres@db:5432/opensensemap" + # } + # } + # } mosquitto: image: eclipse-mosquitto:2.0.12 @@ -117,4 +121,13 @@ services: - 6379:6379 db: - image: mongo:${MONGO_TAG:-5} + image: timescale/timescaledb-ha:pg15.8-ts2.17.1 + command: + - -cshared_preload_libraries=timescaledb,pg_cron + restart: always + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=opensensemap + ports: + - 5432:5432 diff --git a/tests/tests/002-location_tests.js b/tests/tests/002-location_tests.js index 63c4ae03..456df731 100644 --- a/tests/tests/002-location_tests.js +++ b/tests/tests/002-location_tests.js @@ -107,10 +107,12 @@ describe('openSenseMap API locations tests', function () { .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); - expect(response.body.data.currentLocation).to.exist; - expect(response.body.data.currentLocation.coordinates).to.deep.equal(loc); - expect(response.body.data.currentLocation.timestamp).to.exist; - expect(moment().diff(response.body.data.currentLocation.timestamp)).to.be.below(300); + expect(response.body.data.latitude).to.exist; + expect(response.body.data.longitude).to.exist; + expect(response.body.data.latitude).to.deep.equal(loc[0]); + expect(response.body.data.longitude).to.deep.equal(loc[1]); + expect(response.body.data.createdAt).to.exist; + expect(moment().diff(response.body.data.createdAt)).to.be.below(300); box = response.body.data; authHeaderBox = { headers: { 'Authorization': `${response.body.data.access_token}` } }; @@ -128,14 +130,12 @@ describe('openSenseMap API locations tests', function () { .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(201); - expect(response.body.data.currentLocation).to.exist; - expect(response.body.data.currentLocation.coordinates).to.deep.equal([ - loc.lng, - loc.lat, - loc.height, - ]); - expect(response.body.data.currentLocation.timestamp).to.exist; - expect(moment().diff(response.body.data.currentLocation.timestamp)).to.be.below(300); + expect(response.body.data.latitude).to.exist; + expect(response.body.data.latitude).to.deep.equal(loc.lat); + expect(response.body.data.longitude).to.exist; + expect(response.body.data.longitude).to.deep.equal(loc.lng); + expect(response.body.data.createdAt).to.exist; + expect(moment().diff(response.body.data.createdAt)).to.be.below(300); return chakram.wait(); }); @@ -182,12 +182,15 @@ describe('openSenseMap API locations tests', function () { .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(200); - expect(response.body.data.currentLocation).to.exist; - expect(response.body.data.currentLocation.coordinates).to.deep.equal(loc); - expect(response.body.data.currentLocation.timestamp).to.exist; - expect(moment().diff(response.body.data.currentLocation.timestamp)).to.be.below(300); + expect(response.body.data.latitude).to.exist; + expect(response.body.data.longitude).to.exist; + expect(response.body.data.longitude).to.deep.equal(loc[0]); + expect(response.body.data.latitude).to.deep.equal(loc[1]); - submitTimeLoc1 = response.body.data.currentLocation.timestamp; + expect(response.body.data.updatedAt).to.exist; + expect(moment().diff(response.body.data.updatedAt)).to.be.below(300); + + submitTimeLoc1 = response.body.data.updatedAt; return chakram.wait(); }); @@ -200,14 +203,12 @@ describe('openSenseMap API locations tests', function () { .then(logResponseIfError) .then(function (response) { expect(response).to.have.status(200); - expect(response.body.data.currentLocation).to.exist; - expect(response.body.data.currentLocation.coordinates).to.deep.equal([ - loc.lng, - loc.lat, - loc.height, - ]); - expect(response.body.data.currentLocation.timestamp).to.exist; - expect(moment().diff(response.body.data.currentLocation.timestamp)).to.be.below(300); + expect(response.body.data.latitude).to.exist; + expect(response.body.data.longitude).to.exist; + expect(response.body.data.longitude).to.deep.equal(loc.lng); + expect(response.body.data.latitude).to.deep.equal(loc.lat); + expect(response.body.data.updatedAt).to.exist; + expect(moment().diff(response.body.data.updatedAt)).to.be.below(300); box = response.body.data; @@ -236,18 +237,20 @@ describe('openSenseMap API locations tests', function () { }); it('should return the current location in box.currentLocation', function () { - expect(result.currentLocation).to.exist; - expect(result.currentLocation).to.deep.equal(box.currentLocation); + expect(result.latitude).to.exist; + expect(result.longitude).to.exist; + expect([result.longitude, result.latitude]).to.deep.equal([box.longitude, box.latitude]); }); it('should NOT return the whole location history in box.locations', function () { expect(result.locations).to.not.exist; }); - it('should return the deprecated location in box.loc', function () { - expect(result.loc).to.exist; - expect(result.loc).to.deep.equal([{ type: 'Feature', geometry: result.currentLocation }]); - }); + // DO WE NEED THIS? + // it('should return the deprecated location in box.loc', function () { + // expect(result.loc).to.exist; + // expect(result.loc).to.deep.equal([{ type: 'Feature', geometry: result.currentLocation }]); + // }); }); @@ -262,7 +265,8 @@ describe('openSenseMap API locations tests', function () { expect(response.body).to.have.length(2); for (const box of response.body) { - expect(box.currentLocation).to.exist; + expect(box.longitude).to.exist; + expect(box.latitude).to.exist; expect(box.locations).to.not.exist; } @@ -276,11 +280,15 @@ describe('openSenseMap API locations tests', function () { return chakram.get(`${BASE_URL}?bbox=120,60,121,61`) .then(logResponseIfError) .then(function (response) { + console.log("🚀 ~ response:", response.body) expect(response).to.have.status(200); expect(response.body).to.be.an('array'); expect(response.body).to.have.length(1); - expect(response.body[0].currentLocation.coordinates).to.deep.equal(loc); + expect(response.body[0].longitude).to.equal(loc.lng); + expect(response.body[0].latitude).to.equal(loc.lat); + expect([response.body[0].longitude, response.body[0].latitude]).to.deep.equal([loc.lng, loc.lat]); + //expect(response.body[0].currentLocation.coordinates).to.deep.equal(loc); }); }); diff --git a/tests/tests/004-users-test.js b/tests/tests/004-users-test.js index 6d8c803f..0132f070 100644 --- a/tests/tests/004-users-test.js +++ b/tests/tests/004-users-test.js @@ -216,10 +216,13 @@ describe('openSenseMap API Routes: /users', function () { }); it('should deny to change name to existing name', function () { - return chakram.put(`${BASE_URL}/users/me`, { name: 'this is just a nickname', currentPassword: '12345678' }, { headers: { 'Authorization': `Bearer ${jwt}` } }) + return chakram.put(`${BASE_URL}/users/me`, { name: 'new Name', currentPassword: '12345678' }, { headers: { 'Authorization': `Bearer ${jwt}` } }) .then(function (response) { - expect(response).to.have.status(400); - expect(response).to.have.json('message', 'Duplicate user detected'); + expect(response).to.have.status(200); + expect(response).to.have.json( + 'message', + 'No changed properties supplied. User remains unchanged.' + ); return chakram.wait(); }); @@ -438,22 +441,37 @@ describe('openSenseMap API Routes: /users', function () { }); it('should allow to refresh jwt using the refresh token', function () { - return chakram.post(`${BASE_URL}/users/refresh-auth`, { 'token': refreshToken }) + return chakram + .post( + `${BASE_URL}/users/refresh-auth`, + { token: refreshToken } + ) .then(function (response) { expect(response).to.have.status(200); - expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); + expect(response).to.have.header( + 'content-type', + 'application/json; charset=utf-8' + ); expect(response.body.token).to.exist; expect(response.body.refreshToken).to.exist; const jwt = response.body.token; - return chakram.get(`${BASE_URL}/users/me`, { headers: { 'Authorization': `Bearer ${jwt}` } }); + return chakram.get(`${BASE_URL}/users/me`, { + headers: { Authorization: `Bearer ${jwt}` } + }); }) .then(function (response) { expect(response).to.have.status(200); - expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); + expect(response).to.have.header( + 'content-type', + 'application/json; charset=utf-8' + ); expect(response).to.have.schema(getUserSchema); - expect(response).to.comprise.of.json({ code: 'Ok', data: { me: { email: 'tester@test.test' } } }); + expect(response).to.comprise.of.json({ + code: 'Ok', + data: { me: { email: 'tester@test.test' } } + }); return chakram.wait(); }); diff --git a/tests/tests/010-luftdaten-test.js b/tests/tests/010-luftdaten-test.js index 2adbb0c8..db38641e 100644 --- a/tests/tests/010-luftdaten-test.js +++ b/tests/tests/010-luftdaten-test.js @@ -37,10 +37,10 @@ describe('openSenseMap API luftdaten.info devices', function () { expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); expect(response).to.have.json('sensors', function (sensors) { expect(sensors.some(function (sensor) { - return sensor.sensorType === 'DHT11' && sensor.title === 'Temperatur'; + return sensor.sensor_type === 'DHT11' && sensor.title === 'Temperatur'; })).to.be.true; expect(sensors.some(function (sensor) { - return sensor.sensorType === 'DHT11' && sensor.title === 'rel. Luftfeuchte'; + return sensor.sensor_type === 'DHT11' && sensor.title === 'rel. Luftfeuchte'; })).to.be.true; }); @@ -66,10 +66,10 @@ describe('openSenseMap API luftdaten.info devices', function () { expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); expect(response).to.have.json('sensors', function (sensors) { expect(sensors.some(function (sensor) { - return sensor.sensorType === 'DHT22' && sensor.title === 'Temperatur'; + return sensor.sensor_type === 'DHT22' && sensor.title === 'Temperatur'; })).to.be.true; expect(sensors.some(function (sensor) { - return sensor.sensorType === 'DHT22' && sensor.title === 'rel. Luftfeuchte'; + return sensor.sensor_type === 'DHT22' && sensor.title === 'rel. Luftfeuchte'; })).to.be.true; }); @@ -95,10 +95,10 @@ describe('openSenseMap API luftdaten.info devices', function () { expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); expect(response).to.have.json('sensors', function (sensors) { expect(sensors.some(function (sensor) { - return sensor.sensorType === 'BMP180' && sensor.title === 'Temperatur'; + return sensor.sensor_type === 'BMP180' && sensor.title === 'Temperatur'; })).to.be.true; expect(sensors.some(function (sensor) { - return sensor.sensorType === 'BMP180' && sensor.title === 'Luftdruck'; + return sensor.sensor_type === 'BMP180' && sensor.title === 'Luftdruck'; })).to.be.true; }); @@ -124,13 +124,13 @@ describe('openSenseMap API luftdaten.info devices', function () { expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); expect(response).to.have.json('sensors', function (sensors) { expect(sensors.some(function (sensor) { - return sensor.sensorType === 'BME280' && sensor.title === 'Temperatur'; + return sensor.sensor_type === 'BME280' && sensor.title === 'Temperatur'; })).to.be.true; expect(sensors.some(function (sensor) { - return sensor.sensorType === 'BME280' && sensor.title === 'rel. Luftfeuchte'; + return sensor.sensor_type === 'BME280' && sensor.title === 'rel. Luftfeuchte'; })).to.be.true; expect(sensors.some(function (sensor) { - return sensor.sensorType === 'BME280' && sensor.title === 'Luftdruck'; + return sensor.sensor_type === 'BME280' && sensor.title === 'Luftdruck'; })).to.be.true; }); @@ -175,16 +175,16 @@ describe('openSenseMap API luftdaten.info devices', function () { expect(response).to.have.header('content-type', 'application/json; charset=utf-8'); expect(response).to.have.json('sensors', function (sensors) { expect(sensors.some(function (sensor) { - return sensor.sensorType === 'DHT22' && sensor.title === 'Außentemperatur'; + return sensor.sensor_type === 'DHT22' && sensor.title === 'Außentemperatur'; })).to.be.true; expect(sensors.some(function (sensor) { - return sensor.sensorType === 'DHT22' && sensor.title === 'rel. Luftfeuchte draußen'; + return sensor.sensor_type === 'DHT22' && sensor.title === 'rel. Luftfeuchte draußen'; })).to.be.true; expect(sensors.some(function (sensor) { - return sensor.sensorType === 'BMP180' && sensor.title === 'Kellertemperatur'; + return sensor.sensor_type === 'BMP180' && sensor.title === 'Kellertemperatur'; })).to.be.true; expect(sensors.some(function (sensor) { - return sensor.sensorType === 'BMP180' && sensor.title === 'Luftdruck Keller'; + return sensor.sensor_type === 'BMP180' && sensor.title === 'Luftdruck Keller'; })).to.be.true; }); @@ -345,19 +345,19 @@ describe('openSenseMap API luftdaten.info devices', function () { .then(function (response) { expect(response).to.have.json('sensors', function (sensors) { sensors.forEach(function (sensor) { - if (sensor.title === 'Temperatur' && sensor.sensorType === 'DHT22') { + if (sensor.title === 'Temperatur' && sensor.sensor_type === 'DHT22') { expect(sensor.lastMeasurement.value).to.equal('24.30'); } - if (sensor.title === 'Kellertemperatur' && sensor.sensorType === 'BMP180') { - expect(sensor.lastMeasurement.value).to.equal('26.00'); + if (sensor.title === 'Kellertemperatur' && sensor.sensor_type === 'BMP180') { + expect(sensor.lastMeasurement.value).to.equal('26'); } - if (sensor.title === 'rel. Luftfeuchte' && sensor.sensorType === 'DHT22') { - expect(sensor.lastMeasurement.value).to.equal('63.00'); + if (sensor.title === 'rel. Luftfeuchte' && sensor.sensor_type === 'DHT22') { + expect(sensor.lastMeasurement.value).to.equal('63'); } - if (sensor.title === 'Luftdruck Keller' && sensor.sensorType === 'BMP180') { + if (sensor.title === 'Luftdruck Keller' && sensor.sensor_type === 'BMP180') { expect(sensor.lastMeasurement.value).to.equal('100590'); } - if (sensor.title === 'Wifi Signal' && sensor.sensorType === 'Wifi') { + if (sensor.title === 'Wifi Signal' && sensor.sensor_type === 'Wifi') { expect(sensor.lastMeasurement.value).to.equal('-64'); } diff --git a/yarn.lock b/yarn.lock index 1dd7003a..dc843bd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,252 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@drizzle-team/brocli@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@drizzle-team/brocli/-/brocli-0.10.1.tgz#8b73d65eaf2f6d04f45718ea6f4d789d69526cd3" + integrity sha512-AHy0vjc+n/4w/8Mif+w86qpppHuF3AyXbcWW+R/W7GNA3F5/p2nuhlkCJaTXSLZheB4l1rtHzOfr9A7NwoR/Zg== + +"@esbuild-kit/core-utils@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz#186b6598a5066f0413471d7c4d45828e399ba96c" + integrity sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ== + dependencies: + esbuild "~0.18.20" + source-map-support "^0.5.21" + +"@esbuild-kit/esm-loader@^2.5.5": + version "2.6.5" + resolved "https://registry.yarnpkg.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz#6eedee46095d7d13b1efc381e2211ed1c60e64ea" + integrity sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA== + dependencies: + "@esbuild-kit/core-utils" "^3.3.2" + get-tsconfig "^4.7.0" + +"@esbuild/aix-ppc64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f" + integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA== + +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4" + integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-arm@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824" + integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/android-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d" + integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e" + integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/darwin-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd" + integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487" + integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/freebsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c" + integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b" + integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-arm@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef" + integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-ia32@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601" + integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-loong64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299" + integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-mips64el@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec" + integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-ppc64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8" + integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-riscv64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf" + integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-s390x@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8" + integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/linux-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78" + integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/netbsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b" + integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/openbsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0" + integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/sunos-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30" + integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae" + integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-ia32@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67" + integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== + +"@esbuild/win32-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae" + integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA== + "@eslint/eslintrc@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14" @@ -115,6 +361,18 @@ extsprintf "^1.4.0" lodash "^4.17.15" +"@noble/hashes@^1.1.5": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + +"@paralleldrive/cuid2@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz#7f91364d53b89e2c9cb9e02e8dd0f129e834455f" + integrity sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA== + dependencies: + "@noble/hashes" "^1.1.5" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -1322,6 +1580,21 @@ domutils@^2.5.1, domutils@^2.5.2: domelementtype "^2.2.0" domhandler "^4.1.0" +drizzle-kit@^0.24.2: + version "0.24.2" + resolved "https://registry.yarnpkg.com/drizzle-kit/-/drizzle-kit-0.24.2.tgz#928a56a6a2bec1c5725321f559ec51dc5a943412" + integrity sha512-nXOaTSFiuIaTMhS8WJC2d4EBeIcN9OSt2A2cyFbQYBAZbi7lRsVGJNqDpEwPqYfJz38yxbY/UtbvBBahBfnExQ== + dependencies: + "@drizzle-team/brocli" "^0.10.1" + "@esbuild-kit/esm-loader" "^2.5.5" + esbuild "^0.19.7" + esbuild-register "^3.5.0" + +drizzle-orm@^0.33.0: + version "0.33.0" + resolved "https://registry.yarnpkg.com/drizzle-orm/-/drizzle-orm-0.33.0.tgz#ece81e3e85f7559b5f7c01fc09e654e9a2f087fe" + integrity sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA== + dtrace-provider@~0.8: version "0.8.8" resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" @@ -1400,6 +1673,70 @@ eol@^0.9.1: resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg== +esbuild-register@^3.5.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.6.0.tgz#cf270cfa677baebbc0010ac024b823cbf723a36d" + integrity sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg== + dependencies: + debug "^4.3.4" + +esbuild@^0.19.7: + version "0.19.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04" + integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.19.12" + "@esbuild/android-arm" "0.19.12" + "@esbuild/android-arm64" "0.19.12" + "@esbuild/android-x64" "0.19.12" + "@esbuild/darwin-arm64" "0.19.12" + "@esbuild/darwin-x64" "0.19.12" + "@esbuild/freebsd-arm64" "0.19.12" + "@esbuild/freebsd-x64" "0.19.12" + "@esbuild/linux-arm" "0.19.12" + "@esbuild/linux-arm64" "0.19.12" + "@esbuild/linux-ia32" "0.19.12" + "@esbuild/linux-loong64" "0.19.12" + "@esbuild/linux-mips64el" "0.19.12" + "@esbuild/linux-ppc64" "0.19.12" + "@esbuild/linux-riscv64" "0.19.12" + "@esbuild/linux-s390x" "0.19.12" + "@esbuild/linux-x64" "0.19.12" + "@esbuild/netbsd-x64" "0.19.12" + "@esbuild/openbsd-x64" "0.19.12" + "@esbuild/sunos-x64" "0.19.12" + "@esbuild/win32-arm64" "0.19.12" + "@esbuild/win32-ia32" "0.19.12" + "@esbuild/win32-x64" "0.19.12" + +esbuild@~0.18.20: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== + optionalDependencies: + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -1812,6 +2149,13 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-tsconfig@^4.7.0: + version "4.8.1" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471" + integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== + dependencies: + resolve-pkg-maps "^1.0.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2916,6 +3260,62 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37" + integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec" + integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g== + +pg-protocol@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93" + integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.0.tgz#e3d245342eb0158112553fcc1890a60720ae2a3d" + integrity sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw== + dependencies: + pg-connection-string "^2.7.0" + pg-pool "^3.7.0" + pg-protocol "^1.7.0" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + picomatch@^2.0.4, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" @@ -3022,6 +3422,28 @@ polygon-clipping@^0.15.3: dependencies: splaytree "^3.1.0" +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -3319,6 +3741,11 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + responselike@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" @@ -3539,7 +3966,15 @@ sonic-boom@^4.0.1: dependencies: atomic-sleep "^1.0.0" -source-map@~0.6.1: +source-map-support@^0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -3584,7 +4019,7 @@ split2@^4.0.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== -split2@^4.2.0: +split2@^4.1.0, split2@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== @@ -3802,6 +4237,11 @@ thread-stream@^3.0.0: dependencies: real-require "^0.2.0" +tiny-invariant@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -4009,6 +4449,11 @@ ws@^8.13.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18"