diff --git a/.github/workflows/test_bd.yml b/.github/workflows/test_bd.yml index 14033751..13ef543f 100644 --- a/.github/workflows/test_bd.yml +++ b/.github/workflows/test_bd.yml @@ -2,35 +2,34 @@ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: BuildingDepot - on: push: - branches: - - master - - develop + branches: + - master + - develop pull_request: branches: - - master - - develop + - develop jobs: build: - - runs-on: ubuntu-18.04 - + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 - - name: Set up Python 2.7 - uses: actions/setup-python@v1 - with: - python-version: 2.7 - - name: Install BuildingDepot - run: | - sudo bash -x ./script_for_github_actions.sh - - name: Initialize tests - run: | - cd benchmarking-tools/functional-testing-tool - npm install - - name: Run tests - run: | - cd benchmarking-tools/functional-testing-tool - npm test ./tests/all.js + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + architecture: x64 + - name: Display Python version + run: python --version + - name: Install BuildingDepot + run: | + sudo bash -x ./script_for_github_actions.sh + - name: Initialize tests + run: | + cd benchmarking-tools/functional-testing-tool + npm install + - name: Run tests + run: | + cd benchmarking-tools/functional-testing-tool + npm test ./tests/all.js \ No newline at end of file diff --git a/README.md b/README.md index e1e649b8..9556d24a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -BuildingDepot v3.2.9 ([link](https://buildingdepot.org/)) +BuildingDepot v3.3 ([link](https://buildingdepot.org/)) ==================== ![BuildingDepot](https://github.com/synergylabs/BuildingDepot-v3/workflows/BuildingDepot/badge.svg) @@ -89,12 +89,12 @@ What's installed * The following packages are installed using apt-get * openssl - * python-setuptools - * python-dev + * python3-setuptools + * python3-dev * build-essential - * python-software-properties + * python3-software-properties * mongodb - * python-pip + * python3-pip * nginx * supervisor * redis-server @@ -103,15 +103,18 @@ What's installed * The following packages are installed in the python virtual environment * Flask * mongoengine - * flask-restful - * Flask-HTTPAuth - * flask-login - * validate-email - * requests + * Flask-Login * Flask-Script + * Flask-OAuthlib + * jsonschema + * pika + * Sphinx + * sphinx-theme * Flask-WTF - * flask-bootstrap + * Flask-Bootstrap + * uWSGI * redis * influxdb * pymongo + * aniso8601 * firebase-admin diff --git a/benchmarking-tools/BD-Performance-Test/config.json b/benchmarking-tools/BD-Performance-Test/config.json index 95422164..e85a8cd1 100644 --- a/benchmarking-tools/BD-Performance-Test/config.json +++ b/benchmarking-tools/BD-Performance-Test/config.json @@ -1,19 +1,19 @@ { - "uri":{ - "cs":"https://localhost:81", - "ds":"https://localhost:82" - }, - "credentials":{ - "clientId" : "", - "clientSecret" : "" - }, - "defaults":{ - "oauth":true, - "connections":1, - "amount":1 - }, - "testSuite":"./testSuites/post-time-series.json", - "headers":{ - "content-type":"application/json" - } + "uri": { + "cs": "https://localhost:81", + "ds": "https://localhost:82" + }, + "credentials": { + "clientId": "", + "clientSecret": "" + }, + "defaults": { + "oauth": true, + "connections": 1, + "amount": 1 + }, + "testSuite": "./testSuites/post-time-series.json", + "headers": { + "content-type": "application/json" + } } \ No newline at end of file diff --git a/benchmarking-tools/BD-Performance-Test/lib/performanceTest.js b/benchmarking-tools/BD-Performance-Test/lib/performanceTest.js index b376aef2..8d3f0dac 100644 --- a/benchmarking-tools/BD-Performance-Test/lib/performanceTest.js +++ b/benchmarking-tools/BD-Performance-Test/lib/performanceTest.js @@ -15,8 +15,7 @@ let addTestSuite = async function (testSuite) { let updateInterval = await oAuthHandler.handle(); if (testSuite.delay !== undefined) { await delay.inMilliseconds(testSuite.delay) - } - else if (config.defaults.delay !== undefined) { + } else if (config.defaults.delay !== undefined) { await delay.inMilliseconds(config.defaults.delay) } for (let test of testSuite.tests) { @@ -26,8 +25,7 @@ let addTestSuite = async function (testSuite) { let options = await testOptions.create(test); if (test.test === "create sensors for performance-testing") { await createSensors.runTest(options); - } - else { + } else { await runTest(options); } } @@ -39,15 +37,14 @@ let addTestSuite = async function (testSuite) { * Create test suite. */ let createTestSuite = async function (test) { - if(await testOptions.testExists(test)){ + if (await testOptions.testExists(test)) { return { title: test, tests: [{ title: test }] } - } - else{ + } else { console.log('Invalid testSuite or test!'); process.exit(1); } @@ -65,7 +62,7 @@ let runTest = async function (options) { resolve(true) } }; - if(options.title === undefined){ + if (options.title === undefined) { options.title = options.test; } console.log('\nTitle: ' + options.title + '\n'.padEnd(options.title.length + 8, '#')); diff --git a/benchmarking-tools/BD-Performance-Test/lib/testOptions.js b/benchmarking-tools/BD-Performance-Test/lib/testOptions.js index 69f5721d..497b007b 100644 --- a/benchmarking-tools/BD-Performance-Test/lib/testOptions.js +++ b/benchmarking-tools/BD-Performance-Test/lib/testOptions.js @@ -16,7 +16,7 @@ let formOptions = async function (test) { if (test.duration !== undefined) { delete options.amount } - if(await isInvalidUri(options.uri)){ + if (await isInvalidUri(options.uri)) { options.url = uri[request.url]; } if (request.idReplacement !== undefined) @@ -25,32 +25,27 @@ let formOptions = async function (test) { request.body = await dataGenerator.formData(options.formData.saveFile, options.formData.totalSensors, options.formData.totalSamples, options.formData.totalValues, options.formData.dynamic); options['totalSensors'] = options.formData.totalSensors; options['totalSamples'] = options.formData.totalSamples; - } - else if (request.formData !== undefined) { + } else if (request.formData !== undefined) { request.body = await dataGenerator.formData(request.formData.saveFile, request.formData.totalSensors, request.formData.totalSamples, request.formData.totalValues, request.formData.dynamic); options['totalSensors'] = request.formData.totalSensors; options['totalSamples'] = request.formData.totalSamples; - } - else if (request.file !== undefined) { + } else if (request.file !== undefined) { request.body = (test.file === undefined) ? (await fs.readFile(request.file)).toString() : (await fs.readFile(test.file)).toString(); - } - else if (request.body !== undefined) { + } else if (request.body !== undefined) { request.body = (test.body === undefined) ? JSON.stringify(request.body) : JSON.stringify(test.body); - } - else if (request.json !== undefined) { + } else if (request.json !== undefined) { request.body = (test.json === undefined) ? request.json : test.json; } if (options.headers === undefined) { options['headers'] = headers; - } - else { + } else { Object.assign(options.headers, headers); } if (test.title !== undefined) { options['title'] = test.title; } options['requests'] = [request]; - if(options.amount !== undefined && options.connections > options.amount){ + if (options.amount !== undefined && options.connections > options.amount) { console.log("Number of requests can't be less than number of concurrent connections.") process.exit(1) } @@ -70,7 +65,7 @@ let formOptions = async function (test) { */ let testExists = async function (test) { let allTests = Object.keys(tests); - return (allTests.indexOf(test)>-1) + return (allTests.indexOf(test) > -1) }; /** @@ -78,12 +73,12 @@ let testExists = async function (test) { * @return {boolean} Returns true if uri is invalid, false otherwise. */ let isInvalidUri = async function (uri) { - let pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string - '(\\#[-a-z\\d_]*)?$','i'); // fragment locator + let pattern = new RegExp('^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator return !pattern.test(uri); }; diff --git a/benchmarking-tools/BD-Performance-Test/lib/testResultsHandler.js b/benchmarking-tools/BD-Performance-Test/lib/testResultsHandler.js index 62f2dc74..f0ceb892 100644 --- a/benchmarking-tools/BD-Performance-Test/lib/testResultsHandler.js +++ b/benchmarking-tools/BD-Performance-Test/lib/testResultsHandler.js @@ -8,7 +8,7 @@ let handleTestResults = async function (res, options) { res.requests['average2xx'] = (res.requests.total - res.non2xx) / res.duration; res['pointPerSecond'] = res.requests['average2xx'] * options.totalSensors * options.totalSamples; console.log('Total Requests: ' + res.requests.total + ' requests\nAverage RPS: ' + res.requests.average2xx + ' req/s\np99 Latency: ' + res.latency.p99 + ' ms\nTotal throughput: ' + res.throughput.total + ' Bytes\nAverage throughput: ' + res.throughput.average + ' Bytes/sec\nConnections: ' + res.connections + '\nDuration: ' + res.duration + ' seconds\nNon-2xx: ' + res.non2xx); - if(res.pointPerSecond){ + if (res.pointPerSecond) { console.log('Sensors: ' + options.totalSensors + '\nSamples: ' + options.totalSamples + '\nEffective points/sec: ' + res.pointPerSecond.toFixed(2) + ' points/sec written'); } console.log('\n\n') diff --git a/benchmarking-tools/BD-Performance-Test/results/parse.py b/benchmarking-tools/BD-Performance-Test/results/parse.py index 9efb2d97..0c7b9ed3 100644 --- a/benchmarking-tools/BD-Performance-Test/results/parse.py +++ b/benchmarking-tools/BD-Performance-Test/results/parse.py @@ -1,12 +1,23 @@ -from __future__ import print_function -from os import listdir import json -for f in listdir('.'): - if f == 'parse.py': - continue - with open('./'+f) as d: - try: - data = json.load(d) - print(data['title'] , data['requests']['total'] , data['requests']['average'] , data['latency']['p99'] , data['throughput']['total'] , data['throughput']['average'] , data['connections'] , data['duration'] , data['non2xx'], sep='\t') - except Exception as e: - pass \ No newline at end of file +from os import listdir + +for f in listdir("."): + if f == "parse.py": + continue + with open("./" + f) as d: + try: + data = json.load(d) + print( + data["title"], + data["requests"]["total"], + data["requests"]["average"], + data["latency"]["p99"], + data["throughput"]["total"], + data["throughput"]["average"], + data["connections"], + data["duration"], + data["non2xx"], + sep="\t", + ) + except Exception as e: + pass diff --git a/benchmarking-tools/BD-Performance-Test/run.js b/benchmarking-tools/BD-Performance-Test/run.js index fa7c4132..ae0e86bf 100644 --- a/benchmarking-tools/BD-Performance-Test/run.js +++ b/benchmarking-tools/BD-Performance-Test/run.js @@ -6,17 +6,15 @@ let config = require('./config.json'), if (process.argv[2] === undefined) { testSuite = config.testSuite; -} -else { +} else { testSuite = process.argv[2]; } -let runTestSuite = async function(testSuite){ - if(await fileSystemHandler.exists(testSuite)){ +let runTestSuite = async function (testSuite) { + if (await fileSystemHandler.exists(testSuite)) { let testSuiteJSON = (await fileSystemHandler.readFile(testSuite)).toString(); run.addTestSuite(JSON.parse(testSuiteJSON)); - } - else{ + } else { run.addTestSuite(await run.createTestSuite(testSuite)); } }; diff --git a/benchmarking-tools/BD-Performance-Test/sensors.json b/benchmarking-tools/BD-Performance-Test/sensors.json index 024a5cd4..e350872e 100644 --- a/benchmarking-tools/BD-Performance-Test/sensors.json +++ b/benchmarking-tools/BD-Performance-Test/sensors.json @@ -1 +1,2002 @@ -["32818315-32ae-4a3c-afbf-3ca59b79c72c","a4d10069-cdd3-47ae-9ab9-fa6bde8b694b","5b5b8d50-8020-400c-b33c-a1fecf207a62","92eb2cfa-fba5-4d4c-97c9-aa188bd29974","6f9e83ed-d85b-4458-a225-fb5e2efd847b","93668312-dc67-4eb9-ae5f-065e273bb7ec","974bf2a7-beec-46f0-aaab-e20b7849d540","fa412084-50c3-441f-b790-395003534f46","385de1ff-973b-4b87-87c5-63b5c27bf763","633f9368-346b-4fcd-a3d3-de0bf827b775","82506910-6fc5-482d-b1d6-12a714560ea1","a76ace53-c2ad-4ce4-aad9-930d666d371c","af2e0fe7-732c-4e02-8e47-959f9efa7eba","67c4ebd2-edf5-4b85-a591-4e134d67151d","e0f5ec13-158f-431c-babb-a65f624c0772","4466177f-5038-43bb-8dab-f29c5d19cb6d","41467d88-e867-4114-b9f0-8360bb8535fc","026077e8-04e7-4d43-9d69-bd4039686845","03d9ec16-874b-4f7d-b43d-73080663e7ef","c6988776-571c-46b3-b831-716124143b7a","7269b68f-f1d1-46b7-9ec4-84a019ee9d56","a25cde52-fecf-4eee-9e74-f0d4bd26d5fd","71e6d61f-1789-419e-950f-8a9a494d1928","28bc686a-1f2e-4d99-9ee1-455b38501000","6b8f36b1-953a-4a1a-94d7-5e5fe8a329e8","8b3f21b0-4cef-4bb7-a9c1-ef7e12df94f3","73d3eb40-504a-4882-be78-56b9090da329","7a7e03c5-36bc-482c-be4a-f7da140fd0db","49a63f54-f0c7-4ed9-a10c-c3a0a3b50a98","e338bc0c-9d38-4105-ba0c-5b24d1dc9c06","b5742e7b-7dc0-4600-9e9a-137d0ba072a9","d749a405-f559-43cf-9cb5-1113789da955","529efbd2-1a23-4298-87b1-e8b3c5b5f1c5","2b0f8106-3fa4-49c9-9496-f9d936f2b80a","9240a435-b0de-46cd-8f15-dff04617bb99","e30969d0-9334-4534-a6de-b3d90b569214","eb4f93f7-0b07-4056-a58c-9091927dced6","e030fb30-93eb-4b41-9f93-371d93513cec","64be2fd3-f0d7-4ab1-8d17-110bef5ee636","7f4b7e61-d0d4-403d-ba1e-3f4fa76a1e0c","9f849a25-6e07-49b2-97a0-0e2e8a7ff057","4e79accc-4846-427d-af99-b2b79570caa9","71df231b-048a-417d-b96c-ee311027fb90","cd6e070a-7a02-49a2-a474-f51560444e84","3ffe6fa8-d368-44ff-a675-bc846d26df1b","f7b17549-ef9d-4e80-852c-65d873d4dcd1","7a15d6da-0d90-4a69-8029-84afba025c10","e1eaf8fd-6026-43a0-a6cf-9c96be145515","763bd046-ce80-4a3e-a421-3d8737300864","fee75295-9c90-4662-a414-314639c81e74","4c4315d3-41b5-43f6-9dea-c17d318f0d76","ac2099ba-f87c-47bb-9a00-6c3578e0e5a1","62004bf2-cf94-402d-82c7-84114344a948","db45db0c-96e4-4ff2-8f90-5c5f75edc174","d6716cc2-78df-48bd-b2d2-503cfb7a9808","2776d9da-8765-4dcc-831c-bd2324009706","42b911ba-de78-45ab-b7ec-3f145f792367","999a5639-9ea7-46bc-9b01-ce481fe15bf1","9cf13b59-348c-45b4-a59a-60281ca41273","054b3c45-2b1d-4e51-a710-2fdb75376d4b","d45a1168-e6c6-4c9c-a076-33d46521b80c","eacef7ee-c8db-4aad-8543-b4d7736404f7","516858d3-bf10-4dbf-9a38-4291e49f9ef0","2fdecf83-2886-4667-9586-71a06827e77b","8e579552-d15d-46b8-8dfb-9d443299ac19","15bfe64c-42aa-4d61-8191-ad4d614c7c63","7a8b51b3-f88f-4d63-b8e7-77e8c9a95b3c","6e174bd8-4419-4d3e-8a4e-9a8c4bf6ffa4","cef2c879-fda3-49a8-8110-0c4f11e8455e","9dc12b62-7761-4beb-8ac0-811598cdbb7e","1adec0be-fcf3-4d7c-b638-c651b35a84cf","83c863ce-6ef4-4895-b9e8-3cb3f54a0e0c","3d87f391-8ddc-4514-926f-3a6738e903b8","9f69ea6d-7ce4-4ddc-aa59-66050f2a3366","4162ec63-376f-4e17-beb9-4c52378fb3e8","0f6a0d7b-8e38-4a92-8eb1-45f0b7d6922e","45e96611-d53e-4864-b072-c6998d48662e","1086313c-804a-4260-96c8-5608a6cf85cc","94860ff8-f461-4e27-b2be-147affcb0f92","109c2e7d-a87d-438a-b229-2529cdbd003f","befb8448-4f3e-4cc1-b559-ce55ae5236d6","8fa76af1-3e62-46f4-96b2-1332ca4c0ca7","f135fdaf-193e-492e-9920-c0816df5f4e0","9a9ee370-5db1-4c9e-b9a3-ff654a4930f9","63fa4721-af58-4c5e-b8d2-4fae3b46d5da","71893307-112d-49bc-9e87-a8fed7f5b94f","2ccb9a0d-e5fe-497f-ae62-27317ba5c8c3","92990392-45c3-44bc-bb2f-86052ca0d8d4","b78f1d42-cdeb-418b-89ad-2e126169f118","c31f9ff5-9700-4cf8-bd94-f70181664a37","2768c4a0-83d0-4e0e-8b57-d231e0b30eb3","e1037ada-1899-4cfa-82e3-0c19b7b344c3","70af9154-fdd3-4403-a271-dd36113fe36f","430fabb7-873e-4221-bcb0-a600661fc2a3","f3811900-d144-4246-85a1-71c5523f5d81","26eab1a3-8d01-46c2-abe9-4970a9f491a9","aff70a83-77d5-4b9e-993f-b7af465e45f9","89a9137a-a0d4-4d71-970a-06b82f8ad835","dd08eee1-baff-4711-9b1c-a35d534886d4","8cfde1f7-db09-4116-a855-b8cca131665c","25472080-78fa-4c0a-875a-b61c7c151b08","acbf6c9f-1aa9-43f1-b71e-f637df709070","3ba3f54b-c612-4a2f-8d6c-9d1820fdcb53","d8746af8-49ce-4bae-86a8-d357006aeb6b","05bad3d9-70bb-4c0d-aa78-1c566e182718","d5738217-1d47-41c4-9ac5-9aff3c978002","9e4c5d19-a3ca-4cc0-b618-380236bfec16","1ed0a996-727d-498e-91e2-d51d1cbb51e8","ed8d118c-1e11-495d-93e7-f89db43a2907","a9587f3e-b7a0-4b30-bec3-e6d5a692e1a5","c5b29a6e-1b6f-4642-bac8-556eb62ca415","0ce69477-f65e-4490-9df1-171697729b68","2db7203b-4914-4d8b-ba11-ed502cde3e8d","b22d712e-4d21-46d4-8547-5c231e411054","eb0d4677-6181-412e-94c2-026366dc4d24","a8af7c85-3205-401b-a323-33647e131cf7","95a7f6c1-fc7b-4cd9-b03f-f9018f812c6d","615da6b0-7e3a-4082-b9ff-9517fa879e72","052b2d4b-012c-4f33-9b21-e1ed0d9c1a83","258a4bc5-0be1-4f07-98fd-83eaa253f38e","1181df4b-2c46-4107-80a6-9a42f27a9e78","fe7d44c5-786a-42f3-ba04-52ac3b658a36","d89be3d6-6dec-497f-8bf1-99499b930fa2","5ea10676-84df-4b3f-9f66-37c30ad7f298","4ad8f1da-7ea2-4a33-8fb9-b9fdd9c90a77","82758364-7b79-4aaa-a91d-d4e3810fb4c9","5aed7f93-5444-4414-bcd4-1beb29636091","cf2cfc91-ce7e-464f-94cf-b27c087d553b","917022da-dad9-49f6-9598-a5f04cca10d2","0617f316-5fcb-4fde-b66d-6c33ee52d958","b913af74-00a5-44d3-9056-92fc0357e5c0","4aa0c163-c749-454a-9f42-d77c57200438","8ee23a76-370a-4b7f-a943-bdb432a2f71e","e6c04bc2-ea6d-4010-a8bf-144a8ca47b6d","01224e3c-77ff-4358-9a5a-3b509a5aa864","b41262ce-e598-4cfa-b05a-568e2e514afd","a5b35f2a-7b14-4934-bd16-53c95a9ddc01","b41ca67d-c8e4-4a0b-9dff-b78d8f8f3025","900d86c2-8a77-41af-b27f-a5038dcedf2e","c0a53e5b-99ec-45a3-bf14-0ac3eb24e671","2d136ff9-30eb-496b-8e88-e04bdf0db8cd","a814bfd4-db81-4f80-a1f2-eea43bfd9a2f","b648a773-6519-4a60-84dd-da2142579e80","d4c3b234-ee58-48f9-8d9d-f7043e6ea4ae","36bf7bcb-0cf1-4ec8-b80d-43ae747e91e4","c1b2ea00-94d3-4107-aa17-bbc8f33e0f18","c6b60b70-2722-4211-879c-a399c323fbc2","f933e50d-bf0f-47e1-acde-ea40f8ca2d12","e586b64c-c6bf-4d28-9ea9-efe547781b05","5cf4daa5-fc81-4ccc-bd32-285eeb30e147","a1286b12-e2e7-4834-9840-7f79fba9d53c","cbcca31a-25fe-48ac-817d-60c4ddd8fcc3","e063a476-b539-48c9-a015-9d76adc8156c","9ddb3707-12db-4b21-a5ac-ea91d367d8ba","42ebd1b9-2e8c-4491-84aa-98a16956d79d","96782a50-6931-4da0-967f-cc3fed381d18","09498a88-63c4-4468-a1d0-236394a26e2a","ffd962cb-17cf-4a93-87ba-fed5f7ea49a6","21c74c09-9e96-4e8d-b38d-1c36027f7cef","187335c7-e407-4b42-993a-e8aa67a1b0b2","d250adf7-389e-49e5-99bd-2df30cc77220","5d077426-ad2e-4375-9bb0-6351580079ec","8c866285-e4f5-4963-b26d-3ac2a8f428d1","ee0e8f20-b00f-45b1-b25a-513a054babb1","f6f6e0ee-4831-4b36-88e0-6c30c77ca122","fee721e1-832d-4bf9-a231-630e8e465f25","0d3a1867-686b-4ec6-a8f4-075793929492","8f7621cc-234f-492f-96e7-db7eca1af55b","eb4b1587-665b-480e-8c54-d1b7ac025d4b","0c2c785c-cc26-4828-b179-f29331253a5b","619ee6e1-f0fa-48b5-830e-039c66a407ae","e64ff136-24ea-451b-8331-51d4fa7a7edc","69c4c8a7-b120-49dc-bdcb-60cbe38947a9","10b6e28f-f32e-407e-996a-469fa3884e5c","59b690c7-4b4d-42a6-8ff2-53dbbd416eb0","5e3cbd65-4767-43e0-9d91-8cf9e1b8eabf","29d92b27-14c6-4ea4-80cd-c254600b774d","f19b0fd5-7740-40ae-8930-1cf4dc7c7705","ee5f98d3-6c9f-49e5-a461-e0565e91b746","57b33282-9ace-4045-b205-d51f3c56aaf4","2b510b5e-0e9b-4629-a2e4-c43f0274ed60","3524f668-26a6-4c2a-85b7-1d26beedbda5","f589bc40-48d8-4585-957f-7cefeb92aa84","d6c88fcb-daa1-472d-a947-f8714d302d00","1ba11bb0-4204-4db6-8af5-6063d17c985d","0cb739c1-e577-4bdb-aedd-49f5ca71634b","528ff103-1e70-491a-b9b1-206b87650127","1bc5083a-3e1b-40e5-9f99-66bdf21de2c5","6f74a5ad-2091-4a6f-9986-da7ccd07919f","011e8eca-ce92-45c4-9183-3ac5890f5211","631ab22c-611f-4022-977e-5c1c716fec21","64d8cf69-e21e-4cab-ad20-0d835098bc69","7af6c5ce-47a8-4604-8a92-6165167778dd","168b9e12-cf4d-4254-b1ae-84ab173d4c86","84e84385-b399-4b5e-8890-0a1104ece766","15db266a-b0de-4e72-a59f-ed4098c33fbf","3bd9625d-a256-4f35-b97c-1871c0cb38b6","94a5927d-f84b-4508-85c4-8aa588efccce","6034f6ce-1db4-4582-abbb-aa1ad1be2db5","d2ca6ea1-3990-4254-b951-13d0c63688b9","7d5becbb-68f9-4516-81bb-506e2f0f010f","7ac5564f-b148-45cb-9181-df47fa5baf32","3472ce06-819a-408a-a57c-824c87de21bd","39051c5e-8a50-4135-9eed-d831f58da148","26c9ca47-5e88-4f27-9f2c-bf4d2a913370","cf114842-0499-4fc0-a972-03a8a22e945a","c5512ac0-4566-4592-ab70-478d5f77c278","3f507100-cb3a-450d-bf52-80ff9f681552","2efbf34e-3d8b-4e4f-ba89-4138847db12b","fb6a7a89-a9d2-4838-9901-2f6871549520","a5dad9dd-bc54-454a-885c-b2cd72797e17","5972e710-3c87-42e3-b167-1af7f819fde6","2b27e274-7c60-49d1-9dff-21b5e8f9ad51","20a9a36b-8a3e-4398-b6d6-74bf49450005","894d52c1-cd7f-440e-a8b2-b4f224491fcd","9547f591-5f8d-459d-8081-5514ca0fe25a","5e6b2223-734b-4fb0-95ec-1c968a06112c","907695b6-bd98-4f85-9752-631f520464cb","0c3aaf0b-3186-4910-8ada-fa678f25f338","9e778dfb-6f64-4944-b20b-e78bac277424","ac72ef24-034e-4341-a113-34a38c25b28a","e2e5b23c-2fe7-44e4-b630-9a28e9e31ed7","529a5eb0-1a7b-40a6-bed0-f70cb2141f99","10b7e68f-1695-4d0b-97f4-12b336c30b5a","bc327a9d-4815-4272-a350-ac0cbc4f1d8e","7ac67ace-1f73-41db-a0d8-1aec764bb127","19faaf9c-7e74-4160-8ccb-4cc58068f2a9","0f15e4e2-ca32-416b-b1f5-d308546aca96","3f9aaeb2-f234-4d65-9c92-db32aed13e3e","03d414f0-4215-40f7-a5e8-b68c378d2ab1","b0cdd821-3b85-4db4-b9c8-d2dd1583d7d7","a8a38c3c-4705-4230-ac56-de89e3ff439f","07afdbfc-32b7-4d4e-bddc-4474d5a2a879","13d44809-14fa-4399-b935-af7ccc1a119a","53b32f07-002b-4f7b-92f4-ad28a95d8965","e974e214-237e-4642-ad3c-88d77a5ec6d3","11578b7e-4a3d-4d82-ab43-236411a39ebb","a2d0d430-b533-409a-a5f1-3558703e39cc","a2338af9-9f98-4e1e-90a6-4a9747aedfe7","d44f5637-8ef3-48ea-891d-df486357a4d3","6675f61a-5b83-4132-8501-0957e540328e","c7ee423f-1f6a-4a9f-b9c0-ff7eafd2adaf","62618f35-5aaf-4673-8242-0c9058562eac","107fc13f-8757-4ff2-9a32-7b9df8633694","61c83110-0f4b-4787-923e-1f03ba5dc451","cf808229-55b9-4440-8bad-4e072426a186","b65e6842-091a-414a-b9fa-24b26514bff8","d317bc9e-831a-4fa7-af55-ba9ae5db183b","a1ac831a-6f98-43ad-a2a4-94b8577bd7e2","0eb6dc2a-e374-4368-900c-0a57728f32ba","fd3afae1-413f-4de1-a1b4-91f0de651aa3","8bd87c62-db20-4349-b26d-28373d399401","0c7d52c1-b29b-411e-8369-3530223ab37e","4de40531-41c9-4b0a-b0d6-77194b4f0280","fbf98a54-05cb-4324-a2ea-ce73867b6ef9","3badac78-66e6-49ce-a6ed-672e97fb3589","de5ab556-e7fd-4898-bedd-5355b15283e4","d2a42d56-5fb6-4dcc-bd2d-b76cd2ef8d3d","c663008d-f297-47e3-942b-42837d771c65","2cd16972-2471-4161-a058-115356ed79b7","168fe346-646f-4cdb-9e9e-0840956b68be","632cae7d-5fca-4b9d-b0e4-2219e8763500","da784af0-2c34-4fd7-b93d-127f3998dfdc","c0a61941-85b7-4a95-8873-e7040bf9250b","39f0d7a6-dca7-4452-9bfe-0c46bdf75e4e","b8f6a701-4b59-405f-b59b-ab7d8e11e856","fd6b599b-3c5e-4084-81ec-a30045b71386","376017d7-5ea6-4623-b5ea-76d88feb1b52","120d6509-6d87-4dc7-ae97-a5fd713d7624","92e7de44-3264-478b-99e7-05a590d6d812","3bf7ec35-7b90-49e3-8f23-03c16fe3d80d","aefb9f13-ff95-4306-89cc-c70a65622785","fa1700b4-8d7d-474a-9296-788d3281e940","7f0d0d03-a9c3-4e8a-9384-cf619956bc2c","ea3e5edc-0544-46a0-b63f-fc08d3766a73","a6ec1ae3-bfca-4b62-97af-edb2c509a12e","1753ec96-6989-4374-bc35-a1734508bdff","a8a18a6b-8071-49e5-9511-04316e682889","18b186c5-21b8-4470-90c1-911bfb31b831","bb36af36-a871-4702-98d0-a097b7d092e7","708a6819-6c99-4d60-9a1d-21d0117d45c7","d2d23592-2acd-4906-8251-a3e576cadbdd","4dcbcae1-3292-45bc-9e2f-095f2ce60a0e","d2c96bbc-6958-45ee-af01-8f4b78715b32","70f7f616-b59f-46d9-be21-cc0f3437b31d","23f3ccc0-a3c4-4712-aed0-ddea807184ee","2e3935b3-5cac-4b01-a8da-286291a17657","ff54580a-cc5f-48c8-9ff6-142b9ec12ecd","b9961021-a349-473e-b0c5-695b456382b0","e1376ed6-7a67-45aa-b406-12383373e94f","9326765c-627d-44b2-99ac-ba05693e368a","dd221431-fe48-4367-8aac-0237fc9d8583","792a41aa-a5b6-41f9-b930-1d78f797ef5b","7d00c98a-e4e4-40ad-9d24-14df8dfc26e1","35a1f0ff-50e6-4072-8be7-019aab549779","57ed1108-b2f2-48b7-904c-0dbb4e560091","a0dbea4b-cb4d-4379-8eb9-39ff06370aec","6b42fb08-6b18-48b8-913a-4bf1c6dd0302","ecad8ff1-350c-4a86-8c22-6cb08f687d5b","890b82f5-51b2-4592-b786-92e7067c7545","32d6356c-e2ba-42b7-a4ec-5a4e33539c8f","590a40f9-68f7-4ae3-b04a-f1d6f3b93751","712b471d-e0b5-41b6-8127-1942b0a320a5","2be3bba2-4bb4-4e22-bd15-9787612afa34","865ac95a-473c-47fe-87c8-d8ef820c8799","e396c353-df00-449a-ac01-defc1d8d8ec0","a9b976bc-f486-4c17-95b2-0903b3f2d90d","d205f3e3-81f8-4333-b87d-9f3ae1d39991","a2def0cc-3bd0-4537-bd84-4324f66faf0e","c5325eb4-a5c4-4771-96ee-5d45546828db","4fb1c05d-14d5-4e47-8d52-266809a0b5bf","88583b6a-c440-492e-936a-27cfa6fea48f","f0e98485-f178-4b89-965f-de1309c6d6d4","50fc8869-ccd2-4da1-838b-e269ecdd4022","b522ad9a-b07a-4261-b83f-56e52bad1f4f","92cdd1c3-82a7-42ef-9263-da0521ec5bd3","d04d4567-9ff0-4882-acae-f8bee2c15c7a","2c20e1d0-e268-4f90-bdea-9616d411394b","84bb5a4d-a716-4c8c-8dfa-4980e99f75fe","f3e2ff6d-e861-4044-b390-6c40e95eb221","3dff451d-efb2-4a2f-b226-ecca8d6e09dc","6ac13712-3a6f-4479-9022-8b25b9ee8ac9","f396f23d-60fe-4c4c-8f56-9a169bfdf8cf","8c7071da-1b51-4a7e-b109-2f84326370e1","e62c577f-ebcc-440a-bbd4-433d48b69c72","fcde2fef-575f-4f7a-903e-bc00b2d20a4c","eb4404a3-356f-4da0-b960-5e8974a3202e","c9311010-be7d-4387-a630-22b683c05426","e1398d1d-43f1-44f5-a4c1-8bfa821a737e","3f562d82-1f1d-4b7d-a3d1-963aba8fd3f4","773a2976-14c4-4026-b81a-be3d7a727733","98d2941b-c85f-4c6c-a4b8-06a024ae65a2","1318274f-63ac-420a-85af-d5010aa4ea29","51cc1aac-9785-4d07-996a-a8c47d2c002a","a18fe519-0940-4017-aa9e-14eb3e5bb646","a7bf06e4-723a-49a2-9038-302e21a3da99","f4834d86-590e-402a-8845-bf98feaa8884","4e2f618d-d542-4678-bb42-183239ee435d","f04406ab-abc5-4f4c-a81c-764c65ce7e97","2edf29cc-acbe-43e3-9794-de132bcaf9f4","cf091f05-550f-438b-b917-0e53da68f146","9ac00c71-2ece-44a5-8044-12cbae77e532","9cd46c44-18df-4f90-b80d-fab168a89d49","86196bfd-a69c-4ee9-a840-135e96cdd463","964a6f5d-0b33-4fea-b467-3044cd6600cc","3077a017-f216-4baf-9446-84e60c8f6e88","1323e3fc-aa06-462c-8d8f-2ac6e22d9b38","3251ac9c-571e-4e9e-b15e-f457fe4ce8a2","4d17adc0-61ef-4b2b-88be-99311afd9bb1","fed702b0-a3c2-4895-bc41-c8dff2f95db4","2b1a28c2-821d-4a60-8c9e-05f5f9322ed6","d1170f94-1c48-4f48-8091-a5c94e5d19cc","e9223b90-0ecd-4214-b706-b5366533647a","8121363d-96d1-47a7-b457-0b3a701e84fe","6c43a9b6-2868-4550-a87f-8ed8c10d23e7","c8cdc362-ad90-408f-bf43-7087c307a30c","0cf2a923-0b62-4667-9a15-bdb636bebb97","b8553ac0-874a-4446-a668-1d40797856e4","96d00b9b-e137-4ce0-9189-0ce44465284b","86961707-6067-481b-a129-0e96b8a1acb0","0d26141f-1b33-4404-9ec9-d88f94a657a3","fff8d888-a947-488c-9b1a-a48753a3ec1a","4ade5b7f-1d3d-45d3-9adb-d24228f7bc87","da377d67-7049-4707-af17-c914e5850ee8","2eb56b14-11da-4bb9-804a-2fcc56f62c48","4ad7bfa8-6ea2-4927-8be1-05ca66724168","0ec0cfe0-ad1c-4fee-a8c0-f5a00adada14","c1fad8be-01ce-430f-b9bf-11bc2f3ec376","de9f0569-ed10-435e-8f5b-345692d39929","648ae9bc-4ea9-42b7-99a2-9650d16c1e63","af124531-c91a-4561-8b21-1a5bb638b71f","d0d682d7-dc04-40ba-99e1-8fb2a0ca43fd","9aa3a2d6-953f-4d41-b672-71db709b69ae","a848b7cf-26e4-4abe-a4a7-fd7f6c3761bd","7d30b41a-1237-4a1c-8884-96e5882b7571","9431058f-e62c-4076-abff-dddb41f41581","65721459-79b4-496b-8b73-92841c3bfcd8","16f31618-735d-44dc-b161-500b0e83808f","b500bd5d-41fe-4010-997a-75c4066f4d88","9cfd6abf-51ed-4cb6-a123-99cf99b45c82","e1d52467-b9b5-45d6-8fda-39f231a21872","c8562eac-7915-4809-8182-5f330811bc40","cccdba3b-2e5e-4905-b4af-bed5591105bd","6aad8630-aec9-45bf-b1dc-c90f2d2c7d48","6fa11136-5a1f-487a-9aa1-42eb05cd5c63","35cb9b50-21f5-462d-9311-77c22f400542","1531bdd1-10f5-4334-9939-171af1d8c209","fec3063f-2a39-4ed1-bbe9-ce16872c1ef5","adbe9bed-df0b-496c-84dc-679436bf5989","a33344a8-ce74-4c23-8e80-3cea3615a2e9","fb691d30-7350-4b2d-b85d-a03f85678ac0","5d7c23c5-cf50-4859-8279-3f0d62bbf929","0352310e-6518-43ee-b717-0bf9cda88cf8","918cbdd7-4a0f-474a-89cb-c0976bbc1d31","124493ac-ca70-4fea-a6a8-0b60a84f7cb8","1057dc6a-c1f1-4a18-a45f-3917d1d64f65","4f771d41-cc18-4e8b-8f53-a1d502bcd8e0","5146f2f3-43f8-4bb6-bb68-72b037be306b","3946a189-6893-4f47-90c5-fffc096ef3bd","4c98ae6b-7543-4e39-a625-7e32bebef366","b74c5c6a-f865-4256-a271-cc7b02e8ea69","fb7eb7b1-8214-4540-8c3f-95a58bd69cda","15f2196a-d8d0-404a-970a-3c5109349328","69c32f8d-910f-4aab-bd01-14754b1974ec","bcb31f32-40c2-4aba-9f06-db3498d096b7","c668f5c9-7cca-4db2-adc3-439403300f5e","7857fdb5-1111-483c-81cd-4ce983bba37d","763fc5ea-0605-4f47-a4c6-d038ed102926","c281d753-2d27-4bc1-8e81-2a04c8d22176","dfde2f1d-d259-43a0-9e8e-f0334ce89369","71bc0126-3466-47f3-a6fe-46bc99725dc5","11a19de1-af57-40f4-88de-d17187bdeda1","c4e19f94-18a2-42cd-82ce-e2890dcecb60","273f1393-fc69-47c4-92dc-0551f8db1a49","27d26483-52e7-41b4-8902-12a330431041","e0f5553c-9a0c-4f7f-9ba5-776ebf33bf5d","517cb14d-98ac-430f-a3f8-1d67ea901c45","c2c08e06-c4a9-49d6-ad6c-f0ac6a3843f2","b0bd93c7-494c-4854-be43-ef001d045ebb","e781d8c0-1581-4b7a-8b88-6e3b8d3b7c7c","614adba4-8c1a-4780-b103-e63373025669","b4b2dee0-93bf-402a-a6e8-a967076564ec","7635fb43-8c3d-4077-82a4-2b1a281e3fcd","e65f6c28-c41a-4c09-a4ef-f65712bc5ce1","89cfa9fc-795f-4c66-b49a-ea89f3094a3b","ec4b8215-286f-4146-8594-6ca2b85053ba","6d06ca26-8d13-4065-8b7e-0e4d7c4f6516","5746f2ce-8973-4e93-ba6e-7e083b25687c","daf30fbb-ef5d-46ff-b147-0b658430fcda","ea20fc83-a43a-4928-b21d-83cd76b69923","4a671c62-92ca-494f-b480-3d7592763a35","134429ba-75d9-463f-b84f-c0f7be4e792b","641900c7-3051-480b-a313-67421b33e904","8413f3fa-7ddd-478e-985b-5b3fc6a1394e","8f115f32-b4ea-454a-b510-769b857cdd45","cd3cd4ec-5d26-42e8-b686-6d989b4bd3d0","dcfb26ac-dfaa-4daf-ac1b-fd789f5c9d67","4f4bc75b-9f55-47c2-bbf0-32740e574fc5","5dd1ca84-b50f-4fa5-8f3f-7b3e14a2b932","d3687626-7514-45be-9e7b-6f7bd2c1e81d","f84ab7dd-9c87-423e-ba58-6623dc7eb151","84b95ec0-976b-46e7-afa3-99555845cf2f","e5f56d24-7afd-4b53-9a98-a661139904fb","8e9bf557-8167-4b0e-8abb-90c037f415b5","740c7a0e-9df2-4aaf-93fd-95f77271cd3a","7e650bc1-716a-432b-ba82-ace5c4e627bc","8b4d2bae-1e5a-4723-a56a-27cf2466800d","84028e8b-bbc0-4c31-86b0-f83009559277","3060765b-2167-4042-801d-765e600532bd","cc1579e9-091f-4402-9f34-a22f6e0a9f86","9af0505d-34d0-4bd1-ae03-75ebf8bb1a83","4589a390-19e0-4ee6-9cc8-cc33eb900664","0e7c146a-0f23-4db0-a169-32cd4fcd094c","d81f689f-4ec6-40fd-a718-370a08cadd93","d14ab472-0955-45c6-b9bc-e8a3ed32e9a7","4d796254-4654-4715-b14d-56b58e793713","9f66d9e1-fef4-4edc-ab17-dffa21092577","4687f97b-70f9-4492-a1b0-6d786510bf1c","e6475e00-402a-4196-86c9-3b12b2316953","8ba021d3-d853-416b-910c-a29e26b378be","ced246b5-5fd6-4ddb-b5bf-092bea3e18e7","de44a44f-aa4f-45d6-a78b-02ca649368b3","b61a1f97-d553-4002-9806-7c2106d805e6","7607a10c-515d-4df3-b537-b2e3ed91793d","051a0826-4c7b-4c23-a4b1-a4b1ecc1588d","6f6e16bf-cd29-4e33-b9cc-f40f4d314496","293b2ccd-6b7e-435c-9b79-01cde11063d0","2305ad2b-3db1-46e3-bf3d-8d65f47b8b28","3dd8e10d-b86b-403c-9558-4ab101f24b7a","70316aba-6247-4188-a3d2-6bfb9a37d592","63349a70-c0d2-4357-8df3-f994b6718d14","096aa94d-8c13-41b8-80d1-ea92490c80b6","63867acc-73fc-4da8-b09f-6242001edf30","f7f0b9b7-5029-41e1-880b-916ea8b27077","ed7e8e50-abb7-46f5-823d-cbbebdd3e3fd","d031ee94-b45f-4028-91f1-eb03c44972c3","29eeea41-c693-46a5-96d0-e03e7470e750","dca2f3f3-cea4-4324-9910-219d4335dc5f","268f8c75-3d7f-4de0-9999-660c78365620","c453ac94-ec4f-492f-b0dd-3605544efe6c","650e723d-5407-449b-9523-e244beae1392","901d913e-ec73-4a99-9af9-c8552924bdf8","cc847271-b996-469b-8e9e-2d8c9de2733d","bcb5dd37-bb84-49ad-876a-ff25b0715958","924393f9-140b-4b19-a753-08eda30a6fa9","7be3c4d2-ba28-4ff4-8a13-16c6c5566228","e807a8e8-9f82-4663-826a-7649eb14af0f","60541015-9aef-410d-9d70-1a3bc1060112","0b4578e8-1933-4f02-8c89-ab35a040b239","0e8eee16-786d-4151-bb36-f1f18efe563c","9fa0c0f7-ddb1-47e6-a3b3-b8308370007f","53922df6-ad30-4ca7-a6fe-4fd8a8f94e3a","a71da87f-7c5c-4ebc-8417-d9ca3d651fc5","415b5c28-1949-4c10-8f6f-1ff8b05ef065","64508209-9b7b-4897-a404-03f101a54b53","4d009015-4b88-4eae-b7d4-fe9b52a587dd","ac9c649a-08f0-4633-b516-3ec9e92827c3","96a609b7-faa6-4b1f-b3f7-7467e236351d","8afb7e60-2787-4d34-9f1d-1023bdf7626a","fdb97933-4052-43b5-8e49-a174b59b14a0","bb94f164-674f-403c-9e2d-25ca1e569049","70926fb1-c70e-48a5-9634-141c2eb6022e","8d8876be-ff8d-4b65-8734-d49722932f6f","28c9caf3-ac89-4123-8aeb-90365f2a6525","6d85b176-8fa3-44eb-adc9-252f2c277869","41dc2481-0c25-47d2-a6f8-11f93ca5157f","bab3ab61-3399-4108-94a1-450288db49c5","d126f0d1-4cd7-434b-b852-0e0b3fa20d7f","edb736b5-f381-492e-84f9-ba130d9fd549","c41b1e86-54fe-429f-bd89-3962b3be0164","39c78374-c529-4389-805f-0e5fc9ac4be6","8fede89a-889d-44e6-bcbe-f5b713681f5f","3fa96e02-d3f5-498a-a994-53943b1b5274","8bf68c84-9af3-4e4a-9b38-8491e33ffad8","29442b75-ec34-4b86-9606-93a1cabc60eb","e54fe7d8-6797-4a30-90d1-4a2665aa3a27","8c8fe1c1-94ad-4bcd-9838-19362622b1f1","d21b1795-af7c-4836-8c9e-c205bd003f06","a5986cee-bd02-4a4e-8ac3-97ba1c5bb0d2","313aa627-2b37-4239-bcfc-c4060bd47fe0","f9629da6-1f04-4625-8cf1-963698b06fea","e7e5e98d-b4c8-4d79-95dd-53dadf650953","de6b2464-1587-432f-9017-5f1a06a375c6","e5fb54ce-a8eb-4f8c-b5bd-7b5e4517be97","54bca510-1ec7-4e00-96d0-7993b90131b9","897ddd23-94cf-48b6-ba7b-4d5fd6da5fa6","136dfcb2-5879-4ca0-ac41-a35af6a17465","64c62d28-69f7-4c5e-8f49-e5c1292a8dbd","6e9b89fb-45fb-4862-ace0-e3b42f164835","b52a7b8b-4ede-4a78-a583-78e72fad7ed0","f67e9325-4f57-4679-b358-7cd6efbdadf4","7534bd59-bafa-4ad9-958d-c8e8c2b187ae","d203b0df-6046-4c7a-bbda-dbf3bb1bf2dd","d04e007d-ae81-49f9-aced-f2525bae66de","fe8b9127-c596-4e25-bc79-b8ce6ad68106","fc1d6fd0-d794-4927-9763-6c26919622f4","519bb434-ce2b-4397-b7a9-a7eb73cf295e","76f657bd-4401-4562-95c3-96e55333327d","5a404be0-c988-4e0b-baf4-cb57cb0e8496","08b12904-8301-4701-8ae5-723e6ab23bdd","a0ffd19d-a1ab-419c-962b-f58e2dfc5d1a","e5c2a1cc-132b-4983-9bd9-69c1d9bf1408","ec5fbf5e-0465-4681-83cc-ca92aee6bbfe","e4f04b81-0bb1-448e-b513-afc91a01a249","fcc5d73c-9aef-44f8-82dc-d1db2d60215e","0caff746-230d-42a0-9e4d-599aa43fa264","5eb87e46-9667-428d-99a5-582a2501b0e1","06b68c85-4158-482a-8397-9bdf330ead07","a1b7a43c-52fe-4521-9849-92de2c95d0a2","67c87c99-a473-4288-87da-aace07d3d463","8ac125d4-c721-407c-9f19-cb9956c90142","9171f91c-1c85-47f7-ad4b-b2de14223ce6","66de1401-1aac-46b6-8215-ea3b8736d024","c532cb0f-f999-4fcb-9c0b-54a4f437ca25","bbd6e34f-d06a-4335-b98b-ce2ef5ef412c","4811c548-e3d3-453d-ad80-4f55cb2ae747","f0d821fd-4229-46d7-b096-43a96a17ebab","5e2f338d-6826-41e5-855f-d188aa7eeaa7","5553afff-2522-4ec1-9a44-25fd9f220678","0c446df1-4d06-43ac-8080-4757100c8bba","636ec699-e686-46ac-89f1-14c8dccd3ad6","8135a031-0633-4ae6-a2e5-ede5ed46422b","ef9714f3-e229-4df0-8f38-e1a2cbe986c1","b6a71529-549f-405a-a480-a32ebd5c9c13","7cc78b77-34c4-45a5-af82-686dd55de037","d185e5aa-793c-4852-9c65-5e282415a0a6","2e8a8c05-f65b-4993-b86d-2ad5232eeff6","97b00593-0a7e-495b-bc0c-9184796738b8","18d9a133-44a6-47e5-b7ab-edc77c885ee2","1dcdb8ff-3ef5-45c9-846c-ec0f357a5adb","13c8da2c-2337-47da-94e5-1cd9ca1af1fa","a08fa9eb-5a14-4737-9921-659b179f58dd","8dc6d7ec-215c-4d14-a62e-65146cfc0903","8b88af55-8c3c-4d6d-9329-f3e6853d0c64","8e8d4575-dbf4-441b-affc-d30e8b325be6","0cef5ec3-c7e1-4529-9189-587dc1a8138b","8c736196-d3c1-4455-94ac-ab0bd91046f0","0723ea82-0c68-45b5-8f7e-d9ff51202f01","a09e43d9-c051-4f46-bf23-392c725639e3","1a6939ab-3a00-4fba-990a-29efa9226fc7","0d8a7a0a-866c-4813-bce2-47e8d4506a82","1e2aba0c-a79a-49db-a5d4-795f0e0adb97","dc228db3-930c-4098-9798-739ac255dd9b","add9f674-dd81-4e88-bfb1-aaa620a41221","ebea7257-a028-471f-a0f4-84b754bada82","b1f7dbba-245f-4244-bda2-3e36d90783c6","6a672e78-4fbc-4f28-a82d-13dde81464f3","ebd01c3b-d6cb-4b38-b258-5bca51121646","e7170907-63b2-41dc-9cae-40ad6847a246","3ef47eff-18cf-4bea-8a0e-f7c728214741","a18d460a-1c83-4313-a584-aea31c23ffcb","ebd80d43-2f4e-4a02-8541-72ea2eab58fb","733571a4-55fc-418e-83c4-623f5f529fb0","8228787a-1b8d-44d6-ace6-6ec9bfb237d7","aac18e90-9c47-49ba-8042-e0c5bad56e19","9606bb63-406b-421c-8f7a-4b86a38e3ec9","16542bd3-01c9-4ced-ac9a-60c990eb0956","f533e814-e7ba-4fe2-91e5-c16d661dfa67","8ac01073-bb46-406b-9200-2a0a6e5c2c47","5c642db3-58d7-483a-99f6-6a4b04120cf4","716c9531-bd7c-4a23-ad25-59803badcf6b","fc49e13b-c2cb-49b5-80f2-d7ae1a752ad6","cf8ecda4-a9cd-44b5-ab5a-e963de4bb1a2","4d8f818d-899b-4c94-9fc5-e4f0cf97cd60","b7ac6dc7-6d4c-4644-bcbe-bef6e5757207","bcc98cbe-6e96-495e-a422-1628ffbcfdb9","1ae4e0ae-bde1-48e9-a17f-9247f5f09a2b","aadfedbc-008a-4396-9f2b-393eab0a34e3","08d0422a-2f2d-48cc-be6e-15ecc7a98c6e","06342c5f-8dca-4fe8-be8a-115f496c230c","4eaa1240-51aa-489f-b2b3-759b504b2c65","30f42d5c-6290-4ba9-aae6-7c9e3db600a5","163c4b96-deb4-4ed4-9ab0-19d4c824ac6c","a84c3e3d-075b-4eaa-a8c2-c4cee1d24892","2ee269c4-ce71-475b-ba3e-0e2641376094","b596ba13-e474-40b3-bb77-ef78a28e7aee","7c4c5d1c-dc38-4f99-bf93-e82d64536119","68a79737-6fe9-4f6f-be96-15b959603a01","3127e36b-a84c-4ced-9f39-beb63731362a","0936de09-f4bf-4159-b910-1dd4a72766ea","59be9f4a-3768-4bb4-bff9-cebecc634dd5","27d7ec1f-46bb-4ecc-85dc-0f2f406ab021","c146ecb1-2423-4b5d-953c-766aee54b819","cc19005a-e6ea-4ca7-9740-9964605f8416","61f2f16a-febb-40ab-9aee-7547f3440e30","fead492d-5d1b-4256-ae7d-03457ca8d88a","e4af5b94-cb0d-4f05-a16d-b8c70675c995","7ac24073-290c-4970-bb91-48a04953486c","3a49f78f-efbf-4acb-8551-20f076245613","af9daaf0-3e38-4ba2-afd4-39f35b47f37f","b2066f06-9c0a-4a6b-9669-4cd5564d8fa7","e0d9f152-60d0-4520-935b-0b5e49d4646a","718de09b-bc78-4c4c-a166-4dd116896f5a","8c0baa11-db42-4649-b3dd-7d2062132cb2","0ea35f2a-52d7-498c-a57f-aea1b802ab0c","1b31464b-1694-4bb8-974e-a2df8db8f73a","52988315-caf7-4cd7-a313-546b26aec3bb","2d10a225-ad88-40fc-ad17-e1bfd681f347","bcfa6cac-eddc-4b68-9569-af0cf615be3c","9b52a08f-b27d-4b4e-a9ef-499831985adc","6469dca8-37ea-4dcd-ac38-3cee76c1fa3f","334ff44a-9d2a-45d0-9a47-7ffc13718d8d","47e44461-7342-4731-9f47-a6aaeaeb6b4d","3d6c2ae1-1314-4f7c-a500-07705dc8a406","35eab00f-6e61-4947-b0d5-37294caccc54","8b05a8a6-b879-41a5-917c-3a06114dc48c","8ea8da24-4946-4b5d-8372-3fa705f6352f","376281f5-f9a9-4a77-9f61-6a1ac5431bf8","00ca6f40-983a-4108-a347-5717f7491f6a","500fccd3-5828-4a8a-b255-ef2bf1d5eedd","f3db0430-9abc-40f6-9825-3de7e8c913f3","4ee6cdef-9b36-437b-a75b-9f91026b187c","76761c9f-283d-4a62-bec6-a171131c4f70","1a8fc43d-3c0b-4204-ac67-6b8800be6613","a74298ac-ddf8-49f3-8d3f-5ec18f5d4b63","8a1f0c43-b127-4834-b6f3-a76ce21a86a3","636977a7-fb95-4a9f-9a69-93b08259d01a","63cda048-987d-4f0e-a8f1-64c00d2ab76e","fc1ebfe3-f7f5-4cee-95d1-6659e6b21dab","32858620-ee3e-4ae9-9f53-fa0f6d13770b","addf7156-ac30-432f-aba0-c22ea662fbbb","52ffcbea-fb94-48f8-b51e-2cc4b18a2e7e","af86b6bc-842c-437c-b822-b4d3083a5f90","e1dc0145-c0ef-4e37-abc7-ee8874d46264","bdd28f28-9cbe-4ad7-be57-d712e9918619","510796e8-6c56-42ed-bc60-d8ed72bade57","1c2dcee4-7dc5-4416-83ba-ffafb378baac","7352d5b3-5802-4ffb-a914-7a78f6031854","304c3074-5abf-4781-abcf-264655a8ad32","4a579c84-87d6-4251-bc38-2d6ac157dc45","ee480bb4-ba33-45fd-bf43-df0efba7b0a9","0f64d8f6-8f17-44d1-9031-8da489be41f7","14315563-c467-4c1d-9705-163ffe37e874","7d8e0a98-cd6e-46dc-a8b3-10411dfadddf","9c88c4c6-d104-4881-9bfa-f955e2c7ca80","e1b37476-14d9-4416-a412-812b0cd8ccb6","e7078463-cfe9-4676-8a4e-27780a061858","5ad72f80-6c38-4341-96ff-dbf0e8a8ec5d","1065d8e1-b7ca-4f19-9a2c-65d511a90e8b","74ded7c2-672e-445f-bb1f-e6a293d134c2","7ed2483d-7c72-47e2-9d2c-63c43bc0ea9f","6f4392db-1ee3-4f84-b90d-204e95485c6e","88da6560-adec-4966-99a9-6d7d6e624fd2","c41d8d1a-eabb-4566-97b8-534dc118fbd5","08864adc-e9c0-421d-b4b4-65c9ce2cfe26","9f8822a0-2492-461a-81aa-1dba93d5efca","b1d104e2-2f20-4659-81e4-e7f73107081e","3c859287-b94c-44e3-abed-b74a504c5446","b64c9bba-95f8-4892-a4d6-3915d602c7c4","926478ed-62ae-474a-bccf-b3c2bf216b9e","473440b0-94ec-4084-ad4c-cc91bdfc41ab","13919b89-e672-4479-aff4-026461221c30","1c04d603-8682-4f39-ab50-059768bd7035","29aaf2d4-acfd-4f65-9748-8d7d5601fa91","778bafaa-63c2-411a-9e8e-4238c014181b","99500af7-198c-4f3b-ba18-c636f331282e","949c7e10-8833-48ec-b43b-9edf7f6e9e50","7c441418-2d96-4355-9cc9-a8797d2ad2b4","5630c5bb-7847-4508-876d-4174cc932aa0","37822234-b54b-4215-ac49-a7d1fcb378ef","7f806742-3e6c-4641-9370-61a51a155770","83f0a1f2-e842-45ab-810a-b6f7d447ec07","084471cd-5fd1-412b-b6e7-0c5db2106ec7","120cce8a-5ad3-404a-b8e1-1ea1ca55f344","b6e9d3ff-ef28-47a6-aed2-165a26bb94bc","7a73d3d3-9f6a-436c-95e3-c2622eb4f05e","5d5f68f4-1cd1-42de-add8-216ebf34d49a","da05edc3-aae6-4698-b75b-8d71adfeedf8","f0954e14-63a6-4c68-8da3-09a3180ba0bc","36476b52-8320-4c54-8045-3fccb56d5e41","abfb063e-ea74-450d-bda6-1cefac709e11","a1bfc324-4b6f-4399-a163-00d232232bbd","186fdb6d-5183-42bb-aef5-0beb08707e13","cd4fe7e8-734e-4d13-b279-b8a860720027","d999b9fa-e0dc-4a45-99b1-ed42120fe4bb","3029cca1-44d6-47c8-a9ec-5e84b670797c","a964d393-3df6-475d-a314-7ce66246ca7b","14ac9b4f-7c38-4d5d-a4c1-8a09212bef65","0ce422a3-56e1-4a94-8202-e426f7441b29","56a528cb-8e6f-49e3-adce-5c649bcd8272","da1a1e10-52f5-46cd-9ec3-f2bbf6856425","a5936e80-7123-48c8-a149-9153739de236","bb0b6976-c73b-428c-a8e0-061a0e5cf220","d3fd7320-4cc5-4288-ab05-5e4e0d1448c3","566005d9-77d6-49a1-8b4b-a64d6d69b1f9","ba662787-aca3-4f4d-80e5-e456f791b4f9","a5a03023-b849-430c-bf3a-3253a360addc","52047170-77eb-4ae3-a0c2-b1842105c60d","ad384540-2295-4bb4-b001-cc09a5ba77e7","856e8091-4930-427a-b7ff-c28bd731a7f9","bf656dc0-b67f-4686-8d62-e1707411541e","cb7a860c-0086-48de-9ba5-1188f8e58823","ab754527-dd28-40fd-8bb5-a857cdc171ad","78841fb7-fe0c-40c5-b239-02e13ffa851e","d525ae18-01fc-4857-88a1-4bdfd0bc3196","0f361cdd-5170-465e-9c96-880548132b63","720186c7-e092-424d-aa0e-4ffaa7444db4","f762f958-1294-4bf7-affd-b1fc3b44e4b8","bef87d96-4d68-4249-99ae-6165cd9aaea8","d3253b05-6739-4c43-a81b-0775eb99d94d","4512649a-6e4e-49e9-8587-041d68cd6575","d9e834fd-32b1-4da7-8832-0f8d137cdb91","59c9d8e9-0e3c-4a10-90e7-8606c727ce74","43f14021-4977-423b-873b-7252b404f885","370fc8fc-5784-4bd6-838f-1777e8f02e71","793853a3-8250-4d37-8963-1c5d6b2f4832","a335af30-16e2-4ca3-b111-3a8fd4dd02ba","c1952fd3-383b-485f-8e8b-a181552d9793","b4d49ed7-499b-49d1-bbc2-14f2e8c8826a","b7f55de3-0790-4201-8bac-c9cf9acd616f","c141f32e-d03a-421c-aa27-800cd66b519e","88caf671-5ab3-49d4-8244-cd79aec1ea7a","ee5ecefd-2201-4797-85f0-f6bc809b4f1f","782f6fac-8883-4ea3-9ed9-22d5486d148c","ba34a00e-b04f-4964-adbb-cb9d12705ba9","4aa2de2f-e713-4e85-b3ba-e3d8ba3ba809","1ca2ac3a-4771-47a8-9faa-485f56388c60","151308f3-7cf0-471f-a2fe-0cf719e9aac1","56f7ca75-fa08-4a1c-8ddb-e5efa147ffb7","2511527e-cca1-4933-94d5-b6847fee243a","b71abaf0-b464-40e0-b65e-f3dfe03d647b","5374acc7-9a22-49fa-bb2c-9dfb6dc693db","98e4f807-3471-44a5-8141-89ba34651dec","1fedd8e3-1e5d-4e4e-967d-9661b956bf0d","bcbbebed-d621-446a-b6d3-322f93e8244c","37200585-f3f6-4872-b82b-5983283f8427","2cb8bdbf-9e6b-4685-9ccd-af78b6c78775","fcbe99ca-10da-446f-9080-35269990ef23","3fbadc32-fa69-4ab8-9c72-b4f76d0eb5a3","e01f916b-d5b9-4690-98f0-342683a136f4","4b8535d8-4228-4279-b2dc-27956d4006f2","b1582b6c-2181-4ed0-a1df-0dd16ac44912","549ed09d-28f9-4b03-b1e4-2b06a28c0a5a","02424777-8170-418b-9d0d-5a6940fcdedf","9886b9a1-b19d-41af-a184-82828ab0fed4","34f28387-6167-4044-8668-f9851ab21655","759c0e90-0051-4366-a980-2022ffbd5ec0","253e8ce9-dddd-451f-98b6-573a0ccd6138","201abdf7-acbb-4b41-a714-291c229d1a7d","ea38ad7b-729d-404a-b144-7fc906b9fe3c","3ee35f97-a19c-4044-838c-21dce655fbb9","d3180f22-6d13-4dd4-91b4-ab2c4aea35b8","af6822df-01ea-4d11-82b7-fe0e9dba4642","644ae6f3-6fe1-40ba-8778-43e77a076e95","d79ec601-b90c-42fd-a275-ad9fa9160a0f","238a33b8-9cf1-4a3e-a5ae-dc3876bf102a","4882ad6e-cd22-4f96-b5af-60b1e3d363b5","f4c945eb-6613-4e71-af1c-641eedd9c28e","763b455b-45a2-4664-b56e-33f47f58988e","4549cade-f1eb-40d2-b54b-199d824b8f99","4661e72c-8042-4659-bec5-b05f84ccb8bf","b4f0229b-e21a-4304-bff6-44aad3472b22","73e821b4-eda2-4489-b291-d3ae21f81650","e571e162-6e8b-4f86-8246-059d85f44a99","ab43f5fc-54cc-4488-9d71-2f5b10bd0f4a","6a485c1e-ca64-4e66-b4fb-361446b5b4b5","fd6f4097-c3c4-46a5-885b-9b0b5ce2b096","1734e70e-b439-43bc-a605-77c35f119633","8a67269d-3415-4f73-b6f2-0e2c64b9040b","13f9c6ea-343f-42db-a072-365784f303ac","de22bf5e-fc70-4309-b9c0-3e2251109702","d2997cae-aa9a-4979-9b1d-6484db2995c4","94e080f3-2cf6-43c6-90f4-595a15f86565","d1c46591-64b0-448b-bbcb-c06f15323d99","05e6e04b-4d3c-40b8-a9a5-68afdc2a2611","40012e49-dceb-4ac6-afcb-e199ccaf0a9a","3024f660-661b-4e5f-a645-92b56bcd13d8","a38b36f0-72dd-4aa5-810b-897fad9a01cd","57e1886e-8895-4676-b2c6-9845b0f6fd5b","004d5c7c-91e0-4063-8176-0c2c13f6d88e","77520caf-f3a0-42dc-bb20-790d680394b3","f6adb770-f561-45e6-8498-3a760c301669","698f7eb8-fca1-48b1-90e7-7977f13277f0","21f9d1d2-3752-44da-bcff-76f590c514cd","fb0074cf-7acc-4fd5-9e6f-5d885baf1871","986a27a7-6554-4106-8d32-dca96a406326","335cd61f-24ba-4998-b243-69312f351c1f","295de188-af8a-4616-a2d2-f8f5f1f16438","49010cfe-5ec7-43fa-b6af-844caac7783e","2ba368fd-a3c0-4845-a423-534478e34ec3","e75c97cf-69dd-4bc9-a9ef-0e9a873e371e","f471c882-1227-4b5e-b969-4ff295ec17e2","de115619-33dd-42b7-9b03-7263e7cf05c5","cf3192ee-649f-4b04-b8f5-d63623006794","ab26685d-8e03-4cb1-8a1e-9f0466b25ff7","7ffe43d1-068d-4496-8283-f336a2c63255","c824ed71-e7b9-422e-899c-8a33d0930b97","38218d38-c702-4afd-b4aa-b213573ac802","e6e0bab0-550b-4841-8ab0-b77dc677d63a","d8d4b361-4e8c-4082-a473-cc10fd5a46fc","9272b828-c76d-4f9e-b225-559014d3b276","ac8f9a8a-454d-4884-b6ad-8ad5cfd03105","054ddd9b-58f2-4500-b0a5-39368b35bd4f","0252e8a4-74e9-418a-8782-785d6a953cc2","3caef73b-34cd-4a42-bdd5-19a28f71835c","2751aaca-fbcd-444f-8348-4500094e8ae7","c32669ad-0c21-4099-9f0c-d619c0e5e92b","dc75ae41-59a9-4c44-960d-d0e423a3f02d","a13b2250-f904-490e-9402-e6e821888b7e","acc4e56d-e760-4c9f-bf24-a47ac461891f","168b2bc0-cc5b-4051-a222-925f31790d5e","dce55866-ed2c-4aea-a71a-1fa065bc3876","3de935dd-3560-4860-a97b-4b840d1b821b","5e147c27-9b5b-464e-b24d-e7dcc8552d8d","c1581369-dc84-49b9-a481-fac96ea44570","2112aaa7-6ed4-4042-9dd0-c704e2a21e4d","b9431565-c638-402e-ace7-4d41475c41dd","3c3d66c7-aa18-4753-bb88-75f4e93b5fff","faaac612-891d-4fad-a5f9-8516a3a5c517","64fceb34-03ed-4f71-ae64-042be8bcf317","e2e236bb-78fe-459a-a102-e075d1d75b7e","67bad373-6c9d-4947-8ee9-421730dbe1dc","fd5df839-d611-4f1c-904f-59893d9d9f43","cda8a124-25de-43e6-8d03-22b8b0211d1e","2b494f44-36bb-4378-991a-9233b196e9b2","9fac17f0-9f68-46cf-b247-268ec4352ef7","5048cb7e-20ff-454c-865f-7cb36ca05e0f","4aec5c86-dce9-4520-8307-1d05e33e57df","68cab9a8-29b8-454f-a7b9-456f7263b470","6ecf3de8-afa0-4d81-b30d-2f63cfbe6e4a","b18e7d85-9ab8-499c-aa23-351cd7d36a47","68642aab-22a3-47a8-a192-1a0a0a531e01","051a5f90-eb45-4a3a-bb65-0da628bd3487","bace4129-ab70-4187-a016-ea6c57cd64ed","269fe844-7f09-4fc3-8517-bca5c8ce82f4","1c2ba1f1-9766-4e09-b310-cff994145988","c4a56129-f2f2-4e16-90c9-eae653f63850","f5ad2b81-fd87-44af-b0cc-61a856d84997","418e0734-0028-46d9-8de9-da87d93dd5bc","ad9b9e69-67f5-4bd5-b70a-635b65a0d256","62382a06-e6f3-4d47-8f15-1862105caa82","244e1a61-0553-49f6-8543-6481ff1d99e5","4b59389f-12eb-4b7c-b1eb-919dbf6ce691","2a69aea4-c45a-41ef-bc0b-08dce6f7d77f","8ef9ea47-4efc-4dc6-8a9c-c96cab993930","fe60f146-ee06-46f1-91e7-5711f1552682","7420e488-d2cd-431b-abc7-0e1f634f88f9","53d9b4f9-b61f-4d01-9ae1-f2f5268ace16","ea4f2fe7-bcc2-47b5-9da5-7e51e920bf55","4ceb403a-9df6-414c-acde-dad9674927a4","4f047546-1d84-451f-b026-f867ffce1d13","0a56e6a5-f284-4961-b815-e2c47652f11b","6c39588b-228b-4bf4-ab28-21c817465612","e2106556-4bda-4970-abe5-57c9fbda6a0f","21d1ffd2-ee13-41b6-a880-7bd2c2083908","21fe71d7-9985-4a3d-b339-3ea329a2ed16","405a593f-1cb0-408c-9dd8-33a890608053","84ad38ad-54fb-4eaa-8d5b-face5236259f","e8e062e7-499c-4e2f-acd4-4ec9c4b9a841","3972471a-28d2-4c5a-a6ab-cd14656a57e5","87b9f9d2-0777-4875-96ff-c23dd7fa9472","bb411f58-b861-403e-8167-81f614679c94","6ffc08d5-363b-4241-9f2b-9dacf3827b4a","8e5d090f-0c56-485f-9f14-431bb99aed17","9af9d661-7502-4fb5-979a-dba1d3f8dc7f","768db58b-1b4f-4ba7-bdec-8629857cb0f1","06c8276b-9ec7-403b-a612-3f71fcaed723","e00e8109-f697-4190-aaf0-e119e0e79366","25e31b78-0ae9-4f1c-9de0-dfcfeadb800b","f6c53c17-20d9-4831-89bf-fc2ad68866cf","65b4b2d5-a190-4373-8db8-a30fd3593f1c","ced6041f-46f4-49be-88e4-4c16b9c4b172","2c903c2e-97fb-4a89-bc4d-f706f7a5a7bc","94bd3c28-8c94-4397-bd81-46482a46c935","f9f8b03e-049f-48e0-b52a-3023d8e96b26","b907ae23-6fe1-4bc5-8c6b-4dddf7b4ec01","ffbb16d9-1c5b-495f-84c0-9dde8e99ca67","732771d6-4767-4b08-9282-6070fd925dc1","fc512dbc-b96c-4dcc-9f5c-ca66d1391e96","059d0905-61e7-4119-94c9-5840697cb363","cb781bac-b144-40a4-96ad-07972f6935ab","cda9c36b-2ef9-4e44-a84f-6902a03eab0a","36bb0afc-e1a1-4fdc-93c2-01c3aec15494","22f97cc0-459f-411f-b9e9-406e4800ac02","7dd4fb37-a732-479e-920f-94064592d749","b4264910-e6bc-45d5-879e-ff8208dd75d6","d59b04c1-e2bc-496f-8213-91aea5a96595","b8192768-875b-47fb-988c-fd8c988bdc34","fe2dd832-d77d-4c03-b258-5aeb0d7e91c8","9be4b41c-068b-4b91-9bbb-9896542d65a4","ff36fc99-3ea0-4f91-bd52-1d352834a742","16138a12-7ffe-4c44-84c5-2a7eb22c0b7b","20f9c603-0aff-439f-9af1-aebce84504c7","c228bd76-f043-43de-8bb5-4d1bb638d1ca","18031ad8-35db-4240-b055-bda4f7d34a83","89c61673-5617-4c9b-aae7-756065333d7f","d89420d6-f7d2-4285-b510-f1c4e061aea9","47f476b6-5b0b-4ad7-b667-2215686432d0","fd6437d5-99ba-4eef-a38b-c5367c36b8c3","c198448e-e45a-4221-8b8e-f4698b5a0118","ac3ea99c-fa64-45f6-817e-919fa167d1dd","b3304b49-c166-4a7a-8080-ef3823989c85","e98aeee0-bb0a-4aea-bd04-e116608325ec","89ef9120-1d9a-4ee6-9f3a-9eadba2beed7","ac580f9e-c59e-4e27-9508-9f1a8304e993","03c6c56c-2568-4c06-84f2-af3484138135","8c15beaf-6a8e-45c8-9d24-3b90e6d1a63a","22d87be8-a447-4f8b-a9e9-79ce34e71c1d","bbaf5c40-887a-487e-b62f-6d31ce3737c8","ce961946-aa38-4b68-83c0-701acb99d60a","9216ecee-db8d-4e69-befe-cc9f432ad42d","9f28b0ac-c4c0-4f5b-87fe-eae1ddb5d247","11435906-4130-47d1-bae1-61b6beca03c6","f11e7f98-d280-48ec-9e6c-bb854d3c9f38","d138eb3d-3fc7-4ba1-a3f0-a30f39d028bb","6f8d0e89-27c4-4805-8880-9ff47a7b1357","895ba69d-c621-4ba3-b447-6edffb5487a8","b774e3f1-b52d-4c57-a4d4-4dc50189b9c2","3e8d8c90-ab30-4513-9a56-0d983ce783b9","ec5a6c96-ff8c-4d88-8eed-a1e272755f31","4309e806-94cf-4ed6-9c5b-4797388c0dd0","de3e2f74-224b-4cdd-808a-472f6d2943c3","41852c99-2cbe-4abb-8c85-cae078d0bd30","8ed6ab03-25f2-4cf4-9d11-769d52da76f5","2ae3a5d1-cd71-470f-98c0-1f86e967670f","f05faead-6017-4ad9-bb71-907d4b075fdb","32373590-8535-40af-bff3-90f28df2eb79","27c7f06c-2a09-4131-a6ab-b45ea790b2b6","48079981-08fc-4fa8-8929-f3f6215da7fd","113a53b9-4e80-428e-a290-c070b093d91c","93ed6a37-23bf-442d-bca7-c551e099fb86","77a50986-44a6-40fa-aac3-41691d338a33","fd0e39a2-8808-4991-8df8-3b29fb89da58","8d635235-bc7c-4358-89f0-19989250eaae","d47b1d4e-c897-46ae-9bb6-cc06d5316463","c512fbd5-fd87-427c-b3db-76d812a25ae2","58c451b7-6436-4db7-a51f-581166ac5934","d5db1ce7-7337-4837-b74d-14c3defe2924","1c47db84-b5aa-4e4c-86cf-0ec15d0a114b","22eaa81b-b92c-4380-8119-868f735b42c5","5b40260a-b770-47ab-85ad-7840563effca","25b7e04c-69b6-4c85-a57b-918e17fed563","a98a0aed-7f85-404d-8698-cdff6891467e","88cba545-c60c-4fb4-bd14-53f947081f13","b62a68e9-b705-435d-8f85-2dba3008071a","5c6d1ad7-096b-4f66-a6fc-acc1456ebbe4","8d7228ac-f1dc-45df-ad3e-c53bdfefdc66","0181ef85-7bb4-4408-8876-4221140b7891","65a31fd4-069f-411a-99bb-4509606e89f8","cf3214b4-fb43-4b52-9cd4-544fe2f9f177","d21e56b5-215d-42e5-b8eb-ce3e3f3bc473","98caa237-af2b-45cd-92d6-63585632b436","b63cc2fd-8505-46c6-bef0-51b7f5f36b5a","d74b10fc-e4b2-428f-bf91-c81f2b260460","ade8545b-e114-4ab9-aef3-febdefccad8e","94af17a3-3bd5-43c9-b791-ebd92ddb4877","5683a9fe-3966-46e4-872f-a1e0f0c5f58f","6dc47925-64c6-4c9e-bd91-bb5c3347703c","54433101-b829-47a7-a4dd-8e158937d5e7","0cf95e03-fc1f-403c-94d8-b9db95f6773f","3d2708f3-7430-482e-beb8-e205d9662de3","e6f9a53e-5af5-436a-8628-38a8f55aa9de","5b192bf5-6097-4137-95c5-7317ef548d9c","d7c7683a-cc99-4dd1-b660-06179c5a5f38","061fda37-d08d-42a2-a203-915aef6107f3","4ee11ba6-9d55-424c-be07-3cc5c2f725fd","edb6b62b-ae23-437a-bc18-f097ab588112","aa3ffaf9-9ce5-48d4-bf0f-26438f2869ff","96e4cc7a-89a8-4936-a586-6d06e634f825","21ba7d76-c860-442a-bd26-4f32ee0c301a","98f1ee95-4846-44a8-938b-18629892d24c","aa274ef0-31a5-458f-a740-237940f9b92c","e953e58b-b6eb-437c-990a-15428080dba8","fe99cb30-a07f-4bd7-8ce8-b194d11df33d","f63aad99-2483-41c4-afee-a78fe49fa279","53d2069b-02da-450c-8e96-9e275c90e4f5","2822444a-b960-4065-93f6-88183289c4ed","d3e25275-bf9c-4827-9d40-91c9e1f337e2","7bc25f22-9d3d-4214-99b2-bcbed2eefa20","9fb69c55-2625-416f-a3dd-be3cc1b1a8f5","a0e96941-607f-4ba0-837e-979f01a4c942","a85d743e-fd06-4f63-bbee-22e729023a4f","e565517d-674c-4a79-9216-08f428cdd104","3b44b257-d666-4943-9012-f7bc72d8dcdc","04a951f2-f609-4fb3-ad53-5fad8fbc2cb8","fbef9324-fa10-40c0-ab26-74b210c08a04","bb7f4ea5-7c81-4e34-a493-814f946f173c","b4a5912f-1fb5-4911-bacd-1795cf60532a","b584caaa-7fde-4976-89ed-f70465097363","ef40afa5-bb1f-4bf8-8577-caa062bd9444","6e70f47d-29a6-40e1-b05c-724c92f10255","1a94a0eb-b15f-4397-b1b7-7e0c1bbdb270","48a81876-dcc3-41f3-979c-b38d376431d0","dc456c83-a834-4eee-9610-b7fe35f2a054","4fae9437-83a2-44d7-851e-b69bc057f059","2c425724-ebf3-4b3c-8cff-8d5bc4377366","6d38b5a5-32b8-4907-871d-86c0d9118c86","c8fe2eb2-d53d-4ca0-a719-060aa245659f","757ade14-86fe-427f-8a79-c16ec5e44211","3089dfe4-8417-4d15-8269-69571a4a7932","bfdb478b-53e6-4554-99cf-02694994fdef","a8830c2f-57d9-4f01-85b6-bfa303b9cf36","44280f40-c273-4dc1-bdc9-8edd846a1f76","36f312de-73e5-4709-9a25-3feeed74b5f7","dcbecc3b-4e63-4ebf-8535-39e56b528fc7","70543a6c-e941-44e5-90f4-1b7a986d7963","d357a763-2240-4ae5-9768-f2cdd77fdcd7","919f03d7-9a96-4320-a8e3-8706171972c3","d3458956-bdcd-4015-8611-84ff2344b8c8","9552b7b3-28b7-42bd-800f-77c8739b1981","cef23890-629f-4ae4-a2c6-6c6403357ead","d6052309-e19a-4017-875b-04b9fd0e51f0","d7e75f30-a6d5-4b9f-bfbb-282c8785be62","5f5bccf6-2d71-4e46-9a12-c64fbb531c03","5b05e727-cf73-477a-a2c1-4cd36320bec4","ed3e16d0-816f-4c84-b5a9-2c10e2a3802a","71db9a67-0876-4562-8c1b-179a65115410","f8bfd14e-e219-4348-904d-5975d00d7290","271aa7ff-cbd1-4b41-9fc2-10b9163f78d0","9ad7b9d9-f47a-46a9-81f5-5bebedafd940","dd060bef-0e4a-417b-9fbf-45c162f16feb","23def8af-f5b3-476b-bd44-434c4bfceb47","e6487631-9726-4527-b680-1fa79cf08385","07bfcb32-4328-4171-8ece-9b8cbef99b53","ee0b3e92-2856-4ade-97e6-7782766d14e4","4d810ac1-2145-4ca4-bb78-0290653ab9d1","4c897c93-36cc-4da7-b41c-1ac6d964f9f1","38da4fda-881f-48e9-a485-178a19ea74b1","126965e0-89e1-4020-a260-cf024e5b154f","1f1fafc3-a88d-4cb6-b9e3-f96665ac84bd","e5ae333f-0c71-4c0a-bb2d-f19e1ecf5cd6","79ac0c54-ce2c-4a2f-9642-088f7b68a857","6e090138-9f27-469f-bbd4-a19c0bbd4ce6","d96ad819-1c63-482b-98aa-b63ebb4bfa6c","e3e0789b-bf5a-4d88-978b-3ac4122ea142","ea1f91cb-6274-4df6-a213-8af71cbfe38f","46349c64-8d96-4ac1-9aad-2fe8c1a28420","891a9544-fbbd-4c31-88d2-b5b5fe337148","74768844-4bdf-4efb-90e3-b09ab8fbfdaa","d3d9295c-49c7-4f4d-980a-fc3826048a49","482953b2-d001-4279-9365-33672ee77322","317557b4-ef5f-4066-947a-fca75a0cda40","cd2b8e0f-50aa-4c2d-9e76-bca9ac342c59","a13850c9-c19e-46c4-a6f5-c61b222dfc94","a628c6ff-c6c8-4923-a5dc-3135f1a3274a","45a468bd-1104-445e-a82a-3fe18ea54cf2","d4528ea2-e7e1-41c7-b899-a414f543bb0f","9fe3ee96-28e1-4136-a324-dcb06f7df02c","5ee9f3d1-6b63-47ca-97ae-cd7b6ff81771","3785e411-8e3f-405b-b7de-a09e8039b0b6","e57cdea5-e4f5-4bac-9106-86642616137e","fb2e389b-e74c-473d-b7bb-91ab0fe98b9a","1f4f7895-067e-4514-b113-0409bdeb737e","24d617e1-8c9d-4f1f-8cc5-4b40f16afabc","44d684fb-3b63-4136-a5ad-308531ebe8e4","6cbbf1dd-f913-4e41-9d72-606cbd5ce137","ac42b55f-abe0-4416-ac17-6d92dbc28d4a","1fb2b439-e0eb-4d59-844f-4ca7cde0285c","b8570e57-a89f-4625-8e0e-825ef94b13dc","428d62d8-826c-4013-afd8-1754573191e0","06a86ee8-1b02-4b24-bcd7-d78df15d2ec6","3502196a-deab-47e4-b9fa-44b52a0d9856","6ad4b17d-ee91-418c-9c6b-85188eff8a24","1aea77d2-5fcb-4305-bc68-95a97195b49c","24f74c4d-e42f-4247-b0a4-b560e720bc35","23c17109-7496-48cf-8114-9cea835d1e62","58d25cde-05ed-436c-98f9-ca07fcd73446","0bd24678-240e-4441-bf77-af8b4e7e61d5","d0bfa200-9c70-4c68-91b0-cfc0c0882893","cd6262c1-9f2d-426d-8a50-bf863de2f6e8","d0022001-d20f-4ab5-bf9f-80811e53bc06","9576dcce-9a1b-454d-8a34-441da18ce345","8dbf1929-e27f-4542-b983-edcbee9e0e58","39c74af1-ce60-4759-beab-2bea0a9e91b5","8aed1c5a-e13d-4635-8e8a-a10465b5cde6","2eefbdde-bc80-4507-8894-3d7eac56688c","814ddefa-d452-47cb-966b-f2ed39106f78","b708a354-9087-4edc-a9a9-379c0445c518","6ecacbdb-13e3-4dae-886b-2e06c20bf750","b81eb65b-1068-4877-a885-508c41e6eb5c","ebadf50c-6e35-4643-bc8e-eac09dd4e47d","4e178ca9-6fb1-4e78-9cf7-b9179c5a5014","f523ce1d-c598-48b1-8345-ecc61c30e82e","f8b46b79-d442-4037-a2e4-245b1c0c6841","adfdac44-32f5-4891-ba0f-b2d923a1f475","f3618e42-1cf3-4409-916d-1df21aba1c22","38133021-c35b-4096-a2a5-e2c355879795","a2046789-ce3e-4f75-9872-20350ded24f7","08fb8d19-9ca8-460d-ae95-2e177dda16d5","b5c4dea0-be92-4227-abc1-7ea6464caead","8e7e0890-c6b9-4d78-9cd3-c237a979f229","97da2dba-1292-41b2-b615-2145c6c99d26","20784bb2-23e4-4738-a9e5-655d68581f6a","e52df1f4-8ce2-414b-9dc5-49956d0ce116","99fb61ef-e8f3-4c34-8396-ac76a6ccb35e","553f48fc-7c12-4b12-bb5b-5015200b3632","f00e864a-3bdb-4e1b-a467-04020759ee53","56e5e15a-c4ff-46c8-b6a4-8de7f8067e24","dc7cf287-7068-444a-900d-1880dde83397","1d0e2bfc-484a-4264-b786-d08b823f1ee6","50f55a19-325f-41da-8fc7-2aef4e1bad0f","196df16b-0c15-44d9-a1c5-ff101568ee1c","471ae81d-ba7b-40f2-8547-d8735c9a0060","e222a7d4-fa0c-4812-a148-1f8011784819","b55ca0f6-bbee-4bae-9753-b944e2f4a4f9","6e71e4ab-4adb-4971-b86d-41830dab92bf","64d9f376-99ee-4281-9afe-e151342b712b","48ced589-aec3-4cef-bdcf-b5f4818134dc","11d24879-e2a1-461e-ba09-14d7453270da","827f8de4-223c-4929-9ac5-5fa232d09745","d21afe2a-18ee-4ad4-872a-f50cd83c45e0","93509cfd-82de-4944-ba02-7a647a1ec3cb","cd08e739-6d75-4936-b48b-35d3825c5adf","937dbe15-cafc-4dae-a0d3-5533edeca1cc","7cf7808e-15b3-43cf-8131-4799db71b0f2","9186492d-f094-4a8e-b49e-5201e8028171","055d9bbf-18b7-4af9-b46e-e5990091afe1","4f3e4c51-41e3-4b8f-b1a0-81a1bc058856","43ce96ef-9b20-4e8d-8b7c-fc01b0a3fb2c","a57b423c-39e3-4634-9238-dd7de5d905fd","d65828bb-b938-4496-a13a-3513636e16bd","dd9ddee3-906c-4997-9e03-888dcf21006e","a35d54d9-1a0e-452b-b42d-88bea7189068","ca8eca1f-78ad-4e5b-8af7-d78688b13567","dbd05e41-2566-426f-8f41-e1a6f64f505c","81a2444b-1296-4abf-8194-27a5c98bc7da","9c70b98e-5322-46da-8e28-7545ebe6793d","d932012e-11dd-4606-82d3-4260d212ffd5","cda81f68-6318-400b-baa1-7b968e6816e3","2179eb10-f62f-424f-b5cd-21f482aca30a","935b3334-1387-44f4-9ee2-1881a7238ec4","a1da81ce-c5b2-4cd9-8b69-340058394eca","b4221534-851c-4f3f-ad76-cfdab24fbb53","e4ea0481-ee6d-4d21-acdf-5ace685826f2","2cab4caf-05de-41d8-a512-d70db4f80c2d","d9bb9413-e93e-455e-afbf-5390c80d63b7","0c5ab3b6-b042-4a0c-b90e-b013415da388","ac44a7a7-7c0c-4a9c-b638-dd7669ed330e","b3e756d6-5880-48d3-91f1-f7086b2d499b","15516bf0-bc7d-4ffb-8989-9704662cb8e9","befab0b7-f6f1-4b9b-931a-79fe1d488363","49b5581d-ba4f-408e-9f9a-76ab6a9c4929","8ad153eb-fd6b-411d-b78f-9bf8c6c138dc","8db460d7-8008-4258-a661-bd9730f17a25","e707c886-393b-42db-867a-89f41b742c8c","68409aa7-f854-48ac-b68a-bffa978d900e","c0433985-bd82-4758-a997-659aed529508","42708053-325a-452a-bb33-999f21f6fd5a","c1bf8899-ae2b-4081-b723-f987db67cb52","aeaca70d-40b4-4696-a36d-6b3e2f0416ec","25891ac0-a58b-4129-b3fa-9215c15af4b7","bf048425-a50b-4260-b6e4-4bd05cda79a0","edf4ea1f-0c68-42e8-9c27-ef31d8fb991c","84e8408d-b36b-47f9-97f7-c183fa4d8a12","aa816477-e0e7-4635-9760-82749cd45ca0","e07ebbd5-91a2-4fd4-b0a1-ad08bf11a5d2","33a2af40-5d87-44ed-8e10-e83ca04e2495","67e035e7-cc71-43fe-baef-c77d59dd7820","0549d721-c224-4f79-abf1-120600b41882","1601a331-6d53-487a-b528-3f7ab4ef9c20","ef628c11-f19e-457b-a094-72b45182e779","d6efdfea-6ea5-4828-a1e6-4234c365b124","e95ce888-7f9a-4702-a715-514e1c21228d","b3cb2ff2-3417-4499-89d4-f6e4585018cb","d37ebe63-3b67-4a05-9936-752a2382d987","cfc4fb4d-8221-4d3e-8977-39d9d0305867","69e4a809-acd6-4502-a892-b8432d6bf145","e2228b4d-fe5f-4616-8fad-6085b284b471","85bd724a-2bd8-4b50-9df7-9701bc89957c","3ffab80e-6d29-4b5b-94b3-4e67ec4a7e56","886514f4-8d0a-41f5-b8a4-4cb8b37f7fe1","b11592b6-4057-46eb-b9c0-14fe727e8175","6c308ae6-7614-4475-a3dc-5dca96c03acb","75bf2126-729e-44b4-91ca-2c4cec41bf1e","1473ac5a-4036-4496-957b-74f7362e169b","138617a7-d3a4-4ed0-a749-0d5746172dee","a9f97840-4a6c-4026-828e-6725175d1cc3","92561f94-d949-42ed-9936-7b9d0828b14b","e2663684-6671-4866-aba8-e52eb4712954","7d5c041f-57bc-4802-8151-a6e734cd3373","0c07b998-063a-4783-bbea-a097eaa002c1","0e807d77-b81e-4f54-a5ae-388b37b69d8c","e43fd528-df18-40c0-ae6e-7f1e10540f34","baeefdcf-e0e3-4d7d-821c-1192f480fb07","b1114a9b-06be-450c-8954-cf8fd037d8e2","6032ecb5-eb57-4937-abf5-e91f58cc203a","305763a2-d077-4fdd-a2c3-3a9a806551cf","75532063-0780-4ac6-b0bc-61948aed1a25","6d65bdcf-bb13-40bd-939a-93f1e074bd0c","0fbeb766-0d83-44af-bfeb-675b36ea0ff2","8506a0e8-80c1-476b-be1f-44bef18d5211","e1335c9a-810e-4e53-b4e5-470566623908","41de7f8b-ad2f-44a7-bf19-94c9b9ddb1ce","81fb8eb2-10a5-4c6b-828d-d9baf7c7c5d8","ca8159e6-3d16-47a7-9c76-5679480669b6","054ecef6-61fa-4b7a-8b56-63db1b9e146e","7e93b4e9-a194-45f9-ac40-b091b0b02fc5","c5826695-d54f-45f7-9ebb-ae8fd43ceb4f","756ce12f-b2ae-48b5-ab80-1dd01f8a1c38","ca9d56f9-77ba-4ffa-9037-642a9d5e03c0","aef73c78-7fef-4428-9740-dd68a753d13a","96c0dc8b-9ba1-44a6-95c4-a45e8892340d","2367523e-6160-48cb-a491-8e3919de141b","2d25c866-5fa3-4aa3-bd45-2cd9b54af3ce","f11f28a1-58a8-4599-93fb-b045e652da33","db20699b-98c2-40b9-bd79-07d2dfee3e40","b3c7e1d7-776c-4b2d-b22f-343861cd0cde","4020bf8a-73ff-4e59-93c8-d22193c4d8b3","ca279b36-7f79-4e32-83e2-5e8e8c8bbc37","98f565f2-22ad-45e1-a1af-8fb30395591e","92f3a236-9871-47d0-8a2d-aad115366881","a87c2901-9c31-4b08-974e-c95a22729c33","c6f3b359-be86-4fde-a004-a08c85937387","a655fe10-be2e-4cd2-9b43-6dbf899e7f43","3515f7f8-0765-404d-9d13-00f54ab63db0","5137d2c4-1357-4c5b-9e84-caac29681527","40d08365-d1cc-46ce-a53c-1ca1329e05d2","9b983957-cd65-43c4-ac10-f3cd77007242","d64876ce-ee79-4c1f-b81b-7bf392c0b911","19652e11-d457-422d-8c65-7898083d67c5","6749cbcb-d06f-4a87-b011-9e73e068e487","83198e3b-ce78-47a2-8544-5fa882064f8c","53ee8704-a3e8-41ed-9501-069146c85f58","9be5745a-65fb-40da-9979-5e44bad2d9f6","95acfb8e-f524-4a39-8051-7d17e4114517","a7ba3fe7-4035-4157-925d-e2174c52eec3","8045e071-0847-4048-984c-14f10643ef68","7b2861d8-277d-48bf-b160-60a5314ee1b0","088d7f60-b302-4aff-87e8-c89c97deb1b2","dc0bb85b-41f4-4f0b-9313-034ff49a23bf","19b26753-a395-40ad-bad0-e64449b35822","d10a1269-d0f2-4517-b3b4-d068260e133e","9e03857f-b6cd-48cd-b7bb-5721b5df0ac6","50fb873b-3fba-4185-a3b6-2790bb307698","163fda84-f6b0-42be-a3ae-d7168eae9966","8aded531-1801-4f25-9086-b442bcf8d129","f95bec81-f2b5-4ee9-bb7d-2bc2f61eb34e","edfb3f06-cb9f-4f2b-967f-fac532c37a9a","4efbd083-2346-432f-9159-dd4093667982","b2068925-2b2a-40ba-9743-7fcfebb2d94b","ae2cff30-aff2-4543-b3d4-967341691c04","7a524f4a-b1ad-417c-9f26-e7c7cd58d1d9","ec32179a-3dd8-4630-92f4-0361c13115fd","c7592e40-8075-408a-9de8-33dde74a2a17","0b91a083-232e-4968-af43-1a8e94b1cb95","556d8d2c-c2b5-4689-835f-ed27c8b27307","e37f9f31-3cbe-451e-a630-fa9ca8246b9c","1ca1d212-aa6f-4dc3-87dd-59d1059dd46a","6a1fa6a9-81c4-4491-8ca2-56046b158ba5","cf2caa66-5c40-40c9-b956-d275d8a340ca","6e886593-bca4-42bb-ae51-1a4f9fbe88a6","5ab14f91-a3bf-4e5c-9313-5dfe2df43a79","fe0b789f-d1e1-4d33-b448-3b57740bbc9e","fbdb9178-2e5c-42ec-8107-753a48ea626b","e7f99bec-cafa-41f7-930d-397b2cf4efcb","aed78ee7-2f2b-454a-a3b5-50aa9112e0eb","ca8a236f-4ec4-4ee8-a4cf-acc4fc866d0e","a27c9784-ce05-477d-9761-9ea1156eef0a","577606f1-b181-4b5e-804e-3d5cfbb9fde9","e702c1da-6936-48ae-987d-4c672bdabf7b","4f50c37f-00af-4e4e-894c-019a15083bb9","723f131a-8adc-40b3-897c-b53c66ccc7e2","5b26c139-8f98-41c0-a905-d55edef7b70b","32c3ec69-f430-438e-8856-05e1db355ccd","a7106db2-1d97-4472-966f-b959591b5f08","9c5d8a5b-2a4e-4087-bb40-96cecb530050","fb5d58c3-e832-40fe-b211-cfbe8af2d837","e403de9a-aa30-4e1a-97de-1cfcd7c0ff13","e2e09666-55d2-4b1a-b934-d020c1c1ccf9","8d2d13ed-c98e-4479-9420-3716bdbdf66c","117dc5ff-4b8d-45ea-affd-3e3ee094e116","5cde777e-cc85-4ba0-9951-9e925ef8180b","f6274342-52c5-4c52-913a-9b138d6e2809","f10c8182-d73d-4004-a827-ac83f8c5717e","24164198-5ed9-49af-a8ba-aa5676f5f331","1fc28cc0-4860-45db-9247-2b675ea37d6d","f90c6d0b-dbe0-4550-86ce-6d61e8a07d6d","6b7393bb-e443-4a30-a309-3631c37fc832","ff4fd496-0dbc-442a-b9fb-7aa0da567bef","fbdd7678-9164-4fde-8f05-eabe74e6221b","4929c4d8-a822-4633-a93d-c35f190fada0","d6a51ca8-1f12-4625-9715-c64cf970783e","ca05d0ff-74a0-46e6-8760-f9807b94325a","8fdedde1-9794-45f8-b0a8-77fa776088b1","1390d6fc-c39c-43c8-9af1-2fb45a8a0595","8f3e99d1-f4f0-4f6c-91a2-7f3eef080026","153063b5-ba16-4e86-95d9-07f436b9213e","98c81fa6-0b9a-4b22-a764-4c2076561e28","a1638c77-ceac-4ba7-95f6-44018972371f","5af0ad03-db71-4922-8eb4-6ae668de6a53","f2a00fc4-a361-417c-9222-e3e47036d683","b304cb53-2448-45e2-8c24-3ef92253fac3","dceb22bc-d365-4cde-9948-cfbf6f95fb6c","754dc8fa-a926-4698-bbbe-195f325ad3df","fcc00578-64e5-4aaf-ae25-c05994809e60","376af6bb-9df2-4a72-bbd1-3b03473c8126","609380eb-ab99-4657-8d6d-9ef638f3f9e5","efb5cae8-62a9-487b-a017-a76cab415a0b","4884d5b2-a803-4fd7-a45f-226f5a8ebb14","3e0c3cfb-1781-424f-a70a-e103bf898abd","a6b12903-0160-40f9-a27a-6a69899850e4","50924f14-00fc-4759-be8a-629379a30d6b","a50e1565-9566-4564-8125-110e6a2ec8c4","34c3b037-ff59-4fa8-a732-87e809a4f9b8","6e3df040-546c-4c2b-9f2e-21478a1ac539","2119d2eb-4f24-402e-b91b-2e9b7dc7476f","abedc784-a9ef-4a4b-bc6d-d5aecd62aa19","d00c95bb-b18b-4047-80b9-a15a6802c7cb","f656a973-5758-4256-89e4-a9c2620ff68e","5333e85c-bda1-4bb8-aef5-77ca0daee2ff","bce06d76-2a6a-453b-94ad-7b1994ad2609","406c2905-16c2-4af1-8399-2f0b05b214a2","a656f49e-685c-4fec-bfcd-9afe85b022b9","4469749e-dc00-430b-b662-d94b615cbcb3","e8c81b68-12fa-4489-9bc5-b8a2c3b92c9b","a2a28314-f7bf-4dcb-bce1-d3415adb1d63","88828df9-1dd3-4150-aff7-85730d54cecb","82ab0d16-173b-48cf-af1a-78f19774d4ae","ebb2a138-4af1-4d81-a87d-a59c87d33ee4","bbed2dd4-8c46-4398-a1ab-69111f153c62","7bbf038f-85b3-4794-a1d5-d66158eae02e","bbae2c1c-8107-44a4-8a2b-fcdd0f155acc","cacdc817-57b4-4c35-90fb-6ab0afb3ce15","572b1848-04cc-45cb-aba4-6ff2a50aaa7c","91c2ff67-93ec-4867-a919-a05dadf3436b","9c617782-0bdb-448f-abaa-fd3b41ba91fb","a2a3c35c-4c54-4b1b-a817-c531b7a029d1","5a04ec49-9e4d-43a8-8f33-b0734611e871","df79dea2-b323-4e13-8f66-261052d7cde1","c6ae6078-d7a1-41e6-b1d4-f466f4dc920c","84b83633-cc15-4381-b11d-16ae8124d265","94544f78-b5f4-44bf-bb4e-a9ed6ea99f5f","152a97db-0c74-43d9-a016-9d781803897b","47f7eb21-1e7a-4e51-845d-c7add2724867","977789e0-ed7e-44ee-a658-5637a96d3019","bdb7e498-2c5d-4a4b-b01b-81ee076da895","7080e8f7-b59f-4a44-96c8-69bc61d5d9dc","d8e1730b-20ac-4c66-8cc2-1f78741740ba","4c6c8cbb-37b9-4342-8ae3-0b2187e52db0","54f74c25-9cda-4b7d-9108-c5cefce43b3b","310e505d-6d32-41c5-8811-d532ac22a959","39b28321-7210-416a-9509-94d6519c773c","23e8a9cb-7a65-4985-bba4-37f7efa2d753","794da84d-0590-45d2-a4c4-2129bacc0feb","11c2bee0-f11e-4a4e-9208-ae819c5e8aaa","e08117f6-8a2b-424e-b7ef-0c03d71c42da","2cc226d7-8e3d-4ac9-9db6-05b9a36d4109","5056a06e-5433-4460-a01f-c878ff16b0b7","31c02d7d-0f7f-4b61-989d-b3796b78a4b0","41ada0d1-726e-4af9-b6f6-7cda2df14be6","e5cbfe94-e180-4071-8961-57e822e5748f","3290971e-be0f-4866-9d14-aad58fbaa781","649857cc-f581-4eb6-abfa-4448bb8119a1","cee42306-80d3-4635-8dc0-8f3670eba20b","23c8b264-613d-4c70-a965-1423a574629e","1d6651a3-85f8-4ead-b643-2d9dcc0aeeaf","00b1d349-65d9-4a81-8067-bdf3e264d602","b006b891-103a-49b8-b920-25f6d5ae3353","f492eb42-d346-4094-8605-0e1e4c49b78c","306556a5-9806-4201-8f99-07347dd2b6c6","90f9e84f-2645-4b25-8c7b-f3363da4f99f","892e0c81-8999-455c-9b12-1884a98022fb","36573621-57e1-4229-952b-679e7c3f8a93","e6f953df-30f4-4bd5-87cf-b40f97d71d6f","3cd3b9e0-7841-4591-a91d-5d28a95b0e17","75727f15-065f-42b6-a432-70c8720eefab","75442137-d23a-4a97-bf9c-167ec849eeb5","7e1cf81b-0d80-46de-9e3b-e1730c71bf9c","b2afbdb1-93ae-44ca-8ed3-b598117ab9cb","309259e6-b8d7-4d29-8678-9c9aaad0ef49","2a65727e-19b3-45e7-bb03-303ae4f80f5f","9ae2d0b1-6adb-4d0e-8088-721462e94075","90f4bc95-7c8d-46a4-82f9-3c0c590325fb","ed36863e-1949-4588-aaf5-8f6fe30ee478","451f8e57-37cf-4829-804f-0a1e3d4bcc8e","808e4d8f-a93e-47e3-adfa-4f4033586b9e","d2ecacc8-0562-4bbd-b036-5eafd3bf4871","ab645609-ed18-4888-bda3-0a8af88812f8","a66f9ff2-ac5b-4063-a5cf-fa5ef4bdb821","2f47525f-789f-4095-9249-da5551bfceff","939dfafe-df2e-4c5d-9458-d0bdb8f76200","902b9ae8-5ac5-4cc4-9dfe-1f5c0219e2e7","1eab40b6-f1e3-4be6-8ceb-6cb55b98e96d","9b00d5ed-8991-4afc-af5b-4d1d7c3b0493","6de0ed0a-1293-4fd4-bd67-2a12f2013be9","5c23474b-53fd-4a1d-8d03-516515d73abe","eb812c78-3ccd-4860-954f-430d85aa877f","765dfd8c-ac2d-439f-86d0-58b2cf1c1a1c","3d2194fc-6bd6-4d30-8475-257fd385665d","3a84e7a3-1c85-4e56-b8a3-5bfa3034c387","800e20b7-c12c-4312-9f6c-9e14849601f8","779d6ab5-7d27-4198-9f54-ea46af5d332b","e24337c2-b9ad-4071-8783-b2505cce7151","13ac884f-7290-4dd9-80ae-047ad2ca1f05","631c5f32-06da-4ec6-a776-17fa53dab9c6","f8f556f9-e07c-42f1-9089-43c7832eb99d","aa5b4e29-95d6-4a2c-b395-7b2a988bcce0","b8760ef3-d32a-4711-8fff-1c2e49e9cfe1","95fc31ee-db0d-4098-b98d-3b42fb99cf74","e945813b-b95f-4a98-a2fa-461da7d761b6","1e4084e5-cc1b-4fc6-bc47-b7d54392f7a7","659a4a02-0ca5-4a8a-bc89-5e3da0888c6b","c482ac4b-075b-402a-8fc8-90a02828d003","694256ec-b911-4f02-8257-931b645606a6","ea002127-7dab-4894-8d9e-8a672e6660bb","44f3f84f-cae7-4724-8b40-dfb2db60d224","53f2b21a-eaad-4691-a0b8-aa1195ba63d6","48be5edf-1cc7-446a-9669-97e1dc1f43f5","8c4dedda-6e9b-4414-bba4-319289cada7a","31d3a1b8-4f4c-4424-a284-f942a819f1a6","d6436821-03d2-4bc4-bd19-db6a09c32e0c","794ac683-a338-4178-8ce7-c8be35f792e8","10ae764c-195a-4274-aa20-3e182b563452","44ee0dad-c0c7-43e0-a81d-97a37a3e8b85","f2513e9c-7a3f-498d-8845-fdd4c991e934","e445a1ee-d65a-4eaa-809c-7b550d33a4d7","5ce78f16-0323-4c50-bec1-32688fd46c09","822cafd6-912f-464b-8cac-d9af211762f3","5aac0d3a-9e8a-4c6e-a3b4-9d82905c3274","67e4c0ae-1e8b-45a0-be51-b4652dc2c050","158630d3-f7bf-4b50-96da-7a7efb218cd0","30c4b902-9b48-41de-8a94-8db74cad6c48","c250f915-6799-42a7-8f32-9566afb6fd46","f0821d50-b42d-46bb-9d83-0164ce46f06e","b5229471-4a58-4548-8353-8da96fcd1269","2a18c4b9-ab22-4302-9f57-d3e33b4f34d9","d6a657fb-d2d1-4b1c-aec0-91be13d2223f","e29ef7a0-4eba-451f-b72e-35ab0e70f653","ced1d0a6-a64f-4025-9522-a30ea3d65197","02e6376a-2193-4c84-a184-8c74a22a5979","af23a1da-4b3b-4de9-97fb-f4a7dc86e228","1d1e3d70-8788-4895-8801-867cb3e0609e","e8ddef76-9614-4f15-9f48-2de124d60ede","d56edcda-110b-4b26-8200-0fbe16c04e8c","5b0ce5f5-11be-483c-bf72-aa96bd1dbc2b","c8b01731-3aeb-4d24-b900-644a5950fff0","7ebc4295-8f24-4d1e-b23b-f3706259a2bf","5cb122ef-6d3d-4c9a-8a6b-c90cbb5fc88c","b89599dd-6dd5-4427-85aa-ca847c0f0c9b","51f70f6e-1db1-413c-8075-acae294b8c54","84a69f65-a904-4e83-b3ea-7a709deb9506","e5ccad56-5770-4710-9059-d38f4ef07ace","86927246-dc9e-4649-8108-13b02276d573","2583843c-249b-41c9-8de9-5aabde67f2b5","5adae8a0-ec29-4c1d-abdf-245cdf5f8843","24d802a9-c5af-405f-8ad9-79244a5c64b4","a4882612-4e60-43ed-b93d-71290462dcd6","6d258a6d-583f-4703-85ef-d3bb62806fc1","e3e9dbac-0f10-4c67-86da-1caa960d03d8","50e2fb22-092c-44aa-bc5a-ce30e22cc131","525c1eaa-e4d7-4522-a99d-748f7c39d0e3","a20d0d68-5da4-4c5c-b2b9-4f335dff8668","9fcc6153-cb89-407c-8227-9a0a154b7392","f69bd196-66dd-4ba6-8357-2de84f0cf75b","f61c13aa-e8cf-4e93-9479-3c1437e8e61b","104efe3d-6a1d-48a3-a10d-667a4455cf21","c682291b-353c-45e4-8ca9-6645df4a991b","bc3c5140-6ffe-4648-8fb3-58ea1e0516a1","6cd6ddba-86bf-40ec-a75f-1d63630eea21","0d1bdeea-77ff-4b98-b7d7-8cadccbb9b84","ce950ce5-f5e7-423d-b350-f84cefd57e69","1220a6b6-19b1-44f9-be19-4cb224830e5c","b118ae3a-a4ce-4d1a-8b28-8fcb1b23611c","c3d291c8-0e04-4208-86a2-dceffa4363f7","65c6f7c4-e975-4576-ba84-e6b687bbd2ef","5b338764-bf7f-4115-8ae3-e182d1f1201a","e00bdfb5-3e3a-450d-a56d-953a01e40a81","fffa02f7-cbf1-4a96-8e6b-25b99bd75972","7ce81ea8-4ff6-49e0-8b89-9ce840970476","16d47909-5064-4fa4-8bd3-0465c388f889","32f4693a-97a1-4a91-b607-18239e5bac65","ad3b5241-f758-4f80-8a48-0548dd8024e8","6ddde563-4d89-4f23-b4f7-a90f5cd2bb81","b2876e59-d4c9-41f4-a0dd-566780ab635b","52de8e08-1262-486a-af42-c9b969a5c165","3d361380-354b-4ed7-ab15-4c06c2ad33be","9b09ce12-7853-488d-8b24-67224ff132dc","8fd9803f-4534-4b9b-9331-871d6f0ff7a5","5e37eec1-103a-4b0c-9031-1ef12d3ac952","2528c4c2-3968-47b0-a124-44a18f2b5f61","bc31c8f1-d6ee-428d-82e2-f3a796da6abd","7fc277b8-4d9b-4f7c-8f2b-9bc2965e404d","699ef08f-de74-4303-ae7e-8ab710d7be5a","43e65688-604c-45d9-be72-eed20ad433f4","b7e5cfcc-b324-42a7-befa-722c20277189","8b4f1c2a-9b31-4a91-bd0e-ad50dcbcb6dd","4da89f42-3714-4229-8291-1f721fbb98c5","c0d30b9d-93e0-413a-ae9a-cd033946042b","5594c6d9-b1a7-4606-8764-f9d1e81a7c65","dd94e6d0-6ab2-46d9-94ef-0059738c46a6","ab550281-7de5-46f0-bbd3-9defe55b0e9a","182a06c7-4ac7-4b45-991c-8b608e66e40d","4f5ecf90-a007-4290-8a6e-45092b8e4e4b","f692b3a1-a4c1-4f38-baec-7cc1a9cac28e","7096c266-0e24-49c7-a6a8-73f36692095c","0489b59b-746e-4266-a3cd-429aa124c67b","02d319e0-e328-48fa-9666-211b28aa431c","fe09fe01-7a31-4ada-aa5a-0305774d2b1a","8f7371b8-0ad0-44a2-b394-5361364edc3b","5549dbde-fa75-4994-b9d1-8aebe2ff88d3","88d5b73c-dcf8-4b16-8c76-d98bbf75c594","5bc0b0af-450e-4d61-a1de-6092bc310314","b83d955d-eed6-4cc2-9631-0226beac2c78","17530f11-9509-4d3b-a1cb-17602c9d29cb","55d60ec3-29ad-44e7-9f33-45a769747fa8","1ec41e6f-7168-4ff9-90c3-0dd628eb8e12","b4451c83-e889-413c-a84c-2fcb6f42cf45","142eeeef-c2c1-464a-866d-ca088a49ed32","56e132ad-dcb2-4e32-8f0e-94d104c4e07c","d9b26cd4-e522-41b8-9889-1d3f9c713534","90819a7b-2241-4a77-8a8c-db6fdbebae43","4cf7595f-a8a5-4c31-aee3-65fe1451f30a","8e58e957-8811-44f5-b147-7711825fe311","9c6b1e2d-7f87-48d9-93ec-fcac4311f19a","fad7bd7d-0463-46d3-91e2-fc41cee1fa09","4181b4aa-4725-4a16-a047-423c60831de2","efeed4fb-e300-4d84-a00c-ffc11c2adf56","931b785d-aacf-44e8-9182-46d17c77ff2d","bb1b4c4a-e23f-437b-b7a1-d376e4f19db6","b906ac50-ec7d-401b-864f-ec92d0614500","931b002e-3e60-4c77-a15a-7e9c856e4a42","0b99b497-3971-4467-a795-9ac8d747e32a","8677b9d4-8372-433b-bc09-6183e71d650d","1ffb7244-fb3e-4ba5-8cc5-4648053f70c3","2fa17356-051c-4fcd-9167-f0683c09a8f1","bda4d147-5f41-45c6-b7ae-1d8e86c8428f","380cddc3-bf3f-4206-a368-675aa31b687a","091b495d-eefd-407a-aac5-7cafe0fb4505","e991d544-5650-4c97-b248-3b51904dbe8e","47968af3-0961-4073-bee4-19496c3969ef","adba5858-f2b5-4c42-a0d0-99d4321b7893","a45ae2bb-62f5-4357-9fd5-b4885e4cd5cf","8d92b4c4-a348-4e76-b5a1-8348b21b1128","6f885bd1-0fd7-4ba5-b370-194bae1aac7b","bd405691-b811-446a-8d25-e0ed60046d73","11fbc6da-8a0b-4ee5-afd9-bfc2c6a31ed7","1321e617-84ab-4e02-b900-50340f241d95","f841fdbe-4813-416a-a366-4f8494b615a9","0b946506-f8d1-4688-986c-e4e68fcd3752","7cf1e8b4-5dde-4434-9665-be9297fc9e86","40b27b2e-8a2b-4f15-b049-2e4aa3dd90dc","a287cd50-f3da-48ff-a6d5-fa590aa17e57","552f747d-0060-4e4d-a2a0-3e1b21a74f5a","e5a448a6-c61e-46d7-b002-c1665bb97b61","5fd261a4-0ecb-4091-a353-caae267fe884","49ca490f-ee26-4703-a4f5-3c6f06cefbe7","0781d975-616c-42fb-beae-491e65ab9bf4","2e29ccaa-ad46-4670-8617-5c683df77390","79b433c5-9245-4160-bce4-4465576c0871","833ecfd4-d15d-41f0-b508-8dcc07b8c30a","1f045ca9-6d91-474f-85fc-235eb3d94572","1e01760a-75d4-4215-9f3f-aba88d27eeea","7507dc9e-0924-4062-8af7-0cdfd139b718","7dd1de4a-12c2-418c-815e-d3ebfc32c17b","6e13c495-a095-4ad3-877c-4c24de572a59","fd40d01f-fb24-40b0-944d-b298b80fb0b4","4b922088-e2dc-4638-a476-98dd9f2162a9","16b36462-e231-4331-8369-f60220edeb9e","2a4d8887-ea7b-49a8-9218-a6449af750b8","7abb8c3c-e602-4067-baeb-dfb86a0825c3","c28da51b-a9ac-4e78-bcb6-b6f4421a9e81","caaa0044-4a4c-465d-b26b-687b028c1498","841ef59e-1d42-4d6b-89bb-58f7dd0f2f46","2ee5e34f-3472-48b6-b52e-23b13a2b5ea8","5b969fb2-22c9-4f68-bc8a-06ba3ffba7a6","a1fb70a6-e887-4ff0-9f87-bb74537a41c3","c2a2be2d-776a-4b40-b189-34b643d36e85","e0e3bb94-cc23-42f5-b5f2-89de3fdd2d74","095b5634-104d-4b01-bffe-8a521e016cf0","32dd7616-4fd1-4a9f-a5ee-b08897a4eee9","b00c79d3-5f3c-4e6e-8ab8-7f3900fd74e4","00a5c426-2e82-40b6-932f-ff853ab39cf9","4b70c3ea-9e97-40c2-b981-2709f8750a75","3890658d-2fe2-4058-a4fc-2fdbebd09bad","be275e2f-f819-4626-8664-d5d7f34090d8","d6d2c1d0-aa7d-4a86-b609-8cd254797032","5a2621c4-317a-4ed3-ba94-d9c6922759f6","e0a6adf5-bda7-45ea-b312-1b5c6b0c747c","6c695d0d-8c62-42ac-a69f-a03d57b174af","57b5bc29-de82-4202-a6cb-c5ee25369d18","0211c7f7-f951-4791-b662-8f5791b1a3f6","dc40f90c-1a22-49b0-b517-1cc3d5aeaf57","c45f188c-b0c7-40e8-83fd-0f99036c8feb","72a3cf8c-53a6-4ba7-b72c-6eab82faae7b","5c300d67-f9c3-4f4e-99f8-fb91abab3835","0d0c8027-4134-474a-9150-e0c53092607c","30de5049-ce25-4d4a-a012-78110521b4d4","8412092e-272a-437d-a631-50b94190122a","eaef5e09-aa4d-432f-a2b3-5620b41d4668","51cf2f28-a11c-43e0-865a-54d4c312e836","037f85f2-ccbf-4529-9757-758ebd02d32f","d9a9b3b2-3d00-4f6b-815d-58e825c2afc5","491a9663-2c66-41eb-9ad0-5439edefd5e7","46e8cdd1-8205-47eb-ba19-cd0d9472bae5","ff1eaa3a-61e4-4484-82cb-db4f4de1cc55","daee34c4-8a50-4ff9-9883-c1063fe2b840","0a63a827-c070-48f9-b90d-4bbca3548e05","2420d768-75e2-4f19-9a1c-40a2a14251b9","353f680d-2992-499a-b174-199ee150624d","ea0107ef-f147-43fc-b78d-016a737b622a","85eac63b-c937-48ff-a9ee-12f1dcf86672","72e1891e-8bbd-4be2-bb66-0976effda9c9","eeeb0b86-609b-4221-a94a-d5381cb77044","493bb93e-9845-49d6-8942-798a5a0946ce","5f1e2f0f-98ef-47de-8072-ea929e1be18a","7289de8a-2ff7-4c41-a08a-38656857c631","9bebed98-0ab3-46c7-b59f-858c511331b4","f0a47381-daab-4da8-83d4-76ac69975ed2","7f8f6698-5e44-4eb0-84c0-02147297874e","869d643a-ea58-4d7c-9d29-426eb619d1da","3064a09c-f21c-4a73-81e9-82dd9a4f191a","21a140bb-dd4d-4a5c-ae83-b531cc8e7d2b","68cee4b5-e0d7-4b7b-8f3f-d8d90c2f31db","00ddb0f8-4289-4628-b4c6-fc8c6a7ca996","e96031ff-b5a5-4be0-ac55-ce57a66c0827","cfada809-9f84-406d-b374-aab7f8bafe01","808ee189-86ee-4d03-826f-241d07646a2f","12f631a3-f9c1-4d60-ab32-27f3cf85e78f","eca53cb9-8242-41b4-bb93-d9c50986c5e5","b251ee07-a333-4e51-b9a5-c8418e179557","c06ce904-2f57-4576-9c3e-e9c99cda6f62","023e7fa9-e0c2-4398-985b-bf9134223c4b","26352057-d76b-4baf-94c7-334984f9ab08","405c4235-b3cd-4b83-aebf-152a793008f1","c2fa5a56-fa19-4388-a6ba-8f1f7dd7f7ef","9dbbc699-c9d0-4a0b-9c07-6cdf0b99b519","8e5a129b-0a71-4790-bd4d-feabaa3775b8","017b3fb9-1b9b-4e46-8cd4-55a1e47d43fc","ee1ed3e9-4128-4f52-9db2-22d6509b373d","e71d5f03-8b58-46da-9635-35376cf005c8","c78702c5-0894-417d-801b-c76bcf3d85bb","0aa65a60-29ce-44c5-92a0-126ca6fe8832","c21c0898-ae7e-4018-89e8-d16dd7609eec","095f5219-33c7-47f7-b6b5-dde4756ee674","7081176a-c9d5-4798-8dcc-48ba36302736","c6c41a63-54af-49d1-96af-a0789835a16c","3e777421-cb3f-4b46-8968-b49141447a13","9e1b8a7a-d93a-417f-a7bd-7fff50c3a129","d04c6ff2-3d44-466e-977f-c99387a13bd3","9941874c-a6f9-4a1b-9896-d50a8f99e12e","3126c177-f6ac-4027-a665-5d56eaddf115","3f0de91e-674c-412c-b2f4-8a6befac8139","9050ae89-6717-4776-9fd4-be387128f4be","bd65dde5-effd-4274-9b13-83a3a9018896","6fb9e9e4-6f3a-46a7-bf9f-f80fc7c50d24","6fd84480-f2c5-4958-a0a3-de5836d68e34","99993b08-5161-4feb-9b5d-53c6b7b96be9","c77e7206-b6e6-4f36-ab57-1f91defc5a94","a3392116-62fe-4a82-bcc8-927f2fbeec45","5c3a08f2-1859-4a92-841d-4e757399e722","37268b58-8f76-4f34-b990-83c140bec1ac","6b58a3c0-126b-408c-9786-3cfdfb64f4d0","923193e8-c3db-4bc7-a27a-e4ffd122e999","07ccfa8f-53e7-40a9-bddc-991098005c80","7233d5df-eb67-4b83-9a12-d7e21c0a70dd","01d9c1a8-ee6b-43f8-b0d2-d207e199354b","e9db84da-5035-41fb-b375-26bc3479ae6c","89f5678b-d191-4416-a853-f850c8980651","84db63c2-5c26-41ad-bfd6-715690b4b20c","282b8393-022c-4247-b582-2b80c940b458","1b93730d-9f15-4953-8c0d-e6dbe154e866","7de70760-fe7e-40f9-8f20-0c283aa8a189","74eec209-5d2d-45a8-87c1-d5ea6a654010","cdf54695-3e95-4659-b99b-ad622df8124f","8f8cc60b-a9c1-4501-9c9b-edd6a91656f3","5f0b6975-739e-416c-b421-c66ec2c1a600","2349f39c-05c3-4166-a804-a2629bef7c73","34dd5fe9-f9a2-416d-b045-2a532d67e9fd","b35c9307-271b-4612-9e6f-b44379522eb6","e527258f-975b-4c78-91e6-a846a94cbd80","1923ccd0-1d6f-49c7-b7e2-e0346df0e6d0","d6122f1b-f586-42d8-b2ad-536f72771c8c","29e58642-945f-4157-83b0-b5708f4d33c4","38ea938b-90b2-4d9a-8f2d-18aebe988aab","a51359c4-470e-477a-8c7b-a6b405c27123","1258f9b4-79c9-473e-aa15-d6c33677753c","3f2388d4-0f4c-4734-8f4e-9f937c757005","9bc16267-4a56-40a4-807c-6c687c750b31","f4b56a86-82ea-478e-9260-24d16de3af0c","fc87ff6c-cec4-42dc-b558-251655dd5165","1b090bc1-46bd-4b55-9d07-56bab77871ff","f70be1e9-5285-4e1a-886a-931eea06e67b","9b92ad8e-cf71-4bb0-a7b8-1dd0e7cf3ae2","62ccf750-7478-44c0-94e4-346906a86502","86128762-e353-465d-8436-f2157d9c7ee5","28b15b82-a93b-445c-86f7-599c86ff324a","411c5727-1e38-4527-bd2b-8f2c06791722","63a3fc47-0f88-4b32-9208-f6f1a0ae56ee","5669a7f7-37df-46a5-8ff8-b9601a86452c","419ce439-b6e0-43d4-92a0-aec7b958a568","16da6930-f619-4ea8-bb43-f455a0ef7cd3","bd2da806-9e77-4cb2-a4f9-4bfeea82f0d0","91199123-80e4-4576-8977-279fdb35e4c9","0650602a-a3bb-4d8b-9745-056bb88e4fef","0ae94927-91b4-44e6-a65c-5ce1af3163c2","b99b8136-c72c-419c-9345-1dc78741b1d8","e453d3ea-0600-494a-b2f1-5a74841c5021","dbfcc4a3-7d56-47ac-8c3f-a8d7c5e7b65a","83033e9b-e9f7-4d94-996a-6ffab2558c6c","4413dc9b-bd14-4841-9bf9-0c94803f26a3","a1c3849c-d7ae-4151-97b1-fba2fc0645b8","55522c83-ccde-4753-ba99-b1eb2e4fb4e7","ebc2f5a2-4ef2-4fe0-bd75-df0e629e72ab","2d87a7cc-a39b-41f5-8110-ec49c7d2469b","0b70c74a-c69b-4841-bae3-be3f53b661da","321e9768-e85a-476a-bdc5-233c2d3d558a","c548f962-21de-4e63-b3f9-c60d3368fa1b","dcd0c017-e2e6-4b7e-a3e2-3438ec21249f","0e72a6d3-e4b5-4e86-9a0e-28467683d13f","32bd23e4-08ba-4729-9535-2aed340a9bb1","bbaac34c-e293-4e6c-a345-a16d61f24fa9","407b4059-3554-4031-9399-cd4c8bf6d486","f59d76b1-e950-4999-a04e-520ee9ec44ee","70a74e4f-c34b-425f-8a72-50d08d457592","c980e2bc-72f4-48a9-b413-6c3247627aa0","1185620d-37d4-4205-9b4a-7e87264f56d7","2e9fb01e-4a8e-4952-ae36-b80314984ac8","c1d4605c-1324-4976-8cac-b3bd908cec0b","e47f5d16-9a03-4fa4-acdf-4ef0a2e58ec3","0d53749b-ad9c-40f8-9c57-c649cfc71a28","ce14018f-fe78-411d-8897-8f66447907fe","fca008a5-1c01-49c2-9686-4ac87d00be8c","e159ba0c-5e55-47ab-80aa-dc4d6805554f","4b77fe3b-847b-415f-87c5-08b6f83390e6","81bc4987-0552-407f-9273-b31581562188","981f3f51-6e35-4e14-acb5-a33f7ad8b370","06e3edc9-278f-4386-8dda-86587cc8422c","62402163-37d7-489d-ad69-436da8cf7aef","6d598694-3557-4764-ada8-ce9edaa79105","f737b77a-cf50-4a1e-a82b-598aa8da576c","be7714a7-af74-4b00-954e-e1a98aec30ad","0f81f77f-74ba-4ac3-a051-9ff37760bff3","591d3f2b-162a-40d4-8a58-af606e8a08c4","204f687e-7265-4278-8b69-2eaf9be31ee2","9eea83e8-468f-46e5-8808-1a4f76d0fe51","0fd512ac-cd15-480f-b216-1ef08bfc6e05","745af7d7-3837-47cd-af17-710268682a77","767698d4-3f5c-40ea-9176-ee97e65a5641","eea188a9-b16e-4f0a-a5bc-9e3c9b36d6da","f65afdcf-4aa2-4d50-ace2-2d43adbaa2bd","b38c9ed4-ddea-428f-8431-9ca449b13a74","327f79e6-22a3-41fc-aebd-f0287fb98de8","55260592-1f6b-4e72-8c4d-58ad0bcf0452","693a6c84-0823-411d-b295-6d3c62da16a2","d1c6eb61-4208-456c-bf31-dd510db74819","8065f044-d40a-475b-82fb-394de7e262bd","4792ad42-302a-4523-801f-7511b3627843","ce1023c0-e10c-40d3-a5fd-58cb0dab5d01","56eb5dd0-94df-438e-828e-17a53004bc2f","0704cca6-47e6-40f6-b5e9-050c8605bcd1","7d486e82-0c87-45c6-bab0-455694fc46b9","942da1f2-2619-4e2d-ac2a-65d9e30b4036","c67e0a82-07d9-4198-9282-8d795d7cba05","a4598b74-581a-46c5-a83b-4e0b2886068a","69f9f323-31fa-4abf-a4d7-7cdbaf8be934","1cabc87f-33ea-4b56-acb1-ac680c93f3c0","8ba7fc4f-130b-486d-86de-2b2ae0c6d399","b7ab9411-b65e-4209-8eb5-76d6328cc9d2","45f60506-0b14-4758-827b-fb86ad0715a8","0ffc8c87-f39b-49f8-a16b-05e79ffdb41a","3c669d1c-e6e7-4619-91d0-f362b4a9dbe3","a0cc229d-d068-4b0d-b909-be78844e2ea3","31d7c425-3d39-45ba-b29b-2e78c5cba7f0","e6791e04-6f7f-4fa1-aa17-03d9ed381917","8000d546-8f71-449c-967b-7fe99fc4dfeb","3b18497b-06a5-46e6-b7bb-5d7c290da7fc","7bf5a98a-383d-4867-9704-a39d2f585a81","8441b6b5-d940-41e5-8341-ba7b504ebe11","3dddec9c-8885-4066-918f-303c050b4764","322578b6-1a71-4544-b98c-82f712bd02c4","1d35b41c-3588-4d68-be81-d01719d0c6c9","9fa709f9-a6d9-4646-9a86-6c7657f40e63","4e7add4b-0812-4c2b-b650-7ca7b9cd6542","51dd2b45-19f6-4c23-a5e8-40078c77d68f","56af1d14-66f7-47d7-b9fe-73ba2cef8f04","634bd290-2f5b-4eb4-bada-726bb3aa6697","428aade4-d25e-4dab-a6fc-e1dd03c28021","6675a051-9e2b-4f21-b7b5-38ac43c551e4","b944fe94-db0f-4ce3-a7e7-5e4e6ce16e44","df3c4141-b525-4186-8664-d9f553bbe962","fa5a3038-c6ec-498f-924d-4bf369798fdb","a5fc19e5-6346-47b3-924f-a005a03cfde8","7356b696-8cb9-4181-8131-3d6342bd0d13","1d57b73f-9b60-4bab-807d-7fd621f68b7b","942d966b-23e9-4cb8-b65e-bed5076b37d2","f2f2a780-8cf2-4ec1-b193-5a0aa55403ee","82800f01-4083-4f2a-852e-b421f9b2a731","c456e944-2593-4528-9f90-434733967877","f57d4d77-88ee-4f93-a23a-be6ed25325b0","a2f0a755-a456-4ea3-9c73-cc208a45ceb0","6bd5493f-695b-4704-a1a4-dae9a5c4fba9","ca7c68c0-78b2-4b06-9f3b-97dda11d2785","82d4838d-6c95-4a29-bf62-ffaa7bdd8c90","a3b6d182-44f3-4314-8e14-109c5fa1bd6b","1d33d00f-2236-4486-b31f-f727f1e76b77","7d7f03e9-c61b-4136-a4d5-662df43de4d7","6d8b9753-3234-49e4-a820-3f8fb15ad6a7","295cd844-6f3b-447c-817d-59ee2ee1b690","3209e4b0-4092-400f-97f2-76ef9bcd26e2","3d859b6a-ff1b-44ca-b965-5382c9182a72","94bf5942-cdb5-46f5-9f2d-af07b867758e","6d2af655-052a-4537-96f6-123323ec511c","a3daa074-67ac-4fa1-ae9c-9d390f0540bb","23566cbc-dce0-4b5e-a656-b2b06c96648c","2cec0707-50a4-415a-8c3a-ff9f3899402f","8cedf7ed-4076-481e-91ee-ffdc048c427f","4c7b102b-7401-4221-8433-b296ffee296e","70b46f09-65fd-4e52-8bcf-255fac1a9be0","917a8acb-2360-4f43-8275-d0c5f743d074","6837c591-5933-41fa-93f9-22206b4de036","98b92b7d-c971-4229-9332-e30afcec15b0","f8e744e1-117f-49ee-897e-8fdea25dfa7c","1e27a82e-37fd-41fe-a0e1-fbe7b684d144","b4c0a6a8-7325-4161-a555-6a6e0866c226","8938e39c-a9f1-4d68-a108-1b4feba60ec7","7f55aa52-9fb9-487d-a0f4-85ffcadeb3e2","58d10043-311a-490b-a371-34161be7d642","359c8ee9-7bbb-47d0-a019-2060020d329b","0da618b1-b949-4f72-8c18-d089fcdecf3a","963b123e-a75a-4d27-a9d8-89f0c3784381","60f6c584-c5f9-4e29-8198-2217a2a00873","613c3e61-e000-448d-9432-6a67d5d6839e","73103f0c-d0df-4b3b-9cc1-3c5c992d2c04","c87235ec-3a6f-4ad9-bfb9-242b4c4ee25f","89b38b88-e699-48d2-930b-8dac93e44639","b99e6af3-d7ae-4a77-8dab-2d1122e5120f","8af12852-23cf-4701-93e5-29fbbd30986d","25a99a6c-14fd-437c-8790-57e03a5d61fa","b73fed60-cd99-46c4-9597-ffb9ae0da125","8c851bc0-de01-47e4-86ec-1f29fc94c9f0","a04b0351-c7ff-4c99-8def-fa93383e0892","b0a9a9ef-6302-4655-959c-953ecc0e96a0","dccbf305-e46d-4f8d-ba4d-45625d827858","7349eab6-d047-4c82-bff3-64878c00b405","3aaa4024-3abb-4b87-889f-1d0b31f6593b","c8c22a86-4a17-4dda-9e44-f97da8ca55d9","e32274b7-596c-44c9-a6e3-41c97654d732","75577d2c-6486-4fcf-bd7e-7e0f5f601420","3c1243c0-c7f6-48d3-80c1-ae359628ddf9","e0fb8867-c262-4b23-872c-cd0b1484a640","70504ae9-756f-4f6e-8b1b-eb254bb472fc","e5e83c5b-c352-4ea8-acf6-26c83ce9fa98","b008d76e-3bfe-401c-ab02-1fd5785ae7e8","b716ed20-c857-48c3-a7b3-21f8592be1dc","d1d7e8be-2d07-45b9-8f79-ec468096e9d7","4c2ce114-f845-4726-a411-7c13c0f128ad","ea9b3f1d-dacd-403d-89de-3513e75d7f70","c6361323-7da7-4653-923b-8caf7d8f859d","4c411290-c2e8-4449-92ee-059cd708f08c","9dc3e7a2-e922-427e-9fb1-94870179de05","36bf057d-c6a7-4040-853c-01fb9349495a","0bc3bbe2-0a89-41f8-bc2b-ffa22f806168","5a4332d3-764b-4e54-b0f6-5bec8a58ea44","7d7af528-5bcd-4de3-a6d9-41b06de62ec8","478aefb1-80e5-4071-85ee-5b9b4099895f","afe497a6-44b5-467e-ae5a-accc2234329a","5b897b7c-8b5b-4c6e-a913-d4d88b3d2caf","43dbe02f-90e8-4c95-b50a-098233bbb207","1cff7e5c-8620-4072-b48e-062a6c37569f","d44db007-1e03-4129-aa37-74b69a9d4c11","cc9c3433-3fe8-435f-8b11-df51315c52d2","e366ab05-9e6c-4263-8785-a060f771b1fb","741deb7f-5903-4c20-a043-4a8840d168db","13cf8b52-d8d0-4360-af63-8f5ddef31b17","e31e74c1-71f3-43e5-8d4a-9edf5ab10e87","a73a281a-5d3b-4f99-a763-44853cd2ab5a","3334d38b-eaf7-4531-bdf1-60f098abaf7d","9e31ae17-385f-460e-a634-4181eb9b834e","089cf908-5e22-4235-8638-5caeaf7ba6e0","223580d7-789b-4567-b5e0-401c6e0d049f","a96bc3b7-1fcc-4ea5-adcb-b57b9daa805f","4ca938bf-032d-41dc-aa28-d6295e738b11","2d3b03ef-539d-4464-bd07-4eef933e2a4b","8cfdaa3a-3780-4e68-904f-feaad8ee0067","87ef2e46-340d-4e28-bac6-0d1893f1f2eb","cac96612-0887-4a08-8c37-4141b6a67a60","40c949b5-d749-4880-9f41-22e27299a6d6","1425b70b-7e75-45d9-8360-ab7d336127d1","8b016335-053e-40a9-8c35-dc4b05bd54f1","eba2fde3-758e-48be-abd8-c794f2a65b96","4ae08f39-0fb4-4309-906b-c4a633c0c722","9810f406-254d-45b7-945e-eac39fc3747d","08505926-b499-40de-a833-f7145616f7f0","9769e386-e8f8-42a1-8f29-8999c4f6758c","0fc5b793-e174-46c6-ab9f-d2f287a7940b","9d5a6ccb-3d67-42b5-a953-1cdee9e2abb5","1591f3ab-8efa-408f-a43f-981591b9911f","27e0fca8-5672-44b8-81d2-959318ed9016","307242d0-f882-485c-b9cf-33b596881fbd","b8f5b0df-b92c-42bf-9aa6-9b20c3cc708a","a0085df3-2e91-4a82-8892-35f53aebde12","92008ee9-e472-4042-bda5-4aec2900963e","020c2f5e-447d-4688-b10b-32292d30a873","97606bf3-649c-468b-bf28-05288f286f80","44c2e598-749d-4737-9e2b-de1a980befb5","d082fef7-8308-496f-a452-1bfeac87d5fe","7f2de9a0-b4fb-4aed-9f2c-50752c5b6994","2e3e3f67-98c0-49ef-aa78-e1d69850dacc","103eb3da-6616-49b4-9104-d636f4fee05b","3a297df5-2290-4fdc-8dca-01caf2dbf870","8580b0c6-096e-4900-980e-ab21aa8611d6","8cd92d32-c6c3-44f4-958b-9d28ee9b91c0","21b4b964-b2c5-4b15-9177-46219fc706e2","6d3ab1b1-5332-4915-a19a-ab9b721468c6","e246d28c-f17a-4083-a079-2af5cf6b3c5d","0d43260f-44db-4e76-bed0-bf710542a49b","ce9c14dc-355e-4f5c-9aff-3dfaf9de3f8a","9623a5c2-2b3f-43df-91ff-2a6812e16957","1437a839-366c-4169-9b55-5f230c1e01c6","a26b40ca-b5ef-4f40-8375-b1b84c45caad","9f9abdac-0ba4-42fc-9c11-2059877b2f17","d0d2757f-6d66-4cb4-b0e5-982af19e88ce","045899c3-ff82-4d51-a87c-b00adb897580","2f050ed5-6b26-4c57-9271-9586354d513a","cfd241a3-23b2-454e-8746-2eea5ecf6b0d","77266a40-5985-44d0-b889-0284f331a620","b692c208-17a7-4689-8e01-0820105c7ef8","a2eeeb06-3d2b-445f-97e1-228b0babf104","470b5cdd-888c-467f-84b1-121c49d68f10","15bf8f12-4397-4f52-99b6-b5371d3da962","afa8cb18-d235-4d42-a147-0569c372a2fc","304e30d0-fbeb-4136-882e-2e558a2c9d01","ce6589e1-2e8d-426c-9494-e52144cd12a3","6f145b88-0650-4e06-9c7a-b35e9e094264","32456ed0-bf6c-4734-9b2d-9f5159194fc4","c5bb9dde-9bd5-4cbd-a41b-7c3840eb09cd","8b334319-3b14-4194-824d-fa6c9e503d3b","0f456a08-6f3f-48e6-9471-6a72928a46e4","361cfc9c-c15d-4c2f-bdfc-af0cd2ad18af","7a729ed0-e145-437f-a69a-6b057f50883f","2a703063-2157-458d-8fc1-0c54695fe097","7b525630-eb4f-4d5f-bc72-c053e7abb561","b6c8070d-18eb-40d8-b1d4-7868b5b1827c","fd16ad66-6843-4e92-af56-0b5ae5974476","37cc63bd-33f5-4f11-a24f-7a881ce7a6e5","24636b28-6e49-4f46-a2c0-c5b5a1324b6e","4e6fc9f2-92ef-44dc-afde-2fa48d485cf2","29e9b25c-7054-44d4-977f-ddfff40e8575","002f074f-7208-4136-9d08-47b353cf487a","883a4715-ff58-4afa-8909-bd6f247d31e3","d57beeb1-7d62-4f18-9e5d-242c58363dee","c2be2810-7c4e-40f2-bf5c-1fdcb519d4cf","7355cd7a-5806-469e-8aaa-42339aa70868","283f374f-2e86-47eb-91c2-86f0bac79e8f","d8fda959-7a3e-4cb0-bf0d-93ed40701e49","773f1433-10b5-452b-a083-f07f913accb9","19c1956b-a35a-4675-a5f6-55c981daa79c","b4d3c582-e513-4c87-bdbe-2e51b13cc240","a2852d18-ae72-4f9a-b30f-545d2f188204","30e30865-4b79-47a2-af67-d928ae11aeaa","a1fb7af8-b4c3-4411-b5d7-3c93128cfdab","7f011678-0e36-40f5-82fc-3e29df72c6ef","603c6a5f-502e-455b-9ebc-c213017faf96","7052d272-4799-4595-bd6a-0defefdd7ee4","78be2390-5e62-44b9-8067-e1d0cbcf9615","66c3b2dc-aa18-42c2-91f7-7e3d9940148c","c47b2649-8a24-45ef-bf4b-b27a6eb5c958","5e5aede2-2b8f-43ef-8039-7397c68158bb","eca01d0e-a2c2-46f6-a950-52ba75d480f5","5a613e01-d838-4044-95d2-e590491546e6","3fdb8378-258a-4884-bfe4-026208429588","adc0c813-9919-44dc-80fb-4a9068ee5af7","68b48dd1-36e2-42ce-82b3-dbbe34839b39","432ca994-9a28-4030-ad03-2661be5c1031","de89fe74-e577-4c06-9322-c5f3fde74fc8","ffef58a2-1fdc-4c2c-bb21-e1e656bcbc9d","555b3ea5-5c26-440e-a8d9-807a413b5c73","a744ce1f-3d1b-4a4a-b563-7c06076a880b","e5b263ed-eb6b-4c47-9df1-55a0459252bb","937683ab-7706-491d-98c3-1ba80ea0b5f0"] \ No newline at end of file +[ + "32818315-32ae-4a3c-afbf-3ca59b79c72c", + "a4d10069-cdd3-47ae-9ab9-fa6bde8b694b", + "5b5b8d50-8020-400c-b33c-a1fecf207a62", + "92eb2cfa-fba5-4d4c-97c9-aa188bd29974", + "6f9e83ed-d85b-4458-a225-fb5e2efd847b", + "93668312-dc67-4eb9-ae5f-065e273bb7ec", + "974bf2a7-beec-46f0-aaab-e20b7849d540", + "fa412084-50c3-441f-b790-395003534f46", + "385de1ff-973b-4b87-87c5-63b5c27bf763", + "633f9368-346b-4fcd-a3d3-de0bf827b775", + "82506910-6fc5-482d-b1d6-12a714560ea1", + "a76ace53-c2ad-4ce4-aad9-930d666d371c", + "af2e0fe7-732c-4e02-8e47-959f9efa7eba", + "67c4ebd2-edf5-4b85-a591-4e134d67151d", + "e0f5ec13-158f-431c-babb-a65f624c0772", + "4466177f-5038-43bb-8dab-f29c5d19cb6d", + "41467d88-e867-4114-b9f0-8360bb8535fc", + "026077e8-04e7-4d43-9d69-bd4039686845", + "03d9ec16-874b-4f7d-b43d-73080663e7ef", + "c6988776-571c-46b3-b831-716124143b7a", + "7269b68f-f1d1-46b7-9ec4-84a019ee9d56", + "a25cde52-fecf-4eee-9e74-f0d4bd26d5fd", + "71e6d61f-1789-419e-950f-8a9a494d1928", + "28bc686a-1f2e-4d99-9ee1-455b38501000", + "6b8f36b1-953a-4a1a-94d7-5e5fe8a329e8", + "8b3f21b0-4cef-4bb7-a9c1-ef7e12df94f3", + "73d3eb40-504a-4882-be78-56b9090da329", + "7a7e03c5-36bc-482c-be4a-f7da140fd0db", + "49a63f54-f0c7-4ed9-a10c-c3a0a3b50a98", + "e338bc0c-9d38-4105-ba0c-5b24d1dc9c06", + "b5742e7b-7dc0-4600-9e9a-137d0ba072a9", + "d749a405-f559-43cf-9cb5-1113789da955", + "529efbd2-1a23-4298-87b1-e8b3c5b5f1c5", + "2b0f8106-3fa4-49c9-9496-f9d936f2b80a", + "9240a435-b0de-46cd-8f15-dff04617bb99", + "e30969d0-9334-4534-a6de-b3d90b569214", + "eb4f93f7-0b07-4056-a58c-9091927dced6", + "e030fb30-93eb-4b41-9f93-371d93513cec", + "64be2fd3-f0d7-4ab1-8d17-110bef5ee636", + "7f4b7e61-d0d4-403d-ba1e-3f4fa76a1e0c", + "9f849a25-6e07-49b2-97a0-0e2e8a7ff057", + "4e79accc-4846-427d-af99-b2b79570caa9", + "71df231b-048a-417d-b96c-ee311027fb90", + "cd6e070a-7a02-49a2-a474-f51560444e84", + "3ffe6fa8-d368-44ff-a675-bc846d26df1b", + "f7b17549-ef9d-4e80-852c-65d873d4dcd1", + "7a15d6da-0d90-4a69-8029-84afba025c10", + "e1eaf8fd-6026-43a0-a6cf-9c96be145515", + "763bd046-ce80-4a3e-a421-3d8737300864", + "fee75295-9c90-4662-a414-314639c81e74", + "4c4315d3-41b5-43f6-9dea-c17d318f0d76", + "ac2099ba-f87c-47bb-9a00-6c3578e0e5a1", + "62004bf2-cf94-402d-82c7-84114344a948", + "db45db0c-96e4-4ff2-8f90-5c5f75edc174", + "d6716cc2-78df-48bd-b2d2-503cfb7a9808", + "2776d9da-8765-4dcc-831c-bd2324009706", + "42b911ba-de78-45ab-b7ec-3f145f792367", + "999a5639-9ea7-46bc-9b01-ce481fe15bf1", + "9cf13b59-348c-45b4-a59a-60281ca41273", + "054b3c45-2b1d-4e51-a710-2fdb75376d4b", + "d45a1168-e6c6-4c9c-a076-33d46521b80c", + "eacef7ee-c8db-4aad-8543-b4d7736404f7", + "516858d3-bf10-4dbf-9a38-4291e49f9ef0", + "2fdecf83-2886-4667-9586-71a06827e77b", + "8e579552-d15d-46b8-8dfb-9d443299ac19", + "15bfe64c-42aa-4d61-8191-ad4d614c7c63", + "7a8b51b3-f88f-4d63-b8e7-77e8c9a95b3c", + "6e174bd8-4419-4d3e-8a4e-9a8c4bf6ffa4", + "cef2c879-fda3-49a8-8110-0c4f11e8455e", + "9dc12b62-7761-4beb-8ac0-811598cdbb7e", + "1adec0be-fcf3-4d7c-b638-c651b35a84cf", + "83c863ce-6ef4-4895-b9e8-3cb3f54a0e0c", + "3d87f391-8ddc-4514-926f-3a6738e903b8", + "9f69ea6d-7ce4-4ddc-aa59-66050f2a3366", + "4162ec63-376f-4e17-beb9-4c52378fb3e8", + "0f6a0d7b-8e38-4a92-8eb1-45f0b7d6922e", + "45e96611-d53e-4864-b072-c6998d48662e", + "1086313c-804a-4260-96c8-5608a6cf85cc", + "94860ff8-f461-4e27-b2be-147affcb0f92", + "109c2e7d-a87d-438a-b229-2529cdbd003f", + "befb8448-4f3e-4cc1-b559-ce55ae5236d6", + "8fa76af1-3e62-46f4-96b2-1332ca4c0ca7", + "f135fdaf-193e-492e-9920-c0816df5f4e0", + "9a9ee370-5db1-4c9e-b9a3-ff654a4930f9", + "63fa4721-af58-4c5e-b8d2-4fae3b46d5da", + "71893307-112d-49bc-9e87-a8fed7f5b94f", + "2ccb9a0d-e5fe-497f-ae62-27317ba5c8c3", + "92990392-45c3-44bc-bb2f-86052ca0d8d4", + "b78f1d42-cdeb-418b-89ad-2e126169f118", + "c31f9ff5-9700-4cf8-bd94-f70181664a37", + "2768c4a0-83d0-4e0e-8b57-d231e0b30eb3", + "e1037ada-1899-4cfa-82e3-0c19b7b344c3", + "70af9154-fdd3-4403-a271-dd36113fe36f", + "430fabb7-873e-4221-bcb0-a600661fc2a3", + "f3811900-d144-4246-85a1-71c5523f5d81", + "26eab1a3-8d01-46c2-abe9-4970a9f491a9", + "aff70a83-77d5-4b9e-993f-b7af465e45f9", + "89a9137a-a0d4-4d71-970a-06b82f8ad835", + "dd08eee1-baff-4711-9b1c-a35d534886d4", + "8cfde1f7-db09-4116-a855-b8cca131665c", + "25472080-78fa-4c0a-875a-b61c7c151b08", + "acbf6c9f-1aa9-43f1-b71e-f637df709070", + "3ba3f54b-c612-4a2f-8d6c-9d1820fdcb53", + "d8746af8-49ce-4bae-86a8-d357006aeb6b", + "05bad3d9-70bb-4c0d-aa78-1c566e182718", + "d5738217-1d47-41c4-9ac5-9aff3c978002", + "9e4c5d19-a3ca-4cc0-b618-380236bfec16", + "1ed0a996-727d-498e-91e2-d51d1cbb51e8", + "ed8d118c-1e11-495d-93e7-f89db43a2907", + "a9587f3e-b7a0-4b30-bec3-e6d5a692e1a5", + "c5b29a6e-1b6f-4642-bac8-556eb62ca415", + "0ce69477-f65e-4490-9df1-171697729b68", + "2db7203b-4914-4d8b-ba11-ed502cde3e8d", + "b22d712e-4d21-46d4-8547-5c231e411054", + "eb0d4677-6181-412e-94c2-026366dc4d24", + "a8af7c85-3205-401b-a323-33647e131cf7", + "95a7f6c1-fc7b-4cd9-b03f-f9018f812c6d", + "615da6b0-7e3a-4082-b9ff-9517fa879e72", + "052b2d4b-012c-4f33-9b21-e1ed0d9c1a83", + "258a4bc5-0be1-4f07-98fd-83eaa253f38e", + "1181df4b-2c46-4107-80a6-9a42f27a9e78", + "fe7d44c5-786a-42f3-ba04-52ac3b658a36", + "d89be3d6-6dec-497f-8bf1-99499b930fa2", + "5ea10676-84df-4b3f-9f66-37c30ad7f298", + "4ad8f1da-7ea2-4a33-8fb9-b9fdd9c90a77", + "82758364-7b79-4aaa-a91d-d4e3810fb4c9", + "5aed7f93-5444-4414-bcd4-1beb29636091", + "cf2cfc91-ce7e-464f-94cf-b27c087d553b", + "917022da-dad9-49f6-9598-a5f04cca10d2", + "0617f316-5fcb-4fde-b66d-6c33ee52d958", + "b913af74-00a5-44d3-9056-92fc0357e5c0", + "4aa0c163-c749-454a-9f42-d77c57200438", + "8ee23a76-370a-4b7f-a943-bdb432a2f71e", + "e6c04bc2-ea6d-4010-a8bf-144a8ca47b6d", + "01224e3c-77ff-4358-9a5a-3b509a5aa864", + "b41262ce-e598-4cfa-b05a-568e2e514afd", + "a5b35f2a-7b14-4934-bd16-53c95a9ddc01", + "b41ca67d-c8e4-4a0b-9dff-b78d8f8f3025", + "900d86c2-8a77-41af-b27f-a5038dcedf2e", + "c0a53e5b-99ec-45a3-bf14-0ac3eb24e671", + "2d136ff9-30eb-496b-8e88-e04bdf0db8cd", + "a814bfd4-db81-4f80-a1f2-eea43bfd9a2f", + "b648a773-6519-4a60-84dd-da2142579e80", + "d4c3b234-ee58-48f9-8d9d-f7043e6ea4ae", + "36bf7bcb-0cf1-4ec8-b80d-43ae747e91e4", + "c1b2ea00-94d3-4107-aa17-bbc8f33e0f18", + "c6b60b70-2722-4211-879c-a399c323fbc2", + "f933e50d-bf0f-47e1-acde-ea40f8ca2d12", + "e586b64c-c6bf-4d28-9ea9-efe547781b05", + "5cf4daa5-fc81-4ccc-bd32-285eeb30e147", + "a1286b12-e2e7-4834-9840-7f79fba9d53c", + "cbcca31a-25fe-48ac-817d-60c4ddd8fcc3", + "e063a476-b539-48c9-a015-9d76adc8156c", + "9ddb3707-12db-4b21-a5ac-ea91d367d8ba", + "42ebd1b9-2e8c-4491-84aa-98a16956d79d", + "96782a50-6931-4da0-967f-cc3fed381d18", + "09498a88-63c4-4468-a1d0-236394a26e2a", + "ffd962cb-17cf-4a93-87ba-fed5f7ea49a6", + "21c74c09-9e96-4e8d-b38d-1c36027f7cef", + "187335c7-e407-4b42-993a-e8aa67a1b0b2", + "d250adf7-389e-49e5-99bd-2df30cc77220", + "5d077426-ad2e-4375-9bb0-6351580079ec", + "8c866285-e4f5-4963-b26d-3ac2a8f428d1", + "ee0e8f20-b00f-45b1-b25a-513a054babb1", + "f6f6e0ee-4831-4b36-88e0-6c30c77ca122", + "fee721e1-832d-4bf9-a231-630e8e465f25", + "0d3a1867-686b-4ec6-a8f4-075793929492", + "8f7621cc-234f-492f-96e7-db7eca1af55b", + "eb4b1587-665b-480e-8c54-d1b7ac025d4b", + "0c2c785c-cc26-4828-b179-f29331253a5b", + "619ee6e1-f0fa-48b5-830e-039c66a407ae", + "e64ff136-24ea-451b-8331-51d4fa7a7edc", + "69c4c8a7-b120-49dc-bdcb-60cbe38947a9", + "10b6e28f-f32e-407e-996a-469fa3884e5c", + "59b690c7-4b4d-42a6-8ff2-53dbbd416eb0", + "5e3cbd65-4767-43e0-9d91-8cf9e1b8eabf", + "29d92b27-14c6-4ea4-80cd-c254600b774d", + "f19b0fd5-7740-40ae-8930-1cf4dc7c7705", + "ee5f98d3-6c9f-49e5-a461-e0565e91b746", + "57b33282-9ace-4045-b205-d51f3c56aaf4", + "2b510b5e-0e9b-4629-a2e4-c43f0274ed60", + "3524f668-26a6-4c2a-85b7-1d26beedbda5", + "f589bc40-48d8-4585-957f-7cefeb92aa84", + "d6c88fcb-daa1-472d-a947-f8714d302d00", + "1ba11bb0-4204-4db6-8af5-6063d17c985d", + "0cb739c1-e577-4bdb-aedd-49f5ca71634b", + "528ff103-1e70-491a-b9b1-206b87650127", + "1bc5083a-3e1b-40e5-9f99-66bdf21de2c5", + "6f74a5ad-2091-4a6f-9986-da7ccd07919f", + "011e8eca-ce92-45c4-9183-3ac5890f5211", + "631ab22c-611f-4022-977e-5c1c716fec21", + "64d8cf69-e21e-4cab-ad20-0d835098bc69", + "7af6c5ce-47a8-4604-8a92-6165167778dd", + "168b9e12-cf4d-4254-b1ae-84ab173d4c86", + "84e84385-b399-4b5e-8890-0a1104ece766", + "15db266a-b0de-4e72-a59f-ed4098c33fbf", + "3bd9625d-a256-4f35-b97c-1871c0cb38b6", + "94a5927d-f84b-4508-85c4-8aa588efccce", + "6034f6ce-1db4-4582-abbb-aa1ad1be2db5", + "d2ca6ea1-3990-4254-b951-13d0c63688b9", + "7d5becbb-68f9-4516-81bb-506e2f0f010f", + "7ac5564f-b148-45cb-9181-df47fa5baf32", + "3472ce06-819a-408a-a57c-824c87de21bd", + "39051c5e-8a50-4135-9eed-d831f58da148", + "26c9ca47-5e88-4f27-9f2c-bf4d2a913370", + "cf114842-0499-4fc0-a972-03a8a22e945a", + "c5512ac0-4566-4592-ab70-478d5f77c278", + "3f507100-cb3a-450d-bf52-80ff9f681552", + "2efbf34e-3d8b-4e4f-ba89-4138847db12b", + "fb6a7a89-a9d2-4838-9901-2f6871549520", + "a5dad9dd-bc54-454a-885c-b2cd72797e17", + "5972e710-3c87-42e3-b167-1af7f819fde6", + "2b27e274-7c60-49d1-9dff-21b5e8f9ad51", + "20a9a36b-8a3e-4398-b6d6-74bf49450005", + "894d52c1-cd7f-440e-a8b2-b4f224491fcd", + "9547f591-5f8d-459d-8081-5514ca0fe25a", + "5e6b2223-734b-4fb0-95ec-1c968a06112c", + "907695b6-bd98-4f85-9752-631f520464cb", + "0c3aaf0b-3186-4910-8ada-fa678f25f338", + "9e778dfb-6f64-4944-b20b-e78bac277424", + "ac72ef24-034e-4341-a113-34a38c25b28a", + "e2e5b23c-2fe7-44e4-b630-9a28e9e31ed7", + "529a5eb0-1a7b-40a6-bed0-f70cb2141f99", + "10b7e68f-1695-4d0b-97f4-12b336c30b5a", + "bc327a9d-4815-4272-a350-ac0cbc4f1d8e", + "7ac67ace-1f73-41db-a0d8-1aec764bb127", + "19faaf9c-7e74-4160-8ccb-4cc58068f2a9", + "0f15e4e2-ca32-416b-b1f5-d308546aca96", + "3f9aaeb2-f234-4d65-9c92-db32aed13e3e", + "03d414f0-4215-40f7-a5e8-b68c378d2ab1", + "b0cdd821-3b85-4db4-b9c8-d2dd1583d7d7", + "a8a38c3c-4705-4230-ac56-de89e3ff439f", + "07afdbfc-32b7-4d4e-bddc-4474d5a2a879", + "13d44809-14fa-4399-b935-af7ccc1a119a", + "53b32f07-002b-4f7b-92f4-ad28a95d8965", + "e974e214-237e-4642-ad3c-88d77a5ec6d3", + "11578b7e-4a3d-4d82-ab43-236411a39ebb", + "a2d0d430-b533-409a-a5f1-3558703e39cc", + "a2338af9-9f98-4e1e-90a6-4a9747aedfe7", + "d44f5637-8ef3-48ea-891d-df486357a4d3", + "6675f61a-5b83-4132-8501-0957e540328e", + "c7ee423f-1f6a-4a9f-b9c0-ff7eafd2adaf", + "62618f35-5aaf-4673-8242-0c9058562eac", + "107fc13f-8757-4ff2-9a32-7b9df8633694", + "61c83110-0f4b-4787-923e-1f03ba5dc451", + "cf808229-55b9-4440-8bad-4e072426a186", + "b65e6842-091a-414a-b9fa-24b26514bff8", + "d317bc9e-831a-4fa7-af55-ba9ae5db183b", + "a1ac831a-6f98-43ad-a2a4-94b8577bd7e2", + "0eb6dc2a-e374-4368-900c-0a57728f32ba", + "fd3afae1-413f-4de1-a1b4-91f0de651aa3", + "8bd87c62-db20-4349-b26d-28373d399401", + "0c7d52c1-b29b-411e-8369-3530223ab37e", + "4de40531-41c9-4b0a-b0d6-77194b4f0280", + "fbf98a54-05cb-4324-a2ea-ce73867b6ef9", + "3badac78-66e6-49ce-a6ed-672e97fb3589", + "de5ab556-e7fd-4898-bedd-5355b15283e4", + "d2a42d56-5fb6-4dcc-bd2d-b76cd2ef8d3d", + "c663008d-f297-47e3-942b-42837d771c65", + "2cd16972-2471-4161-a058-115356ed79b7", + "168fe346-646f-4cdb-9e9e-0840956b68be", + "632cae7d-5fca-4b9d-b0e4-2219e8763500", + "da784af0-2c34-4fd7-b93d-127f3998dfdc", + "c0a61941-85b7-4a95-8873-e7040bf9250b", + "39f0d7a6-dca7-4452-9bfe-0c46bdf75e4e", + "b8f6a701-4b59-405f-b59b-ab7d8e11e856", + "fd6b599b-3c5e-4084-81ec-a30045b71386", + "376017d7-5ea6-4623-b5ea-76d88feb1b52", + "120d6509-6d87-4dc7-ae97-a5fd713d7624", + "92e7de44-3264-478b-99e7-05a590d6d812", + "3bf7ec35-7b90-49e3-8f23-03c16fe3d80d", + "aefb9f13-ff95-4306-89cc-c70a65622785", + "fa1700b4-8d7d-474a-9296-788d3281e940", + "7f0d0d03-a9c3-4e8a-9384-cf619956bc2c", + "ea3e5edc-0544-46a0-b63f-fc08d3766a73", + "a6ec1ae3-bfca-4b62-97af-edb2c509a12e", + "1753ec96-6989-4374-bc35-a1734508bdff", + "a8a18a6b-8071-49e5-9511-04316e682889", + "18b186c5-21b8-4470-90c1-911bfb31b831", + "bb36af36-a871-4702-98d0-a097b7d092e7", + "708a6819-6c99-4d60-9a1d-21d0117d45c7", + "d2d23592-2acd-4906-8251-a3e576cadbdd", + "4dcbcae1-3292-45bc-9e2f-095f2ce60a0e", + "d2c96bbc-6958-45ee-af01-8f4b78715b32", + "70f7f616-b59f-46d9-be21-cc0f3437b31d", + "23f3ccc0-a3c4-4712-aed0-ddea807184ee", + "2e3935b3-5cac-4b01-a8da-286291a17657", + "ff54580a-cc5f-48c8-9ff6-142b9ec12ecd", + "b9961021-a349-473e-b0c5-695b456382b0", + "e1376ed6-7a67-45aa-b406-12383373e94f", + "9326765c-627d-44b2-99ac-ba05693e368a", + "dd221431-fe48-4367-8aac-0237fc9d8583", + "792a41aa-a5b6-41f9-b930-1d78f797ef5b", + "7d00c98a-e4e4-40ad-9d24-14df8dfc26e1", + "35a1f0ff-50e6-4072-8be7-019aab549779", + "57ed1108-b2f2-48b7-904c-0dbb4e560091", + "a0dbea4b-cb4d-4379-8eb9-39ff06370aec", + "6b42fb08-6b18-48b8-913a-4bf1c6dd0302", + "ecad8ff1-350c-4a86-8c22-6cb08f687d5b", + "890b82f5-51b2-4592-b786-92e7067c7545", + "32d6356c-e2ba-42b7-a4ec-5a4e33539c8f", + "590a40f9-68f7-4ae3-b04a-f1d6f3b93751", + "712b471d-e0b5-41b6-8127-1942b0a320a5", + "2be3bba2-4bb4-4e22-bd15-9787612afa34", + "865ac95a-473c-47fe-87c8-d8ef820c8799", + "e396c353-df00-449a-ac01-defc1d8d8ec0", + "a9b976bc-f486-4c17-95b2-0903b3f2d90d", + "d205f3e3-81f8-4333-b87d-9f3ae1d39991", + "a2def0cc-3bd0-4537-bd84-4324f66faf0e", + "c5325eb4-a5c4-4771-96ee-5d45546828db", + "4fb1c05d-14d5-4e47-8d52-266809a0b5bf", + "88583b6a-c440-492e-936a-27cfa6fea48f", + "f0e98485-f178-4b89-965f-de1309c6d6d4", + "50fc8869-ccd2-4da1-838b-e269ecdd4022", + "b522ad9a-b07a-4261-b83f-56e52bad1f4f", + "92cdd1c3-82a7-42ef-9263-da0521ec5bd3", + "d04d4567-9ff0-4882-acae-f8bee2c15c7a", + "2c20e1d0-e268-4f90-bdea-9616d411394b", + "84bb5a4d-a716-4c8c-8dfa-4980e99f75fe", + "f3e2ff6d-e861-4044-b390-6c40e95eb221", + "3dff451d-efb2-4a2f-b226-ecca8d6e09dc", + "6ac13712-3a6f-4479-9022-8b25b9ee8ac9", + "f396f23d-60fe-4c4c-8f56-9a169bfdf8cf", + "8c7071da-1b51-4a7e-b109-2f84326370e1", + "e62c577f-ebcc-440a-bbd4-433d48b69c72", + "fcde2fef-575f-4f7a-903e-bc00b2d20a4c", + "eb4404a3-356f-4da0-b960-5e8974a3202e", + "c9311010-be7d-4387-a630-22b683c05426", + "e1398d1d-43f1-44f5-a4c1-8bfa821a737e", + "3f562d82-1f1d-4b7d-a3d1-963aba8fd3f4", + "773a2976-14c4-4026-b81a-be3d7a727733", + "98d2941b-c85f-4c6c-a4b8-06a024ae65a2", + "1318274f-63ac-420a-85af-d5010aa4ea29", + "51cc1aac-9785-4d07-996a-a8c47d2c002a", + "a18fe519-0940-4017-aa9e-14eb3e5bb646", + "a7bf06e4-723a-49a2-9038-302e21a3da99", + "f4834d86-590e-402a-8845-bf98feaa8884", + "4e2f618d-d542-4678-bb42-183239ee435d", + "f04406ab-abc5-4f4c-a81c-764c65ce7e97", + "2edf29cc-acbe-43e3-9794-de132bcaf9f4", + "cf091f05-550f-438b-b917-0e53da68f146", + "9ac00c71-2ece-44a5-8044-12cbae77e532", + "9cd46c44-18df-4f90-b80d-fab168a89d49", + "86196bfd-a69c-4ee9-a840-135e96cdd463", + "964a6f5d-0b33-4fea-b467-3044cd6600cc", + "3077a017-f216-4baf-9446-84e60c8f6e88", + "1323e3fc-aa06-462c-8d8f-2ac6e22d9b38", + "3251ac9c-571e-4e9e-b15e-f457fe4ce8a2", + "4d17adc0-61ef-4b2b-88be-99311afd9bb1", + "fed702b0-a3c2-4895-bc41-c8dff2f95db4", + "2b1a28c2-821d-4a60-8c9e-05f5f9322ed6", + "d1170f94-1c48-4f48-8091-a5c94e5d19cc", + "e9223b90-0ecd-4214-b706-b5366533647a", + "8121363d-96d1-47a7-b457-0b3a701e84fe", + "6c43a9b6-2868-4550-a87f-8ed8c10d23e7", + "c8cdc362-ad90-408f-bf43-7087c307a30c", + "0cf2a923-0b62-4667-9a15-bdb636bebb97", + "b8553ac0-874a-4446-a668-1d40797856e4", + "96d00b9b-e137-4ce0-9189-0ce44465284b", + "86961707-6067-481b-a129-0e96b8a1acb0", + "0d26141f-1b33-4404-9ec9-d88f94a657a3", + "fff8d888-a947-488c-9b1a-a48753a3ec1a", + "4ade5b7f-1d3d-45d3-9adb-d24228f7bc87", + "da377d67-7049-4707-af17-c914e5850ee8", + "2eb56b14-11da-4bb9-804a-2fcc56f62c48", + "4ad7bfa8-6ea2-4927-8be1-05ca66724168", + "0ec0cfe0-ad1c-4fee-a8c0-f5a00adada14", + "c1fad8be-01ce-430f-b9bf-11bc2f3ec376", + "de9f0569-ed10-435e-8f5b-345692d39929", + "648ae9bc-4ea9-42b7-99a2-9650d16c1e63", + "af124531-c91a-4561-8b21-1a5bb638b71f", + "d0d682d7-dc04-40ba-99e1-8fb2a0ca43fd", + "9aa3a2d6-953f-4d41-b672-71db709b69ae", + "a848b7cf-26e4-4abe-a4a7-fd7f6c3761bd", + "7d30b41a-1237-4a1c-8884-96e5882b7571", + "9431058f-e62c-4076-abff-dddb41f41581", + "65721459-79b4-496b-8b73-92841c3bfcd8", + "16f31618-735d-44dc-b161-500b0e83808f", + "b500bd5d-41fe-4010-997a-75c4066f4d88", + "9cfd6abf-51ed-4cb6-a123-99cf99b45c82", + "e1d52467-b9b5-45d6-8fda-39f231a21872", + "c8562eac-7915-4809-8182-5f330811bc40", + "cccdba3b-2e5e-4905-b4af-bed5591105bd", + "6aad8630-aec9-45bf-b1dc-c90f2d2c7d48", + "6fa11136-5a1f-487a-9aa1-42eb05cd5c63", + "35cb9b50-21f5-462d-9311-77c22f400542", + "1531bdd1-10f5-4334-9939-171af1d8c209", + "fec3063f-2a39-4ed1-bbe9-ce16872c1ef5", + "adbe9bed-df0b-496c-84dc-679436bf5989", + "a33344a8-ce74-4c23-8e80-3cea3615a2e9", + "fb691d30-7350-4b2d-b85d-a03f85678ac0", + "5d7c23c5-cf50-4859-8279-3f0d62bbf929", + "0352310e-6518-43ee-b717-0bf9cda88cf8", + "918cbdd7-4a0f-474a-89cb-c0976bbc1d31", + "124493ac-ca70-4fea-a6a8-0b60a84f7cb8", + "1057dc6a-c1f1-4a18-a45f-3917d1d64f65", + "4f771d41-cc18-4e8b-8f53-a1d502bcd8e0", + "5146f2f3-43f8-4bb6-bb68-72b037be306b", + "3946a189-6893-4f47-90c5-fffc096ef3bd", + "4c98ae6b-7543-4e39-a625-7e32bebef366", + "b74c5c6a-f865-4256-a271-cc7b02e8ea69", + "fb7eb7b1-8214-4540-8c3f-95a58bd69cda", + "15f2196a-d8d0-404a-970a-3c5109349328", + "69c32f8d-910f-4aab-bd01-14754b1974ec", + "bcb31f32-40c2-4aba-9f06-db3498d096b7", + "c668f5c9-7cca-4db2-adc3-439403300f5e", + "7857fdb5-1111-483c-81cd-4ce983bba37d", + "763fc5ea-0605-4f47-a4c6-d038ed102926", + "c281d753-2d27-4bc1-8e81-2a04c8d22176", + "dfde2f1d-d259-43a0-9e8e-f0334ce89369", + "71bc0126-3466-47f3-a6fe-46bc99725dc5", + "11a19de1-af57-40f4-88de-d17187bdeda1", + "c4e19f94-18a2-42cd-82ce-e2890dcecb60", + "273f1393-fc69-47c4-92dc-0551f8db1a49", + "27d26483-52e7-41b4-8902-12a330431041", + "e0f5553c-9a0c-4f7f-9ba5-776ebf33bf5d", + "517cb14d-98ac-430f-a3f8-1d67ea901c45", + "c2c08e06-c4a9-49d6-ad6c-f0ac6a3843f2", + "b0bd93c7-494c-4854-be43-ef001d045ebb", + "e781d8c0-1581-4b7a-8b88-6e3b8d3b7c7c", + "614adba4-8c1a-4780-b103-e63373025669", + "b4b2dee0-93bf-402a-a6e8-a967076564ec", + "7635fb43-8c3d-4077-82a4-2b1a281e3fcd", + "e65f6c28-c41a-4c09-a4ef-f65712bc5ce1", + "89cfa9fc-795f-4c66-b49a-ea89f3094a3b", + "ec4b8215-286f-4146-8594-6ca2b85053ba", + "6d06ca26-8d13-4065-8b7e-0e4d7c4f6516", + "5746f2ce-8973-4e93-ba6e-7e083b25687c", + "daf30fbb-ef5d-46ff-b147-0b658430fcda", + "ea20fc83-a43a-4928-b21d-83cd76b69923", + "4a671c62-92ca-494f-b480-3d7592763a35", + "134429ba-75d9-463f-b84f-c0f7be4e792b", + "641900c7-3051-480b-a313-67421b33e904", + "8413f3fa-7ddd-478e-985b-5b3fc6a1394e", + "8f115f32-b4ea-454a-b510-769b857cdd45", + "cd3cd4ec-5d26-42e8-b686-6d989b4bd3d0", + "dcfb26ac-dfaa-4daf-ac1b-fd789f5c9d67", + "4f4bc75b-9f55-47c2-bbf0-32740e574fc5", + "5dd1ca84-b50f-4fa5-8f3f-7b3e14a2b932", + "d3687626-7514-45be-9e7b-6f7bd2c1e81d", + "f84ab7dd-9c87-423e-ba58-6623dc7eb151", + "84b95ec0-976b-46e7-afa3-99555845cf2f", + "e5f56d24-7afd-4b53-9a98-a661139904fb", + "8e9bf557-8167-4b0e-8abb-90c037f415b5", + "740c7a0e-9df2-4aaf-93fd-95f77271cd3a", + "7e650bc1-716a-432b-ba82-ace5c4e627bc", + "8b4d2bae-1e5a-4723-a56a-27cf2466800d", + "84028e8b-bbc0-4c31-86b0-f83009559277", + "3060765b-2167-4042-801d-765e600532bd", + "cc1579e9-091f-4402-9f34-a22f6e0a9f86", + "9af0505d-34d0-4bd1-ae03-75ebf8bb1a83", + "4589a390-19e0-4ee6-9cc8-cc33eb900664", + "0e7c146a-0f23-4db0-a169-32cd4fcd094c", + "d81f689f-4ec6-40fd-a718-370a08cadd93", + "d14ab472-0955-45c6-b9bc-e8a3ed32e9a7", + "4d796254-4654-4715-b14d-56b58e793713", + "9f66d9e1-fef4-4edc-ab17-dffa21092577", + "4687f97b-70f9-4492-a1b0-6d786510bf1c", + "e6475e00-402a-4196-86c9-3b12b2316953", + "8ba021d3-d853-416b-910c-a29e26b378be", + "ced246b5-5fd6-4ddb-b5bf-092bea3e18e7", + "de44a44f-aa4f-45d6-a78b-02ca649368b3", + "b61a1f97-d553-4002-9806-7c2106d805e6", + "7607a10c-515d-4df3-b537-b2e3ed91793d", + "051a0826-4c7b-4c23-a4b1-a4b1ecc1588d", + "6f6e16bf-cd29-4e33-b9cc-f40f4d314496", + "293b2ccd-6b7e-435c-9b79-01cde11063d0", + "2305ad2b-3db1-46e3-bf3d-8d65f47b8b28", + "3dd8e10d-b86b-403c-9558-4ab101f24b7a", + "70316aba-6247-4188-a3d2-6bfb9a37d592", + "63349a70-c0d2-4357-8df3-f994b6718d14", + "096aa94d-8c13-41b8-80d1-ea92490c80b6", + "63867acc-73fc-4da8-b09f-6242001edf30", + "f7f0b9b7-5029-41e1-880b-916ea8b27077", + "ed7e8e50-abb7-46f5-823d-cbbebdd3e3fd", + "d031ee94-b45f-4028-91f1-eb03c44972c3", + "29eeea41-c693-46a5-96d0-e03e7470e750", + "dca2f3f3-cea4-4324-9910-219d4335dc5f", + "268f8c75-3d7f-4de0-9999-660c78365620", + "c453ac94-ec4f-492f-b0dd-3605544efe6c", + "650e723d-5407-449b-9523-e244beae1392", + "901d913e-ec73-4a99-9af9-c8552924bdf8", + "cc847271-b996-469b-8e9e-2d8c9de2733d", + "bcb5dd37-bb84-49ad-876a-ff25b0715958", + "924393f9-140b-4b19-a753-08eda30a6fa9", + "7be3c4d2-ba28-4ff4-8a13-16c6c5566228", + "e807a8e8-9f82-4663-826a-7649eb14af0f", + "60541015-9aef-410d-9d70-1a3bc1060112", + "0b4578e8-1933-4f02-8c89-ab35a040b239", + "0e8eee16-786d-4151-bb36-f1f18efe563c", + "9fa0c0f7-ddb1-47e6-a3b3-b8308370007f", + "53922df6-ad30-4ca7-a6fe-4fd8a8f94e3a", + "a71da87f-7c5c-4ebc-8417-d9ca3d651fc5", + "415b5c28-1949-4c10-8f6f-1ff8b05ef065", + "64508209-9b7b-4897-a404-03f101a54b53", + "4d009015-4b88-4eae-b7d4-fe9b52a587dd", + "ac9c649a-08f0-4633-b516-3ec9e92827c3", + "96a609b7-faa6-4b1f-b3f7-7467e236351d", + "8afb7e60-2787-4d34-9f1d-1023bdf7626a", + "fdb97933-4052-43b5-8e49-a174b59b14a0", + "bb94f164-674f-403c-9e2d-25ca1e569049", + "70926fb1-c70e-48a5-9634-141c2eb6022e", + "8d8876be-ff8d-4b65-8734-d49722932f6f", + "28c9caf3-ac89-4123-8aeb-90365f2a6525", + "6d85b176-8fa3-44eb-adc9-252f2c277869", + "41dc2481-0c25-47d2-a6f8-11f93ca5157f", + "bab3ab61-3399-4108-94a1-450288db49c5", + "d126f0d1-4cd7-434b-b852-0e0b3fa20d7f", + "edb736b5-f381-492e-84f9-ba130d9fd549", + "c41b1e86-54fe-429f-bd89-3962b3be0164", + "39c78374-c529-4389-805f-0e5fc9ac4be6", + "8fede89a-889d-44e6-bcbe-f5b713681f5f", + "3fa96e02-d3f5-498a-a994-53943b1b5274", + "8bf68c84-9af3-4e4a-9b38-8491e33ffad8", + "29442b75-ec34-4b86-9606-93a1cabc60eb", + "e54fe7d8-6797-4a30-90d1-4a2665aa3a27", + "8c8fe1c1-94ad-4bcd-9838-19362622b1f1", + "d21b1795-af7c-4836-8c9e-c205bd003f06", + "a5986cee-bd02-4a4e-8ac3-97ba1c5bb0d2", + "313aa627-2b37-4239-bcfc-c4060bd47fe0", + "f9629da6-1f04-4625-8cf1-963698b06fea", + "e7e5e98d-b4c8-4d79-95dd-53dadf650953", + "de6b2464-1587-432f-9017-5f1a06a375c6", + "e5fb54ce-a8eb-4f8c-b5bd-7b5e4517be97", + "54bca510-1ec7-4e00-96d0-7993b90131b9", + "897ddd23-94cf-48b6-ba7b-4d5fd6da5fa6", + "136dfcb2-5879-4ca0-ac41-a35af6a17465", + "64c62d28-69f7-4c5e-8f49-e5c1292a8dbd", + "6e9b89fb-45fb-4862-ace0-e3b42f164835", + "b52a7b8b-4ede-4a78-a583-78e72fad7ed0", + "f67e9325-4f57-4679-b358-7cd6efbdadf4", + "7534bd59-bafa-4ad9-958d-c8e8c2b187ae", + "d203b0df-6046-4c7a-bbda-dbf3bb1bf2dd", + "d04e007d-ae81-49f9-aced-f2525bae66de", + "fe8b9127-c596-4e25-bc79-b8ce6ad68106", + "fc1d6fd0-d794-4927-9763-6c26919622f4", + "519bb434-ce2b-4397-b7a9-a7eb73cf295e", + "76f657bd-4401-4562-95c3-96e55333327d", + "5a404be0-c988-4e0b-baf4-cb57cb0e8496", + "08b12904-8301-4701-8ae5-723e6ab23bdd", + "a0ffd19d-a1ab-419c-962b-f58e2dfc5d1a", + "e5c2a1cc-132b-4983-9bd9-69c1d9bf1408", + "ec5fbf5e-0465-4681-83cc-ca92aee6bbfe", + "e4f04b81-0bb1-448e-b513-afc91a01a249", + "fcc5d73c-9aef-44f8-82dc-d1db2d60215e", + "0caff746-230d-42a0-9e4d-599aa43fa264", + "5eb87e46-9667-428d-99a5-582a2501b0e1", + "06b68c85-4158-482a-8397-9bdf330ead07", + "a1b7a43c-52fe-4521-9849-92de2c95d0a2", + "67c87c99-a473-4288-87da-aace07d3d463", + "8ac125d4-c721-407c-9f19-cb9956c90142", + "9171f91c-1c85-47f7-ad4b-b2de14223ce6", + "66de1401-1aac-46b6-8215-ea3b8736d024", + "c532cb0f-f999-4fcb-9c0b-54a4f437ca25", + "bbd6e34f-d06a-4335-b98b-ce2ef5ef412c", + "4811c548-e3d3-453d-ad80-4f55cb2ae747", + "f0d821fd-4229-46d7-b096-43a96a17ebab", + "5e2f338d-6826-41e5-855f-d188aa7eeaa7", + "5553afff-2522-4ec1-9a44-25fd9f220678", + "0c446df1-4d06-43ac-8080-4757100c8bba", + "636ec699-e686-46ac-89f1-14c8dccd3ad6", + "8135a031-0633-4ae6-a2e5-ede5ed46422b", + "ef9714f3-e229-4df0-8f38-e1a2cbe986c1", + "b6a71529-549f-405a-a480-a32ebd5c9c13", + "7cc78b77-34c4-45a5-af82-686dd55de037", + "d185e5aa-793c-4852-9c65-5e282415a0a6", + "2e8a8c05-f65b-4993-b86d-2ad5232eeff6", + "97b00593-0a7e-495b-bc0c-9184796738b8", + "18d9a133-44a6-47e5-b7ab-edc77c885ee2", + "1dcdb8ff-3ef5-45c9-846c-ec0f357a5adb", + "13c8da2c-2337-47da-94e5-1cd9ca1af1fa", + "a08fa9eb-5a14-4737-9921-659b179f58dd", + "8dc6d7ec-215c-4d14-a62e-65146cfc0903", + "8b88af55-8c3c-4d6d-9329-f3e6853d0c64", + "8e8d4575-dbf4-441b-affc-d30e8b325be6", + "0cef5ec3-c7e1-4529-9189-587dc1a8138b", + "8c736196-d3c1-4455-94ac-ab0bd91046f0", + "0723ea82-0c68-45b5-8f7e-d9ff51202f01", + "a09e43d9-c051-4f46-bf23-392c725639e3", + "1a6939ab-3a00-4fba-990a-29efa9226fc7", + "0d8a7a0a-866c-4813-bce2-47e8d4506a82", + "1e2aba0c-a79a-49db-a5d4-795f0e0adb97", + "dc228db3-930c-4098-9798-739ac255dd9b", + "add9f674-dd81-4e88-bfb1-aaa620a41221", + "ebea7257-a028-471f-a0f4-84b754bada82", + "b1f7dbba-245f-4244-bda2-3e36d90783c6", + "6a672e78-4fbc-4f28-a82d-13dde81464f3", + "ebd01c3b-d6cb-4b38-b258-5bca51121646", + "e7170907-63b2-41dc-9cae-40ad6847a246", + "3ef47eff-18cf-4bea-8a0e-f7c728214741", + "a18d460a-1c83-4313-a584-aea31c23ffcb", + "ebd80d43-2f4e-4a02-8541-72ea2eab58fb", + "733571a4-55fc-418e-83c4-623f5f529fb0", + "8228787a-1b8d-44d6-ace6-6ec9bfb237d7", + "aac18e90-9c47-49ba-8042-e0c5bad56e19", + "9606bb63-406b-421c-8f7a-4b86a38e3ec9", + "16542bd3-01c9-4ced-ac9a-60c990eb0956", + "f533e814-e7ba-4fe2-91e5-c16d661dfa67", + "8ac01073-bb46-406b-9200-2a0a6e5c2c47", + "5c642db3-58d7-483a-99f6-6a4b04120cf4", + "716c9531-bd7c-4a23-ad25-59803badcf6b", + "fc49e13b-c2cb-49b5-80f2-d7ae1a752ad6", + "cf8ecda4-a9cd-44b5-ab5a-e963de4bb1a2", + "4d8f818d-899b-4c94-9fc5-e4f0cf97cd60", + "b7ac6dc7-6d4c-4644-bcbe-bef6e5757207", + "bcc98cbe-6e96-495e-a422-1628ffbcfdb9", + "1ae4e0ae-bde1-48e9-a17f-9247f5f09a2b", + "aadfedbc-008a-4396-9f2b-393eab0a34e3", + "08d0422a-2f2d-48cc-be6e-15ecc7a98c6e", + "06342c5f-8dca-4fe8-be8a-115f496c230c", + "4eaa1240-51aa-489f-b2b3-759b504b2c65", + "30f42d5c-6290-4ba9-aae6-7c9e3db600a5", + "163c4b96-deb4-4ed4-9ab0-19d4c824ac6c", + "a84c3e3d-075b-4eaa-a8c2-c4cee1d24892", + "2ee269c4-ce71-475b-ba3e-0e2641376094", + "b596ba13-e474-40b3-bb77-ef78a28e7aee", + "7c4c5d1c-dc38-4f99-bf93-e82d64536119", + "68a79737-6fe9-4f6f-be96-15b959603a01", + "3127e36b-a84c-4ced-9f39-beb63731362a", + "0936de09-f4bf-4159-b910-1dd4a72766ea", + "59be9f4a-3768-4bb4-bff9-cebecc634dd5", + "27d7ec1f-46bb-4ecc-85dc-0f2f406ab021", + "c146ecb1-2423-4b5d-953c-766aee54b819", + "cc19005a-e6ea-4ca7-9740-9964605f8416", + "61f2f16a-febb-40ab-9aee-7547f3440e30", + "fead492d-5d1b-4256-ae7d-03457ca8d88a", + "e4af5b94-cb0d-4f05-a16d-b8c70675c995", + "7ac24073-290c-4970-bb91-48a04953486c", + "3a49f78f-efbf-4acb-8551-20f076245613", + "af9daaf0-3e38-4ba2-afd4-39f35b47f37f", + "b2066f06-9c0a-4a6b-9669-4cd5564d8fa7", + "e0d9f152-60d0-4520-935b-0b5e49d4646a", + "718de09b-bc78-4c4c-a166-4dd116896f5a", + "8c0baa11-db42-4649-b3dd-7d2062132cb2", + "0ea35f2a-52d7-498c-a57f-aea1b802ab0c", + "1b31464b-1694-4bb8-974e-a2df8db8f73a", + "52988315-caf7-4cd7-a313-546b26aec3bb", + "2d10a225-ad88-40fc-ad17-e1bfd681f347", + "bcfa6cac-eddc-4b68-9569-af0cf615be3c", + "9b52a08f-b27d-4b4e-a9ef-499831985adc", + "6469dca8-37ea-4dcd-ac38-3cee76c1fa3f", + "334ff44a-9d2a-45d0-9a47-7ffc13718d8d", + "47e44461-7342-4731-9f47-a6aaeaeb6b4d", + "3d6c2ae1-1314-4f7c-a500-07705dc8a406", + "35eab00f-6e61-4947-b0d5-37294caccc54", + "8b05a8a6-b879-41a5-917c-3a06114dc48c", + "8ea8da24-4946-4b5d-8372-3fa705f6352f", + "376281f5-f9a9-4a77-9f61-6a1ac5431bf8", + "00ca6f40-983a-4108-a347-5717f7491f6a", + "500fccd3-5828-4a8a-b255-ef2bf1d5eedd", + "f3db0430-9abc-40f6-9825-3de7e8c913f3", + "4ee6cdef-9b36-437b-a75b-9f91026b187c", + "76761c9f-283d-4a62-bec6-a171131c4f70", + "1a8fc43d-3c0b-4204-ac67-6b8800be6613", + "a74298ac-ddf8-49f3-8d3f-5ec18f5d4b63", + "8a1f0c43-b127-4834-b6f3-a76ce21a86a3", + "636977a7-fb95-4a9f-9a69-93b08259d01a", + "63cda048-987d-4f0e-a8f1-64c00d2ab76e", + "fc1ebfe3-f7f5-4cee-95d1-6659e6b21dab", + "32858620-ee3e-4ae9-9f53-fa0f6d13770b", + "addf7156-ac30-432f-aba0-c22ea662fbbb", + "52ffcbea-fb94-48f8-b51e-2cc4b18a2e7e", + "af86b6bc-842c-437c-b822-b4d3083a5f90", + "e1dc0145-c0ef-4e37-abc7-ee8874d46264", + "bdd28f28-9cbe-4ad7-be57-d712e9918619", + "510796e8-6c56-42ed-bc60-d8ed72bade57", + "1c2dcee4-7dc5-4416-83ba-ffafb378baac", + "7352d5b3-5802-4ffb-a914-7a78f6031854", + "304c3074-5abf-4781-abcf-264655a8ad32", + "4a579c84-87d6-4251-bc38-2d6ac157dc45", + "ee480bb4-ba33-45fd-bf43-df0efba7b0a9", + "0f64d8f6-8f17-44d1-9031-8da489be41f7", + "14315563-c467-4c1d-9705-163ffe37e874", + "7d8e0a98-cd6e-46dc-a8b3-10411dfadddf", + "9c88c4c6-d104-4881-9bfa-f955e2c7ca80", + "e1b37476-14d9-4416-a412-812b0cd8ccb6", + "e7078463-cfe9-4676-8a4e-27780a061858", + "5ad72f80-6c38-4341-96ff-dbf0e8a8ec5d", + "1065d8e1-b7ca-4f19-9a2c-65d511a90e8b", + "74ded7c2-672e-445f-bb1f-e6a293d134c2", + "7ed2483d-7c72-47e2-9d2c-63c43bc0ea9f", + "6f4392db-1ee3-4f84-b90d-204e95485c6e", + "88da6560-adec-4966-99a9-6d7d6e624fd2", + "c41d8d1a-eabb-4566-97b8-534dc118fbd5", + "08864adc-e9c0-421d-b4b4-65c9ce2cfe26", + "9f8822a0-2492-461a-81aa-1dba93d5efca", + "b1d104e2-2f20-4659-81e4-e7f73107081e", + "3c859287-b94c-44e3-abed-b74a504c5446", + "b64c9bba-95f8-4892-a4d6-3915d602c7c4", + "926478ed-62ae-474a-bccf-b3c2bf216b9e", + "473440b0-94ec-4084-ad4c-cc91bdfc41ab", + "13919b89-e672-4479-aff4-026461221c30", + "1c04d603-8682-4f39-ab50-059768bd7035", + "29aaf2d4-acfd-4f65-9748-8d7d5601fa91", + "778bafaa-63c2-411a-9e8e-4238c014181b", + "99500af7-198c-4f3b-ba18-c636f331282e", + "949c7e10-8833-48ec-b43b-9edf7f6e9e50", + "7c441418-2d96-4355-9cc9-a8797d2ad2b4", + "5630c5bb-7847-4508-876d-4174cc932aa0", + "37822234-b54b-4215-ac49-a7d1fcb378ef", + "7f806742-3e6c-4641-9370-61a51a155770", + "83f0a1f2-e842-45ab-810a-b6f7d447ec07", + "084471cd-5fd1-412b-b6e7-0c5db2106ec7", + "120cce8a-5ad3-404a-b8e1-1ea1ca55f344", + "b6e9d3ff-ef28-47a6-aed2-165a26bb94bc", + "7a73d3d3-9f6a-436c-95e3-c2622eb4f05e", + "5d5f68f4-1cd1-42de-add8-216ebf34d49a", + "da05edc3-aae6-4698-b75b-8d71adfeedf8", + "f0954e14-63a6-4c68-8da3-09a3180ba0bc", + "36476b52-8320-4c54-8045-3fccb56d5e41", + "abfb063e-ea74-450d-bda6-1cefac709e11", + "a1bfc324-4b6f-4399-a163-00d232232bbd", + "186fdb6d-5183-42bb-aef5-0beb08707e13", + "cd4fe7e8-734e-4d13-b279-b8a860720027", + "d999b9fa-e0dc-4a45-99b1-ed42120fe4bb", + "3029cca1-44d6-47c8-a9ec-5e84b670797c", + "a964d393-3df6-475d-a314-7ce66246ca7b", + "14ac9b4f-7c38-4d5d-a4c1-8a09212bef65", + "0ce422a3-56e1-4a94-8202-e426f7441b29", + "56a528cb-8e6f-49e3-adce-5c649bcd8272", + "da1a1e10-52f5-46cd-9ec3-f2bbf6856425", + "a5936e80-7123-48c8-a149-9153739de236", + "bb0b6976-c73b-428c-a8e0-061a0e5cf220", + "d3fd7320-4cc5-4288-ab05-5e4e0d1448c3", + "566005d9-77d6-49a1-8b4b-a64d6d69b1f9", + "ba662787-aca3-4f4d-80e5-e456f791b4f9", + "a5a03023-b849-430c-bf3a-3253a360addc", + "52047170-77eb-4ae3-a0c2-b1842105c60d", + "ad384540-2295-4bb4-b001-cc09a5ba77e7", + "856e8091-4930-427a-b7ff-c28bd731a7f9", + "bf656dc0-b67f-4686-8d62-e1707411541e", + "cb7a860c-0086-48de-9ba5-1188f8e58823", + "ab754527-dd28-40fd-8bb5-a857cdc171ad", + "78841fb7-fe0c-40c5-b239-02e13ffa851e", + "d525ae18-01fc-4857-88a1-4bdfd0bc3196", + "0f361cdd-5170-465e-9c96-880548132b63", + "720186c7-e092-424d-aa0e-4ffaa7444db4", + "f762f958-1294-4bf7-affd-b1fc3b44e4b8", + "bef87d96-4d68-4249-99ae-6165cd9aaea8", + "d3253b05-6739-4c43-a81b-0775eb99d94d", + "4512649a-6e4e-49e9-8587-041d68cd6575", + "d9e834fd-32b1-4da7-8832-0f8d137cdb91", + "59c9d8e9-0e3c-4a10-90e7-8606c727ce74", + "43f14021-4977-423b-873b-7252b404f885", + "370fc8fc-5784-4bd6-838f-1777e8f02e71", + "793853a3-8250-4d37-8963-1c5d6b2f4832", + "a335af30-16e2-4ca3-b111-3a8fd4dd02ba", + "c1952fd3-383b-485f-8e8b-a181552d9793", + "b4d49ed7-499b-49d1-bbc2-14f2e8c8826a", + "b7f55de3-0790-4201-8bac-c9cf9acd616f", + "c141f32e-d03a-421c-aa27-800cd66b519e", + "88caf671-5ab3-49d4-8244-cd79aec1ea7a", + "ee5ecefd-2201-4797-85f0-f6bc809b4f1f", + "782f6fac-8883-4ea3-9ed9-22d5486d148c", + "ba34a00e-b04f-4964-adbb-cb9d12705ba9", + "4aa2de2f-e713-4e85-b3ba-e3d8ba3ba809", + "1ca2ac3a-4771-47a8-9faa-485f56388c60", + "151308f3-7cf0-471f-a2fe-0cf719e9aac1", + "56f7ca75-fa08-4a1c-8ddb-e5efa147ffb7", + "2511527e-cca1-4933-94d5-b6847fee243a", + "b71abaf0-b464-40e0-b65e-f3dfe03d647b", + "5374acc7-9a22-49fa-bb2c-9dfb6dc693db", + "98e4f807-3471-44a5-8141-89ba34651dec", + "1fedd8e3-1e5d-4e4e-967d-9661b956bf0d", + "bcbbebed-d621-446a-b6d3-322f93e8244c", + "37200585-f3f6-4872-b82b-5983283f8427", + "2cb8bdbf-9e6b-4685-9ccd-af78b6c78775", + "fcbe99ca-10da-446f-9080-35269990ef23", + "3fbadc32-fa69-4ab8-9c72-b4f76d0eb5a3", + "e01f916b-d5b9-4690-98f0-342683a136f4", + "4b8535d8-4228-4279-b2dc-27956d4006f2", + "b1582b6c-2181-4ed0-a1df-0dd16ac44912", + "549ed09d-28f9-4b03-b1e4-2b06a28c0a5a", + "02424777-8170-418b-9d0d-5a6940fcdedf", + "9886b9a1-b19d-41af-a184-82828ab0fed4", + "34f28387-6167-4044-8668-f9851ab21655", + "759c0e90-0051-4366-a980-2022ffbd5ec0", + "253e8ce9-dddd-451f-98b6-573a0ccd6138", + "201abdf7-acbb-4b41-a714-291c229d1a7d", + "ea38ad7b-729d-404a-b144-7fc906b9fe3c", + "3ee35f97-a19c-4044-838c-21dce655fbb9", + "d3180f22-6d13-4dd4-91b4-ab2c4aea35b8", + "af6822df-01ea-4d11-82b7-fe0e9dba4642", + "644ae6f3-6fe1-40ba-8778-43e77a076e95", + "d79ec601-b90c-42fd-a275-ad9fa9160a0f", + "238a33b8-9cf1-4a3e-a5ae-dc3876bf102a", + "4882ad6e-cd22-4f96-b5af-60b1e3d363b5", + "f4c945eb-6613-4e71-af1c-641eedd9c28e", + "763b455b-45a2-4664-b56e-33f47f58988e", + "4549cade-f1eb-40d2-b54b-199d824b8f99", + "4661e72c-8042-4659-bec5-b05f84ccb8bf", + "b4f0229b-e21a-4304-bff6-44aad3472b22", + "73e821b4-eda2-4489-b291-d3ae21f81650", + "e571e162-6e8b-4f86-8246-059d85f44a99", + "ab43f5fc-54cc-4488-9d71-2f5b10bd0f4a", + "6a485c1e-ca64-4e66-b4fb-361446b5b4b5", + "fd6f4097-c3c4-46a5-885b-9b0b5ce2b096", + "1734e70e-b439-43bc-a605-77c35f119633", + "8a67269d-3415-4f73-b6f2-0e2c64b9040b", + "13f9c6ea-343f-42db-a072-365784f303ac", + "de22bf5e-fc70-4309-b9c0-3e2251109702", + "d2997cae-aa9a-4979-9b1d-6484db2995c4", + "94e080f3-2cf6-43c6-90f4-595a15f86565", + "d1c46591-64b0-448b-bbcb-c06f15323d99", + "05e6e04b-4d3c-40b8-a9a5-68afdc2a2611", + "40012e49-dceb-4ac6-afcb-e199ccaf0a9a", + "3024f660-661b-4e5f-a645-92b56bcd13d8", + "a38b36f0-72dd-4aa5-810b-897fad9a01cd", + "57e1886e-8895-4676-b2c6-9845b0f6fd5b", + "004d5c7c-91e0-4063-8176-0c2c13f6d88e", + "77520caf-f3a0-42dc-bb20-790d680394b3", + "f6adb770-f561-45e6-8498-3a760c301669", + "698f7eb8-fca1-48b1-90e7-7977f13277f0", + "21f9d1d2-3752-44da-bcff-76f590c514cd", + "fb0074cf-7acc-4fd5-9e6f-5d885baf1871", + "986a27a7-6554-4106-8d32-dca96a406326", + "335cd61f-24ba-4998-b243-69312f351c1f", + "295de188-af8a-4616-a2d2-f8f5f1f16438", + "49010cfe-5ec7-43fa-b6af-844caac7783e", + "2ba368fd-a3c0-4845-a423-534478e34ec3", + "e75c97cf-69dd-4bc9-a9ef-0e9a873e371e", + "f471c882-1227-4b5e-b969-4ff295ec17e2", + "de115619-33dd-42b7-9b03-7263e7cf05c5", + "cf3192ee-649f-4b04-b8f5-d63623006794", + "ab26685d-8e03-4cb1-8a1e-9f0466b25ff7", + "7ffe43d1-068d-4496-8283-f336a2c63255", + "c824ed71-e7b9-422e-899c-8a33d0930b97", + "38218d38-c702-4afd-b4aa-b213573ac802", + "e6e0bab0-550b-4841-8ab0-b77dc677d63a", + "d8d4b361-4e8c-4082-a473-cc10fd5a46fc", + "9272b828-c76d-4f9e-b225-559014d3b276", + "ac8f9a8a-454d-4884-b6ad-8ad5cfd03105", + "054ddd9b-58f2-4500-b0a5-39368b35bd4f", + "0252e8a4-74e9-418a-8782-785d6a953cc2", + "3caef73b-34cd-4a42-bdd5-19a28f71835c", + "2751aaca-fbcd-444f-8348-4500094e8ae7", + "c32669ad-0c21-4099-9f0c-d619c0e5e92b", + "dc75ae41-59a9-4c44-960d-d0e423a3f02d", + "a13b2250-f904-490e-9402-e6e821888b7e", + "acc4e56d-e760-4c9f-bf24-a47ac461891f", + "168b2bc0-cc5b-4051-a222-925f31790d5e", + "dce55866-ed2c-4aea-a71a-1fa065bc3876", + "3de935dd-3560-4860-a97b-4b840d1b821b", + "5e147c27-9b5b-464e-b24d-e7dcc8552d8d", + "c1581369-dc84-49b9-a481-fac96ea44570", + "2112aaa7-6ed4-4042-9dd0-c704e2a21e4d", + "b9431565-c638-402e-ace7-4d41475c41dd", + "3c3d66c7-aa18-4753-bb88-75f4e93b5fff", + "faaac612-891d-4fad-a5f9-8516a3a5c517", + "64fceb34-03ed-4f71-ae64-042be8bcf317", + "e2e236bb-78fe-459a-a102-e075d1d75b7e", + "67bad373-6c9d-4947-8ee9-421730dbe1dc", + "fd5df839-d611-4f1c-904f-59893d9d9f43", + "cda8a124-25de-43e6-8d03-22b8b0211d1e", + "2b494f44-36bb-4378-991a-9233b196e9b2", + "9fac17f0-9f68-46cf-b247-268ec4352ef7", + "5048cb7e-20ff-454c-865f-7cb36ca05e0f", + "4aec5c86-dce9-4520-8307-1d05e33e57df", + "68cab9a8-29b8-454f-a7b9-456f7263b470", + "6ecf3de8-afa0-4d81-b30d-2f63cfbe6e4a", + "b18e7d85-9ab8-499c-aa23-351cd7d36a47", + "68642aab-22a3-47a8-a192-1a0a0a531e01", + "051a5f90-eb45-4a3a-bb65-0da628bd3487", + "bace4129-ab70-4187-a016-ea6c57cd64ed", + "269fe844-7f09-4fc3-8517-bca5c8ce82f4", + "1c2ba1f1-9766-4e09-b310-cff994145988", + "c4a56129-f2f2-4e16-90c9-eae653f63850", + "f5ad2b81-fd87-44af-b0cc-61a856d84997", + "418e0734-0028-46d9-8de9-da87d93dd5bc", + "ad9b9e69-67f5-4bd5-b70a-635b65a0d256", + "62382a06-e6f3-4d47-8f15-1862105caa82", + "244e1a61-0553-49f6-8543-6481ff1d99e5", + "4b59389f-12eb-4b7c-b1eb-919dbf6ce691", + "2a69aea4-c45a-41ef-bc0b-08dce6f7d77f", + "8ef9ea47-4efc-4dc6-8a9c-c96cab993930", + "fe60f146-ee06-46f1-91e7-5711f1552682", + "7420e488-d2cd-431b-abc7-0e1f634f88f9", + "53d9b4f9-b61f-4d01-9ae1-f2f5268ace16", + "ea4f2fe7-bcc2-47b5-9da5-7e51e920bf55", + "4ceb403a-9df6-414c-acde-dad9674927a4", + "4f047546-1d84-451f-b026-f867ffce1d13", + "0a56e6a5-f284-4961-b815-e2c47652f11b", + "6c39588b-228b-4bf4-ab28-21c817465612", + "e2106556-4bda-4970-abe5-57c9fbda6a0f", + "21d1ffd2-ee13-41b6-a880-7bd2c2083908", + "21fe71d7-9985-4a3d-b339-3ea329a2ed16", + "405a593f-1cb0-408c-9dd8-33a890608053", + "84ad38ad-54fb-4eaa-8d5b-face5236259f", + "e8e062e7-499c-4e2f-acd4-4ec9c4b9a841", + "3972471a-28d2-4c5a-a6ab-cd14656a57e5", + "87b9f9d2-0777-4875-96ff-c23dd7fa9472", + "bb411f58-b861-403e-8167-81f614679c94", + "6ffc08d5-363b-4241-9f2b-9dacf3827b4a", + "8e5d090f-0c56-485f-9f14-431bb99aed17", + "9af9d661-7502-4fb5-979a-dba1d3f8dc7f", + "768db58b-1b4f-4ba7-bdec-8629857cb0f1", + "06c8276b-9ec7-403b-a612-3f71fcaed723", + "e00e8109-f697-4190-aaf0-e119e0e79366", + "25e31b78-0ae9-4f1c-9de0-dfcfeadb800b", + "f6c53c17-20d9-4831-89bf-fc2ad68866cf", + "65b4b2d5-a190-4373-8db8-a30fd3593f1c", + "ced6041f-46f4-49be-88e4-4c16b9c4b172", + "2c903c2e-97fb-4a89-bc4d-f706f7a5a7bc", + "94bd3c28-8c94-4397-bd81-46482a46c935", + "f9f8b03e-049f-48e0-b52a-3023d8e96b26", + "b907ae23-6fe1-4bc5-8c6b-4dddf7b4ec01", + "ffbb16d9-1c5b-495f-84c0-9dde8e99ca67", + "732771d6-4767-4b08-9282-6070fd925dc1", + "fc512dbc-b96c-4dcc-9f5c-ca66d1391e96", + "059d0905-61e7-4119-94c9-5840697cb363", + "cb781bac-b144-40a4-96ad-07972f6935ab", + "cda9c36b-2ef9-4e44-a84f-6902a03eab0a", + "36bb0afc-e1a1-4fdc-93c2-01c3aec15494", + "22f97cc0-459f-411f-b9e9-406e4800ac02", + "7dd4fb37-a732-479e-920f-94064592d749", + "b4264910-e6bc-45d5-879e-ff8208dd75d6", + "d59b04c1-e2bc-496f-8213-91aea5a96595", + "b8192768-875b-47fb-988c-fd8c988bdc34", + "fe2dd832-d77d-4c03-b258-5aeb0d7e91c8", + "9be4b41c-068b-4b91-9bbb-9896542d65a4", + "ff36fc99-3ea0-4f91-bd52-1d352834a742", + "16138a12-7ffe-4c44-84c5-2a7eb22c0b7b", + "20f9c603-0aff-439f-9af1-aebce84504c7", + "c228bd76-f043-43de-8bb5-4d1bb638d1ca", + "18031ad8-35db-4240-b055-bda4f7d34a83", + "89c61673-5617-4c9b-aae7-756065333d7f", + "d89420d6-f7d2-4285-b510-f1c4e061aea9", + "47f476b6-5b0b-4ad7-b667-2215686432d0", + "fd6437d5-99ba-4eef-a38b-c5367c36b8c3", + "c198448e-e45a-4221-8b8e-f4698b5a0118", + "ac3ea99c-fa64-45f6-817e-919fa167d1dd", + "b3304b49-c166-4a7a-8080-ef3823989c85", + "e98aeee0-bb0a-4aea-bd04-e116608325ec", + "89ef9120-1d9a-4ee6-9f3a-9eadba2beed7", + "ac580f9e-c59e-4e27-9508-9f1a8304e993", + "03c6c56c-2568-4c06-84f2-af3484138135", + "8c15beaf-6a8e-45c8-9d24-3b90e6d1a63a", + "22d87be8-a447-4f8b-a9e9-79ce34e71c1d", + "bbaf5c40-887a-487e-b62f-6d31ce3737c8", + "ce961946-aa38-4b68-83c0-701acb99d60a", + "9216ecee-db8d-4e69-befe-cc9f432ad42d", + "9f28b0ac-c4c0-4f5b-87fe-eae1ddb5d247", + "11435906-4130-47d1-bae1-61b6beca03c6", + "f11e7f98-d280-48ec-9e6c-bb854d3c9f38", + "d138eb3d-3fc7-4ba1-a3f0-a30f39d028bb", + "6f8d0e89-27c4-4805-8880-9ff47a7b1357", + "895ba69d-c621-4ba3-b447-6edffb5487a8", + "b774e3f1-b52d-4c57-a4d4-4dc50189b9c2", + "3e8d8c90-ab30-4513-9a56-0d983ce783b9", + "ec5a6c96-ff8c-4d88-8eed-a1e272755f31", + "4309e806-94cf-4ed6-9c5b-4797388c0dd0", + "de3e2f74-224b-4cdd-808a-472f6d2943c3", + "41852c99-2cbe-4abb-8c85-cae078d0bd30", + "8ed6ab03-25f2-4cf4-9d11-769d52da76f5", + "2ae3a5d1-cd71-470f-98c0-1f86e967670f", + "f05faead-6017-4ad9-bb71-907d4b075fdb", + "32373590-8535-40af-bff3-90f28df2eb79", + "27c7f06c-2a09-4131-a6ab-b45ea790b2b6", + "48079981-08fc-4fa8-8929-f3f6215da7fd", + "113a53b9-4e80-428e-a290-c070b093d91c", + "93ed6a37-23bf-442d-bca7-c551e099fb86", + "77a50986-44a6-40fa-aac3-41691d338a33", + "fd0e39a2-8808-4991-8df8-3b29fb89da58", + "8d635235-bc7c-4358-89f0-19989250eaae", + "d47b1d4e-c897-46ae-9bb6-cc06d5316463", + "c512fbd5-fd87-427c-b3db-76d812a25ae2", + "58c451b7-6436-4db7-a51f-581166ac5934", + "d5db1ce7-7337-4837-b74d-14c3defe2924", + "1c47db84-b5aa-4e4c-86cf-0ec15d0a114b", + "22eaa81b-b92c-4380-8119-868f735b42c5", + "5b40260a-b770-47ab-85ad-7840563effca", + "25b7e04c-69b6-4c85-a57b-918e17fed563", + "a98a0aed-7f85-404d-8698-cdff6891467e", + "88cba545-c60c-4fb4-bd14-53f947081f13", + "b62a68e9-b705-435d-8f85-2dba3008071a", + "5c6d1ad7-096b-4f66-a6fc-acc1456ebbe4", + "8d7228ac-f1dc-45df-ad3e-c53bdfefdc66", + "0181ef85-7bb4-4408-8876-4221140b7891", + "65a31fd4-069f-411a-99bb-4509606e89f8", + "cf3214b4-fb43-4b52-9cd4-544fe2f9f177", + "d21e56b5-215d-42e5-b8eb-ce3e3f3bc473", + "98caa237-af2b-45cd-92d6-63585632b436", + "b63cc2fd-8505-46c6-bef0-51b7f5f36b5a", + "d74b10fc-e4b2-428f-bf91-c81f2b260460", + "ade8545b-e114-4ab9-aef3-febdefccad8e", + "94af17a3-3bd5-43c9-b791-ebd92ddb4877", + "5683a9fe-3966-46e4-872f-a1e0f0c5f58f", + "6dc47925-64c6-4c9e-bd91-bb5c3347703c", + "54433101-b829-47a7-a4dd-8e158937d5e7", + "0cf95e03-fc1f-403c-94d8-b9db95f6773f", + "3d2708f3-7430-482e-beb8-e205d9662de3", + "e6f9a53e-5af5-436a-8628-38a8f55aa9de", + "5b192bf5-6097-4137-95c5-7317ef548d9c", + "d7c7683a-cc99-4dd1-b660-06179c5a5f38", + "061fda37-d08d-42a2-a203-915aef6107f3", + "4ee11ba6-9d55-424c-be07-3cc5c2f725fd", + "edb6b62b-ae23-437a-bc18-f097ab588112", + "aa3ffaf9-9ce5-48d4-bf0f-26438f2869ff", + "96e4cc7a-89a8-4936-a586-6d06e634f825", + "21ba7d76-c860-442a-bd26-4f32ee0c301a", + "98f1ee95-4846-44a8-938b-18629892d24c", + "aa274ef0-31a5-458f-a740-237940f9b92c", + "e953e58b-b6eb-437c-990a-15428080dba8", + "fe99cb30-a07f-4bd7-8ce8-b194d11df33d", + "f63aad99-2483-41c4-afee-a78fe49fa279", + "53d2069b-02da-450c-8e96-9e275c90e4f5", + "2822444a-b960-4065-93f6-88183289c4ed", + "d3e25275-bf9c-4827-9d40-91c9e1f337e2", + "7bc25f22-9d3d-4214-99b2-bcbed2eefa20", + "9fb69c55-2625-416f-a3dd-be3cc1b1a8f5", + "a0e96941-607f-4ba0-837e-979f01a4c942", + "a85d743e-fd06-4f63-bbee-22e729023a4f", + "e565517d-674c-4a79-9216-08f428cdd104", + "3b44b257-d666-4943-9012-f7bc72d8dcdc", + "04a951f2-f609-4fb3-ad53-5fad8fbc2cb8", + "fbef9324-fa10-40c0-ab26-74b210c08a04", + "bb7f4ea5-7c81-4e34-a493-814f946f173c", + "b4a5912f-1fb5-4911-bacd-1795cf60532a", + "b584caaa-7fde-4976-89ed-f70465097363", + "ef40afa5-bb1f-4bf8-8577-caa062bd9444", + "6e70f47d-29a6-40e1-b05c-724c92f10255", + "1a94a0eb-b15f-4397-b1b7-7e0c1bbdb270", + "48a81876-dcc3-41f3-979c-b38d376431d0", + "dc456c83-a834-4eee-9610-b7fe35f2a054", + "4fae9437-83a2-44d7-851e-b69bc057f059", + "2c425724-ebf3-4b3c-8cff-8d5bc4377366", + "6d38b5a5-32b8-4907-871d-86c0d9118c86", + "c8fe2eb2-d53d-4ca0-a719-060aa245659f", + "757ade14-86fe-427f-8a79-c16ec5e44211", + "3089dfe4-8417-4d15-8269-69571a4a7932", + "bfdb478b-53e6-4554-99cf-02694994fdef", + "a8830c2f-57d9-4f01-85b6-bfa303b9cf36", + "44280f40-c273-4dc1-bdc9-8edd846a1f76", + "36f312de-73e5-4709-9a25-3feeed74b5f7", + "dcbecc3b-4e63-4ebf-8535-39e56b528fc7", + "70543a6c-e941-44e5-90f4-1b7a986d7963", + "d357a763-2240-4ae5-9768-f2cdd77fdcd7", + "919f03d7-9a96-4320-a8e3-8706171972c3", + "d3458956-bdcd-4015-8611-84ff2344b8c8", + "9552b7b3-28b7-42bd-800f-77c8739b1981", + "cef23890-629f-4ae4-a2c6-6c6403357ead", + "d6052309-e19a-4017-875b-04b9fd0e51f0", + "d7e75f30-a6d5-4b9f-bfbb-282c8785be62", + "5f5bccf6-2d71-4e46-9a12-c64fbb531c03", + "5b05e727-cf73-477a-a2c1-4cd36320bec4", + "ed3e16d0-816f-4c84-b5a9-2c10e2a3802a", + "71db9a67-0876-4562-8c1b-179a65115410", + "f8bfd14e-e219-4348-904d-5975d00d7290", + "271aa7ff-cbd1-4b41-9fc2-10b9163f78d0", + "9ad7b9d9-f47a-46a9-81f5-5bebedafd940", + "dd060bef-0e4a-417b-9fbf-45c162f16feb", + "23def8af-f5b3-476b-bd44-434c4bfceb47", + "e6487631-9726-4527-b680-1fa79cf08385", + "07bfcb32-4328-4171-8ece-9b8cbef99b53", + "ee0b3e92-2856-4ade-97e6-7782766d14e4", + "4d810ac1-2145-4ca4-bb78-0290653ab9d1", + "4c897c93-36cc-4da7-b41c-1ac6d964f9f1", + "38da4fda-881f-48e9-a485-178a19ea74b1", + "126965e0-89e1-4020-a260-cf024e5b154f", + "1f1fafc3-a88d-4cb6-b9e3-f96665ac84bd", + "e5ae333f-0c71-4c0a-bb2d-f19e1ecf5cd6", + "79ac0c54-ce2c-4a2f-9642-088f7b68a857", + "6e090138-9f27-469f-bbd4-a19c0bbd4ce6", + "d96ad819-1c63-482b-98aa-b63ebb4bfa6c", + "e3e0789b-bf5a-4d88-978b-3ac4122ea142", + "ea1f91cb-6274-4df6-a213-8af71cbfe38f", + "46349c64-8d96-4ac1-9aad-2fe8c1a28420", + "891a9544-fbbd-4c31-88d2-b5b5fe337148", + "74768844-4bdf-4efb-90e3-b09ab8fbfdaa", + "d3d9295c-49c7-4f4d-980a-fc3826048a49", + "482953b2-d001-4279-9365-33672ee77322", + "317557b4-ef5f-4066-947a-fca75a0cda40", + "cd2b8e0f-50aa-4c2d-9e76-bca9ac342c59", + "a13850c9-c19e-46c4-a6f5-c61b222dfc94", + "a628c6ff-c6c8-4923-a5dc-3135f1a3274a", + "45a468bd-1104-445e-a82a-3fe18ea54cf2", + "d4528ea2-e7e1-41c7-b899-a414f543bb0f", + "9fe3ee96-28e1-4136-a324-dcb06f7df02c", + "5ee9f3d1-6b63-47ca-97ae-cd7b6ff81771", + "3785e411-8e3f-405b-b7de-a09e8039b0b6", + "e57cdea5-e4f5-4bac-9106-86642616137e", + "fb2e389b-e74c-473d-b7bb-91ab0fe98b9a", + "1f4f7895-067e-4514-b113-0409bdeb737e", + "24d617e1-8c9d-4f1f-8cc5-4b40f16afabc", + "44d684fb-3b63-4136-a5ad-308531ebe8e4", + "6cbbf1dd-f913-4e41-9d72-606cbd5ce137", + "ac42b55f-abe0-4416-ac17-6d92dbc28d4a", + "1fb2b439-e0eb-4d59-844f-4ca7cde0285c", + "b8570e57-a89f-4625-8e0e-825ef94b13dc", + "428d62d8-826c-4013-afd8-1754573191e0", + "06a86ee8-1b02-4b24-bcd7-d78df15d2ec6", + "3502196a-deab-47e4-b9fa-44b52a0d9856", + "6ad4b17d-ee91-418c-9c6b-85188eff8a24", + "1aea77d2-5fcb-4305-bc68-95a97195b49c", + "24f74c4d-e42f-4247-b0a4-b560e720bc35", + "23c17109-7496-48cf-8114-9cea835d1e62", + "58d25cde-05ed-436c-98f9-ca07fcd73446", + "0bd24678-240e-4441-bf77-af8b4e7e61d5", + "d0bfa200-9c70-4c68-91b0-cfc0c0882893", + "cd6262c1-9f2d-426d-8a50-bf863de2f6e8", + "d0022001-d20f-4ab5-bf9f-80811e53bc06", + "9576dcce-9a1b-454d-8a34-441da18ce345", + "8dbf1929-e27f-4542-b983-edcbee9e0e58", + "39c74af1-ce60-4759-beab-2bea0a9e91b5", + "8aed1c5a-e13d-4635-8e8a-a10465b5cde6", + "2eefbdde-bc80-4507-8894-3d7eac56688c", + "814ddefa-d452-47cb-966b-f2ed39106f78", + "b708a354-9087-4edc-a9a9-379c0445c518", + "6ecacbdb-13e3-4dae-886b-2e06c20bf750", + "b81eb65b-1068-4877-a885-508c41e6eb5c", + "ebadf50c-6e35-4643-bc8e-eac09dd4e47d", + "4e178ca9-6fb1-4e78-9cf7-b9179c5a5014", + "f523ce1d-c598-48b1-8345-ecc61c30e82e", + "f8b46b79-d442-4037-a2e4-245b1c0c6841", + "adfdac44-32f5-4891-ba0f-b2d923a1f475", + "f3618e42-1cf3-4409-916d-1df21aba1c22", + "38133021-c35b-4096-a2a5-e2c355879795", + "a2046789-ce3e-4f75-9872-20350ded24f7", + "08fb8d19-9ca8-460d-ae95-2e177dda16d5", + "b5c4dea0-be92-4227-abc1-7ea6464caead", + "8e7e0890-c6b9-4d78-9cd3-c237a979f229", + "97da2dba-1292-41b2-b615-2145c6c99d26", + "20784bb2-23e4-4738-a9e5-655d68581f6a", + "e52df1f4-8ce2-414b-9dc5-49956d0ce116", + "99fb61ef-e8f3-4c34-8396-ac76a6ccb35e", + "553f48fc-7c12-4b12-bb5b-5015200b3632", + "f00e864a-3bdb-4e1b-a467-04020759ee53", + "56e5e15a-c4ff-46c8-b6a4-8de7f8067e24", + "dc7cf287-7068-444a-900d-1880dde83397", + "1d0e2bfc-484a-4264-b786-d08b823f1ee6", + "50f55a19-325f-41da-8fc7-2aef4e1bad0f", + "196df16b-0c15-44d9-a1c5-ff101568ee1c", + "471ae81d-ba7b-40f2-8547-d8735c9a0060", + "e222a7d4-fa0c-4812-a148-1f8011784819", + "b55ca0f6-bbee-4bae-9753-b944e2f4a4f9", + "6e71e4ab-4adb-4971-b86d-41830dab92bf", + "64d9f376-99ee-4281-9afe-e151342b712b", + "48ced589-aec3-4cef-bdcf-b5f4818134dc", + "11d24879-e2a1-461e-ba09-14d7453270da", + "827f8de4-223c-4929-9ac5-5fa232d09745", + "d21afe2a-18ee-4ad4-872a-f50cd83c45e0", + "93509cfd-82de-4944-ba02-7a647a1ec3cb", + "cd08e739-6d75-4936-b48b-35d3825c5adf", + "937dbe15-cafc-4dae-a0d3-5533edeca1cc", + "7cf7808e-15b3-43cf-8131-4799db71b0f2", + "9186492d-f094-4a8e-b49e-5201e8028171", + "055d9bbf-18b7-4af9-b46e-e5990091afe1", + "4f3e4c51-41e3-4b8f-b1a0-81a1bc058856", + "43ce96ef-9b20-4e8d-8b7c-fc01b0a3fb2c", + "a57b423c-39e3-4634-9238-dd7de5d905fd", + "d65828bb-b938-4496-a13a-3513636e16bd", + "dd9ddee3-906c-4997-9e03-888dcf21006e", + "a35d54d9-1a0e-452b-b42d-88bea7189068", + "ca8eca1f-78ad-4e5b-8af7-d78688b13567", + "dbd05e41-2566-426f-8f41-e1a6f64f505c", + "81a2444b-1296-4abf-8194-27a5c98bc7da", + "9c70b98e-5322-46da-8e28-7545ebe6793d", + "d932012e-11dd-4606-82d3-4260d212ffd5", + "cda81f68-6318-400b-baa1-7b968e6816e3", + "2179eb10-f62f-424f-b5cd-21f482aca30a", + "935b3334-1387-44f4-9ee2-1881a7238ec4", + "a1da81ce-c5b2-4cd9-8b69-340058394eca", + "b4221534-851c-4f3f-ad76-cfdab24fbb53", + "e4ea0481-ee6d-4d21-acdf-5ace685826f2", + "2cab4caf-05de-41d8-a512-d70db4f80c2d", + "d9bb9413-e93e-455e-afbf-5390c80d63b7", + "0c5ab3b6-b042-4a0c-b90e-b013415da388", + "ac44a7a7-7c0c-4a9c-b638-dd7669ed330e", + "b3e756d6-5880-48d3-91f1-f7086b2d499b", + "15516bf0-bc7d-4ffb-8989-9704662cb8e9", + "befab0b7-f6f1-4b9b-931a-79fe1d488363", + "49b5581d-ba4f-408e-9f9a-76ab6a9c4929", + "8ad153eb-fd6b-411d-b78f-9bf8c6c138dc", + "8db460d7-8008-4258-a661-bd9730f17a25", + "e707c886-393b-42db-867a-89f41b742c8c", + "68409aa7-f854-48ac-b68a-bffa978d900e", + "c0433985-bd82-4758-a997-659aed529508", + "42708053-325a-452a-bb33-999f21f6fd5a", + "c1bf8899-ae2b-4081-b723-f987db67cb52", + "aeaca70d-40b4-4696-a36d-6b3e2f0416ec", + "25891ac0-a58b-4129-b3fa-9215c15af4b7", + "bf048425-a50b-4260-b6e4-4bd05cda79a0", + "edf4ea1f-0c68-42e8-9c27-ef31d8fb991c", + "84e8408d-b36b-47f9-97f7-c183fa4d8a12", + "aa816477-e0e7-4635-9760-82749cd45ca0", + "e07ebbd5-91a2-4fd4-b0a1-ad08bf11a5d2", + "33a2af40-5d87-44ed-8e10-e83ca04e2495", + "67e035e7-cc71-43fe-baef-c77d59dd7820", + "0549d721-c224-4f79-abf1-120600b41882", + "1601a331-6d53-487a-b528-3f7ab4ef9c20", + "ef628c11-f19e-457b-a094-72b45182e779", + "d6efdfea-6ea5-4828-a1e6-4234c365b124", + "e95ce888-7f9a-4702-a715-514e1c21228d", + "b3cb2ff2-3417-4499-89d4-f6e4585018cb", + "d37ebe63-3b67-4a05-9936-752a2382d987", + "cfc4fb4d-8221-4d3e-8977-39d9d0305867", + "69e4a809-acd6-4502-a892-b8432d6bf145", + "e2228b4d-fe5f-4616-8fad-6085b284b471", + "85bd724a-2bd8-4b50-9df7-9701bc89957c", + "3ffab80e-6d29-4b5b-94b3-4e67ec4a7e56", + "886514f4-8d0a-41f5-b8a4-4cb8b37f7fe1", + "b11592b6-4057-46eb-b9c0-14fe727e8175", + "6c308ae6-7614-4475-a3dc-5dca96c03acb", + "75bf2126-729e-44b4-91ca-2c4cec41bf1e", + "1473ac5a-4036-4496-957b-74f7362e169b", + "138617a7-d3a4-4ed0-a749-0d5746172dee", + "a9f97840-4a6c-4026-828e-6725175d1cc3", + "92561f94-d949-42ed-9936-7b9d0828b14b", + "e2663684-6671-4866-aba8-e52eb4712954", + "7d5c041f-57bc-4802-8151-a6e734cd3373", + "0c07b998-063a-4783-bbea-a097eaa002c1", + "0e807d77-b81e-4f54-a5ae-388b37b69d8c", + "e43fd528-df18-40c0-ae6e-7f1e10540f34", + "baeefdcf-e0e3-4d7d-821c-1192f480fb07", + "b1114a9b-06be-450c-8954-cf8fd037d8e2", + "6032ecb5-eb57-4937-abf5-e91f58cc203a", + "305763a2-d077-4fdd-a2c3-3a9a806551cf", + "75532063-0780-4ac6-b0bc-61948aed1a25", + "6d65bdcf-bb13-40bd-939a-93f1e074bd0c", + "0fbeb766-0d83-44af-bfeb-675b36ea0ff2", + "8506a0e8-80c1-476b-be1f-44bef18d5211", + "e1335c9a-810e-4e53-b4e5-470566623908", + "41de7f8b-ad2f-44a7-bf19-94c9b9ddb1ce", + "81fb8eb2-10a5-4c6b-828d-d9baf7c7c5d8", + "ca8159e6-3d16-47a7-9c76-5679480669b6", + "054ecef6-61fa-4b7a-8b56-63db1b9e146e", + "7e93b4e9-a194-45f9-ac40-b091b0b02fc5", + "c5826695-d54f-45f7-9ebb-ae8fd43ceb4f", + "756ce12f-b2ae-48b5-ab80-1dd01f8a1c38", + "ca9d56f9-77ba-4ffa-9037-642a9d5e03c0", + "aef73c78-7fef-4428-9740-dd68a753d13a", + "96c0dc8b-9ba1-44a6-95c4-a45e8892340d", + "2367523e-6160-48cb-a491-8e3919de141b", + "2d25c866-5fa3-4aa3-bd45-2cd9b54af3ce", + "f11f28a1-58a8-4599-93fb-b045e652da33", + "db20699b-98c2-40b9-bd79-07d2dfee3e40", + "b3c7e1d7-776c-4b2d-b22f-343861cd0cde", + "4020bf8a-73ff-4e59-93c8-d22193c4d8b3", + "ca279b36-7f79-4e32-83e2-5e8e8c8bbc37", + "98f565f2-22ad-45e1-a1af-8fb30395591e", + "92f3a236-9871-47d0-8a2d-aad115366881", + "a87c2901-9c31-4b08-974e-c95a22729c33", + "c6f3b359-be86-4fde-a004-a08c85937387", + "a655fe10-be2e-4cd2-9b43-6dbf899e7f43", + "3515f7f8-0765-404d-9d13-00f54ab63db0", + "5137d2c4-1357-4c5b-9e84-caac29681527", + "40d08365-d1cc-46ce-a53c-1ca1329e05d2", + "9b983957-cd65-43c4-ac10-f3cd77007242", + "d64876ce-ee79-4c1f-b81b-7bf392c0b911", + "19652e11-d457-422d-8c65-7898083d67c5", + "6749cbcb-d06f-4a87-b011-9e73e068e487", + "83198e3b-ce78-47a2-8544-5fa882064f8c", + "53ee8704-a3e8-41ed-9501-069146c85f58", + "9be5745a-65fb-40da-9979-5e44bad2d9f6", + "95acfb8e-f524-4a39-8051-7d17e4114517", + "a7ba3fe7-4035-4157-925d-e2174c52eec3", + "8045e071-0847-4048-984c-14f10643ef68", + "7b2861d8-277d-48bf-b160-60a5314ee1b0", + "088d7f60-b302-4aff-87e8-c89c97deb1b2", + "dc0bb85b-41f4-4f0b-9313-034ff49a23bf", + "19b26753-a395-40ad-bad0-e64449b35822", + "d10a1269-d0f2-4517-b3b4-d068260e133e", + "9e03857f-b6cd-48cd-b7bb-5721b5df0ac6", + "50fb873b-3fba-4185-a3b6-2790bb307698", + "163fda84-f6b0-42be-a3ae-d7168eae9966", + "8aded531-1801-4f25-9086-b442bcf8d129", + "f95bec81-f2b5-4ee9-bb7d-2bc2f61eb34e", + "edfb3f06-cb9f-4f2b-967f-fac532c37a9a", + "4efbd083-2346-432f-9159-dd4093667982", + "b2068925-2b2a-40ba-9743-7fcfebb2d94b", + "ae2cff30-aff2-4543-b3d4-967341691c04", + "7a524f4a-b1ad-417c-9f26-e7c7cd58d1d9", + "ec32179a-3dd8-4630-92f4-0361c13115fd", + "c7592e40-8075-408a-9de8-33dde74a2a17", + "0b91a083-232e-4968-af43-1a8e94b1cb95", + "556d8d2c-c2b5-4689-835f-ed27c8b27307", + "e37f9f31-3cbe-451e-a630-fa9ca8246b9c", + "1ca1d212-aa6f-4dc3-87dd-59d1059dd46a", + "6a1fa6a9-81c4-4491-8ca2-56046b158ba5", + "cf2caa66-5c40-40c9-b956-d275d8a340ca", + "6e886593-bca4-42bb-ae51-1a4f9fbe88a6", + "5ab14f91-a3bf-4e5c-9313-5dfe2df43a79", + "fe0b789f-d1e1-4d33-b448-3b57740bbc9e", + "fbdb9178-2e5c-42ec-8107-753a48ea626b", + "e7f99bec-cafa-41f7-930d-397b2cf4efcb", + "aed78ee7-2f2b-454a-a3b5-50aa9112e0eb", + "ca8a236f-4ec4-4ee8-a4cf-acc4fc866d0e", + "a27c9784-ce05-477d-9761-9ea1156eef0a", + "577606f1-b181-4b5e-804e-3d5cfbb9fde9", + "e702c1da-6936-48ae-987d-4c672bdabf7b", + "4f50c37f-00af-4e4e-894c-019a15083bb9", + "723f131a-8adc-40b3-897c-b53c66ccc7e2", + "5b26c139-8f98-41c0-a905-d55edef7b70b", + "32c3ec69-f430-438e-8856-05e1db355ccd", + "a7106db2-1d97-4472-966f-b959591b5f08", + "9c5d8a5b-2a4e-4087-bb40-96cecb530050", + "fb5d58c3-e832-40fe-b211-cfbe8af2d837", + "e403de9a-aa30-4e1a-97de-1cfcd7c0ff13", + "e2e09666-55d2-4b1a-b934-d020c1c1ccf9", + "8d2d13ed-c98e-4479-9420-3716bdbdf66c", + "117dc5ff-4b8d-45ea-affd-3e3ee094e116", + "5cde777e-cc85-4ba0-9951-9e925ef8180b", + "f6274342-52c5-4c52-913a-9b138d6e2809", + "f10c8182-d73d-4004-a827-ac83f8c5717e", + "24164198-5ed9-49af-a8ba-aa5676f5f331", + "1fc28cc0-4860-45db-9247-2b675ea37d6d", + "f90c6d0b-dbe0-4550-86ce-6d61e8a07d6d", + "6b7393bb-e443-4a30-a309-3631c37fc832", + "ff4fd496-0dbc-442a-b9fb-7aa0da567bef", + "fbdd7678-9164-4fde-8f05-eabe74e6221b", + "4929c4d8-a822-4633-a93d-c35f190fada0", + "d6a51ca8-1f12-4625-9715-c64cf970783e", + "ca05d0ff-74a0-46e6-8760-f9807b94325a", + "8fdedde1-9794-45f8-b0a8-77fa776088b1", + "1390d6fc-c39c-43c8-9af1-2fb45a8a0595", + "8f3e99d1-f4f0-4f6c-91a2-7f3eef080026", + "153063b5-ba16-4e86-95d9-07f436b9213e", + "98c81fa6-0b9a-4b22-a764-4c2076561e28", + "a1638c77-ceac-4ba7-95f6-44018972371f", + "5af0ad03-db71-4922-8eb4-6ae668de6a53", + "f2a00fc4-a361-417c-9222-e3e47036d683", + "b304cb53-2448-45e2-8c24-3ef92253fac3", + "dceb22bc-d365-4cde-9948-cfbf6f95fb6c", + "754dc8fa-a926-4698-bbbe-195f325ad3df", + "fcc00578-64e5-4aaf-ae25-c05994809e60", + "376af6bb-9df2-4a72-bbd1-3b03473c8126", + "609380eb-ab99-4657-8d6d-9ef638f3f9e5", + "efb5cae8-62a9-487b-a017-a76cab415a0b", + "4884d5b2-a803-4fd7-a45f-226f5a8ebb14", + "3e0c3cfb-1781-424f-a70a-e103bf898abd", + "a6b12903-0160-40f9-a27a-6a69899850e4", + "50924f14-00fc-4759-be8a-629379a30d6b", + "a50e1565-9566-4564-8125-110e6a2ec8c4", + "34c3b037-ff59-4fa8-a732-87e809a4f9b8", + "6e3df040-546c-4c2b-9f2e-21478a1ac539", + "2119d2eb-4f24-402e-b91b-2e9b7dc7476f", + "abedc784-a9ef-4a4b-bc6d-d5aecd62aa19", + "d00c95bb-b18b-4047-80b9-a15a6802c7cb", + "f656a973-5758-4256-89e4-a9c2620ff68e", + "5333e85c-bda1-4bb8-aef5-77ca0daee2ff", + "bce06d76-2a6a-453b-94ad-7b1994ad2609", + "406c2905-16c2-4af1-8399-2f0b05b214a2", + "a656f49e-685c-4fec-bfcd-9afe85b022b9", + "4469749e-dc00-430b-b662-d94b615cbcb3", + "e8c81b68-12fa-4489-9bc5-b8a2c3b92c9b", + "a2a28314-f7bf-4dcb-bce1-d3415adb1d63", + "88828df9-1dd3-4150-aff7-85730d54cecb", + "82ab0d16-173b-48cf-af1a-78f19774d4ae", + "ebb2a138-4af1-4d81-a87d-a59c87d33ee4", + "bbed2dd4-8c46-4398-a1ab-69111f153c62", + "7bbf038f-85b3-4794-a1d5-d66158eae02e", + "bbae2c1c-8107-44a4-8a2b-fcdd0f155acc", + "cacdc817-57b4-4c35-90fb-6ab0afb3ce15", + "572b1848-04cc-45cb-aba4-6ff2a50aaa7c", + "91c2ff67-93ec-4867-a919-a05dadf3436b", + "9c617782-0bdb-448f-abaa-fd3b41ba91fb", + "a2a3c35c-4c54-4b1b-a817-c531b7a029d1", + "5a04ec49-9e4d-43a8-8f33-b0734611e871", + "df79dea2-b323-4e13-8f66-261052d7cde1", + "c6ae6078-d7a1-41e6-b1d4-f466f4dc920c", + "84b83633-cc15-4381-b11d-16ae8124d265", + "94544f78-b5f4-44bf-bb4e-a9ed6ea99f5f", + "152a97db-0c74-43d9-a016-9d781803897b", + "47f7eb21-1e7a-4e51-845d-c7add2724867", + "977789e0-ed7e-44ee-a658-5637a96d3019", + "bdb7e498-2c5d-4a4b-b01b-81ee076da895", + "7080e8f7-b59f-4a44-96c8-69bc61d5d9dc", + "d8e1730b-20ac-4c66-8cc2-1f78741740ba", + "4c6c8cbb-37b9-4342-8ae3-0b2187e52db0", + "54f74c25-9cda-4b7d-9108-c5cefce43b3b", + "310e505d-6d32-41c5-8811-d532ac22a959", + "39b28321-7210-416a-9509-94d6519c773c", + "23e8a9cb-7a65-4985-bba4-37f7efa2d753", + "794da84d-0590-45d2-a4c4-2129bacc0feb", + "11c2bee0-f11e-4a4e-9208-ae819c5e8aaa", + "e08117f6-8a2b-424e-b7ef-0c03d71c42da", + "2cc226d7-8e3d-4ac9-9db6-05b9a36d4109", + "5056a06e-5433-4460-a01f-c878ff16b0b7", + "31c02d7d-0f7f-4b61-989d-b3796b78a4b0", + "41ada0d1-726e-4af9-b6f6-7cda2df14be6", + "e5cbfe94-e180-4071-8961-57e822e5748f", + "3290971e-be0f-4866-9d14-aad58fbaa781", + "649857cc-f581-4eb6-abfa-4448bb8119a1", + "cee42306-80d3-4635-8dc0-8f3670eba20b", + "23c8b264-613d-4c70-a965-1423a574629e", + "1d6651a3-85f8-4ead-b643-2d9dcc0aeeaf", + "00b1d349-65d9-4a81-8067-bdf3e264d602", + "b006b891-103a-49b8-b920-25f6d5ae3353", + "f492eb42-d346-4094-8605-0e1e4c49b78c", + "306556a5-9806-4201-8f99-07347dd2b6c6", + "90f9e84f-2645-4b25-8c7b-f3363da4f99f", + "892e0c81-8999-455c-9b12-1884a98022fb", + "36573621-57e1-4229-952b-679e7c3f8a93", + "e6f953df-30f4-4bd5-87cf-b40f97d71d6f", + "3cd3b9e0-7841-4591-a91d-5d28a95b0e17", + "75727f15-065f-42b6-a432-70c8720eefab", + "75442137-d23a-4a97-bf9c-167ec849eeb5", + "7e1cf81b-0d80-46de-9e3b-e1730c71bf9c", + "b2afbdb1-93ae-44ca-8ed3-b598117ab9cb", + "309259e6-b8d7-4d29-8678-9c9aaad0ef49", + "2a65727e-19b3-45e7-bb03-303ae4f80f5f", + "9ae2d0b1-6adb-4d0e-8088-721462e94075", + "90f4bc95-7c8d-46a4-82f9-3c0c590325fb", + "ed36863e-1949-4588-aaf5-8f6fe30ee478", + "451f8e57-37cf-4829-804f-0a1e3d4bcc8e", + "808e4d8f-a93e-47e3-adfa-4f4033586b9e", + "d2ecacc8-0562-4bbd-b036-5eafd3bf4871", + "ab645609-ed18-4888-bda3-0a8af88812f8", + "a66f9ff2-ac5b-4063-a5cf-fa5ef4bdb821", + "2f47525f-789f-4095-9249-da5551bfceff", + "939dfafe-df2e-4c5d-9458-d0bdb8f76200", + "902b9ae8-5ac5-4cc4-9dfe-1f5c0219e2e7", + "1eab40b6-f1e3-4be6-8ceb-6cb55b98e96d", + "9b00d5ed-8991-4afc-af5b-4d1d7c3b0493", + "6de0ed0a-1293-4fd4-bd67-2a12f2013be9", + "5c23474b-53fd-4a1d-8d03-516515d73abe", + "eb812c78-3ccd-4860-954f-430d85aa877f", + "765dfd8c-ac2d-439f-86d0-58b2cf1c1a1c", + "3d2194fc-6bd6-4d30-8475-257fd385665d", + "3a84e7a3-1c85-4e56-b8a3-5bfa3034c387", + "800e20b7-c12c-4312-9f6c-9e14849601f8", + "779d6ab5-7d27-4198-9f54-ea46af5d332b", + "e24337c2-b9ad-4071-8783-b2505cce7151", + "13ac884f-7290-4dd9-80ae-047ad2ca1f05", + "631c5f32-06da-4ec6-a776-17fa53dab9c6", + "f8f556f9-e07c-42f1-9089-43c7832eb99d", + "aa5b4e29-95d6-4a2c-b395-7b2a988bcce0", + "b8760ef3-d32a-4711-8fff-1c2e49e9cfe1", + "95fc31ee-db0d-4098-b98d-3b42fb99cf74", + "e945813b-b95f-4a98-a2fa-461da7d761b6", + "1e4084e5-cc1b-4fc6-bc47-b7d54392f7a7", + "659a4a02-0ca5-4a8a-bc89-5e3da0888c6b", + "c482ac4b-075b-402a-8fc8-90a02828d003", + "694256ec-b911-4f02-8257-931b645606a6", + "ea002127-7dab-4894-8d9e-8a672e6660bb", + "44f3f84f-cae7-4724-8b40-dfb2db60d224", + "53f2b21a-eaad-4691-a0b8-aa1195ba63d6", + "48be5edf-1cc7-446a-9669-97e1dc1f43f5", + "8c4dedda-6e9b-4414-bba4-319289cada7a", + "31d3a1b8-4f4c-4424-a284-f942a819f1a6", + "d6436821-03d2-4bc4-bd19-db6a09c32e0c", + "794ac683-a338-4178-8ce7-c8be35f792e8", + "10ae764c-195a-4274-aa20-3e182b563452", + "44ee0dad-c0c7-43e0-a81d-97a37a3e8b85", + "f2513e9c-7a3f-498d-8845-fdd4c991e934", + "e445a1ee-d65a-4eaa-809c-7b550d33a4d7", + "5ce78f16-0323-4c50-bec1-32688fd46c09", + "822cafd6-912f-464b-8cac-d9af211762f3", + "5aac0d3a-9e8a-4c6e-a3b4-9d82905c3274", + "67e4c0ae-1e8b-45a0-be51-b4652dc2c050", + "158630d3-f7bf-4b50-96da-7a7efb218cd0", + "30c4b902-9b48-41de-8a94-8db74cad6c48", + "c250f915-6799-42a7-8f32-9566afb6fd46", + "f0821d50-b42d-46bb-9d83-0164ce46f06e", + "b5229471-4a58-4548-8353-8da96fcd1269", + "2a18c4b9-ab22-4302-9f57-d3e33b4f34d9", + "d6a657fb-d2d1-4b1c-aec0-91be13d2223f", + "e29ef7a0-4eba-451f-b72e-35ab0e70f653", + "ced1d0a6-a64f-4025-9522-a30ea3d65197", + "02e6376a-2193-4c84-a184-8c74a22a5979", + "af23a1da-4b3b-4de9-97fb-f4a7dc86e228", + "1d1e3d70-8788-4895-8801-867cb3e0609e", + "e8ddef76-9614-4f15-9f48-2de124d60ede", + "d56edcda-110b-4b26-8200-0fbe16c04e8c", + "5b0ce5f5-11be-483c-bf72-aa96bd1dbc2b", + "c8b01731-3aeb-4d24-b900-644a5950fff0", + "7ebc4295-8f24-4d1e-b23b-f3706259a2bf", + "5cb122ef-6d3d-4c9a-8a6b-c90cbb5fc88c", + "b89599dd-6dd5-4427-85aa-ca847c0f0c9b", + "51f70f6e-1db1-413c-8075-acae294b8c54", + "84a69f65-a904-4e83-b3ea-7a709deb9506", + "e5ccad56-5770-4710-9059-d38f4ef07ace", + "86927246-dc9e-4649-8108-13b02276d573", + "2583843c-249b-41c9-8de9-5aabde67f2b5", + "5adae8a0-ec29-4c1d-abdf-245cdf5f8843", + "24d802a9-c5af-405f-8ad9-79244a5c64b4", + "a4882612-4e60-43ed-b93d-71290462dcd6", + "6d258a6d-583f-4703-85ef-d3bb62806fc1", + "e3e9dbac-0f10-4c67-86da-1caa960d03d8", + "50e2fb22-092c-44aa-bc5a-ce30e22cc131", + "525c1eaa-e4d7-4522-a99d-748f7c39d0e3", + "a20d0d68-5da4-4c5c-b2b9-4f335dff8668", + "9fcc6153-cb89-407c-8227-9a0a154b7392", + "f69bd196-66dd-4ba6-8357-2de84f0cf75b", + "f61c13aa-e8cf-4e93-9479-3c1437e8e61b", + "104efe3d-6a1d-48a3-a10d-667a4455cf21", + "c682291b-353c-45e4-8ca9-6645df4a991b", + "bc3c5140-6ffe-4648-8fb3-58ea1e0516a1", + "6cd6ddba-86bf-40ec-a75f-1d63630eea21", + "0d1bdeea-77ff-4b98-b7d7-8cadccbb9b84", + "ce950ce5-f5e7-423d-b350-f84cefd57e69", + "1220a6b6-19b1-44f9-be19-4cb224830e5c", + "b118ae3a-a4ce-4d1a-8b28-8fcb1b23611c", + "c3d291c8-0e04-4208-86a2-dceffa4363f7", + "65c6f7c4-e975-4576-ba84-e6b687bbd2ef", + "5b338764-bf7f-4115-8ae3-e182d1f1201a", + "e00bdfb5-3e3a-450d-a56d-953a01e40a81", + "fffa02f7-cbf1-4a96-8e6b-25b99bd75972", + "7ce81ea8-4ff6-49e0-8b89-9ce840970476", + "16d47909-5064-4fa4-8bd3-0465c388f889", + "32f4693a-97a1-4a91-b607-18239e5bac65", + "ad3b5241-f758-4f80-8a48-0548dd8024e8", + "6ddde563-4d89-4f23-b4f7-a90f5cd2bb81", + "b2876e59-d4c9-41f4-a0dd-566780ab635b", + "52de8e08-1262-486a-af42-c9b969a5c165", + "3d361380-354b-4ed7-ab15-4c06c2ad33be", + "9b09ce12-7853-488d-8b24-67224ff132dc", + "8fd9803f-4534-4b9b-9331-871d6f0ff7a5", + "5e37eec1-103a-4b0c-9031-1ef12d3ac952", + "2528c4c2-3968-47b0-a124-44a18f2b5f61", + "bc31c8f1-d6ee-428d-82e2-f3a796da6abd", + "7fc277b8-4d9b-4f7c-8f2b-9bc2965e404d", + "699ef08f-de74-4303-ae7e-8ab710d7be5a", + "43e65688-604c-45d9-be72-eed20ad433f4", + "b7e5cfcc-b324-42a7-befa-722c20277189", + "8b4f1c2a-9b31-4a91-bd0e-ad50dcbcb6dd", + "4da89f42-3714-4229-8291-1f721fbb98c5", + "c0d30b9d-93e0-413a-ae9a-cd033946042b", + "5594c6d9-b1a7-4606-8764-f9d1e81a7c65", + "dd94e6d0-6ab2-46d9-94ef-0059738c46a6", + "ab550281-7de5-46f0-bbd3-9defe55b0e9a", + "182a06c7-4ac7-4b45-991c-8b608e66e40d", + "4f5ecf90-a007-4290-8a6e-45092b8e4e4b", + "f692b3a1-a4c1-4f38-baec-7cc1a9cac28e", + "7096c266-0e24-49c7-a6a8-73f36692095c", + "0489b59b-746e-4266-a3cd-429aa124c67b", + "02d319e0-e328-48fa-9666-211b28aa431c", + "fe09fe01-7a31-4ada-aa5a-0305774d2b1a", + "8f7371b8-0ad0-44a2-b394-5361364edc3b", + "5549dbde-fa75-4994-b9d1-8aebe2ff88d3", + "88d5b73c-dcf8-4b16-8c76-d98bbf75c594", + "5bc0b0af-450e-4d61-a1de-6092bc310314", + "b83d955d-eed6-4cc2-9631-0226beac2c78", + "17530f11-9509-4d3b-a1cb-17602c9d29cb", + "55d60ec3-29ad-44e7-9f33-45a769747fa8", + "1ec41e6f-7168-4ff9-90c3-0dd628eb8e12", + "b4451c83-e889-413c-a84c-2fcb6f42cf45", + "142eeeef-c2c1-464a-866d-ca088a49ed32", + "56e132ad-dcb2-4e32-8f0e-94d104c4e07c", + "d9b26cd4-e522-41b8-9889-1d3f9c713534", + "90819a7b-2241-4a77-8a8c-db6fdbebae43", + "4cf7595f-a8a5-4c31-aee3-65fe1451f30a", + "8e58e957-8811-44f5-b147-7711825fe311", + "9c6b1e2d-7f87-48d9-93ec-fcac4311f19a", + "fad7bd7d-0463-46d3-91e2-fc41cee1fa09", + "4181b4aa-4725-4a16-a047-423c60831de2", + "efeed4fb-e300-4d84-a00c-ffc11c2adf56", + "931b785d-aacf-44e8-9182-46d17c77ff2d", + "bb1b4c4a-e23f-437b-b7a1-d376e4f19db6", + "b906ac50-ec7d-401b-864f-ec92d0614500", + "931b002e-3e60-4c77-a15a-7e9c856e4a42", + "0b99b497-3971-4467-a795-9ac8d747e32a", + "8677b9d4-8372-433b-bc09-6183e71d650d", + "1ffb7244-fb3e-4ba5-8cc5-4648053f70c3", + "2fa17356-051c-4fcd-9167-f0683c09a8f1", + "bda4d147-5f41-45c6-b7ae-1d8e86c8428f", + "380cddc3-bf3f-4206-a368-675aa31b687a", + "091b495d-eefd-407a-aac5-7cafe0fb4505", + "e991d544-5650-4c97-b248-3b51904dbe8e", + "47968af3-0961-4073-bee4-19496c3969ef", + "adba5858-f2b5-4c42-a0d0-99d4321b7893", + "a45ae2bb-62f5-4357-9fd5-b4885e4cd5cf", + "8d92b4c4-a348-4e76-b5a1-8348b21b1128", + "6f885bd1-0fd7-4ba5-b370-194bae1aac7b", + "bd405691-b811-446a-8d25-e0ed60046d73", + "11fbc6da-8a0b-4ee5-afd9-bfc2c6a31ed7", + "1321e617-84ab-4e02-b900-50340f241d95", + "f841fdbe-4813-416a-a366-4f8494b615a9", + "0b946506-f8d1-4688-986c-e4e68fcd3752", + "7cf1e8b4-5dde-4434-9665-be9297fc9e86", + "40b27b2e-8a2b-4f15-b049-2e4aa3dd90dc", + "a287cd50-f3da-48ff-a6d5-fa590aa17e57", + "552f747d-0060-4e4d-a2a0-3e1b21a74f5a", + "e5a448a6-c61e-46d7-b002-c1665bb97b61", + "5fd261a4-0ecb-4091-a353-caae267fe884", + "49ca490f-ee26-4703-a4f5-3c6f06cefbe7", + "0781d975-616c-42fb-beae-491e65ab9bf4", + "2e29ccaa-ad46-4670-8617-5c683df77390", + "79b433c5-9245-4160-bce4-4465576c0871", + "833ecfd4-d15d-41f0-b508-8dcc07b8c30a", + "1f045ca9-6d91-474f-85fc-235eb3d94572", + "1e01760a-75d4-4215-9f3f-aba88d27eeea", + "7507dc9e-0924-4062-8af7-0cdfd139b718", + "7dd1de4a-12c2-418c-815e-d3ebfc32c17b", + "6e13c495-a095-4ad3-877c-4c24de572a59", + "fd40d01f-fb24-40b0-944d-b298b80fb0b4", + "4b922088-e2dc-4638-a476-98dd9f2162a9", + "16b36462-e231-4331-8369-f60220edeb9e", + "2a4d8887-ea7b-49a8-9218-a6449af750b8", + "7abb8c3c-e602-4067-baeb-dfb86a0825c3", + "c28da51b-a9ac-4e78-bcb6-b6f4421a9e81", + "caaa0044-4a4c-465d-b26b-687b028c1498", + "841ef59e-1d42-4d6b-89bb-58f7dd0f2f46", + "2ee5e34f-3472-48b6-b52e-23b13a2b5ea8", + "5b969fb2-22c9-4f68-bc8a-06ba3ffba7a6", + "a1fb70a6-e887-4ff0-9f87-bb74537a41c3", + "c2a2be2d-776a-4b40-b189-34b643d36e85", + "e0e3bb94-cc23-42f5-b5f2-89de3fdd2d74", + "095b5634-104d-4b01-bffe-8a521e016cf0", + "32dd7616-4fd1-4a9f-a5ee-b08897a4eee9", + "b00c79d3-5f3c-4e6e-8ab8-7f3900fd74e4", + "00a5c426-2e82-40b6-932f-ff853ab39cf9", + "4b70c3ea-9e97-40c2-b981-2709f8750a75", + "3890658d-2fe2-4058-a4fc-2fdbebd09bad", + "be275e2f-f819-4626-8664-d5d7f34090d8", + "d6d2c1d0-aa7d-4a86-b609-8cd254797032", + "5a2621c4-317a-4ed3-ba94-d9c6922759f6", + "e0a6adf5-bda7-45ea-b312-1b5c6b0c747c", + "6c695d0d-8c62-42ac-a69f-a03d57b174af", + "57b5bc29-de82-4202-a6cb-c5ee25369d18", + "0211c7f7-f951-4791-b662-8f5791b1a3f6", + "dc40f90c-1a22-49b0-b517-1cc3d5aeaf57", + "c45f188c-b0c7-40e8-83fd-0f99036c8feb", + "72a3cf8c-53a6-4ba7-b72c-6eab82faae7b", + "5c300d67-f9c3-4f4e-99f8-fb91abab3835", + "0d0c8027-4134-474a-9150-e0c53092607c", + "30de5049-ce25-4d4a-a012-78110521b4d4", + "8412092e-272a-437d-a631-50b94190122a", + "eaef5e09-aa4d-432f-a2b3-5620b41d4668", + "51cf2f28-a11c-43e0-865a-54d4c312e836", + "037f85f2-ccbf-4529-9757-758ebd02d32f", + "d9a9b3b2-3d00-4f6b-815d-58e825c2afc5", + "491a9663-2c66-41eb-9ad0-5439edefd5e7", + "46e8cdd1-8205-47eb-ba19-cd0d9472bae5", + "ff1eaa3a-61e4-4484-82cb-db4f4de1cc55", + "daee34c4-8a50-4ff9-9883-c1063fe2b840", + "0a63a827-c070-48f9-b90d-4bbca3548e05", + "2420d768-75e2-4f19-9a1c-40a2a14251b9", + "353f680d-2992-499a-b174-199ee150624d", + "ea0107ef-f147-43fc-b78d-016a737b622a", + "85eac63b-c937-48ff-a9ee-12f1dcf86672", + "72e1891e-8bbd-4be2-bb66-0976effda9c9", + "eeeb0b86-609b-4221-a94a-d5381cb77044", + "493bb93e-9845-49d6-8942-798a5a0946ce", + "5f1e2f0f-98ef-47de-8072-ea929e1be18a", + "7289de8a-2ff7-4c41-a08a-38656857c631", + "9bebed98-0ab3-46c7-b59f-858c511331b4", + "f0a47381-daab-4da8-83d4-76ac69975ed2", + "7f8f6698-5e44-4eb0-84c0-02147297874e", + "869d643a-ea58-4d7c-9d29-426eb619d1da", + "3064a09c-f21c-4a73-81e9-82dd9a4f191a", + "21a140bb-dd4d-4a5c-ae83-b531cc8e7d2b", + "68cee4b5-e0d7-4b7b-8f3f-d8d90c2f31db", + "00ddb0f8-4289-4628-b4c6-fc8c6a7ca996", + "e96031ff-b5a5-4be0-ac55-ce57a66c0827", + "cfada809-9f84-406d-b374-aab7f8bafe01", + "808ee189-86ee-4d03-826f-241d07646a2f", + "12f631a3-f9c1-4d60-ab32-27f3cf85e78f", + "eca53cb9-8242-41b4-bb93-d9c50986c5e5", + "b251ee07-a333-4e51-b9a5-c8418e179557", + "c06ce904-2f57-4576-9c3e-e9c99cda6f62", + "023e7fa9-e0c2-4398-985b-bf9134223c4b", + "26352057-d76b-4baf-94c7-334984f9ab08", + "405c4235-b3cd-4b83-aebf-152a793008f1", + "c2fa5a56-fa19-4388-a6ba-8f1f7dd7f7ef", + "9dbbc699-c9d0-4a0b-9c07-6cdf0b99b519", + "8e5a129b-0a71-4790-bd4d-feabaa3775b8", + "017b3fb9-1b9b-4e46-8cd4-55a1e47d43fc", + "ee1ed3e9-4128-4f52-9db2-22d6509b373d", + "e71d5f03-8b58-46da-9635-35376cf005c8", + "c78702c5-0894-417d-801b-c76bcf3d85bb", + "0aa65a60-29ce-44c5-92a0-126ca6fe8832", + "c21c0898-ae7e-4018-89e8-d16dd7609eec", + "095f5219-33c7-47f7-b6b5-dde4756ee674", + "7081176a-c9d5-4798-8dcc-48ba36302736", + "c6c41a63-54af-49d1-96af-a0789835a16c", + "3e777421-cb3f-4b46-8968-b49141447a13", + "9e1b8a7a-d93a-417f-a7bd-7fff50c3a129", + "d04c6ff2-3d44-466e-977f-c99387a13bd3", + "9941874c-a6f9-4a1b-9896-d50a8f99e12e", + "3126c177-f6ac-4027-a665-5d56eaddf115", + "3f0de91e-674c-412c-b2f4-8a6befac8139", + "9050ae89-6717-4776-9fd4-be387128f4be", + "bd65dde5-effd-4274-9b13-83a3a9018896", + "6fb9e9e4-6f3a-46a7-bf9f-f80fc7c50d24", + "6fd84480-f2c5-4958-a0a3-de5836d68e34", + "99993b08-5161-4feb-9b5d-53c6b7b96be9", + "c77e7206-b6e6-4f36-ab57-1f91defc5a94", + "a3392116-62fe-4a82-bcc8-927f2fbeec45", + "5c3a08f2-1859-4a92-841d-4e757399e722", + "37268b58-8f76-4f34-b990-83c140bec1ac", + "6b58a3c0-126b-408c-9786-3cfdfb64f4d0", + "923193e8-c3db-4bc7-a27a-e4ffd122e999", + "07ccfa8f-53e7-40a9-bddc-991098005c80", + "7233d5df-eb67-4b83-9a12-d7e21c0a70dd", + "01d9c1a8-ee6b-43f8-b0d2-d207e199354b", + "e9db84da-5035-41fb-b375-26bc3479ae6c", + "89f5678b-d191-4416-a853-f850c8980651", + "84db63c2-5c26-41ad-bfd6-715690b4b20c", + "282b8393-022c-4247-b582-2b80c940b458", + "1b93730d-9f15-4953-8c0d-e6dbe154e866", + "7de70760-fe7e-40f9-8f20-0c283aa8a189", + "74eec209-5d2d-45a8-87c1-d5ea6a654010", + "cdf54695-3e95-4659-b99b-ad622df8124f", + "8f8cc60b-a9c1-4501-9c9b-edd6a91656f3", + "5f0b6975-739e-416c-b421-c66ec2c1a600", + "2349f39c-05c3-4166-a804-a2629bef7c73", + "34dd5fe9-f9a2-416d-b045-2a532d67e9fd", + "b35c9307-271b-4612-9e6f-b44379522eb6", + "e527258f-975b-4c78-91e6-a846a94cbd80", + "1923ccd0-1d6f-49c7-b7e2-e0346df0e6d0", + "d6122f1b-f586-42d8-b2ad-536f72771c8c", + "29e58642-945f-4157-83b0-b5708f4d33c4", + "38ea938b-90b2-4d9a-8f2d-18aebe988aab", + "a51359c4-470e-477a-8c7b-a6b405c27123", + "1258f9b4-79c9-473e-aa15-d6c33677753c", + "3f2388d4-0f4c-4734-8f4e-9f937c757005", + "9bc16267-4a56-40a4-807c-6c687c750b31", + "f4b56a86-82ea-478e-9260-24d16de3af0c", + "fc87ff6c-cec4-42dc-b558-251655dd5165", + "1b090bc1-46bd-4b55-9d07-56bab77871ff", + "f70be1e9-5285-4e1a-886a-931eea06e67b", + "9b92ad8e-cf71-4bb0-a7b8-1dd0e7cf3ae2", + "62ccf750-7478-44c0-94e4-346906a86502", + "86128762-e353-465d-8436-f2157d9c7ee5", + "28b15b82-a93b-445c-86f7-599c86ff324a", + "411c5727-1e38-4527-bd2b-8f2c06791722", + "63a3fc47-0f88-4b32-9208-f6f1a0ae56ee", + "5669a7f7-37df-46a5-8ff8-b9601a86452c", + "419ce439-b6e0-43d4-92a0-aec7b958a568", + "16da6930-f619-4ea8-bb43-f455a0ef7cd3", + "bd2da806-9e77-4cb2-a4f9-4bfeea82f0d0", + "91199123-80e4-4576-8977-279fdb35e4c9", + "0650602a-a3bb-4d8b-9745-056bb88e4fef", + "0ae94927-91b4-44e6-a65c-5ce1af3163c2", + "b99b8136-c72c-419c-9345-1dc78741b1d8", + "e453d3ea-0600-494a-b2f1-5a74841c5021", + "dbfcc4a3-7d56-47ac-8c3f-a8d7c5e7b65a", + "83033e9b-e9f7-4d94-996a-6ffab2558c6c", + "4413dc9b-bd14-4841-9bf9-0c94803f26a3", + "a1c3849c-d7ae-4151-97b1-fba2fc0645b8", + "55522c83-ccde-4753-ba99-b1eb2e4fb4e7", + "ebc2f5a2-4ef2-4fe0-bd75-df0e629e72ab", + "2d87a7cc-a39b-41f5-8110-ec49c7d2469b", + "0b70c74a-c69b-4841-bae3-be3f53b661da", + "321e9768-e85a-476a-bdc5-233c2d3d558a", + "c548f962-21de-4e63-b3f9-c60d3368fa1b", + "dcd0c017-e2e6-4b7e-a3e2-3438ec21249f", + "0e72a6d3-e4b5-4e86-9a0e-28467683d13f", + "32bd23e4-08ba-4729-9535-2aed340a9bb1", + "bbaac34c-e293-4e6c-a345-a16d61f24fa9", + "407b4059-3554-4031-9399-cd4c8bf6d486", + "f59d76b1-e950-4999-a04e-520ee9ec44ee", + "70a74e4f-c34b-425f-8a72-50d08d457592", + "c980e2bc-72f4-48a9-b413-6c3247627aa0", + "1185620d-37d4-4205-9b4a-7e87264f56d7", + "2e9fb01e-4a8e-4952-ae36-b80314984ac8", + "c1d4605c-1324-4976-8cac-b3bd908cec0b", + "e47f5d16-9a03-4fa4-acdf-4ef0a2e58ec3", + "0d53749b-ad9c-40f8-9c57-c649cfc71a28", + "ce14018f-fe78-411d-8897-8f66447907fe", + "fca008a5-1c01-49c2-9686-4ac87d00be8c", + "e159ba0c-5e55-47ab-80aa-dc4d6805554f", + "4b77fe3b-847b-415f-87c5-08b6f83390e6", + "81bc4987-0552-407f-9273-b31581562188", + "981f3f51-6e35-4e14-acb5-a33f7ad8b370", + "06e3edc9-278f-4386-8dda-86587cc8422c", + "62402163-37d7-489d-ad69-436da8cf7aef", + "6d598694-3557-4764-ada8-ce9edaa79105", + "f737b77a-cf50-4a1e-a82b-598aa8da576c", + "be7714a7-af74-4b00-954e-e1a98aec30ad", + "0f81f77f-74ba-4ac3-a051-9ff37760bff3", + "591d3f2b-162a-40d4-8a58-af606e8a08c4", + "204f687e-7265-4278-8b69-2eaf9be31ee2", + "9eea83e8-468f-46e5-8808-1a4f76d0fe51", + "0fd512ac-cd15-480f-b216-1ef08bfc6e05", + "745af7d7-3837-47cd-af17-710268682a77", + "767698d4-3f5c-40ea-9176-ee97e65a5641", + "eea188a9-b16e-4f0a-a5bc-9e3c9b36d6da", + "f65afdcf-4aa2-4d50-ace2-2d43adbaa2bd", + "b38c9ed4-ddea-428f-8431-9ca449b13a74", + "327f79e6-22a3-41fc-aebd-f0287fb98de8", + "55260592-1f6b-4e72-8c4d-58ad0bcf0452", + "693a6c84-0823-411d-b295-6d3c62da16a2", + "d1c6eb61-4208-456c-bf31-dd510db74819", + "8065f044-d40a-475b-82fb-394de7e262bd", + "4792ad42-302a-4523-801f-7511b3627843", + "ce1023c0-e10c-40d3-a5fd-58cb0dab5d01", + "56eb5dd0-94df-438e-828e-17a53004bc2f", + "0704cca6-47e6-40f6-b5e9-050c8605bcd1", + "7d486e82-0c87-45c6-bab0-455694fc46b9", + "942da1f2-2619-4e2d-ac2a-65d9e30b4036", + "c67e0a82-07d9-4198-9282-8d795d7cba05", + "a4598b74-581a-46c5-a83b-4e0b2886068a", + "69f9f323-31fa-4abf-a4d7-7cdbaf8be934", + "1cabc87f-33ea-4b56-acb1-ac680c93f3c0", + "8ba7fc4f-130b-486d-86de-2b2ae0c6d399", + "b7ab9411-b65e-4209-8eb5-76d6328cc9d2", + "45f60506-0b14-4758-827b-fb86ad0715a8", + "0ffc8c87-f39b-49f8-a16b-05e79ffdb41a", + "3c669d1c-e6e7-4619-91d0-f362b4a9dbe3", + "a0cc229d-d068-4b0d-b909-be78844e2ea3", + "31d7c425-3d39-45ba-b29b-2e78c5cba7f0", + "e6791e04-6f7f-4fa1-aa17-03d9ed381917", + "8000d546-8f71-449c-967b-7fe99fc4dfeb", + "3b18497b-06a5-46e6-b7bb-5d7c290da7fc", + "7bf5a98a-383d-4867-9704-a39d2f585a81", + "8441b6b5-d940-41e5-8341-ba7b504ebe11", + "3dddec9c-8885-4066-918f-303c050b4764", + "322578b6-1a71-4544-b98c-82f712bd02c4", + "1d35b41c-3588-4d68-be81-d01719d0c6c9", + "9fa709f9-a6d9-4646-9a86-6c7657f40e63", + "4e7add4b-0812-4c2b-b650-7ca7b9cd6542", + "51dd2b45-19f6-4c23-a5e8-40078c77d68f", + "56af1d14-66f7-47d7-b9fe-73ba2cef8f04", + "634bd290-2f5b-4eb4-bada-726bb3aa6697", + "428aade4-d25e-4dab-a6fc-e1dd03c28021", + "6675a051-9e2b-4f21-b7b5-38ac43c551e4", + "b944fe94-db0f-4ce3-a7e7-5e4e6ce16e44", + "df3c4141-b525-4186-8664-d9f553bbe962", + "fa5a3038-c6ec-498f-924d-4bf369798fdb", + "a5fc19e5-6346-47b3-924f-a005a03cfde8", + "7356b696-8cb9-4181-8131-3d6342bd0d13", + "1d57b73f-9b60-4bab-807d-7fd621f68b7b", + "942d966b-23e9-4cb8-b65e-bed5076b37d2", + "f2f2a780-8cf2-4ec1-b193-5a0aa55403ee", + "82800f01-4083-4f2a-852e-b421f9b2a731", + "c456e944-2593-4528-9f90-434733967877", + "f57d4d77-88ee-4f93-a23a-be6ed25325b0", + "a2f0a755-a456-4ea3-9c73-cc208a45ceb0", + "6bd5493f-695b-4704-a1a4-dae9a5c4fba9", + "ca7c68c0-78b2-4b06-9f3b-97dda11d2785", + "82d4838d-6c95-4a29-bf62-ffaa7bdd8c90", + "a3b6d182-44f3-4314-8e14-109c5fa1bd6b", + "1d33d00f-2236-4486-b31f-f727f1e76b77", + "7d7f03e9-c61b-4136-a4d5-662df43de4d7", + "6d8b9753-3234-49e4-a820-3f8fb15ad6a7", + "295cd844-6f3b-447c-817d-59ee2ee1b690", + "3209e4b0-4092-400f-97f2-76ef9bcd26e2", + "3d859b6a-ff1b-44ca-b965-5382c9182a72", + "94bf5942-cdb5-46f5-9f2d-af07b867758e", + "6d2af655-052a-4537-96f6-123323ec511c", + "a3daa074-67ac-4fa1-ae9c-9d390f0540bb", + "23566cbc-dce0-4b5e-a656-b2b06c96648c", + "2cec0707-50a4-415a-8c3a-ff9f3899402f", + "8cedf7ed-4076-481e-91ee-ffdc048c427f", + "4c7b102b-7401-4221-8433-b296ffee296e", + "70b46f09-65fd-4e52-8bcf-255fac1a9be0", + "917a8acb-2360-4f43-8275-d0c5f743d074", + "6837c591-5933-41fa-93f9-22206b4de036", + "98b92b7d-c971-4229-9332-e30afcec15b0", + "f8e744e1-117f-49ee-897e-8fdea25dfa7c", + "1e27a82e-37fd-41fe-a0e1-fbe7b684d144", + "b4c0a6a8-7325-4161-a555-6a6e0866c226", + "8938e39c-a9f1-4d68-a108-1b4feba60ec7", + "7f55aa52-9fb9-487d-a0f4-85ffcadeb3e2", + "58d10043-311a-490b-a371-34161be7d642", + "359c8ee9-7bbb-47d0-a019-2060020d329b", + "0da618b1-b949-4f72-8c18-d089fcdecf3a", + "963b123e-a75a-4d27-a9d8-89f0c3784381", + "60f6c584-c5f9-4e29-8198-2217a2a00873", + "613c3e61-e000-448d-9432-6a67d5d6839e", + "73103f0c-d0df-4b3b-9cc1-3c5c992d2c04", + "c87235ec-3a6f-4ad9-bfb9-242b4c4ee25f", + "89b38b88-e699-48d2-930b-8dac93e44639", + "b99e6af3-d7ae-4a77-8dab-2d1122e5120f", + "8af12852-23cf-4701-93e5-29fbbd30986d", + "25a99a6c-14fd-437c-8790-57e03a5d61fa", + "b73fed60-cd99-46c4-9597-ffb9ae0da125", + "8c851bc0-de01-47e4-86ec-1f29fc94c9f0", + "a04b0351-c7ff-4c99-8def-fa93383e0892", + "b0a9a9ef-6302-4655-959c-953ecc0e96a0", + "dccbf305-e46d-4f8d-ba4d-45625d827858", + "7349eab6-d047-4c82-bff3-64878c00b405", + "3aaa4024-3abb-4b87-889f-1d0b31f6593b", + "c8c22a86-4a17-4dda-9e44-f97da8ca55d9", + "e32274b7-596c-44c9-a6e3-41c97654d732", + "75577d2c-6486-4fcf-bd7e-7e0f5f601420", + "3c1243c0-c7f6-48d3-80c1-ae359628ddf9", + "e0fb8867-c262-4b23-872c-cd0b1484a640", + "70504ae9-756f-4f6e-8b1b-eb254bb472fc", + "e5e83c5b-c352-4ea8-acf6-26c83ce9fa98", + "b008d76e-3bfe-401c-ab02-1fd5785ae7e8", + "b716ed20-c857-48c3-a7b3-21f8592be1dc", + "d1d7e8be-2d07-45b9-8f79-ec468096e9d7", + "4c2ce114-f845-4726-a411-7c13c0f128ad", + "ea9b3f1d-dacd-403d-89de-3513e75d7f70", + "c6361323-7da7-4653-923b-8caf7d8f859d", + "4c411290-c2e8-4449-92ee-059cd708f08c", + "9dc3e7a2-e922-427e-9fb1-94870179de05", + "36bf057d-c6a7-4040-853c-01fb9349495a", + "0bc3bbe2-0a89-41f8-bc2b-ffa22f806168", + "5a4332d3-764b-4e54-b0f6-5bec8a58ea44", + "7d7af528-5bcd-4de3-a6d9-41b06de62ec8", + "478aefb1-80e5-4071-85ee-5b9b4099895f", + "afe497a6-44b5-467e-ae5a-accc2234329a", + "5b897b7c-8b5b-4c6e-a913-d4d88b3d2caf", + "43dbe02f-90e8-4c95-b50a-098233bbb207", + "1cff7e5c-8620-4072-b48e-062a6c37569f", + "d44db007-1e03-4129-aa37-74b69a9d4c11", + "cc9c3433-3fe8-435f-8b11-df51315c52d2", + "e366ab05-9e6c-4263-8785-a060f771b1fb", + "741deb7f-5903-4c20-a043-4a8840d168db", + "13cf8b52-d8d0-4360-af63-8f5ddef31b17", + "e31e74c1-71f3-43e5-8d4a-9edf5ab10e87", + "a73a281a-5d3b-4f99-a763-44853cd2ab5a", + "3334d38b-eaf7-4531-bdf1-60f098abaf7d", + "9e31ae17-385f-460e-a634-4181eb9b834e", + "089cf908-5e22-4235-8638-5caeaf7ba6e0", + "223580d7-789b-4567-b5e0-401c6e0d049f", + "a96bc3b7-1fcc-4ea5-adcb-b57b9daa805f", + "4ca938bf-032d-41dc-aa28-d6295e738b11", + "2d3b03ef-539d-4464-bd07-4eef933e2a4b", + "8cfdaa3a-3780-4e68-904f-feaad8ee0067", + "87ef2e46-340d-4e28-bac6-0d1893f1f2eb", + "cac96612-0887-4a08-8c37-4141b6a67a60", + "40c949b5-d749-4880-9f41-22e27299a6d6", + "1425b70b-7e75-45d9-8360-ab7d336127d1", + "8b016335-053e-40a9-8c35-dc4b05bd54f1", + "eba2fde3-758e-48be-abd8-c794f2a65b96", + "4ae08f39-0fb4-4309-906b-c4a633c0c722", + "9810f406-254d-45b7-945e-eac39fc3747d", + "08505926-b499-40de-a833-f7145616f7f0", + "9769e386-e8f8-42a1-8f29-8999c4f6758c", + "0fc5b793-e174-46c6-ab9f-d2f287a7940b", + "9d5a6ccb-3d67-42b5-a953-1cdee9e2abb5", + "1591f3ab-8efa-408f-a43f-981591b9911f", + "27e0fca8-5672-44b8-81d2-959318ed9016", + "307242d0-f882-485c-b9cf-33b596881fbd", + "b8f5b0df-b92c-42bf-9aa6-9b20c3cc708a", + "a0085df3-2e91-4a82-8892-35f53aebde12", + "92008ee9-e472-4042-bda5-4aec2900963e", + "020c2f5e-447d-4688-b10b-32292d30a873", + "97606bf3-649c-468b-bf28-05288f286f80", + "44c2e598-749d-4737-9e2b-de1a980befb5", + "d082fef7-8308-496f-a452-1bfeac87d5fe", + "7f2de9a0-b4fb-4aed-9f2c-50752c5b6994", + "2e3e3f67-98c0-49ef-aa78-e1d69850dacc", + "103eb3da-6616-49b4-9104-d636f4fee05b", + "3a297df5-2290-4fdc-8dca-01caf2dbf870", + "8580b0c6-096e-4900-980e-ab21aa8611d6", + "8cd92d32-c6c3-44f4-958b-9d28ee9b91c0", + "21b4b964-b2c5-4b15-9177-46219fc706e2", + "6d3ab1b1-5332-4915-a19a-ab9b721468c6", + "e246d28c-f17a-4083-a079-2af5cf6b3c5d", + "0d43260f-44db-4e76-bed0-bf710542a49b", + "ce9c14dc-355e-4f5c-9aff-3dfaf9de3f8a", + "9623a5c2-2b3f-43df-91ff-2a6812e16957", + "1437a839-366c-4169-9b55-5f230c1e01c6", + "a26b40ca-b5ef-4f40-8375-b1b84c45caad", + "9f9abdac-0ba4-42fc-9c11-2059877b2f17", + "d0d2757f-6d66-4cb4-b0e5-982af19e88ce", + "045899c3-ff82-4d51-a87c-b00adb897580", + "2f050ed5-6b26-4c57-9271-9586354d513a", + "cfd241a3-23b2-454e-8746-2eea5ecf6b0d", + "77266a40-5985-44d0-b889-0284f331a620", + "b692c208-17a7-4689-8e01-0820105c7ef8", + "a2eeeb06-3d2b-445f-97e1-228b0babf104", + "470b5cdd-888c-467f-84b1-121c49d68f10", + "15bf8f12-4397-4f52-99b6-b5371d3da962", + "afa8cb18-d235-4d42-a147-0569c372a2fc", + "304e30d0-fbeb-4136-882e-2e558a2c9d01", + "ce6589e1-2e8d-426c-9494-e52144cd12a3", + "6f145b88-0650-4e06-9c7a-b35e9e094264", + "32456ed0-bf6c-4734-9b2d-9f5159194fc4", + "c5bb9dde-9bd5-4cbd-a41b-7c3840eb09cd", + "8b334319-3b14-4194-824d-fa6c9e503d3b", + "0f456a08-6f3f-48e6-9471-6a72928a46e4", + "361cfc9c-c15d-4c2f-bdfc-af0cd2ad18af", + "7a729ed0-e145-437f-a69a-6b057f50883f", + "2a703063-2157-458d-8fc1-0c54695fe097", + "7b525630-eb4f-4d5f-bc72-c053e7abb561", + "b6c8070d-18eb-40d8-b1d4-7868b5b1827c", + "fd16ad66-6843-4e92-af56-0b5ae5974476", + "37cc63bd-33f5-4f11-a24f-7a881ce7a6e5", + "24636b28-6e49-4f46-a2c0-c5b5a1324b6e", + "4e6fc9f2-92ef-44dc-afde-2fa48d485cf2", + "29e9b25c-7054-44d4-977f-ddfff40e8575", + "002f074f-7208-4136-9d08-47b353cf487a", + "883a4715-ff58-4afa-8909-bd6f247d31e3", + "d57beeb1-7d62-4f18-9e5d-242c58363dee", + "c2be2810-7c4e-40f2-bf5c-1fdcb519d4cf", + "7355cd7a-5806-469e-8aaa-42339aa70868", + "283f374f-2e86-47eb-91c2-86f0bac79e8f", + "d8fda959-7a3e-4cb0-bf0d-93ed40701e49", + "773f1433-10b5-452b-a083-f07f913accb9", + "19c1956b-a35a-4675-a5f6-55c981daa79c", + "b4d3c582-e513-4c87-bdbe-2e51b13cc240", + "a2852d18-ae72-4f9a-b30f-545d2f188204", + "30e30865-4b79-47a2-af67-d928ae11aeaa", + "a1fb7af8-b4c3-4411-b5d7-3c93128cfdab", + "7f011678-0e36-40f5-82fc-3e29df72c6ef", + "603c6a5f-502e-455b-9ebc-c213017faf96", + "7052d272-4799-4595-bd6a-0defefdd7ee4", + "78be2390-5e62-44b9-8067-e1d0cbcf9615", + "66c3b2dc-aa18-42c2-91f7-7e3d9940148c", + "c47b2649-8a24-45ef-bf4b-b27a6eb5c958", + "5e5aede2-2b8f-43ef-8039-7397c68158bb", + "eca01d0e-a2c2-46f6-a950-52ba75d480f5", + "5a613e01-d838-4044-95d2-e590491546e6", + "3fdb8378-258a-4884-bfe4-026208429588", + "adc0c813-9919-44dc-80fb-4a9068ee5af7", + "68b48dd1-36e2-42ce-82b3-dbbe34839b39", + "432ca994-9a28-4030-ad03-2661be5c1031", + "de89fe74-e577-4c06-9322-c5f3fde74fc8", + "ffef58a2-1fdc-4c2c-bb21-e1e656bcbc9d", + "555b3ea5-5c26-440e-a8d9-807a413b5c73", + "a744ce1f-3d1b-4a4a-b563-7c06076a880b", + "e5b263ed-eb6b-4c47-9df1-55a0459252bb", + "937683ab-7706-491d-98c3-1ba80ea0b5f0" +] \ No newline at end of file diff --git a/benchmarking-tools/BD-Performance-Test/test-suite.json b/benchmarking-tools/BD-Performance-Test/test-suite.json index ce5f933a..9e0f1a2d 100644 --- a/benchmarking-tools/BD-Performance-Test/test-suite.json +++ b/benchmarking-tools/BD-Performance-Test/test-suite.json @@ -1,26 +1,28 @@ -{ "title":"Different number of sensors:", - "defaults": { - "test": "post time-series data" - }, - "tests": [ - { - "title": "Post Time-series : 1000 sensors", - "formData": { - "saveFile": "log.json", - "totalSensors": 1000, - "totalSamples": 1, - "totalValues": 1, - "dynamic": true - } - }, - { - "title": "Post Time-series : 1000 sensors", - "formData": { - "saveFile": "log.json", - "totalSensors": 1000, - "totalSamples": 1, - "totalValues": 1, - "dynamic": false - } - }] +{ + "title": "Different number of sensors:", + "defaults": { + "test": "post time-series data" + }, + "tests": [ + { + "title": "Post Time-series : 1000 sensors", + "formData": { + "saveFile": "log.json", + "totalSensors": 1000, + "totalSamples": 1, + "totalValues": 1, + "dynamic": true + } + }, + { + "title": "Post Time-series : 1000 sensors", + "formData": { + "saveFile": "log.json", + "totalSensors": 1000, + "totalSamples": 1, + "totalValues": 1, + "dynamic": false + } + } + ] } \ No newline at end of file diff --git a/benchmarking-tools/BD-Performance-Test/testSuites/all.json b/benchmarking-tools/BD-Performance-Test/testSuites/all.json index 25759ec3..6db75a4f 100644 --- a/benchmarking-tools/BD-Performance-Test/testSuites/all.json +++ b/benchmarking-tools/BD-Performance-Test/testSuites/all.json @@ -1,8 +1,9 @@ { - "title":"All tests", - "tests":[{ - "test": "create a tagtype" - }, + "title": "All tests", + "tests": [ + { + "test": "create a tagtype" + }, { "test": "get a tagtype" }, @@ -146,5 +147,6 @@ }, { "test": "delete a tagtype" - }] + } + ] } \ No newline at end of file diff --git a/benchmarking-tools/BD-Performance-Test/testSuites/createASensor.json b/benchmarking-tools/BD-Performance-Test/testSuites/createASensor.json index 0abdc5de..c645d2c3 100644 --- a/benchmarking-tools/BD-Performance-Test/testSuites/createASensor.json +++ b/benchmarking-tools/BD-Performance-Test/testSuites/createASensor.json @@ -1,7 +1,9 @@ -{ "title":"Create a sensor:", - "test": "create a sensor", - "tests": [ - { - "title": "Create a sensor" - }] +{ + "title": "Create a sensor:", + "test": "create a sensor", + "tests": [ + { + "title": "Create a sensor" + } + ] } \ No newline at end of file diff --git a/benchmarking-tools/BD-Performance-Test/testSuites/createSensors.json b/benchmarking-tools/BD-Performance-Test/testSuites/createSensors.json index 58133e1e..bd007196 100644 --- a/benchmarking-tools/BD-Performance-Test/testSuites/createSensors.json +++ b/benchmarking-tools/BD-Performance-Test/testSuites/createSensors.json @@ -1,8 +1,10 @@ -{ "title":"create sensors:", +{ + "title": "create sensors:", "test": "create sensors for performance-testing", "tests": [ { "amount": 500, "connections": 5 - }] + } + ] } \ No newline at end of file diff --git a/benchmarking-tools/BD-Performance-Test/testSuites/searchSensors.json b/benchmarking-tools/BD-Performance-Test/testSuites/searchSensors.json index 8a4827f8..6584a191 100644 --- a/benchmarking-tools/BD-Performance-Test/testSuites/searchSensors.json +++ b/benchmarking-tools/BD-Performance-Test/testSuites/searchSensors.json @@ -4,5 +4,6 @@ "title": "search sensors", "amount": 1, "connections": 1 - }] + } + ] } \ No newline at end of file diff --git a/benchmarking-tools/BD-Performance-Test/tests.json b/benchmarking-tools/BD-Performance-Test/tests.json index f6818629..7c0b637a 100644 --- a/benchmarking-tools/BD-Performance-Test/tests.json +++ b/benchmarking-tools/BD-Performance-Test/tests.json @@ -1,50 +1,52 @@ { - "create a tagtype":{ - "url":"cs", - "path":"/api/tagtype", - "method":"POST", - "idReplacement": true, - "body":{ - "data":{ - "name":"tag-~id~", - "description":"performance-testing" - } - } - }, - "get a tagtype":{ - "url":"cs", - "path":"/api/tagtype/tag-~id~", - "method":"GET", - "idReplacement": true - }, - "create a building template":{ - "url":"cs", - "path":"/api/template", - "method":"POST", - "idReplacement": true, - "file":"post-data-files/create a building template.json" - }, - "create a sensor":{ - "url":"cs", - "path":"/api/sensor", - "method":"POST", - "idReplacement": true, - "body":{ - "data":{ - "name":"performance-testing", - "building":"testing", - "tags":[{ - "name":"testing", - "value":"1" - }] - } - } - }, - "post time-series data":{ - "url":"ds", - "path":"/api/component_test/influx", - "method":"POST", - "idReplacement": true, - "json":"{\"data\": [{\"sensor_id\":\"a76650b9-9442-446f-a3bb-3af29aaf655f\",\"samples\":[{\"time\":~tm~,\"value\":55.12286237520166}]}]}" - } + "create a tagtype": { + "url": "cs", + "path": "/api/tagtype", + "method": "POST", + "idReplacement": true, + "body": { + "data": { + "name": "tag-~id~", + "description": "performance-testing" + } + } + }, + "get a tagtype": { + "url": "cs", + "path": "/api/tagtype/tag-~id~", + "method": "GET", + "idReplacement": true + }, + "create a building template": { + "url": "cs", + "path": "/api/template", + "method": "POST", + "idReplacement": true, + "file": "post-data-files/create a building template.json" + }, + "create a sensor": { + "url": "cs", + "path": "/api/sensor", + "method": "POST", + "idReplacement": true, + "body": { + "data": { + "name": "performance-testing", + "building": "testing", + "tags": [ + { + "name": "testing", + "value": "1" + } + ] + } + } + }, + "post time-series data": { + "url": "ds", + "path": "/api/component_test/influx", + "method": "POST", + "idReplacement": true, + "json": "{\"data\": [{\"sensor_id\":\"a76650b9-9442-446f-a3bb-3af29aaf655f\",\"samples\":[{\"time\":~tm~,\"value\":55.12286237520166}]}]}" + } } \ No newline at end of file diff --git a/benchmarking-tools/functional-testing-tool/index.js b/benchmarking-tools/functional-testing-tool/index.js index 4fa4276a..c8eab13b 100644 --- a/benchmarking-tools/functional-testing-tool/index.js +++ b/benchmarking-tools/functional-testing-tool/index.js @@ -7,20 +7,19 @@ const mocha = new Mocha(); const glob = require('glob'); const directory = process.argv[2] || './tests'; -if (directory.substr(-3) === '.js'){ +if (directory.substr(-3) === '.js') { mocha.addFile( - path.resolve( directory ) + path.resolve(directory) ); -} -else{ - glob.sync( `${directory}/**/*.js` ).forEach( function( file ) { +} else { + glob.sync(`${directory}/**/*.js`).forEach(function (file) { mocha.addFile( - path.resolve( file ) + path.resolve(file) ); }); } // Run the tests. -mocha.run(function(failures) { +mocha.run(function (failures) { process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures }); \ No newline at end of file diff --git a/benchmarking-tools/functional-testing-tool/tests/TagTypes/create.js b/benchmarking-tools/functional-testing-tool/tests/TagTypes/create.js index b533c86b..d43128e6 100644 --- a/benchmarking-tools/functional-testing-tool/tests/TagTypes/create.js +++ b/benchmarking-tools/functional-testing-tool/tests/TagTypes/create.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('Tagtype APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -28,7 +28,7 @@ describe('Tagtype APIs should handle ', function () { centralApi.post('/api/tagtype') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ + data: { name: data.get('tagtype'), description: 'integration-test' } diff --git a/benchmarking-tools/functional-testing-tool/tests/all.js b/benchmarking-tools/functional-testing-tool/tests/all.js index d138f8ac..d6f4d8dc 100644 --- a/benchmarking-tools/functional-testing-tool/tests/all.js +++ b/benchmarking-tools/functional-testing-tool/tests/all.js @@ -8,8 +8,9 @@ var expect = require('chai').expect, const uuid = require('uuid/v4') describe('An authorized superuser should be able to ', function () { - this.timeout(5000); + this.timeout(10000); it('generate an access token', function (done) { + console.log("config file contents:",config) if (config['clientID'].length && config['clientSecret'].length) { centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { @@ -31,13 +32,12 @@ describe('An authorized superuser should be able to ', function () { } }) .end(function (err, res) { - console.log(res.body) expect(res.status).to.equal(200) expect(res.body).to.have.property('success') expect(res.body).to.have.property('access_token') data.set('authorizedToken', res.body.access_token) expect(res.body.success, res.body.error).to.equal('True') - // console.log('Using access token: ' + data.get('authorizedToken')) + console.log('Using access token: ' + data.get('authorizedToken')) done() }) } else { diff --git a/benchmarking-tools/functional-testing-tool/tests/buildings.js b/benchmarking-tools/functional-testing-tool/tests/buildings.js index e9e1f9d9..a50a6a21 100644 --- a/benchmarking-tools/functional-testing-tool/tests/buildings.js +++ b/benchmarking-tools/functional-testing-tool/tests/buildings.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('Buildings APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -27,7 +27,7 @@ describe('Buildings APIs should handle ', function () { centralApi.post('/api/building') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ + data: { name: null, description: 'integration-test', template: 'testing' diff --git a/benchmarking-tools/functional-testing-tool/tests/config.json b/benchmarking-tools/functional-testing-tool/tests/config.json index e5e3d0e5..80388c45 100644 --- a/benchmarking-tools/functional-testing-tool/tests/config.json +++ b/benchmarking-tools/functional-testing-tool/tests/config.json @@ -1,6 +1,6 @@ { - "centralServiceUri": "http://localhost:81", - "dataServiceUri": "http://localhost:82", + "centralServiceUri": "http://127.0.0.1:81", + "dataServiceUri": "http://127.0.0.1:82", "clientID": "", "clientSecret": "", "email": "admin@buildingdepot.org", diff --git a/benchmarking-tools/functional-testing-tool/tests/dataservice.js b/benchmarking-tools/functional-testing-tool/tests/dataservice.js index 88963c56..648c5bad 100644 --- a/benchmarking-tools/functional-testing-tool/tests/dataservice.js +++ b/benchmarking-tools/functional-testing-tool/tests/dataservice.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('DataService APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -28,11 +28,11 @@ describe('DataService APIs should handle ', function () { centralApi.post('/api/dataservice') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ + data: { name: data.get('dataservice'), description: 'integration-test', - host:'127.0.0.1', - port:83 + host: '127.0.0.1', + port: 83 } }) .end(function (err, res) { diff --git a/benchmarking-tools/functional-testing-tool/tests/permissions.js b/benchmarking-tools/functional-testing-tool/tests/permissions.js index 1a099fb2..c898add9 100644 --- a/benchmarking-tools/functional-testing-tool/tests/permissions.js +++ b/benchmarking-tools/functional-testing-tool/tests/permissions.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('Permission APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -32,7 +32,7 @@ describe('Permission APIs should handle ', function () { centralApi.post('/api/permission') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:data.get('permission') + data: data.get('permission') }) .end(function (err, res) { // console.log(res.body) @@ -52,7 +52,7 @@ describe('Permission APIs should handle ', function () { centralApi.post('/api/permission') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:data.get('permission') + data: data.get('permission') }) .end(function (err, res) { // console.log(res.body) @@ -72,7 +72,7 @@ describe('Permission APIs should handle ', function () { centralApi.post('/api/permission') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:data.get('permission') + data: data.get('permission') }) .end(function (err, res) { // console.log(res.body) @@ -92,7 +92,7 @@ describe('Permission APIs should handle ', function () { centralApi.post('/api/permission') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:data.get('permission') + data: data.get('permission') }) .end(function (err, res) { // console.log(res.body) diff --git a/benchmarking-tools/functional-testing-tool/tests/sensorgroups.js b/benchmarking-tools/functional-testing-tool/tests/sensorgroups.js index 8dc9d942..359f6a86 100644 --- a/benchmarking-tools/functional-testing-tool/tests/sensorgroups.js +++ b/benchmarking-tools/functional-testing-tool/tests/sensorgroups.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('SensorGroup APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -28,7 +28,7 @@ describe('SensorGroup APIs should handle ', function () { centralApi.post('/api/sensor_group') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ + data: { name: data.get('sensor_group'), building: data.get('building'), description: 'integration-test' diff --git a/benchmarking-tools/functional-testing-tool/tests/sensors.js b/benchmarking-tools/functional-testing-tool/tests/sensors.js index 799fa120..721422bc 100644 --- a/benchmarking-tools/functional-testing-tool/tests/sensors.js +++ b/benchmarking-tools/functional-testing-tool/tests/sensors.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('Sensor APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -29,7 +29,7 @@ describe('Sensor APIs should handle ', function () { centralApi.post('/api/sensor') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ + data: { name: data.get('source_name'), identifier: data.get('source_identifier'), building: null, diff --git a/benchmarking-tools/functional-testing-tool/tests/templates.js b/benchmarking-tools/functional-testing-tool/tests/templates.js index 1e95e536..23933fb0 100644 --- a/benchmarking-tools/functional-testing-tool/tests/templates.js +++ b/benchmarking-tools/functional-testing-tool/tests/templates.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('Building Template APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -28,7 +28,7 @@ describe('Building Template APIs should handle ', function () { centralApi.post('/api/template') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ + data: { name: data.get('template'), description: 'integration-test' } @@ -47,7 +47,7 @@ describe('Building Template APIs should handle ', function () { centralApi.post('/api/template') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ + data: { name: data.get('template'), description: 'integration-test' } @@ -66,7 +66,7 @@ describe('Building Template APIs should handle ', function () { centralApi.post('/api/template') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ + data: { name: data.get('template'), description: 'integration-test', tag_types: [uuid()] diff --git a/benchmarking-tools/functional-testing-tool/tests/user.js b/benchmarking-tools/functional-testing-tool/tests/user.js index f4ecc191..35132be2 100644 --- a/benchmarking-tools/functional-testing-tool/tests/user.js +++ b/benchmarking-tools/functional-testing-tool/tests/user.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('User APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -28,11 +28,11 @@ describe('User APIs should handle ', function () { centralApi.post('/api/user') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) .send({ - data:{ - first_name:'Integration', - last_name:'Test', - email:data.get('email'), - role:'default' + data: { + first_name: 'Integration', + last_name: 'Test', + email: data.get('email'), + role: 'default' } }) .end(function (err, res) { diff --git a/benchmarking-tools/functional-testing-tool/tests/usergroup.js b/benchmarking-tools/functional-testing-tool/tests/usergroup.js index 0bb3037d..62459843 100644 --- a/benchmarking-tools/functional-testing-tool/tests/usergroup.js +++ b/benchmarking-tools/functional-testing-tool/tests/usergroup.js @@ -10,7 +10,7 @@ const uuid = require('uuid/v4') describe('UserGroup APIs should handle ', function () { it('generate an access token', function (done) { - centralApi.get('/oauth/access_token/client_id='+ config['clientID'] +'/client_secret='+ config['clientSecret']) + centralApi.get('/oauth/access_token/client_id=' + config['clientID'] + '/client_secret=' + config['clientSecret']) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) @@ -27,8 +27,7 @@ describe('UserGroup APIs should handle ', function () { data.set('user_group', null) centralApi.post('/api/user_group') .set('Authorization', 'Bearer ' + data.get('authorizedToken')) - .send({ - }) + .send({}) .end(function (err, res) { // console.log(res.body) expect(res.status).to.equal(200) diff --git a/buildingdepot/CentralReplica/config.py b/buildingdepot/CentralReplica/config.py index 467740a5..e1675eb0 100644 --- a/buildingdepot/CentralReplica/config.py +++ b/buildingdepot/CentralReplica/config.py @@ -1,7 +1,7 @@ class Config: - MONGODB_DATABASE = 'buildingdepot' - MONGODB_HOST = '127.0.0.1' + MONGODB_DATABASE = "buildingdepot" + MONGODB_HOST = "127.0.0.1" MONGODB_PORT = 27017 - SECRET_KEY = 'This Is Secret Key. Please Make It Complicated' + SECRET_KEY = "This Is Secret Key. Please Make It Complicated" TOKEN_EXPIRATION = 3600 - REDIS_HOST = 'localhost' + REDIS_HOST = "localhost" diff --git a/buildingdepot/CentralReplica/demo.py b/buildingdepot/CentralReplica/demo.py index 0e0ec987..70b5fe4f 100755 --- a/buildingdepot/CentralReplica/demo.py +++ b/buildingdepot/CentralReplica/demo.py @@ -1,5 +1,5 @@ from models import * -connect('buildingdepot') -print Role.objects(name='default').first().permission +connect("buildingdepot") +print((Role.objects(name="default").first().permission)) diff --git a/buildingdepot/CentralReplica/main.py b/buildingdepot/CentralReplica/main.py index e91c1364..e2154f3d 100755 --- a/buildingdepot/CentralReplica/main.py +++ b/buildingdepot/CentralReplica/main.py @@ -5,116 +5,127 @@ Contains the definitions for all the RPC's that the DataService calls in order to avoid talking to the CentralService all the time. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ import redis -from SimpleXMLRPCServer import SimpleXMLRPCServer -from SocketServer import ThreadingMixIn +from config import Config from models import * from mongoengine import connect -from config import Config +from socketserver import ThreadingMixIn +from xmlrpc.server import SimpleXMLRPCServer + +connect( + db=Config.MONGODB_DATABASE, + host=Config.MONGODB_HOST, + port=Config.MONGODB_PORT, + username=Config.MONGODB_USERNAME, + password=Config.MONGODB_PWD, + authentication_source="admin", +) -connect(db=Config.MONGODB_DATABASE, - host=Config.MONGODB_HOST, - port=Config.MONGODB_PORT, - username=Config.MONGODB_USERNAME, - password=Config.MONGODB_PWD, - authentication_source='admin' - ) +r = redis.Redis( + host=Config.REDIS_HOST, password=Config.REDIS_PWD, decode_responses=True +) -r = redis.Redis(host=Config.REDIS_HOST,password=Config.REDIS_PWD) class ThreadXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer): pass + def get_user(email, password): user = User.objects(email=email).first() if user is not None and user.verify_password(password): return True return False -def create_sensor(sensor_id, email, fields = None, parent = None): - r.set('owner:{}'.format(sensor_id), email) + +def create_sensor(sensor_id, email, fields=None, parent=None): + r.set("owner:{}".format(sensor_id), email) if parent: - r.set('owner:{}'.format(sensor_id), email) - r.sadd('views:{}'.format(parent), sensor_id) - r.set('fields:{}'.format(sensor_id), fields) - r.set('parent:{}'.format(sensor_id), parent) + r.set("owner:{}".format(sensor_id), email) + r.sadd("views:{}".format(parent), sensor_id) + r.set("fields:{}".format(sensor_id), fields) + r.set("parent:{}".format(sensor_id), parent) fields = [field.strip() for field in fields.split(",")] for field in fields: - r.sadd('{}:{}'.format(parent, field), sensor_id) - r.sadd('views', sensor_id) + r.sadd("{}:{}".format(parent, field), sensor_id) + r.sadd("views", sensor_id) def invalidate_sensor(sensor_id): - r.delete('sensor:{}'.format(sensor_id)) + r.delete("sensor:{}".format(sensor_id)) + -def delete_sensor(sensor_id, parent = None): - r.delete('sensor:{}'.format(sensor_id)) - r.delete('owner:{}'.format(sensor_id)) +def delete_sensor(sensor_id, parent=None): + r.delete("sensor:{}".format(sensor_id)) + r.delete("owner:{}".format(sensor_id)) if parent: - fields = r.get('fields:{}'.format(sensor_id)) + fields = r.get("fields:{}".format(sensor_id)) if fields: fields = fields.split(",") fields = [field.strip() for field in fields] for field in fields: - r.srem('{}:{}'.format(parent, field), sensor_id) - r.delete('fields:{}'.format(sensor_id)) - r.delete('parent:{}'.format(sensor_id)) - emails = list(r.hgetall(sensor_id).keys()) - r.hdel(sensor_id, emails) - r.srem('views', sensor_id) + r.srem("{}:{}".format(parent, field), sensor_id) + r.delete("fields:{}".format(sensor_id)) + r.delete("parent:{}".format(sensor_id)) + r.delete(sensor_id) + r.srem("views", sensor_id) # cache process done Sensor.objects(name=sensor_id).delete() def create_permission(user_group, sensor_group, email, permission): invalidate_permission(sensor_group) - r.hset('permission:{}:{}'.format(user_group, sensor_group), "permission", permission) - r.hset('permission:{}:{}'.format(user_group, sensor_group), "owner", email) + r.hset( + "permission:{}:{}".format(user_group, sensor_group), "permission", permission + ) + r.hset("permission:{}:{}".format(user_group, sensor_group), "owner", email) + def delete_permission(user_group, sensor_group): invalidate_permission(sensor_group) - r.delete('permission:{}:{}'.format(user_group, sensor_group)) + r.delete("permission:{}:{}".format(user_group, sensor_group)) + def invalidate_permission(sensorgroup): - """ Takes the name of a sensor group and invalidates all the sensors - in redis that belong to this sensor group""" - sg_tags = SensorGroup.objects(name=sensorgroup).first()['tags'] + """Takes the name of a sensor group and invalidates all the sensors + in redis that belong to this sensor group""" + sg_tags = SensorGroup.objects(name=sensorgroup).first()["tags"] if len(sg_tags) == 0: return collection = Sensor._get_collection().find(form_query(sg_tags)) pipe = r.pipeline() for sensor in collection: - name = sensor.get('name') - emails = list(r.hgetall(name).keys()) - pipe.hdel(name, emails) + pipe.delete(sensor.get("name")) pipe.execute() + def invalidate_user(usergroup, email): """Takes the id of the user that made the request and invalidates - all the entries for this user (in redis) in every sensor in the - sensor_group""" + all the entries for this user (in redis) in every sensor in the + sensor_group""" pipe = r.pipeline() permissions = Permission.objects(user_group=usergroup) for permission in permissions: - sg_tags = SensorGroup.objects(name=permission['sensor_group']).first()['tags'] + sg_tags = SensorGroup.objects(name=permission["sensor_group"]).first()["tags"] collection = Sensor._get_collection().find(form_query(sg_tags)) for sensor in collection: - pipe.hdel(sensor.get('name'), email) + pipe.hdel(sensor.get("name"), email) pipe.execute() + def form_query(values): res = [] args = {} for tag in values: - current_tag = {"tags.name": tag['name'], "tags.value": tag['value']} + current_tag = {"tags.name": tag["name"], "tags.value": tag["value"]} res.append(current_tag) args["$and"] = res return args + def get_admins(name): """Get the list of admins in the DataService""" obj = DataService.objects(name=name).first() diff --git a/buildingdepot/CentralReplica/models.py b/buildingdepot/CentralReplica/models.py index cc046665..52d2b72f 100755 --- a/buildingdepot/CentralReplica/models.py +++ b/buildingdepot/CentralReplica/models.py @@ -7,17 +7,17 @@ these tables can have any of the parameters defined within the class. Needs them for the RPCs -@copyright: (c) 2016 SynergyLabs +@copyright: (c) 2021 SynergyLabs @license: See License file for details. """ -from mongoengine import * -from itsdangerous import TimedJSONWebSignatureSerializer as Serializer -from werkzeug.security import check_password_hash from config import Config from flask import current_app from flask_login import UserMixin from itsdangerous import TimedJSONWebSignatureSerializer as Serializer +from itsdangerous import TimedJSONWebSignatureSerializer as Serializer +from mongoengine import * +from werkzeug.security import check_password_hash from werkzeug.security import generate_password_hash, check_password_hash @@ -76,6 +76,7 @@ class TagOwned(EmbeddedDocument): building = StringField() tags = ListField(EmbeddedDocumentField(Node)) + class User(Document): email = StringField(required=True, unique=True) password = StringField(required=True) @@ -91,25 +92,25 @@ def verify_password(self, password): return check_password_hash(self.password, password) def generate_auth_token(self, expiration): - s = Serializer(current_app.config['SECRET_KEY'], - expires_in=expiration) - return s.dumps({'email': self.email}) + s = Serializer(current_app.config["SECRET_KEY"], expires_in=expiration) + return s.dumps({"email": self.email}) @staticmethod def verify_auth_token(token): - s = Serializer(current_app.config['SECRET_KEY']) + s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(token) except: return None - return User.objects(email=data['email']).first() + return User.objects(email=data["email"]).first() def is_super(self): - return self.role.type == 'super' + return self.role.type == "super" def is_local(self): - return self.role.type == 'local' + return self.role.type == "local" + class Sensor(Document): name = StringField(required=True, unique=True) @@ -130,7 +131,3 @@ class SensorGroup(Document): building = StringField() tags = ListField(EmbeddedDocumentField(Node)) owner = StringField() - - - - diff --git a/buildingdepot/CentralService/__init__.py b/buildingdepot/CentralService/__init__.py index d5b89104..d0f49974 100755 --- a/buildingdepot/CentralService/__init__.py +++ b/buildingdepot/CentralService/__init__.py @@ -6,24 +6,24 @@ config file or falls back to the default one. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ import os -from app import create_app -from app.models.cs_models import User -from flask_script import Manager, Shell -from app.rest_api.register import register_view -app = create_app(os.getenv('FLASK_CONFIG') or 'dev') -manager = Manager(app) +from .app import create_app +from .app.models.cs_models import User +from .app.rest_api.register import register_view + +app = create_app(os.getenv("FLASK_CONFIG") or "dev") register_view(app) + +@app.shell_context_processor def make_shell_context(): return dict(app=app, User=User) -manager.add_command("shell", Shell(make_context=make_shell_context)) -if __name__ == '__main__': - manager.run() +if __name__ == "__main__": + app.run() diff --git a/buildingdepot/CentralService/app/__init__.py b/buildingdepot/CentralService/app/__init__.py index 2e68836f..37ee3bb0 100755 --- a/buildingdepot/CentralService/app/__init__.py +++ b/buildingdepot/CentralService/app/__init__.py @@ -11,69 +11,80 @@ services such as the main centralservice,auth service are registered as blueprints. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ +import redis from flask import Flask -from mongoengine import connect -from flask_login import LoginManager from flask_bootstrap import Bootstrap +from flask_login import LoginManager from flask_oauthlib.provider import OAuth2Provider -from xmlrpclib import ServerProxy -import redis - +from mongoengine import connect +from xmlrpc.client import ServerProxy app = Flask(__name__) -app.config.from_envvar('CS_SETTINGS') -permissions = {"rw": "r/w", "r": "r", "dr": "d/r","rwp":"r/w/p"} +app.config.from_envvar("CS_SETTINGS") +permissions = {"rw": "r/w", "r": "r", "dr": "d/r", "rwp": "r/w/p"} login_manager = LoginManager() -login_manager.session_protection = 'strong' -login_manager.login_view = 'auth.login' +login_manager.session_protection = "strong" +login_manager.login_view = "auth.login" bootstrap = Bootstrap() oauth = OAuth2Provider() svr = ServerProxy("http://localhost:8080") -r = redis.Redis(host=app.config['REDIS_HOST'],password=app.config['REDIS_PWD']) +r = redis.Redis( + host=app.config["REDIS_HOST"], + password=app.config["REDIS_PWD"], + decode_responses=True, +) try: - notification_type = app.config['NOTIFICATION_TYPE'] + notification_type = app.config["NOTIFICATION_TYPE"] except KeyError: notification_type = "RABBITMQ" try: - firebase_credentials = app.config['FIREBASE_CREDENTIALS'] + firebase_credentials = app.config["FIREBASE_CREDENTIALS"] except KeyError: firebase_credentials = None -def create_app(config_mode): # TODO: remove config_mode - connect(db=app.config['MONGODB_DATABASE'], - host=app.config['MONGODB_HOST'], - port=app.config['MONGODB_PORT'], - username=app.config['MONGODB_USERNAME'], - password=app.config['MONGODB_PWD'], - authentication_source='admin') - +def create_app(config_mode): # TODO: remove config_mode + + connect( + db=app.config["MONGODB_DATABASE"], + host=app.config["MONGODB_HOST"], + port=app.config["MONGODB_PORT"], + username=app.config["MONGODB_USERNAME"], + password=app.config["MONGODB_PWD"], + authentication_source="admin", + ) + login_manager.init_app(app) bootstrap.init_app(app) oauth.init_app(app) from .main import main as main_blueprint + app.register_blueprint(main_blueprint) from .auth import auth as auth_blueprint - app.register_blueprint(auth_blueprint, url_prefix='/auth') + + app.register_blueprint(auth_blueprint, url_prefix="/auth") from .central import central as central_blueprint - app.register_blueprint(central_blueprint, url_prefix='/central') + + app.register_blueprint(central_blueprint, url_prefix="/central") from .rest_api import api as api_blueprint - app.register_blueprint(api_blueprint, url_prefix='/api') + + app.register_blueprint(api_blueprint, url_prefix="/api") from .oauth_bd import oauth_bd as oauth_bd_blueprint - app.register_blueprint(oauth_bd_blueprint, url_prefix='/oauth') + + app.register_blueprint(oauth_bd_blueprint, url_prefix="/oauth") return app diff --git a/buildingdepot/CentralService/app/api_0_0/errors.py b/buildingdepot/CentralService/app/api_0_0/errors.py index 2f91fc37..7bf09b28 100755 --- a/buildingdepot/CentralService/app/api_0_0/errors.py +++ b/buildingdepot/CentralService/app/api_0_0/errors.py @@ -2,36 +2,36 @@ def bad_request(message): - response = jsonify({'error': 'Bad request', 'message': message}) + response = jsonify({"error": "Bad request", "message": message}) response.status_code = 400 return response def unauthorized(message): - response = jsonify({'error': 'Unauthorized', 'message': message}) + response = jsonify({"error": "Unauthorized", "message": message}) response.status_code = 401 return response def forbidden(message): - response = jsonify({'error': 'Forbidden', 'message': message}) + response = jsonify({"error": "Forbidden", "message": message}) response.status_code = 403 return response def not_exist(message): - response = jsonify({'error': 'Not exists', 'message': message}) + response = jsonify({"error": "Not exists", "message": message}) response.status_code = 404 return response def not_allowed(message): - response = jsonify({'error': 'Not allowed', 'message': message}) + response = jsonify({"error": "Not allowed", "message": message}) response.status_code = 405 return response def internal_error(message): - response = jsonify({'error': 'Internal server error', 'message': message}) + response = jsonify({"error": "Internal server error", "message": message}) response.status_code = 500 - return response \ No newline at end of file + return response diff --git a/buildingdepot/CentralService/app/api_0_0/resources/__init__.py b/buildingdepot/CentralService/app/api_0_0/resources/__init__.py index 96d2e319..6d236971 100755 --- a/buildingdepot/CentralService/app/api_0_0/resources/__init__.py +++ b/buildingdepot/CentralService/app/api_0_0/resources/__init__.py @@ -1 +1 @@ -__author__ = 'hp' +__author__ = "hp" diff --git a/buildingdepot/CentralService/app/api_0_0/resources/utils.py b/buildingdepot/CentralService/app/api_0_0/resources/utils.py index cbb4b8be..ef06355d 100755 --- a/buildingdepot/CentralService/app/api_0_0/resources/utils.py +++ b/buildingdepot/CentralService/app/api_0_0/resources/utils.py @@ -1,24 +1,24 @@ +from flask import g, jsonify +from flask_restful import marshal, reqparse from functools import wraps -from flask import g, jsonify -from app.common import PAGE_SIZE +from ..common import PAGE_SIZE from ..errors import not_allowed -from ...rest_api.helper import get_email -from ...rest_api import responses from ...models.cs_models import User -from flask_restful import marshal, reqparse +from ...rest_api import responses +from ...rest_api.helper import get_email def is_super(user): - return user.role.type == 'super' + return user.role.type == "super" def is_local(user): - return user.role.type == 'local' + return user.role.type == "local" def is_default(user): - return user.role.type == 'default' + return user.role.type == "default" def is_managed_by_local(local_admin, user): @@ -26,8 +26,9 @@ def is_managed_by_local(local_admin, user): return True return False + def success(): - response = jsonify({'success': 'True'}) + response = jsonify({"success": "True"}) response.status_code = 200 return response @@ -36,26 +37,25 @@ def page_validator(page_num): try: page_num = int(page_num) if page_num <= 0: - raise ValueError('Please input positive integer number for page number') + raise ValueError("Please input positive integer number for page number") except: - raise ValueError('Please input integer number for page number') + raise ValueError("Please input integer number for page number") return page_num def pagination_get(document_class, res_fields, api_class): parser = reqparse.RequestParser() - parser.add_argument('page', type=page_validator, default=1, location='args') + parser.add_argument("page", type=page_validator, default=1, location="args") - page = parser.parse_args()['page'] - skip_size = (page-1) * PAGE_SIZE + page = parser.parse_args()["page"] + skip_size = (page - 1) * PAGE_SIZE objs = document_class.objects().skip(skip_size).limit(PAGE_SIZE) from ... import api - res = {'data': [marshal(obj, res_fields) for obj in objs]} + + res = {"data": [marshal(obj, res_fields) for obj in objs]} if page > 1: - res['prev'] = api.url_for(api_class, _external=True, page=page-1) - if len(res['data']) == PAGE_SIZE: - res['next'] = api.url_for(api_class, _external=True, page=page+1) + res["prev"] = api.url_for(api_class, _external=True, page=page - 1) + if len(res["data"]) == PAGE_SIZE: + res["next"] = api.url_for(api_class, _external=True, page=page + 1) return res - - diff --git a/buildingdepot/CentralService/app/auth/__init__.py b/buildingdepot/CentralService/app/auth/__init__.py index e54b37dc..8aabdc5d 100755 --- a/buildingdepot/CentralService/app/auth/__init__.py +++ b/buildingdepot/CentralService/app/auth/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -auth = Blueprint('auth', __name__) +auth = Blueprint("auth", __name__) from . import views diff --git a/buildingdepot/CentralService/app/auth/access_control.py b/buildingdepot/CentralService/app/auth/access_control.py index a425d2a2..e106050f 100755 --- a/buildingdepot/CentralService/app/auth/access_control.py +++ b/buildingdepot/CentralService/app/auth/access_control.py @@ -1,11 +1,12 @@ import sys +from flask import request + from .. import r -from ..models.cs_models import * from ..api_0_0.errors import * -from ..rest_api.helper import get_admins, check_if_super, get_ds -from ..rest_api import responses +from ..models.cs_models import * from ..oauth_bd.views import Token -from flask import request +from ..rest_api import responses +from ..rest_api.helper import get_admins, check_if_super, get_ds permissions_val = {"u/d": 1, "r/w/p": 2, "r/w": 3, "r": 4, "d/r": 5} @@ -13,12 +14,12 @@ def super_required(f): def decorated_function(*args, **kwargs): email = get_email() - if r.sismember('superusers', email): + if r.sismember("superusers", email): return f(*args, **kwargs) user = User.objects(email=email).first() - if user.role != 'super': + if user.role != "super": return jsonify(responses.super_user_required) - r.sadd('superusers', email) + r.sadd("superusers", email) return f(*args, **kwargs) return decorated_function @@ -26,27 +27,34 @@ def decorated_function(*args, **kwargs): def authenticate_acl(permission_required): """This is the function that defines the acl's and what level of access - the user has to the specified sensor""" + the user has to the specified sensor""" def authenticate_write(f): def decorated_function(*args, **kwargs): try: - sensor_name = kwargs['name'] + sensor_name = kwargs["name"] except KeyError: - sensor_name = request.get_json()['sensor_id'] + sensor_name = request.get_json()["sensor_id"] # Check what level of access this user has to the sensor response = permission(sensor_name) - if response == 'u/d': + if response == "u/d": if Sensor.objects(name=sensor_name).first() is None: - return jsonify({'success': 'False', - 'error': 'Sensor does not exist'}) + return jsonify( + {"success": "False", "error": "Sensor does not exist"} + ) else: - return jsonify({'success': 'False', 'error': 'Permission not defined'}) + return jsonify( + {"success": "False", "error": "Permission not defined"} + ) elif permissions_val[response] <= permissions_val[permission_required]: return f(*args, **kwargs) else: - return jsonify({'success': 'False', - 'error': 'You are not authenticated for this operation on the sensor'}) + return jsonify( + { + "success": "False", + "error": "You are not authenticated for this operation on the sensor", + } + ) return decorated_function @@ -54,7 +62,8 @@ def decorated_function(*args, **kwargs): def permission(sensor_name, email=None, omit_sensorgroup=None): - if email is None: email = get_email() + if email is None: + email = get_email() # Check if permission already cached current_res = r.hget(sensor_name, email) @@ -63,16 +72,16 @@ def permission(sensor_name, email=None, omit_sensorgroup=None): sensor = Sensor.objects(name=sensor_name).first() if sensor is None: - return 'u/d' + return "u/d" # check if super user if check_if_super(email) or email in get_admins(get_ds(sensor_name)): - return 'r/w/p' + return "r/w/p" # if admin of the sensor's dataservice or owner of sensor then give complete access - if r.get('owner:{}'.format(sensor_name)) == email: - r.hset(sensor_name, email, 'r/w/p') - return 'r/w/p' + if r.get("owner:{}".format(sensor_name)) == email: + r.hset(sensor_name, email, "r/w/p") + return "r/w/p" current_res = check_db(sensor_name, email, omit_sensorgroup=omit_sensorgroup) # cache the latest permission @@ -83,12 +92,12 @@ def permission(sensor_name, email=None, omit_sensorgroup=None): def check_db(sensor, email, omit_sensorgroup=None): sensor_obj = Sensor.objects(name=sensor).first() if sensor_obj.owner == email: - return 'r/w/p' + return "r/w/p" tag_list = [] # Retrieve sensor tags and form search query for Sensor groups - for tag in sensor_obj['tags']: - current_tag = {"name": tag['name'], "value": tag['value']} + for tag in sensor_obj["tags"]: + current_tag = {"name": tag["name"], "value": tag["value"]} tag_list.append(current_tag) args = {} @@ -98,62 +107,70 @@ def check_db(sensor, email, omit_sensorgroup=None): args = {} args["users__user_id"] = email usergroups = UserGroup.objects(**args) - current_res = 'u/d' + current_res = "u/d" # Iterate over all sensor and user group combinations and find # resultant permission for usergroup in usergroups: for sensorgroup in sensorgroups: - if omit_sensorgroup and omit_sensorgroup == sensorgroup['name']: + if omit_sensorgroup and omit_sensorgroup == sensorgroup["name"]: continue # Multiple permissions may exists for the same user and sensor relation. # This one chooses the most restrictive one by counting the number of tags - res = r.hget('permission:{}:{}'.format(usergroup['name'], sensorgroup['name']), "permission") + res = r.hget( + "permission:{}:{}".format(usergroup["name"], sensorgroup["name"]), + "permission", + ) if res is None: - permission_val = Permission.objects(sensor_group=sensorgroup['name'], - user_group=usergroup['name']) + permission_val = Permission.objects( + sensor_group=sensorgroup["name"], user_group=usergroup["name"] + ) if permission_val: - res = permission_val.first()['permission'] - owner_email = r.hget('permission:{}:{}'.format(usergroup['name'], sensorgroup['name']), "owner") - if res is not None and permission(sensor, owner_email) == 'r/w/p': + res = permission_val.first()["permission"] + owner_email = r.hget( + "permission:{}:{}".format(usergroup["name"], sensorgroup["name"]), + "owner", + ) + if res is not None and permission(sensor, owner_email) == "r/w/p": if permissions_val[res] > permissions_val[current_res]: current_res = res return current_res def authorize_user(user_group, sensorgroup_name, email=None): - if email is None: email = get_email() + if email is None: + email = get_email() sensor_group = SensorGroup.objects(name=sensorgroup_name).first() tag_list = [] - for tag in sensor_group['tags']: - current_tag = {"name": tag['name'], "value": tag['value']} + for tag in sensor_group["tags"]: + current_tag = {"name": tag["name"], "value": tag["value"]} tag_list.append(current_tag) args = {} - args['building'] = sensor_group['building'] - args['tags__all'] = tag_list + args["building"] = sensor_group["building"] + args["tags__all"] = tag_list sensors = Sensor.objects(**args) for sensor in sensors: - print sensor['name'] - if permission(sensor['name'], email) != 'r/w/p': + print(sensor["name"]) + if permission(sensor["name"], email) != "r/w/p": return False return True def authorize_addition(usergroup_name, email): user_group = UserGroup.objects(name=usergroup_name).first() - if user_group['owner'] == email: + if user_group["owner"] == email: return True for user in user_group.users: - print type(user['manager']) - if user['user_id'] == email and user['manager']: + print(type(user["manager"])) + if user["user_id"] == email and user["manager"]: return True return False def get_email(): headers = request.headers - token = headers['Authorization'][7:] - user = r.get(''.join(['oauth:', token])) + token = headers["Authorization"][7:] + user = r.get("".join(["oauth:", token])) if user: return user token = Token.objects(access_token=token).first() @@ -167,13 +184,13 @@ def permission_allowed(sensorgroup, email): args = {} tag_list = [] # Retrieve sensor tags and form search query for Sensors - for tag in sensorgroup_obj['tags']: - current_tag = {"name": tag['name'], "value": tag['value']} + for tag in sensorgroup_obj["tags"]: + current_tag = {"name": tag["name"], "value": tag["value"]} tag_list.append(current_tag) args["tags__size"] = len(tag_list) args["tags__all"] = tag_list sensors = Sensor.objects(**args) for sensor in sensors: - if permission(sensor.name, email, sensorgroup) == 'r/w/p': + if permission(sensor.name, email, sensorgroup) == "r/w/p": return True - return False \ No newline at end of file + return False diff --git a/buildingdepot/CentralService/app/auth/acl_cache.py b/buildingdepot/CentralService/app/auth/acl_cache.py index 6d7d37c0..7687d4f7 100755 --- a/buildingdepot/CentralService/app/auth/acl_cache.py +++ b/buildingdepot/CentralService/app/auth/acl_cache.py @@ -1,43 +1,40 @@ from .. import r from ..models.cs_models import * + def invalidate_permission(sensorgroup): - """ Takes the name of a sensor group and invalidates all the sensors - in redis that belong to this sensor group""" - sg_tags = SensorGroup.objects(name=sensorgroup).first()['tags'] + """Takes the name of a sensor group and invalidates all the sensors + in redis that belong to this sensor group""" + sg_tags = SensorGroup.objects(name=sensorgroup).first()["tags"] collection = Sensor._get_collection().find(form_query(sg_tags)) try: pipe = r.pipeline() for sensor in collection: - emails = list(r.hgetall(sensor.get('name')).keys()) - pipe.hdel(sensor.get('name'), emails) + pipe.delete(sensor.get("name")) pipe.execute() except Exception as e: - print (e) + print(e) + -def invalidate_user(usergroup,email): +def invalidate_user(usergroup, email): """Takes the id of the user that made the request and invalidates - all the entries for this user (in redis) in every sensor in the - sensor_group""" + all the entries for this user (in redis) in every sensor in the + sensor_group""" pipe = r.pipeline() permissions = Permission.objects(user_group=usergroup) for permission in permissions: - sg_tags = SensorGroup.objects(name=permission['sensor_group']).first()['tags'] + sg_tags = SensorGroup.objects(name=permission["sensor_group"]).first()["tags"] collection = Sensor._get_collection().find(form_query(sg_tags)) for sensor in collection: - pipe.hdel(sensor.get('name'),email) + pipe.hdel(sensor.get("name"), email) pipe.execute() + def form_query(values): res = [] args = {} for tag in values: - current_tag = {"tags.name": tag['name'], "tags.value": tag['value']} + current_tag = {"tags.name": tag["name"], "tags.value": tag["value"]} res.append(current_tag) args["$and"] = res return args - - - - - diff --git a/buildingdepot/CentralService/app/auth/forms.py b/buildingdepot/CentralService/app/auth/forms.py index 10a2f764..c249b714 100755 --- a/buildingdepot/CentralService/app/auth/forms.py +++ b/buildingdepot/CentralService/app/auth/forms.py @@ -5,30 +5,36 @@ Contains all the forms for the CentralService authorization functions. The two forms that are used are for login and for creating a new user. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -from flask_wtf import Form +from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField -from wtforms.validators import DataRequired, Email, Length, EqualTo from wtforms import ValidationError +from wtforms.validators import DataRequired, Email, Length, EqualTo + from ..models.cs_models import User -class LoginForm(Form): - email = StringField('Email', validators=[DataRequired(), Email()]) - password = PasswordField('Password', validators=[DataRequired()]) - remember_me = BooleanField('Keep me logged in') - submit = SubmitField('Log In') +class LoginForm(FlaskForm): + email = StringField("Email", validators=[DataRequired(), Email()]) + password = PasswordField("Password", validators=[DataRequired()]) + remember_me = BooleanField("Keep me logged in") + submit = SubmitField("Log In") -class RegistrationForm(Form): - password = PasswordField('Password', - validators=[DataRequired(), EqualTo('password2', message='Passwords must match')]) - password2 = PasswordField('Confirm password', validators=[DataRequired()]) - submit = SubmitField('Register') + +class RegistrationForm(FlaskForm): + password = PasswordField( + "Password", + validators=[ + DataRequired(), + EqualTo("password2", message="Passwords must match"), + ], + ) + password2 = PasswordField("Confirm password", validators=[DataRequired()]) + submit = SubmitField("Register") def validate_email(self, field): if User.objects(email=field.data).first() is not None: - raise ValidationError('Email already registered.') - + raise ValidationError("Email already registered.") diff --git a/buildingdepot/CentralService/app/auth/views.py b/buildingdepot/CentralService/app/auth/views.py index 3de17e27..adace4dc 100755 --- a/buildingdepot/CentralService/app/auth/views.py +++ b/buildingdepot/CentralService/app/auth/views.py @@ -6,21 +6,23 @@ The functionalities available are for registering a new user,logging in and logging out. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -import sys, os, binascii +import binascii +import os +import sys +from datetime import datetime, timedelta from flask import render_template, redirect, request from flask import session, url_for, flash, make_response -from . import auth -from datetime import datetime, timedelta from flask_login import login_user, login_required, logout_user +from werkzeug.security import generate_password_hash, gen_salt + +from . import auth from .forms import LoginForm, RegistrationForm -from ..models.cs_models import * -from werkzeug.security import generate_password_hash,gen_salt from .. import r - +from ..models.cs_models import * class Client(Document): @@ -32,7 +34,7 @@ class Client(Document): @property def client_type(self): - return 'public' + return "public" @property def redirect_uris(self): @@ -72,9 +74,9 @@ def token_gen(client_id, client_secret): client = Client.objects(client_id=client_id, client_secret=client_secret).first() if client is not None: toks = Token.objects(client=client) - previous_tokens = ['oauth'] + previous_tokens = ["oauth"] for t in toks: - previous_tokens.append(''.join(['oauth:', t.access_token])) + previous_tokens.append("".join(["oauth:", t.access_token])) t.delete() r.delete(*previous_tokens) # Set token expiry period and create it @@ -83,54 +85,66 @@ def token_gen(client_id, client_secret): tok = Token( access_token=str(binascii.hexlify(os.urandom(16))), refresh_token=str(binascii.hexlify(os.urandom(16))), - token_type='Bearer', - _scopes='email', + token_type="Bearer", + _scopes="email", expires=expires, client=client, user=client.user, - email=client.user).save() - r.setex(''.join(['oauth:', tok.access_token]), client.user, expires_in) + email=client.user, + ).save() + r.setex("".join(["oauth:", tok.access_token]), expires_in, client.user) return tok.access_token def oauth_gen(email): keys = [{"client_id": gen_salt(40), "client_secret": gen_salt(50)}] item = Client( - client_id=keys[0]['client_id'], - client_secret=keys[0]['client_secret'], - _redirect_uris=' '.join([ - 'http://localhost:8000/authorized', - 'http://127.0.0.1:8000/authorized', - 'http://127.0.1:8000/authorized', - 'http://127.1:8000/authorized']), - _default_scopes='email', - user=email).save() - token = token_gen(keys[0]['client_id'], keys[0]['client_secret']) + client_id=keys[0]["client_id"], + client_secret=keys[0]["client_secret"], + _redirect_uris=" ".join( + [ + "http://localhost:8000/authorized", + "http://127.0.0.1:8000/authorized", + "http://127.0.1:8000/authorized", + "http://127.1:8000/authorized", + ] + ), + _default_scopes="email", + user=email, + ).save() + token = token_gen(keys[0]["client_id"], keys[0]["client_secret"]) return token -@auth.route('/register', methods=['GET', 'POST']) +@auth.route("/register", methods=["GET", "POST"]) def register(): """Register a new user""" form = RegistrationForm() if form.validate_on_submit(): - user = User.objects(email=session['email']).first() - if user.update(set__password=generate_password_hash(form.password.data), set__first_login=False): - flash('Your Password is successfully reset. You can now login.') - return redirect(url_for('auth.login')) - return render_template('auth/register.html', form=form) + user = User.objects(email=session["email"]).first() + if user.update( + set__password=generate_password_hash(form.password.data), + set__first_login=False, + ): + flash("Your Password is successfully reset. You can now login.") + return redirect(url_for("auth.login")) + return render_template("auth/register.html", form=form) -@auth.route('/login', methods=['GET', 'POST']) +@auth.route("/login", methods=["GET", "POST"]) def login(): """Login a user if credentials are valid""" form = LoginForm() if form.validate_on_submit(): user = User.objects(email=form.email.data).first() - if user is not None and user.first_login and user.verify_password(form.password.data): - flash('You have logged in for the first time. Create a new password') - session['email'] = form.email.data - return redirect(url_for('auth.register')) + if ( + user is not None + and user.first_login + and user.verify_password(form.password.data) + ): + flash("You have logged in for the first time. Create a new password") + session["email"] = form.email.data + return redirect(url_for("auth.register")) elif user is not None and user.verify_password(form.password.data): if len(Client.objects(user=form.email.data)) > 0: clientkeys = Client.objects(user=form.email.data).first() @@ -138,21 +152,24 @@ def login(): else: token = oauth_gen(form.email.data) login_user(user, form.remember_me.data) - session['email'] = form.email.data - session['token'] = token - session['headers'] = {'Authorization': 'Bearer ' + session['token'], 'Content-Type': 'application/json'} - resp = make_response(redirect(url_for('central.sensor'))) - resp.set_cookie('access_token', value=token) + session["email"] = form.email.data + session["token"] = token + session["headers"] = { + "Authorization": "Bearer " + session["token"], + "Content-Type": "application/json", + } + resp = make_response(redirect(url_for("central.sensor"))) + resp.set_cookie("access_token", value=token) return resp - #return redirect(request.args.get('next') or url_for('main.index')) - flash('Invalid email or password') - return render_template('auth/login.html', form=form) + # return redirect(request.args.get('next') or url_for('main.index')) + flash("Invalid email or password") + return render_template("auth/login.html", form=form) -@auth.route('/logout') +@auth.route("/logout") @login_required def logout(): """ Logout user""" logout_user() - flash('You have been logged out') - return redirect(url_for('main.index')) + flash("You have been logged out") + return redirect(url_for("main.index")) diff --git a/buildingdepot/CentralService/app/central/__init__.py b/buildingdepot/CentralService/app/central/__init__.py index 54d3d7cb..0eae1e5a 100755 --- a/buildingdepot/CentralService/app/central/__init__.py +++ b/buildingdepot/CentralService/app/central/__init__.py @@ -1,6 +1,5 @@ from flask import Blueprint -central = Blueprint('central', __name__) +central = Blueprint("central", __name__) from . import views - diff --git a/buildingdepot/CentralService/app/central/forms.py b/buildingdepot/CentralService/app/central/forms.py index 9c3cb0fe..c84e7692 100755 --- a/buildingdepot/CentralService/app/central/forms.py +++ b/buildingdepot/CentralService/app/central/forms.py @@ -6,81 +6,96 @@ in the frontend of the CentralService such as the form to create new buildings, tags,dataserivces etc. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -from flask_wtf import Form -from wtforms import StringField, SelectMultipleField, SubmitField, SelectField, BooleanField +from flask_wtf import FlaskForm +from wtforms import ( + StringField, + SelectMultipleField, + SubmitField, + SelectField, + BooleanField, +) from wtforms.validators import DataRequired -class TagTypeForm(Form): - name = StringField('Name', validators=[DataRequired()]) - description = StringField('Description') - parents = SelectMultipleField('Parents') - submit = SubmitField('Submit') +class TagTypeForm(FlaskForm): + name = StringField("Name", validators=[DataRequired()]) + description = StringField("Description") + parents = SelectMultipleField("Parents") + submit = SubmitField("Submit") -class BuildingTemplateForm(Form): - name = StringField('Name', validators=[DataRequired()]) - description = StringField('Description') - tag_types = SelectMultipleField('Tag types') - submit = SubmitField('Submit') +class BuildingTemplateForm(FlaskForm): + name = StringField("Name", validators=[DataRequired()]) + description = StringField("Description") + tag_types = SelectMultipleField("Tag types") + submit = SubmitField("Submit") -class BuildingForm(Form): - name = StringField('Name', validators=[DataRequired()]) - description = StringField('Description') - template = SelectField('Template', validators=[DataRequired()]) - submit = SubmitField('Submit') +class BuildingForm(FlaskForm): + name = StringField("Name", validators=[DataRequired()]) + description = StringField("Description") + template = SelectField("Template", validators=[DataRequired()]) + submit = SubmitField("Submit") -class RoleForm(Form): - name = StringField('Name', validators=[DataRequired()]) - description = StringField('Description') - permission = SelectField('Permission', choices=[('r', 'r'), ('r/w', 'r/w')], validators=[DataRequired()]) - type = SelectField('Type', - choices=[('default', 'default'), ('local', 'local'), ('super', 'super')], - validators=[DataRequired()]) - submit = SubmitField('Submit') +class RoleForm(FlaskForm): + name = StringField("Name", validators=[DataRequired()]) + description = StringField("Description") + permission = SelectField( + "Permission", choices=[("r", "r"), ("r/w", "r/w")], validators=[DataRequired()] + ) + type = SelectField( + "Type", + choices=[("default", "default"), ("local", "local"), ("super", "super")], + validators=[DataRequired()], + ) + submit = SubmitField("Submit") -class DataServiceForm(Form): - name = StringField('Name', validators=[DataRequired()]) - description = StringField('Description') - host = StringField('Host', validators=[DataRequired()]) - port = StringField('Port', validators=[DataRequired()]) - submit = SubmitField('Submit') +class DataServiceForm(FlaskForm): + name = StringField("Name", validators=[DataRequired()]) + description = StringField("Description") + host = StringField("Host", validators=[DataRequired()]) + port = StringField("Port", validators=[DataRequired()]) + submit = SubmitField("Submit") -class SensorForm(Form): - source_name = StringField('Source Name') - source_identifier = StringField('Source Identifier') - building = SelectField('Building', validators=[DataRequired()]) - submit = SubmitField('Submit') +class SensorForm(FlaskForm): + source_name = StringField("Source Name") + source_identifier = StringField("Source Identifier") + building = SelectField("Building", validators=[DataRequired()]) + submit = SubmitField("Submit") -class SensorGroupForm(Form): - name = StringField('Name', validators=[DataRequired()]) - description = StringField('Description') - building = SelectField('Building', validators=[DataRequired()]) - submit = SubmitField('Submit') +class SensorGroupForm(FlaskForm): + name = StringField("Name", validators=[DataRequired()]) + description = StringField("Description") + building = SelectField("Building", validators=[DataRequired()]) + submit = SubmitField("Submit") -class UserGroupForm(Form): - name = StringField('Name', validators=[DataRequired()]) - description = StringField('Description') - submit = SubmitField('Submit') +class UserGroupForm(FlaskForm): + name = StringField("Name", validators=[DataRequired()]) + description = StringField("Description") + submit = SubmitField("Submit") -class PermissionForm(Form): - user_group = SelectField('User Group', validators=[DataRequired()]) - sensor_group = SelectField('Sensor Group', validators=[DataRequired()]) - permission = SelectField('Building', choices=[('r', 'r'),('r/w', 'r/w'),('d/r','d/r'),('r/w/p', 'r/w/p')], validators=[DataRequired()]) - submit = SubmitField('Submit') +class PermissionForm(FlaskForm): + user_group = SelectField("User Group", validators=[DataRequired()]) + sensor_group = SelectField("Sensor Group", validators=[DataRequired()]) + permission = SelectField( + "Building", + choices=[("r", "r"), ("r/w", "r/w"), ("d/r", "d/r"), ("r/w/p", "r/w/p")], + validators=[DataRequired()], + ) + submit = SubmitField("Submit") -class PermissionQueryForm(Form): - user = StringField('User Email', validators=[DataRequired()]) - sensor = StringField('Sensor ID', validators=[DataRequired()]) - submit = SubmitField('Submit') + +class PermissionQueryForm(FlaskForm): + user = StringField("User Email", validators=[DataRequired()]) + sensor = StringField("Sensor ID", validators=[DataRequired()]) + submit = SubmitField("Submit") diff --git a/buildingdepot/CentralService/app/central/utils.py b/buildingdepot/CentralService/app/central/utils.py index 4b919a40..a4418bef 100755 --- a/buildingdepot/CentralService/app/central/utils.py +++ b/buildingdepot/CentralService/app/central/utils.py @@ -5,8 +5,8 @@ Contains the definitions for all helper functions that are used by the main CentralService. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from ..models.cs_models import TagType @@ -14,8 +14,10 @@ def get_choices(cls): """Given a class type retrieve all its instances from MongoDB""" - names = [obj['name'] for obj in cls._get_collection().find({}, {'_id': 0, 'name': 1})] - return zip(names, names) + names = [ + obj["name"] for obj in cls._get_collection().find({}, {"_id": 0, "name": 1}) + ] + return list(zip(names, names)) graph = {tag_type.name: tag_type.children for tag_type in TagType.objects} @@ -33,4 +35,3 @@ def get_tag_descendant_pairs(): """ For a given tag get all it descendants""" res = {name: get_all_descendants(name) for name in graph} return res - diff --git a/buildingdepot/CentralService/app/central/views.py b/buildingdepot/CentralService/app/central/views.py index 807a2e2b..6f91e1b9 100755 --- a/buildingdepot/CentralService/app/central/views.py +++ b/buildingdepot/CentralService/app/central/views.py @@ -9,40 +9,47 @@ For example opening up http://localhost:81/central/tagtype on your installation of BD will call the tagtype() function -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -import json,requests,math -from app.common import PAGE_SIZE -from uuid import uuid4 +import json +import math +import requests from flask import render_template, request, redirect, url_for, jsonify, session, flash from flask_login import login_required +from uuid import uuid4 from werkzeug.security import generate_password_hash, gen_salt -from .. import r from . import central from .forms import * -from ..rpc import defs -from ..models.cs_models import * from .utils import get_choices, get_tag_descendant_pairs -from ..oauth_bd.views import Client -from ..rest_api.helper import check_if_super,get_building_choices -from ..rest_api.helper import validate_users,form_query -from ..auth.access_control import authorize_addition,permission, permission_allowed +from .. import r +from ..auth.access_control import authorize_addition, permission, permission_allowed from ..auth.acl_cache import invalidate_permission +from ..common import PAGE_SIZE +from ..models.cs_models import * +from ..oauth_bd.views import Client +from ..rest_api.helper import check_if_super, get_building_choices +from ..rest_api.helper import validate_users, form_query +from ..rpc import defs + -@central.route('/tagtype', methods=['GET', 'POST']) +@central.route("/tagtype", methods=["GET", "POST"]) @login_required def tagtype(): """Returns the list of TagTypes currently present in the system and adds a new - type if the form is submitted and succesfully validated""" - page = request.args.get('page', 1, type=int) + type if the form is submitted and succesfully validated""" + page = request.args.get("page", 1, type=int) skip_size = (page - 1) * PAGE_SIZE objs = TagType.objects().skip(skip_size).limit(PAGE_SIZE) # Can be deleted only if no child has a dependency on it for obj in objs: - if not obj.children and BuildingTemplate._get_collection().find({'tag_types': obj.name}).count() == 0: + if ( + not obj.children + and BuildingTemplate.objects(tag_types=obj.name).count() + == 0 + ): obj.can_delete = True else: obj.can_delete = False @@ -50,36 +57,43 @@ def tagtype(): form.parents.choices = get_choices(TagType) if form.validate_on_submit(): # Create the tag - payload = {'data': { - "name": str(form.name.data), - "description": str(form.description.data), - "parents": [str(parent) for parent in form.parents.data], - "acl_tag": check_if_super(session['email']) - }} - res = requests.post(request.url_root + "api/tagtype", data=json.dumps(payload), - headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.tagtype')) - return render_template('central/tagtype.html', objs=objs, form=form) - - -@central.route('/tagtype_delete', methods=['POST']) + payload = { + "data": { + "name": str(form.name.data), + "description": str(form.description.data), + "parents": [str(parent) for parent in form.parents.data], + "acl_tag": check_if_super(session["email"]), + } + } + res = requests.post( + request.url_root + "api/tagtype", + data=json.dumps(payload), + headers=session["headers"], + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.tagtype")) + return render_template("central/tagtype.html", objs=objs, form=form) + + +@central.route("/tagtype_delete", methods=["POST"]) @login_required def tagtype_delete(): """Deletes a tag""" - name = request.form.get('name') - res = requests.delete(request.url_root + "api/tagtype/" + name, headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.tagtype')) + name = request.form.get("name") + res = requests.delete( + request.url_root + "api/tagtype/" + name, headers=session["headers"] + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.tagtype")) -@central.route('/buildingtemplate', methods=['GET', 'POST']) +@central.route("/buildingtemplate", methods=["GET", "POST"]) @login_required def buildingtemplate(): """Create a buildingtemplate or retrieve the list of the current ones""" - page = request.args.get('page', 1, type=int) + page = request.args.get("page", 1, type=int) skip_size = (page - 1) * PAGE_SIZE objs = BuildingTemplate.objects().skip(skip_size).limit(PAGE_SIZE) # If there are buildings using this template then mark as cannot be deleted @@ -88,43 +102,50 @@ def buildingtemplate(): obj.can_delete = False else: obj.can_delete = True - obj.tag_types = map(str, obj.tag_types) + obj.tag_types = list(map(str, obj.tag_types)) form = BuildingTemplateForm() # Get list of tags that this building can use form.tag_types.choices = get_choices(TagType) if form.validate_on_submit(): - payload = {'data': { - "name": str(form.name.data), - "description": str(form.description.data), - "tag_types": form.tag_types.data - }} - res = requests.post(request.url_root + "api/template", data=json.dumps(payload), - headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.buildingtemplate')) - return render_template('central/buildingtemplate.html', objs=objs, form=form) - - -@central.route('/buildingtemplate_delete', methods=['POST']) + payload = { + "data": { + "name": str(form.name.data), + "description": str(form.description.data), + "tag_types": form.tag_types.data, + } + } + res = requests.post( + request.url_root + "api/template", + data=json.dumps(payload), + headers=session["headers"], + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.buildingtemplate")) + return render_template("central/buildingtemplate.html", objs=objs, form=form) + + +@central.route("/buildingtemplate_delete", methods=["POST"]) @login_required def buildingtemplate_delete(): - name = request.form.get('name') - res = requests.delete(request.url_root + "api/template/" + name, headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.buildingtemplate')) + name = request.form.get("name") + res = requests.delete( + request.url_root + "api/template/" + name, headers=session["headers"] + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.buildingtemplate")) -@central.route('/building', methods=['GET', 'POST']) +@central.route("/building", methods=["GET", "POST"]) @login_required def building(): """Create a new building or retrive the list of buildings currently in - the system""" - page = request.args.get('page', 1, type=int) + the system""" + page = request.args.get("page", 1, type=int) skip_size = (page - 1) * PAGE_SIZE - objs = Building.objects[skip_size:skip_size+PAGE_SIZE] - #objs = Building.objects().skip(skip_size).limit(PAGE_SIZE) + objs = Building.objects[skip_size : skip_size + PAGE_SIZE] + # objs = Building.objects().skip(skip_size).limit(PAGE_SIZE) # If the building doesn't have any tags associated to it then mark # it as can be deleted for obj in objs: @@ -136,80 +157,94 @@ def building(): form.template.choices = get_choices(BuildingTemplate) if form.validate_on_submit(): # Create the building - payload = {'data': { - "name": str(form.name.data), - "description": str(form.description.data), - "template": form.template.data - }} - res = requests.post(request.url_root + "api/building", data=json.dumps(payload), - headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.building')) - return render_template('central/building.html', objs=objs, form=form) - - -@central.route('/building_delete', methods=['POST']) + payload = { + "data": { + "name": str(form.name.data), + "description": str(form.description.data), + "template": form.template.data, + } + } + res = requests.post( + request.url_root + "api/building", + data=json.dumps(payload), + headers=session["headers"], + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.building")) + return render_template("central/building.html", objs=objs, form=form) + + +@central.route("/building_delete", methods=["POST"]) @login_required def building_delete(): - name = request.form.get('name') - res = requests.delete(request.url_root + "api/building/" + name, headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.building')) + name = request.form.get("name") + res = requests.delete( + request.url_root + "api/building/" + name, headers=session["headers"] + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.building")) # api -@central.route('/building//metadata', methods=['GET', 'POST']) +@central.route("/building//metadata", methods=["GET", "POST"]) @login_required def building_metadata(name): """If the request is a GET then retrieve the metadata associated with it. If it is a POST - then update the metadata""" - if request.method == 'GET': - metadata = Building._get_collection().find({'name': name}, {'metadata': 1, '_id': 0})[0]['metadata'] - metadata = [{'name': key, 'value': val} for key, val in metadata.iteritems()] - return jsonify({'data': metadata}) + then update the metadata""" + if request.method == "GET": + metadata = Building._get_collection().find( + {"name": name}, {"metadata": 1, "_id": 0} + )[0]["metadata"] + metadata = [{"name": key, "value": val} for key, val in list(metadata.items())] + return jsonify({"data": metadata}) else: # Update the metadata - metadata = {pair['name']: pair['value'] for pair in request.get_json()['data']} + metadata = {pair["name"]: pair["value"] for pair in request.get_json()["data"]} Building.objects(name=name).update(set__metadata=metadata) - return jsonify({'success': 'True'}) + return jsonify({"success": "True"}) -@central.route('/building//tags', methods=['GET']) +@central.route("/building//tags", methods=["GET"]) @login_required def building_tags(building_name): - return render_template('central/buildingtags.html', building_name=building_name) + return render_template("central/buildingtags.html", building_name=building_name) -@central.route('/building//tags_delete', methods=['POST']) +@central.route("/building//tags_delete", methods=["POST"]) @login_required def building_tags_delete(building_name): """Delete specific tags associated with the building""" - data = {'data': { - 'name': request.form.get('tag_name'), - 'value': request.form.get('tag_value') - }} - res = requests.delete(request.url_root + "api/building/" + building_name + "/tags", data=json.dumps(data), - headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.building_tags', building_name=building_name)) - - -@central.route('/user', methods=['GET']) + data = { + "data": { + "name": request.form.get("tag_name"), + "value": request.form.get("tag_value"), + } + } + res = requests.delete( + request.url_root + "api/building/" + building_name + "/tags", + data=json.dumps(data), + headers=session["headers"], + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.building_tags", building_name=building_name)) + + +@central.route("/user", methods=["GET"]) @login_required def user(): - default = User.objects(role='default') - super = User.objects(role='super') - return render_template('central/user.html', super_user=super, default=default) + default = User.objects(role="default") + super = User.objects(role="super") + return render_template("central/user.html", super_user=super, default=default) -@central.route('/user//tags_owned', methods=['GET', 'POST']) +@central.route("/user//tags_owned", methods=["GET", "POST"]) @login_required def user_tags_owned(email): """Retrieve or update the list of tags associated with this user""" - if request.method == 'GET': + if request.method == "GET": user = User.objects(email=email).first() buildings = user.buildings data = {} @@ -217,29 +252,36 @@ def user_tags_owned(email): # building specific tags for building in buildings: tags = Building._get_collection().find( - {'name': building}, {'tags.name': 1, 'tags.value': 1, '_id': 0})[0]['tags'] + {"name": building}, {"tags.name": 1, "tags.value": 1, "_id": 0} + )[0]["tags"] sub = {} for tag in tags: - if tag['name'] in sub: - sub[tag['name']].append(tag['value']) + if tag["name"] in sub: + sub[tag["name"]].append(tag["value"]) else: - sub[tag['name']] = [tag['value']] + sub[tag["name"]] = [tag["value"]] data[building] = sub # Form a list of dicts containing the building name and the tags which the user has for # that building - triples = [{'building': item.building, - 'tags': [{'name': elem.name, 'value': elem.value} for elem in item.tags]} - for item in user.tags_owned] - print triples, data - return jsonify({'data': data, 'triples': triples}) + triples = [ + { + "building": item.building, + "tags": [ + {"name": elem.name, "value": elem.value} for elem in item.tags + ], + } + for item in user.tags_owned + ] + print((triples, data)) + return jsonify({"data": data, "triples": triples}) else: - tags_owned = request.get_json()['data'] + tags_owned = request.get_json()["data"] # Update the tags in MongoDB User.objects(email=email).update(set__tags_owned=tags_owned) - return jsonify({'success': 'True'}) + return jsonify({"success": "True"}) -@central.route('/dataservice', methods=['GET', 'POST']) +@central.route("/dataservice", methods=["GET", "POST"]) @login_required def dataservice(): """Create a new DataService""" @@ -249,96 +291,116 @@ def dataservice(): form = DataServiceForm() if form.validate_on_submit(): # Create the DataService - payload = {'data': { - "name": str(form.name.data), - "description": str(form.description.data), - "host": str(form.host.data), - "port": str(form.port.data) - }} - res = requests.post(request.url_root + "api/dataservice", data=json.dumps(payload), - headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.dataservice')) - return render_template('central/dataservice.html', objs=objs, form=form) - - -@central.route('/dataservice//buildings', methods=['GET', 'POST']) + payload = { + "data": { + "name": str(form.name.data), + "description": str(form.description.data), + "host": str(form.host.data), + "port": str(form.port.data), + } + } + res = requests.post( + request.url_root + "api/dataservice", + data=json.dumps(payload), + headers=session["headers"], + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.dataservice")) + return render_template("central/dataservice.html", objs=objs, form=form) + + +@central.route("/dataservice//buildings", methods=["GET", "POST"]) @login_required def dataservice_buildings(name): """Retreive or update the list of buildings that are attached to this DataService""" - if request.method == 'GET': - buildings = DataService._get_collection().find({'name': name}, {'buildings': 1, '_id': 0})[0]['buildings'] + if request.method == "GET": + buildings = DataService._get_collection().find( + {"name": name}, {"buildings": 1, "_id": 0} + )[0]["buildings"] building_names = [building.name for building in Building.objects] - return jsonify({'buildings': buildings, 'building_names': building_names}) + return jsonify({"buildings": buildings, "building_names": building_names}) else: - DataService.objects(name=name).update(set__buildings=request.get_json()['data']) - return jsonify({'success': 'True'}) + DataService.objects(name=name).update(set__buildings=request.get_json()["data"]) + return jsonify({"success": "True"}) -@central.route('/dataservice//admins', methods=['GET', 'POST']) +@central.route("/dataservice//admins", methods=["GET", "POST"]) @login_required def dataservice_admins(name): """ Retrieve or update the list of admins for this DataService""" - if request.method == 'GET': - admins = DataService._get_collection().find({'name': name}, {'admins': 1, '_id': 0})[0]['admins'] + if request.method == "GET": + admins = DataService._get_collection().find( + {"name": name}, {"admins": 1, "_id": 0} + )[0]["admins"] user_emails = [user.email for user in User.objects] - return jsonify({'admins': admins, 'user_emails': user_emails}) + return jsonify({"admins": admins, "user_emails": user_emails}) else: - DataService.objects(name=name).update(set__admins=request.get_json()['data']) - return jsonify({'success': 'True'}) + DataService.objects(name=name).update(set__admins=request.get_json()["data"]) + return jsonify({"success": "True"}) -@central.route('/dataservice_delete', methods=['POST']) +@central.route("/dataservice_delete", methods=["POST"]) @login_required def dataservice_delete(): - name = request.form.get('name') - res = requests.delete(request.url_root + "api/dataservice/" + name, headers=session['headers']).json() - if res['success'] == 'False': - flash(res['error']) - return redirect(url_for('central.dataservice')) + name = request.form.get("name") + res = requests.delete( + request.url_root + "api/dataservice/" + name, headers=session["headers"] + ).json() + if res["success"] == "False": + flash(res["error"]) + return redirect(url_for("central.dataservice")) -@central.route('/oauth_gen', methods=['GET', 'POST']) +@central.route("/oauth_gen", methods=["GET", "POST"]) @login_required def oauth_gen(): keys = [] """If a post request is made then generate a client id and secret key that the user can use later to generate an OAuth token""" - if request.method == 'POST': + if request.method == "POST": keys.append({"client_id": gen_salt(40), "client_secret": gen_salt(50)}) item = Client( - client_id=keys[0]['client_id'], - client_secret=keys[0]['client_secret'], - _redirect_uris=' '.join([ - 'http://localhost:8000/authorized', - 'http://127.0.0.1:8000/authorized', - 'http://127.0.1:8000/authorized', - 'http://127.1:8000/authorized']), - _default_scopes='email', - user=request.form.get('name')).save() - clientkeys = Client.objects(user=session['email']) - return render_template('central/oauth_gen.html', keys=clientkeys) - - -@central.route('/oauth_delete', methods=['POST']) + client_id=keys[0]["client_id"], + client_secret=keys[0]["client_secret"], + _redirect_uris=" ".join( + [ + "http://localhost:8000/authorized", + "http://127.0.0.1:8000/authorized", + "http://127.0.1:8000/authorized", + "http://127.1:8000/authorized", + ] + ), + _default_scopes="email", + user=request.form.get("name"), + ).save() + clientkeys = Client.objects(user=session["email"]) + return render_template("central/oauth_gen.html", keys=clientkeys) + + +@central.route("/oauth_delete", methods=["POST"]) @login_required def oauth_delete(): - if request.method == 'POST': - Client.objects(client_id=request.form.get('client_id')).delete() - return redirect(url_for('central.oauth_gen')) + if request.method == "POST": + Client.objects(client_id=request.form.get("client_id")).delete() + return redirect(url_for("central.oauth_gen")) + -@central.route('/sensor', methods=['GET', 'POST']) +@central.route("/sensor", methods=["GET", "POST"]) @login_required def sensor(): # Show the user PAGE_SIZE number of sensors on each page - page = request.args.get('page', 1, type=int) + page = request.args.get("page", 1, type=int) skip_size = (page - 1) * PAGE_SIZE - objs = Sensor.objects(source_identifier__ne='SensorView').skip(skip_size).limit(PAGE_SIZE) + objs = ( + Sensor.objects(source_identifier__ne="SensorView") + .skip(skip_size) + .limit(PAGE_SIZE) + ) for obj in objs: - obj.can_delete = not r.get('parent:{}'.format(obj.name)) - total = Sensor.objects(source_identifier__ne='SensorView').count() - if (total): + obj.can_delete = not r.get("parent:{}".format(obj.name)) + total = Sensor.objects(source_identifier__ne="SensorView").count() + if total: pages = int(math.ceil(float(total) / PAGE_SIZE)) else: pages = 0 @@ -348,48 +410,58 @@ def sensor(): # Create a Sensor if form.validate_on_submit(): uuid = str(uuid4()) - if defs.create_sensor(uuid,session['email'],form.building.data): - Sensor(name=uuid, - source_name=str(form.source_name.data), - source_identifier=str(form.source_identifier.data), - building=str(form.building.data), - owner=session['email']).save() - r.set('owner:{}'.format(uuid), session['email']) - return redirect(url_for('central.sensor')) + if defs.create_sensor(uuid, session["email"], form.building.data): + Sensor( + name=uuid, + source_name=str(form.source_name.data), + source_identifier=str(form.source_identifier.data), + building=str(form.building.data), + owner=session["email"], + ).save() + r.set("owner:{}".format(uuid), session["email"]) + return redirect(url_for("central.sensor")) else: - flash('Unable to communicate with the DataService') - return render_template('central/sensor.html', objs=objs, form=form, total=total, - pages=pages, current_page=page, pagesize=PAGE_SIZE) - - -@central.route('/sensor_delete', methods=['POST']) + flash("Unable to communicate with the DataService") + return render_template( + "central/sensor.html", + objs=objs, + form=form, + total=total, + pages=pages, + current_page=page, + pagesize=PAGE_SIZE, + ) + + +@central.route("/sensor_delete", methods=["POST"]) @login_required def sensor_delete(): - sensor = Sensor.objects(name=request.form.get('name')).first() - if r.get('parent:{}'.format(request.form.get('name'))): - flash('Sensor view can\'t be deleted.') - return redirect(url_for('central.sensor')) - views = r.smembers('views:{}'.format(sensor.name)) + sensor = Sensor.objects(name=request.form.get("name")).first() + if r.get("parent:{}".format(request.form.get("name"))): + flash("Sensor view can't be deleted.") + return redirect(url_for("central.sensor")) + views = r.smembers("views:{}".format(sensor.name)) for view in views: if defs.delete_sensor(view): - r.delete('sensor:{}'.format(view)) - r.delete('owner:{}'.format(view)) + r.delete("sensor:{}".format(view)) + r.delete("owner:{}".format(view)) # cache process done Sensor.objects(name=view).delete() # cache process - if defs.delete_sensor(request.form.get('name')): - r.delete('sensor:{}'.format(sensor.name)) - r.delete('owner:{}'.format(sensor.name)) + if defs.delete_sensor(request.form.get("name")): + r.delete("sensor:{}".format(sensor.name)) + r.delete("owner:{}".format(sensor.name)) # cache process done Sensor.objects(name=sensor.name).delete() else: - flash('Unable to communicate with the DataService') - return redirect(url_for('central.sensor')) + flash("Unable to communicate with the DataService") + return redirect(url_for("central.sensor")) + -@central.route('/sensorgroup', methods=['GET', 'POST']) +@central.route("/sensorgroup", methods=["GET", "POST"]) @login_required def sensorgroup(): - page = request.args.get('page', 1, type=int) + page = request.args.get("page", 1, type=int) skip_size = (page - 1) * PAGE_SIZE objs = SensorGroup.objects().skip(skip_size).limit(PAGE_SIZE) for obj in objs: @@ -400,33 +472,37 @@ def sensorgroup(): form = SensorGroupForm() # Get list of valid buildings for this DataService and create a sensorgroup form.building.choices = get_building_choices() - print "Got building choices" + print("Got building choices") if form.validate_on_submit(): - SensorGroup(name=str(form.name.data), - description=str(form.description.data), - building=str(form.building.data), - owner = session['email']).save() - return redirect(url_for('central.sensorgroup')) - return render_template('central/sensorgroup.html', objs=objs, form=form) - -@central.route('/sensorgroup_delete', methods=['POST']) + SensorGroup( + name=str(form.name.data), + description=str(form.description.data), + building=str(form.building.data), + owner=session["email"], + ).save() + return redirect(url_for("central.sensorgroup")) + return render_template("central/sensorgroup.html", objs=objs, form=form) + + +@central.route("/sensorgroup_delete", methods=["POST"]) @login_required def sensorgroup_delete(): # cache process - sensorgroup = SensorGroup.objects(name=request.form.get('name')).first() - if sensorgroup['owner'] == session['email']: - if defs.invalidate_permission(request.form.get('name')): + sensorgroup = SensorGroup.objects(name=request.form.get("name")).first() + if sensorgroup["owner"] == session["email"]: + if defs.invalidate_permission(request.form.get("name")): SensorGroup.objects(name=sensorgroup.name).delete() else: - flash('Unable to communicate with the DataService') + flash("Unable to communicate with the DataService") else: - flash('You are not authorized to delete this sensor group') - return redirect(url_for('central.sensorgroup')) + flash("You are not authorized to delete this sensor group") + return redirect(url_for("central.sensorgroup")) + -@central.route('/usergroup', methods=['GET', 'POST']) +@central.route("/usergroup", methods=["GET", "POST"]) @login_required def usergroup(): - page = request.args.get('page', 1, type=int) + page = request.args.get("page", 1, type=int) skip_size = (page - 1) * PAGE_SIZE objs = UserGroup.objects().skip(skip_size).limit(PAGE_SIZE) for obj in objs: @@ -437,125 +513,162 @@ def usergroup(): form = UserGroupForm() if form.validate_on_submit(): - UserGroup(name=str(form.name.data), - description=str(form.description.data), - owner = session['email']).save() - return redirect(url_for('central.usergroup')) - return render_template('central/usergroup.html', objs=objs, form=form) + UserGroup( + name=str(form.name.data), + description=str(form.description.data), + owner=session["email"], + ).save() + return redirect(url_for("central.usergroup")) + return render_template("central/usergroup.html", objs=objs, form=form) -@central.route('/usergroup_delete', methods=['POST']) + +@central.route("/usergroup_delete", methods=["POST"]) @login_required def usergroup_delete(): # cahce process - name = request.form.get('name') - if authorize_addition(name,session['email']): + name = request.form.get("name") + if authorize_addition(name, session["email"]): UserGroup.objects(name=name).delete() else: - flash('You are not authorized to delete this user group') - return redirect(url_for('central.usergroup')) + flash("You are not authorized to delete this user group") + return redirect(url_for("central.usergroup")) + -@central.route('/permission', methods=['GET', 'POST'], endpoint="permission") +@central.route("/permission", methods=["GET", "POST"], endpoint="permission") @login_required def permission_create(): - page = request.args.get('page', 1, type=int) + page = request.args.get("page", 1, type=int) skip_size = (page - 1) * PAGE_SIZE objs = Permission.objects().skip(skip_size).limit(PAGE_SIZE) for obj in objs: obj.can_delete = True form = PermissionForm() - form.user_group.choices = sorted([(obj.name, obj.name) for obj in UserGroup.objects]) - form.sensor_group.choices = sorted([(obj.name, obj.name) for obj in SensorGroup.objects]) + form.user_group.choices = sorted( + [(obj.name, obj.name) for obj in UserGroup.objects] + ) + form.sensor_group.choices = sorted( + [(obj.name, obj.name) for obj in SensorGroup.objects] + ) if form.validate_on_submit(): # Doesn't allow duplicate permissions - if Permission.objects(user_group=form.user_group.data, sensor_group=form.sensor_group.data).first() is not None: - flash('There is already permission pair {} - {} specified'.format( - form.user_group.data, form.sensor_group.data)) - return redirect(url_for('central.permission')) - if defs.create_permission(form.user_group.data,form.sensor_group.data,session['email'],form.permission.data): + if ( + Permission.objects( + user_group=form.user_group.data, sensor_group=form.sensor_group.data + ).first() + is not None + ): + flash( + "There is already permission pair {} - {} specified".format( + form.user_group.data, form.sensor_group.data + ) + ) + return redirect(url_for("central.permission")) + if defs.create_permission( + form.user_group.data, + form.sensor_group.data, + session["email"], + form.permission.data, + ): if not len(SensorGroup.objects(name=form.sensor_group.data).first().tags): - flash('No tags present in the SensorGroup') - return redirect(url_for('central.permission')) + flash("No tags present in the SensorGroup") + return redirect(url_for("central.permission")) # If permission doesn't exist then create it - if permission_allowed(form.sensor_group.data, session['email']): - Permission(user_group=str(form.user_group.data), - sensor_group=str(form.sensor_group.data), - permission=str(form.permission.data), - owner=session['email']).save() + if permission_allowed(form.sensor_group.data, session["email"]): + Permission( + user_group=str(form.user_group.data), + sensor_group=str(form.sensor_group.data), + permission=str(form.permission.data), + owner=session["email"], + ).save() invalidate_permission(str(form.sensor_group.data)) - r.hset('permission:{}:{}'.format(form.user_group.data, form.sensor_group.data), "permission", - form.permission.data) - r.hset('permission:{}:{}'.format(form.user_group.data, form.sensor_group.data), "owner", - session['email']) + r.hset( + "permission:{}:{}".format( + form.user_group.data, form.sensor_group.data + ), + "permission", + form.permission.data, + ) + r.hset( + "permission:{}:{}".format( + form.user_group.data, form.sensor_group.data + ), + "owner", + session["email"], + ) else: - flash('Not authorized to create permissions for the specified SensorGroup tags') + flash( + "Not authorized to create permissions for the specified SensorGroup tags" + ) else: - flash('Unable to communicate with the DataService') - return redirect(url_for('central.permission')) - return render_template('central/permission.html', objs=objs, form=form) + flash("Unable to communicate with the DataService") + return redirect(url_for("central.permission")) + return render_template("central/permission.html", objs=objs, form=form) -@central.route('/permission_delete', methods=['POST']) +@central.route("/permission_delete", methods=["POST"]) @login_required def permission_delete(): - code = request.form.get('name').split(':-:') + code = request.form.get("name").split(":-:") permission = Permission.objects(user_group=code[0], sensor_group=code[1]).first() - if permission['owner'] == session['email']: - if defs.delete_permission(code[0],code[1]): + if permission["owner"] == session["email"]: + if defs.delete_permission(code[0], code[1]): permission.delete() - r.delete('permission:{}:{}'.format(code[0], code[1])) + r.delete("permission:{}:{}".format(code[0], code[1])) invalidate_permission(code[1]) else: - flash('Unable to communicate with the DataService') + flash("Unable to communicate with the DataService") else: - flash('You are not authorized to delete this permission') - return redirect(url_for('central.permission')) + flash("You are not authorized to delete this permission") + return redirect(url_for("central.permission")) + -@central.route('/permission_query', methods=['GET', 'POST']) +@central.route("/permission_query", methods=["GET", "POST"]) @login_required def permission_query(): - """ Input taken from the user is their email and the sensor id they want to - check the permission for. The result returned is what type of access - permission the user has to that specific sensor """ + """Input taken from the user is their email and the sensor id they want to + check the permission for. The result returned is what type of access + permission the user has to that specific sensor""" form = PermissionQueryForm() res = None if form.validate_on_submit(): - if not validate_users([form.user.data],True): - flash('User {} does not exist'.format(form.user.data)) - return render_template('central/query.html', form=form, res=res) + if not validate_users([form.user.data], True): + flash("User {} does not exist".format(form.user.data)) + return render_template("central/query.html", form=form, res=res) sensor = Sensor.objects(name=form.sensor.data).first() if sensor is None: - flash('Sensor {} does not exist'.format(form.sensor.data)) - return render_template('central/query.html', form=form, res=res) + flash("Sensor {} does not exist".format(form.sensor.data)) + return render_template("central/query.html", form=form, res=res) res = permission(form.sensor.data, form.user.data) - return render_template('central/query.html', form=form, res=res) + return render_template("central/query.html", form=form, res=res) -@central.route('/sensor/search', methods=['GET', 'POST']) + +@central.route("/sensor/search", methods=["GET", "POST"]) @login_required def sensors_search(): - data = json.loads(request.args.get('q')) + data = json.loads(request.args.get("q")) args = {} - for key, values in data.iteritems(): - if key == 'Building': - form_query('building',values,args,"$and") - elif key == 'SourceName': - form_query('source_name',values,args,"$and") - elif key == 'SourceIdentifier': - form_query('source_identifier',values,args,"$and") - elif key == 'Owner': - form_query('owner', values, args, "$and") - elif key == 'ID': - form_query('name',values,args,"$and") - elif key == 'Tags': - form_query('tags',values,args,"$and") - elif key == 'MetaData': - form_query('metadata',values,args,"$and") - print args + for key, values in list(data.items()): + if key == "Building": + form_query("building", values, args, "$and") + elif key == "SourceName": + form_query("source_name", values, args, "$and") + elif key == "SourceIdentifier": + form_query("source_identifier", values, args, "$and") + elif key == "Owner": + form_query("owner", values, args, "$and") + elif key == "ID": + form_query("name", values, args, "$and") + elif key == "Tags": + form_query("tags", values, args, "$and") + elif key == "MetaData": + form_query("metadata", values, args, "$and") + print(args) # Show the user PAGE_SIZE number of sensors on each page - page = request.args.get('page', 1, type=int) + page = request.args.get("page", 1, type=int) skip_size = (page - 1) * PAGE_SIZE total_sensors = Sensor.objects(__raw__=args) total_cnt = total_sensors.count() @@ -566,7 +679,13 @@ def sensors_search(): form = SensorForm() # Get the list of valid buildings for this DataService form.building.choices = get_building_choices() - return render_template('central/sensor.html', objs=sensors, form=form, total=total_cnt, - #return render_template('central/sensor.html', objs=sensor_list, form=form, total=total, - pages=pages, current_page=page, pagesize=PAGE_SIZE) - + return render_template( + "central/sensor.html", + objs=sensors, + form=form, + total=total_cnt, + # return render_template('central/sensor.html', objs=sensor_list, form=form, total=total, + pages=pages, + current_page=page, + pagesize=PAGE_SIZE, + ) diff --git a/buildingdepot/CentralService/app/main/__init__.py b/buildingdepot/CentralService/app/main/__init__.py index 90380f84..bd0531d6 100755 --- a/buildingdepot/CentralService/app/main/__init__.py +++ b/buildingdepot/CentralService/app/main/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -main = Blueprint('main', __name__) +main = Blueprint("main", __name__) from . import views, errors diff --git a/buildingdepot/CentralService/app/main/errors.py b/buildingdepot/CentralService/app/main/errors.py index be0fb23a..7bf09b28 100755 --- a/buildingdepot/CentralService/app/main/errors.py +++ b/buildingdepot/CentralService/app/main/errors.py @@ -2,36 +2,36 @@ def bad_request(message): - response = jsonify({'error': 'Bad request', 'message': message}) + response = jsonify({"error": "Bad request", "message": message}) response.status_code = 400 return response def unauthorized(message): - response = jsonify({'error': 'Unauthorized', 'message': message}) + response = jsonify({"error": "Unauthorized", "message": message}) response.status_code = 401 return response def forbidden(message): - response = jsonify({'error': 'Forbidden', 'message': message}) + response = jsonify({"error": "Forbidden", "message": message}) response.status_code = 403 return response def not_exist(message): - response = jsonify({'error': 'Not exists', 'message': message}) + response = jsonify({"error": "Not exists", "message": message}) response.status_code = 404 return response def not_allowed(message): - response = jsonify({'error': 'Not allowed', 'message': message}) + response = jsonify({"error": "Not allowed", "message": message}) response.status_code = 405 return response def internal_error(message): - response = jsonify({'error': 'Internal server error', 'message': message}) + response = jsonify({"error": "Internal server error", "message": message}) response.status_code = 500 return response diff --git a/buildingdepot/CentralService/app/main/forms.py b/buildingdepot/CentralService/app/main/forms.py index e072be18..0a3abd11 100755 --- a/buildingdepot/CentralService/app/main/forms.py +++ b/buildingdepot/CentralService/app/main/forms.py @@ -1,8 +1,8 @@ -from flask_wtf import Form +from flask_wtf import FlaskForm from wtforms import StringField, SubmitField from wtforms.validators import Required -class NameForm(Form): - name = StringField('What is your name?', validators=[Required()]) - submit = SubmitField('Submit') +class NameForm(FlaskForm): + name = StringField("What is your name?", validators=[Required()]) + submit = SubmitField("Submit") diff --git a/buildingdepot/CentralService/app/main/views.py b/buildingdepot/CentralService/app/main/views.py index c8520dea..30cd2a87 100755 --- a/buildingdepot/CentralService/app/main/views.py +++ b/buildingdepot/CentralService/app/main/views.py @@ -1,7 +1,8 @@ from flask import render_template + from . import main -@main.route('/') +@main.route("/") def index(): - return render_template('index.html') + return render_template("index.html") diff --git a/buildingdepot/CentralService/app/models/cs_models.py b/buildingdepot/CentralService/app/models/cs_models.py index 2f9112a9..c8beb8e0 100755 --- a/buildingdepot/CentralService/app/models/cs_models.py +++ b/buildingdepot/CentralService/app/models/cs_models.py @@ -6,14 +6,14 @@ Each class here is a Table in MongoDB where each value that is inserted into these tables can have any of the paramteres defined within the class -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import current_app -from mongoengine import * from flask_login import UserMixin from itsdangerous import TimedJSONWebSignatureSerializer as Serializer +from mongoengine import * from werkzeug.security import generate_password_hash, check_password_hash @@ -28,11 +28,13 @@ class DataService(Document): admins = ListField(StringField()) + class PermissionRequest(Document): email = StringField() timestamp = StringField() requests = DictField() + class TagType(Document): name = StringField(required=True, unique=True) description = StringField() @@ -41,6 +43,7 @@ class TagType(Document): children = ListField(StringField()) acl_tag = BooleanField() + class BuildingTemplate(Document): name = StringField(required=True, unique=True) description = StringField() @@ -91,31 +94,31 @@ def verify_password(self, password): return check_password_hash(self.password, password) def generate_auth_token(self, expiration): - s = Serializer(current_app.config['SECRET_KEY'], - expires_in=expiration) - return s.dumps({'email': self.email}) + s = Serializer(current_app.config["SECRET_KEY"], expires_in=expiration) + return s.dumps({"email": self.email}) @staticmethod def verify_auth_token(token): - s = Serializer(current_app.config['SECRET_KEY']) + s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(token) except: return None - return User.objects(email=data['email']).first() + return User.objects(email=data["email"]).first() def is_super(self): - return self.role.type == 'super' + return self.role.type == "super" def is_local(self): - return self.role.type == 'local' + return self.role.type == "local" class UserGroupNode(EmbeddedDocument): user_id = StringField() manager = BooleanField() + class Sensor(Document): name = StringField(required=True, unique=True) source_name = StringField() @@ -127,10 +130,12 @@ class Sensor(Document): tags = ListField(EmbeddedDocumentField(Node)) subscribers = ListField(StringField()) + class NotificationClientId(Document): email = StringField(required=True, unique=True) client_id = StringField(required=True, unique=True) + class SensorGroup(Document): name = StringField(required=True, unique=True) description = StringField() @@ -139,6 +144,7 @@ class SensorGroup(Document): tags = ListField(EmbeddedDocumentField(Node)) owner = StringField() + class UserGroup(Document): name = StringField(required=True, unique=True) description = StringField() diff --git a/buildingdepot/CentralService/app/oauth_bd/__init__.py b/buildingdepot/CentralService/app/oauth_bd/__init__.py index 5a4acb18..32954259 100755 --- a/buildingdepot/CentralService/app/oauth_bd/__init__.py +++ b/buildingdepot/CentralService/app/oauth_bd/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -oauth_bd = Blueprint('oauth_bd', __name__) +oauth_bd = Blueprint("oauth_bd", __name__) from . import views diff --git a/buildingdepot/CentralService/app/oauth_bd/views.py b/buildingdepot/CentralService/app/oauth_bd/views.py index 9ac37db8..0478f316 100755 --- a/buildingdepot/CentralService/app/oauth_bd/views.py +++ b/buildingdepot/CentralService/app/oauth_bd/views.py @@ -5,27 +5,29 @@ Contains all the class and method definitions required for the OAuth token generation and verification -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -from . import oauth_bd -from .. import oauth, r +import binascii +import os +import sys +from bson.objectid import ObjectId from datetime import datetime, timedelta from flask import Flask, current_app, Blueprint -from flask import session, request from flask import render_template, redirect, jsonify +from flask import session, request from flask_oauthlib.client import OAuth -from werkzeug.security import gen_salt -from bson.objectid import ObjectId -from xmlrpclib import ServerProxy -from ..rest_api import responses -import sys, os, binascii - -from ..models.cs_models import User from mongoengine import * from mongoengine.context_managers import switch_db from uuid import uuid4 +from werkzeug.security import gen_salt +from xmlrpc.client import ServerProxy + +from . import oauth_bd +from .. import oauth, r +from ..models.cs_models import User +from ..rest_api import responses expires_in = 34560 @@ -39,7 +41,7 @@ class Client(Document): @property def client_type(self): - return 'public' + return "public" @property def redirect_uris(self): @@ -99,44 +101,45 @@ def get_user_oauth(email): def current_user(): - if 'email' in session: - email = session['email'] + if "email" in session: + email = session["email"] return get_user_oauth(email) return None -@oauth_bd.route('/', methods=('GET', 'POST')) +@oauth_bd.route("/", methods=("GET", "POST")) def home(): - if request.method == 'POST': - email = request.form.get('username') + if request.method == "POST": + email = request.form.get("username") user = get_user_oauth(email) if not user: - return jsonify({'response': 'Access Denied'}) - session['email'] = user - return redirect('/') + return jsonify({"response": "Access Denied"}) + session["email"] = user + return redirect("/") user_current = current_user() - return render_template('home.html', user=user_current) + return render_template("home.html", user=user_current) -@oauth_bd.route('/client') +@oauth_bd.route("/client") def client(): user_current = current_user() if not user_current: - return redirect('/') + return redirect("/") item = Client( client_id=gen_salt(40), client_secret=gen_salt(50), - _redirect_uris=' '.join([ - 'http://localhost:8000/authorized', - 'http://127.0.0.1:8000/authorized', - 'http://127.0.1:8000/authorized', - 'http://127.1:8000/authorized']), - _default_scopes='email', - user=get_user_oauth(user_current)).save() - return jsonify( - client_id=item.client_id, - client_secret=item.client_secret - ) + _redirect_uris=" ".join( + [ + "http://localhost:8000/authorized", + "http://127.0.0.1:8000/authorized", + "http://127.0.1:8000/authorized", + "http://127.1:8000/authorized", + ] + ), + _default_scopes="email", + user=get_user_oauth(user_current), + ).save() + return jsonify(client_id=item.client_id, client_secret=item.client_secret) @oauth.clientgetter @@ -146,8 +149,9 @@ def load_client(client_id): @oauth.grantgetter def load_grant(client_id, code): - return Grant.objects(client=Client.objects(client_id=client_id).first(), - code=code).first() + return Grant.objects( + client=Client.objects(client_id=client_id).first(), code=code + ).first() @oauth.grantsetter @@ -155,10 +159,12 @@ def save_grant(client_id, code, request, *args, **kwargs): expires = datetime.utcnow() + timedelta(seconds=100) grant = Grant( client=Client.objects(client_id=client_id).first(), - code=code['code'], + code=code["code"], redirect_uri=request.redirect_uri, - _scopes=' '.join(request.scopes), - user=get_user_oauth(current_user()), expires=expires) + _scopes=" ".join(request.scopes), + user=get_user_oauth(current_user()), + expires=expires, + ) grant.save() return grant @@ -174,89 +180,106 @@ def load_token(access_token=None, refresh_token=None): @oauth.tokensetter def save_token(token, request, *args, **kwargs): toks = Token.objects(client=request.client, user=request.user) - previous_tokens = ['oauth'] + previous_tokens = ["oauth"] for t in toks: - previous_tokens.append(''.join(['oauth:', t.access_token])) + previous_tokens.append("".join(["oauth:", t.access_token])) t.delete() r.delete(*previous_tokens) - expires_in = token.pop('expires_in') + expires_in = token.pop("expires_in") expires = datetime.utcnow() + timedelta(seconds=expires_in) tok = Token( - access_token=token['access_token'], - refresh_token=token['refresh_token'], - token_type=token['token_type'], - _scopes=token['scope'], + access_token=token["access_token"], + refresh_token=token["refresh_token"], + token_type=token["token_type"], + _scopes=token["scope"], expires=expires, client=request.client, user=request.user, - email=request.user).save() - r.setex(''.join(['oauth:', tok.access_token]), client.user, expires_in) + email=request.user, + ).save() + r.setex("".join(["oauth:", tok.access_token]), expires_in, client.user) return tok -@oauth_bd.route('/access_token/client_id=/client_secret=', methods=['GET']) +@oauth_bd.route( + "/access_token/client_id=/client_secret=", methods=["GET"] +) def get_access_token(client_id, client_secret): - """ Generates and returns an access token to the user if the client_id and - client_secret provided by them are valid""" + """Generates and returns an access token to the user if the client_id and + client_secret provided by them are valid""" client = Client.objects(client_id=client_id, client_secret=client_secret).first() if client != None: # Set token expiry period and create it expires = datetime.utcnow() + timedelta(seconds=expires_in) tok = Token( - access_token=str(binascii.hexlify(os.urandom(16))), - refresh_token=str(binascii.hexlify(os.urandom(16))), - token_type='Bearer', - _scopes='email', + access_token=binascii.hexlify(os.urandom(16)).decode(), + refresh_token=binascii.hexlify(os.urandom(16)).decode(), + token_type="Bearer", + _scopes="email", expires=expires, client=client, user=client.user, - email=client.user).save() - r.setex(''.join(['oauth:', tok.access_token]), client.user, expires_in) - return jsonify({'success': 'True', 'access_token': tok.access_token}) - return jsonify({'success': 'False', 'access_token': 'Invalid credentials'}) + email=client.user, + ).save() + r.setex("".join(["oauth:", tok.access_token]), expires_in, client.user) + return jsonify({"success": "True", "access_token": tok.access_token}) + return jsonify({"success": "False", "access_token": "Invalid credentials"}) -@oauth_bd.route('/generate', methods=['POST']) +@oauth_bd.route("/generate", methods=["POST"]) def generate_credentials(): - cred = request.get_json()['data'] - print cred['email'], type(cred) - email = cred['email'] - password = cred['password'] - print email, password + cred = request.get_json()["data"] + print((cred["email"], type(cred))) + email = cred["email"] + password = cred["password"] + print((email, password)) user = User.objects(email=email).first() if user is not None and user.first_login and user.verify_password(password): response = dict(responses.success_true) - response.update({'login': 'You have logged in for the first time.Please change your\ - password on the UI'}) + response.update( + { + "login": "You have logged in for the first time.Please change your\ + password on the UI" + } + ) return jsonify(response) elif user is not None and user.verify_password(password): if len(Client.objects(user=email)) > 0: keys = [{"client_id": gen_salt(40), "client_secret": gen_salt(50)}] item = Client( - client_id=keys[0]['client_id'], - client_secret=keys[0]['client_secret'], - _redirect_uris=' '.join([ - 'http://localhost:8000/authorized', - 'http://127.0.0.1:8000/authorized', - 'http://127.0.1:8000/authorized', - 'http://127.1:8000/authorized']), - _default_scopes='email', - user=email).save() + client_id=keys[0]["client_id"], + client_secret=keys[0]["client_secret"], + _redirect_uris=" ".join( + [ + "http://localhost:8000/authorized", + "http://127.0.0.1:8000/authorized", + "http://127.0.1:8000/authorized", + "http://127.1:8000/authorized", + ] + ), + _default_scopes="email", + user=email, + ).save() response = dict(responses.success_true) - response.update({'client_id': keys[0]['client_id'], - 'client_key': keys[0]['client_secret']}) + response.update( + { + "client_id": keys[0]["client_id"], + "client_key": keys[0]["client_secret"], + } + ) return jsonify(response) else: - response = dict({'success': 'False', 'error': 'Wrong Username and Password'}) + response = dict({"success": "False", "error": "Wrong Username and Password"}) return jsonify(response) -@oauth_bd.route('/fetch', methods=['POST']) + +@oauth_bd.route("/fetch", methods=["POST"]) def fetch_credentials(): """This API will fetch client ID/key if there exists at least one key/secret, or generate a new one if there are no ID/key""" - cred = request.get_json()['data'] - email = cred['email'] - password = cred['password'] + cred = request.get_json()["data"] + email = cred["email"] + password = cred["password"] user = User.objects(email=email).first() # TODO: disable change password for first login feature if user is not None and user.verify_password(password): @@ -265,89 +288,112 @@ def fetch_credentials(): if creds_size > 0: creds = Client.objects(user=email) response = dict(responses.success_true) - response.update({'client_id': creds[creds_size - 1]['client_id'], - 'client_key': creds[creds_size - 1]['client_secret']}) + response.update( + { + "client_id": creds[creds_size - 1]["client_id"], + "client_key": creds[creds_size - 1]["client_secret"], + } + ) return jsonify(response) else: # generate new credentials keys = [{"client_id": gen_salt(40), "client_secret": gen_salt(50)}] item = Client( - client_id=keys[0]['client_id'], - client_secret=keys[0]['client_secret'], - _redirect_uris=' '.join([ - 'http://localhost:8000/authorized', - 'http://127.0.0.1:8000/authorized', - 'http://127.0.1:8000/authorized', - 'http://127.1:8000/authorized']), - _default_scopes='email', - user=email).save() + client_id=keys[0]["client_id"], + client_secret=keys[0]["client_secret"], + _redirect_uris=" ".join( + [ + "http://localhost:8000/authorized", + "http://127.0.0.1:8000/authorized", + "http://127.0.1:8000/authorized", + "http://127.1:8000/authorized", + ] + ), + _default_scopes="email", + user=email, + ).save() response = dict(responses.success_true) - response.update({'client_id': keys[0]['client_id'], - 'client_key': keys[0]['client_secret']}) + response.update( + { + "client_id": keys[0]["client_id"], + "client_key": keys[0]["client_secret"], + } + ) return jsonify(response) else: - response = dict({'success': 'False', 'error': 'Wrong Username and Password'}) + response = dict({"success": "False", "error": "Wrong Username and Password"}) return jsonify(response) -@oauth_bd.route('/login', methods=['POST']) + +@oauth_bd.route("/login", methods=["POST"]) def login_credentials(): """This API will fetch client ID/key if there exists at least one key/secret, or generate a new one if there are no ID/key""" - cred = request.get_json()['data'] - email = cred['email'] - password = cred['password'] + cred = request.get_json()["data"] + email = cred["email"] + password = cred["password"] user = User.objects(email=email).first() if user is not None and user.verify_password(password): creds = Client.objects(user=email) creds_size = len(creds) if creds_size > 0: - client = Client.objects(client_id=creds[creds_size - 1]['client_id'], client_secret=creds[creds_size - 1]['client_secret']).first() + client = Client.objects( + client_id=creds[creds_size - 1]["client_id"], + client_secret=creds[creds_size - 1]["client_secret"], + ).first() if client != None: # Set token expiry period and create it expires = datetime.utcnow() + timedelta(seconds=expires_in) tok = Token( - access_token=str(binascii.hexlify(os.urandom(16))), - refresh_token=str(binascii.hexlify(os.urandom(16))), - token_type='Bearer', - _scopes='email', - expires=expires, - client=client, - user=client.user, - email=client.user).save() + access_token=binascii.hexlify(os.urandom(16)).decode(), + refresh_token=binascii.hexlify(os.urandom(16)).decode(), + token_type="Bearer", + _scopes="email", + expires=expires, + client=client, + user=client.user, + email=client.user, + ).save() response = dict(responses.success_true) - response.update({'access_token': tok.access_token}) + response.update({"access_token": tok.access_token}) return jsonify(response) else: # generate new credentials keys = [{"client_id": gen_salt(40), "client_secret": gen_salt(50)}] item = Client( - client_id=keys[0]['client_id'], - client_secret=keys[0]['client_secret'], - _redirect_uris=' '.join([ - 'http://localhost:8000/authorized', - 'http://127.0.0.1:8000/authorized', - 'http://127.0.1:8000/authorized', - 'http://127.1:8000/authorized']), - _default_scopes='email', - user=email).save() - - client = Client.objects(client_id=keys[0]['client_id'], - client_secret=keys[0]['client_secret']).first() + client_id=keys[0]["client_id"], + client_secret=keys[0]["client_secret"], + _redirect_uris=" ".join( + [ + "http://localhost:8000/authorized", + "http://127.0.0.1:8000/authorized", + "http://127.0.1:8000/authorized", + "http://127.1:8000/authorized", + ] + ), + _default_scopes="email", + user=email, + ).save() + + client = Client.objects( + client_id=keys[0]["client_id"], client_secret=keys[0]["client_secret"] + ).first() if client != None: # Set token expiry period and create it expires = datetime.utcnow() + timedelta(seconds=expires_in) tok = Token( - access_token=str(binascii.hexlify(os.urandom(16))), - refresh_token=str(binascii.hexlify(os.urandom(16))), - token_type='Bearer', - _scopes='email', + access_token=binascii.hexlify(os.urandom(16)).decode(), + refresh_token=binascii.hexlify(os.urandom(16)).decode(), + token_type="Bearer", + _scopes="email", expires=expires, client=client, user=client.user, - email=client.user).save() + email=client.user, + ).save() response = dict(responses.success_true) - response.update({'access_token': tok.access_token}) + response.update({"access_token": tok.access_token}) return jsonify(response) else: - response = dict({'success': 'False', 'error': 'Wrong Username and Password'}) + response = dict({"success": "False", "error": "Wrong Username and Password"}) return jsonify(response) diff --git a/buildingdepot/CentralService/app/rest_api/__init__.py b/buildingdepot/CentralService/app/rest_api/__init__.py index 63ce0b19..0d58e33b 100755 --- a/buildingdepot/CentralService/app/rest_api/__init__.py +++ b/buildingdepot/CentralService/app/rest_api/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -api = Blueprint('api', __name__) +api = Blueprint("api", __name__) -from . import views \ No newline at end of file +from . import views diff --git a/buildingdepot/CentralService/app/rest_api/buildings/building.py b/buildingdepot/CentralService/app/rest_api/buildings/building.py index 3b22f63a..560a12ed 100755 --- a/buildingdepot/CentralService/app/rest_api/buildings/building.py +++ b/buildingdepot/CentralService/app/rest_api/buildings/building.py @@ -7,79 +7,79 @@ defined for any of the tagtypes that the template it is based on contains. Buildings can also have metadata attached to them. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -from flask import jsonify,request,current_app +from flask import jsonify, request, current_app from flask.views import MethodView -from ...models.cs_models import Building,BuildingTemplate,DataService + from .. import responses from ..helper import gen_update, check_oauth from ... import oauth from ...auth.access_control import super_required +from ...models.cs_models import Building, BuildingTemplate, DataService class BuildingService(MethodView): - - params = ['name','template','description'] + params = ["name", "template", "description"] @check_oauth @super_required def post(self): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) try: - name = data['name'] - template = data['template'] + name = data["name"] + template = data["template"] except KeyError: return jsonify(responses.missing_parameters) if BuildingTemplate.objects(name=template).first() is None: - return jsonify(responses.invalid_template) + return jsonify(responses.invalid_template) if not name: - return jsonify({'success':'False', 'error': 'Invalid Building name.'}) + return jsonify({"success": "False", "error": "Invalid Building name."}) building = Building.objects(name=name).first() if building is None: - Building(**gen_update(self.params,data)).save() + Building(**gen_update(self.params, data)).save() else: collection = Building._get_collection() - collection.update({'name': name},{"$set":gen_update(self.params,data)}) + collection.update_one({"name": name}, {"$set": gen_update(self.params, data)}) return jsonify(responses.success_true) @check_oauth - def get(self,name): - if name == 'list': + def get(self, name): + if name == "list": building = [] response = dict(responses.success_true) collection = Building.objects for i in collection: building.append(i.name) - response.update({'buildings': building}) + response.update({"buildings": building}) else: building = Building.objects(name=name).first() if building is None: return jsonify(responses.invalid_building) response = dict(responses.success_true) - response.update({'name': building['name'], - 'description': building['description'], - 'template': building['template']}) + response.update( + { + "name": building["name"], + "description": building["description"], + "template": building["template"], + } + ) return jsonify(response) @check_oauth @super_required - def delete(self,name): + def delete(self, name): building = Building.objects(name=name).first() if building is None: return jsonify(responses.invalid_building) - collection = DataService._get_collection() - if len(building.tags) == 0 and collection.count({'buildings': name}) == 0: + collection_objects = DataService.objects(buildings=name) + if len(building.tags) == 0 and collection_objects.count() == 0: building.delete() else: return jsonify(responses.building_in_use) return jsonify(responses.success_true) - - - - diff --git a/buildingdepot/CentralService/app/rest_api/buildings/building_tags.py b/buildingdepot/CentralService/app/rest_api/buildings/building_tags.py index ecc24153..66bb1352 100755 --- a/buildingdepot/CentralService/app/rest_api/buildings/building_tags.py +++ b/buildingdepot/CentralService/app/rest_api/buildings/building_tags.py @@ -7,30 +7,31 @@ For each of the tagtypes present in the template on which this building is based on, can have multiple unique values defined for them. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -from flask.views import MethodView from flask import request, jsonify -from ...models.cs_models import Building, BuildingTemplate -from ...models.cs_models import TagType, DataService, User +from flask.views import MethodView + from .. import responses from ..helper import get_email, check_oauth from ... import oauth from ...auth.access_control import super_required +from ...models.cs_models import Building, BuildingTemplate +from ...models.cs_models import TagType, DataService, User class BuildingTagsService(MethodView): @check_oauth def post(self, building_name): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except: return jsonify(responses.missing_data) try: - name = data['name'] - value = data['value'] + name = data["name"] + value = data["value"] except: return jsonify(responses.missing_parameters) # collection = Building._get_collection() @@ -38,40 +39,48 @@ def post(self, building_name): building = Building.objects(name=building_name).first() if building is None: return jsonify(responses.invalid_building) - template = BuildingTemplate.objects(name=building['template']).first() - if name not in template['tag_types']: + template = BuildingTemplate.objects(name=building["template"]).first() + if name not in template["tag_types"]: return jsonify(responses.invalid_tagtype) collection = Building._get_collection() - metadata = data.get('metadata') - parents = data.get('parents') - if parents and len(parents)!=0: + metadata = data.get("metadata") + parents = data.get("parents") + if parents and len(parents) != 0: search_list = [] for element in parents: - search_list.append({'$elemMatch': element}) - if collection.find({"tags": {"$all": search_list}}).count() == 0: + search_list.append({"$elemMatch": element}) + if collection.count_documents({"tags": {"$all": search_list}}) == 0: return jsonify(responses.invalid_parents) tag = { - 'name': str(name), - 'value': str(value), - 'metadata': metadata if metadata else [], - 'parents': parents if parents else [], + "name": str(name), + "value": str(value), + "metadata": metadata if metadata else [], + "parents": parents if parents else [], } - tag_exists = collection.find({'name': building_name, - 'tags': - {"$elemMatch": {"name": name, "value": value}}}) - if tag_exists.count() == 0: - collection.update( - {'name': building_name}, - {'$addToSet': {'tags': tag}} - ) + tag_exists = collection.count_documents( + { + "name": building_name, + "tags": {"$elemMatch": {"name": name, "value": value}}, + } + ) + if tag_exists == 0: + collection.update_one({"name": building_name}, {"$addToSet": {"tags": tag}}) else: if parents: - collection.update({'name': building_name, - 'tags': {"$elemMatch": {"name": name, "value": value}}}, - {"$set": {"tags.$.parents": []}}) - collection.update({'name': building_name, - 'tags': {"$elemMatch": {"name": name, "value": value}}}, - {"$push": {"tags.$.parents": {"$each": parents}}}) + collection.update_one( + { + "name": building_name, + "tags": {"$elemMatch": {"name": name, "value": value}}, + }, + {"$set": {"tags.$.parents": []}}, + ) + collection.update_one( + { + "name": building_name, + "tags": {"$elemMatch": {"name": name, "value": value}}, + }, + {"$push": {"tags.$.parents": {"$each": parents}}}, + ) return jsonify(responses.success_true) @check_oauth @@ -79,19 +88,21 @@ def get(self, building_name): building = Building.objects(name=building_name).first() if building is None: return jsonify(responses.invalid_building) - template = building['template'] + template = building["template"] names = BuildingTemplate.objects(name=template).first().tag_types pairs = {name: TagType.objects(name=name).first().parents for name in names} tags = Building._get_collection().find( - {'name': building_name}, - {'_id': 0, 'tags.name': 1, 'tags.value': 1, 'tags.parents': 1} - )[0]['tags'] - parents = set([pair['name'] + pair['value'] for tag in tags for pair in tag['parents']]) + {"name": building_name}, + {"_id": 0, "tags.name": 1, "tags.value": 1, "tags.parents": 1}, + )[0]["tags"] + parents = set( + [pair["name"] + pair["value"] for tag in tags for pair in tag["parents"]] + ) # Response contains parameters that define whether tag can be deleted or not for tag in tags: - tag['can_delete'] = tag['name'] + tag['value'] not in parents + tag["can_delete"] = tag["name"] + tag["value"] not in parents response = dict(responses.success_true) - response.update({'pairs': pairs, 'tags': tags}) + response.update({"pairs": pairs, "tags": tags}) return jsonify(response) @check_oauth @@ -100,24 +111,27 @@ def delete(self, building_name): if building is None: return jsonify(responses.invalid_building) try: - data = request.get_json()['data'] + data = request.get_json()["data"] except: return jsonify(responses.missing_data) try: - name = data['name'] - value = data['value'] + name = data["name"] + value = data["value"] except: return jsonify(responses.missing_parameters) collection = Building._get_collection() - tag_exists = collection.find({'tags': - {"$elemMatch": {"name": name, "value": value}}}) - if tag_exists.count() == 0: + tag_exists = collection.count_documents( + {"tags": {"$elemMatch": {"name": name, "value": value}}} + ) + if tag_exists == 0: return jsonify(responses.invalid_tag_value) - tag_use = collection.find({'tags.parents': - {"$elemMatch": {"name": name, "value": value}}}) - if tag_use.count() > 0: + tag_use = collection.count_documents( + {"tags.parents": {"$elemMatch": {"name": name, "value": value}}} + ) + if tag_use > 0: return jsonify(responses.tagtype_referenced) - collection.update({'name': building_name}, - {'$pull': {'tags': {'name': name, 'value': value}}}) + collection.update_one( + {"name": building_name}, {"$pull": {"tags": {"name": name, "value": value}}} + ) return jsonify(responses.success_true) diff --git a/buildingdepot/CentralService/app/rest_api/buildingtemplate/buildingtemplate.py b/buildingdepot/CentralService/app/rest_api/buildingtemplate/buildingtemplate.py index fac55725..f4d6421e 100644 --- a/buildingdepot/CentralService/app/rest_api/buildingtemplate/buildingtemplate.py +++ b/buildingdepot/CentralService/app/rest_api/buildingtemplate/buildingtemplate.py @@ -8,36 +8,39 @@ can be used by all buildings that are based on that specific building template. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import jsonify, request from flask.views import MethodView + from .. import responses from ..helper import add_delete, gen_update, check_oauth -from ...models.cs_models import TagType, BuildingTemplate, Building from ... import oauth from ...auth.access_control import super_required +from ...models.cs_models import TagType, BuildingTemplate, Building class BuildingTemplateService(MethodView): - params = ['name', 'description', 'tag_types'] + params = ["name", "description", "tag_types"] @check_oauth @super_required def post(self): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) try: - name = data['name'] + name = data["name"] except: return jsonify(responses.missing_parameters) if not name: - return jsonify({'success':'False', 'error': 'Invalid Building Template name.'}) + return jsonify( + {"success": "False", "error": "Invalid Building Template name."} + ) template = BuildingTemplate.objects(name=name).first() - tagtypes = data.get('tag_types') + tagtypes = data.get("tag_types") if not tagtypes: tagtypes = [] for tagtype in tagtypes: @@ -47,13 +50,13 @@ def post(self): if template is None: BuildingTemplate(**gen_update(self.params, data)).save() else: - added, deleted = add_delete(template['tag_types'], tagtypes) + added, deleted = add_delete(template["tag_types"], tagtypes) collection = Building._get_collection() for tagtype in deleted: - if collection.find({"template": name, "tags.name": name}).count() > 0: + if collection.count_documents({"template": name, "tags.name": name}) > 0: return jsonify(responses.tagtype_in_use) collection = BuildingTemplate._get_collection() - collection.update({'name': name}, {'$set': gen_update(self.params, data)}) + collection.update_one({"name": name}, {"$set": gen_update(self.params, data)}) return jsonify(responses.success_true) @check_oauth @@ -62,9 +65,13 @@ def get(self, name): if template is None: return jsonify(responses.invalid_template) response = dict(responses.success_true) - response.update({'name': template['name'], - 'description': template['description'], - 'tag_types': template['tag_types']}) + response.update( + { + "name": template["name"], + "description": template["description"], + "tag_types": template["tag_types"], + } + ) return jsonify(response) @check_oauth diff --git a/buildingdepot/CentralService/app/rest_api/buildingtemplate/buildingtemplate_tagtypes.py b/buildingdepot/CentralService/app/rest_api/buildingtemplate/buildingtemplate_tagtypes.py index 6956ca47..f61a67be 100644 --- a/buildingdepot/CentralService/app/rest_api/buildingtemplate/buildingtemplate_tagtypes.py +++ b/buildingdepot/CentralService/app/rest_api/buildingtemplate/buildingtemplate_tagtypes.py @@ -5,16 +5,16 @@ This module handles the interactions with the buildingtemplate tagtypes. Takes care of all the CRUD operations on buildingtemplate tagtypes. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -from flask.views import MethodView from flask import request, jsonify +from flask.views import MethodView -from ...models.cs_models import BuildingTemplate, TagType from .. import responses from ..helper import check_oauth +from ...models.cs_models import BuildingTemplate, TagType class BuildingTemplateTagtypeService(MethodView): @@ -23,22 +23,24 @@ def post(self, name): building_template = BuildingTemplate.objects(name=name).first() if building_template is None: return jsonify(responses.invalid_template) - data = request.get_json()['data'] + data = request.get_json()["data"] def tagtype_exists(tagtype): return TagType.objects(name=tagtype).first() valid_tagtypes = [] invalid_tagtypes = [] - for tagtype in data['tagtypes']: + for tagtype in data["tagtypes"]: if tagtype_exists(tagtype): valid_tagtypes.append(tagtype) else: invalid_tagtypes.append(tagtype) - return jsonify({'success': 'True', 'invalid_tagtypes': invalid_tagtypes}) \ - if building_template.update(add_to_set__tag_types=valid_tagtypes) \ - else jsonify({'success': 'False'}) + return ( + jsonify({"success": "True", "invalid_tagtypes": invalid_tagtypes}) + if building_template.update(add_to_set__tag_types=valid_tagtypes) + else jsonify({"success": "False"}) + ) @check_oauth def get(self, name): @@ -46,7 +48,7 @@ def get(self, name): if buildingtemplate is None: return jsonify(responses.invalid_template) response = dict(responses.success_true) - response.update({'tag_types': buildingtemplate.tag_types}) + response.update({"tag_types": buildingtemplate.tag_types}) return jsonify(response) @check_oauth @@ -54,17 +56,37 @@ def delete(self, name): buildingtemplate = BuildingTemplate.objects(name=name).first() if buildingtemplate is None: return jsonify(responses.invalid_template) - data = request.get_json()['data'] - return jsonify({'success': 'True', 'tagtypes_absent':list(set(data['tagtypes'])-set(buildingtemplate.tag_types))}) if buildingtemplate.update(pull_all__tag_types=data['tagtypes'])\ - else jsonify({'success': 'False'}) + data = request.get_json()["data"] + return ( + jsonify( + { + "success": "True", + "tagtypes_absent": list( + set(data["tagtypes"]) - set(buildingtemplate.tag_types) + ), + } + ) + if buildingtemplate.update(pull_all__tag_types=data["tagtypes"]) + else jsonify({"success": "False"}) + ) @check_oauth def put(self, name): buildingtemplate = BuildingTemplate.objects(name=name).first() if buildingtemplate is None: return jsonify(responses.invalid_template) - data = request.get_json()['data'] + data = request.get_json()["data"] tagtype_exists = lambda tagtype: TagType.objects(name=tagtype).first() - valid_tagtypes = filter(tagtype_exists, data['tagtypes']) - return jsonify({'success': 'True', 'invalid_tagtypes': list(set(data['tagtypes']) - set(valid_tagtypes))}) if buildingtemplate.update(set__tag_types=valid_tagtypes)\ - else jsonify({'success': 'False'}) \ No newline at end of file + valid_tagtypes = list(filter(tagtype_exists, data["tagtypes"])) + return ( + jsonify( + { + "success": "True", + "invalid_tagtypes": list( + set(data["tagtypes"]) - set(valid_tagtypes) + ), + } + ) + if buildingtemplate.update(set__tag_types=valid_tagtypes) + else jsonify({"success": "False"}) + ) diff --git a/buildingdepot/CentralService/app/rest_api/dataservices/dataservice.py b/buildingdepot/CentralService/app/rest_api/dataservices/dataservice.py index 5a601464..5841790e 100755 --- a/buildingdepot/CentralService/app/rest_api/dataservices/dataservice.py +++ b/buildingdepot/CentralService/app/rest_api/dataservices/dataservice.py @@ -6,53 +6,56 @@ of all the CRUD operations on dataservices. Each dataservice will have a list of buildings and admins that belong to it. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import request, jsonify from flask.views import MethodView + from .. import responses from ..helper import xstr, gen_update, check_oauth -from ...models.cs_models import DataService from ... import oauth from ...auth.access_control import super_required +from ...models.cs_models import DataService class DataserviceService(MethodView): - params = ['description', 'host', 'port'] + params = ["description", "host", "port"] @check_oauth @super_required def post(self): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) try: - name = data['name'] + name = data["name"] except KeyError: return jsonify(responses.missing_parameters) if not name: - return jsonify({'success':'False', 'error': 'Invalid Data Service name.'}) + return jsonify({"success": "False", "error": "Invalid Data Service name."}) dataservice = DataService.objects(name=name).first() if dataservice is None: - DataService(name=name, - description=xstr(data.get('description')), - host=str(data.get('host')), - port=str(data.get('port'))).save() + DataService( + name=name, + description=xstr(data.get("description")), + host=str(data.get("host")), + port=str(data.get("port")), + ).save() else: collection = DataService._get_collection() - collection.update({'name': name}, {'$set': gen_update(self.params, data)}) + collection.update_one({"name": name}, {"$set": gen_update(self.params, data)}) return jsonify(responses.success_true) @check_oauth def get(self, name): - print "Name :", name + print(("Name :", name)) if name == "list": - all_dataservices=[] + all_dataservices = [] collection = DataService.objects for i in collection: all_dataservices.append(i.name) @@ -63,10 +66,14 @@ def get(self, name): if dataservice is None: return jsonify(responses.invalid_dataservice) response = dict(responses.success_true) - response.update({'name': name, - 'description': xstr(dataservice.description), - 'host': xstr(dataservice.host), - 'port': xstr(dataservice.port)}) + response.update( + { + "name": name, + "description": xstr(dataservice.description), + "host": xstr(dataservice.host), + "port": xstr(dataservice.port), + } + ) return jsonify(response) @check_oauth diff --git a/buildingdepot/CentralService/app/rest_api/dataservices/ds_admins.py b/buildingdepot/CentralService/app/rest_api/dataservices/ds_admins.py index 9c9ae84c..4b4ca441 100755 --- a/buildingdepot/CentralService/app/rest_api/dataservices/ds_admins.py +++ b/buildingdepot/CentralService/app/rest_api/dataservices/ds_admins.py @@ -6,30 +6,30 @@ dataservice. It handles all the CRUD operations for the admins list present in each dataservice. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ - -from flask import jsonify,request +from flask import jsonify, request from flask.views import MethodView -from ...models.cs_models import Building,DataService,User + from .. import responses +from ..helper import check_oauth from ... import oauth from ...auth.access_control import super_required -from ..helper import check_oauth +from ...models.cs_models import Building, DataService, User -class DataserviceAdminService(MethodView): +class DataserviceAdminService(MethodView): @check_oauth @super_required - def post(self,name): + def post(self, name): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) - admins = data.get('admins') + admins = data.get("admins") if admins is None: return jsonify(responses.missing_parameters) @@ -45,19 +45,19 @@ def post(self,name): return jsonify(responses.success_true) @check_oauth - def get(self,name): + def get(self, name): dataservice = DataService.objects(name=name).first() if dataservice is None: return jsonify(responses.invalid_dataservice) response = dict(responses.success_true) - response.update({'admins':dataservice.admins}) + response.update({"admins": dataservice.admins}) return jsonify(response) @check_oauth @super_required - def delete(self,name): + def delete(self, name): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) @@ -65,11 +65,10 @@ def delete(self,name): if dataservice is None: return jsonify(responses.invalid_dataservice) - admins = data.get('admins') + admins = data.get("admins") if admins is None: return jsonify(responses.missing_parameters) collection = DataService._get_collection() - collection.update({'name':name}, - {'$pullAll': {'admins':admins}}) + collection.update_one({"name": name}, {"$pullAll": {"admins": admins}}) return jsonify(responses.success_true) diff --git a/buildingdepot/CentralService/app/rest_api/dataservices/ds_buildings.py b/buildingdepot/CentralService/app/rest_api/dataservices/ds_buildings.py index 42b67ec8..e39e336c 100755 --- a/buildingdepot/CentralService/app/rest_api/dataservices/ds_buildings.py +++ b/buildingdepot/CentralService/app/rest_api/dataservices/ds_buildings.py @@ -6,17 +6,18 @@ dataservice. It handles all the CRUD operations for the buildings list present in each dataservice. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import jsonify, request from flask.views import MethodView -from ...models.cs_models import Building, DataService + from .. import responses +from ..helper import check_oauth from ... import oauth from ...auth.access_control import super_required -from ..helper import check_oauth +from ...models.cs_models import Building, DataService class DataserviceBuildingsService(MethodView): @@ -24,11 +25,11 @@ class DataserviceBuildingsService(MethodView): @super_required def post(self, name): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) - buildings = data.get('buildings') + buildings = data.get("buildings") if buildings is None: return jsonify(responses.missing_parameters) @@ -49,14 +50,14 @@ def get(self, name): if dataservice is None: return jsonify(responses.invalid_dataservice) response = dict(responses.success_true) - response.update({'buildings': dataservice.buildings}) + response.update({"buildings": dataservice.buildings}) return jsonify(response) @check_oauth @super_required def delete(self, name): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) @@ -64,11 +65,10 @@ def delete(self, name): if dataservice is None: return jsonify(responses.invalid_dataservice) - buildings = data.get('buildings') + buildings = data.get("buildings") if buildings is None: return jsonify(responses.missing_parameters) collection = DataService._get_collection() - collection.update({'name': name}, - {'$pullAll': {'buildings': buildings}}) + collection.update_one({"name": name}, {"$pullAll": {"buildings": buildings}}) return jsonify(responses.success_true) diff --git a/buildingdepot/CentralService/app/rest_api/helper.py b/buildingdepot/CentralService/app/rest_api/helper.py index e6b0e654..73105754 100755 --- a/buildingdepot/CentralService/app/rest_api/helper.py +++ b/buildingdepot/CentralService/app/rest_api/helper.py @@ -5,23 +5,27 @@ This module contains all the helper functions needed for the api's such as conversion of timestamps, strings etc. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ +import base64 +import requests +import smtplib from datetime import datetime +from flask import current_app, request, abort from functools import wraps -from flask import current_app, request, abort -from ..models.cs_models import TagType, DataService, User -from ..models.cs_models import Building, SensorGroup, Sensor -from ..oauth_bd.views import Token -import smtplib, requests, base64 from . import responses from .. import r +from ..models.cs_models import Building, SensorGroup, Sensor +from ..models.cs_models import TagType, DataService, User +from ..oauth_bd.views import Token url = "https://www.googleapis.com/oauth2/v3/token" -headers = {'content-type': 'application/x-www-form-urlencoded', - 'user-agent': 'BuildingDepot'} +headers = { + "content-type": "application/x-www-form-urlencoded", + "user-agent": "BuildingDepot", +} def add_delete(old, now): @@ -45,33 +49,40 @@ def xstr(s): def gen_update(params, data): """Takes in a list of params and searches for those - in the data dict. Forms a resultant dict that has - only those terms. - Arg as data: - params: list of params that can be accpeted - data: the data sent by the user - Returns: - dict containing only keys that were available - in the params list + in the data dict. Forms a resultant dict that has + only those terms. + Arg as data: + params: list of params that can be accpeted + data: the data sent by the user + Returns: + dict containing only keys that were available + in the params list """ result = {} - for key, value in data.iteritems(): + for key, value in list(data.items()): if key in params: result[key] = value return result def send_local_smtp(user_name, to_email, password): - sender = current_app.config['EMAIL_ID'] + sender = current_app.config["EMAIL_ID"] receivers = [to_email] - message = responses.registration_email % (sender, user_name, to_email, to_email, password, '/'.join(request.base_url.split('/')[:3])) + message = responses.registration_email % ( + sender, + user_name, + to_email, + to_email, + password, + "/".join(request.base_url.split("/")[:3]), + ) try: - smtpObj = smtplib.SMTP('localhost') + smtpObj = smtplib.SMTP("localhost") smtpObj.sendmail(sender, receivers, message) except smtplib.SMTPException: - print "Failed to send registration mail to %s" % (to_email) + print(("Failed to send registration mail to %s" % (to_email))) def GenerateOAuth2String(username, access_token, base64_encode=True): @@ -87,47 +98,59 @@ def GenerateOAuth2String(username, access_token, base64_encode=True): Returns: The SASL argument for the OAuth2 mechanism. """ - auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token) + auth_string = "user=%s\1auth=Bearer %s\1\1" % (username, access_token) if base64_encode: auth_string = base64.b64encode(auth_string) return auth_string def get_access_token(): - params = "client_id=" + current_app.config['CLIENT_ID'] - params += "&client_secret=" + current_app.config['CLIENT_SECRET'] - params += "&refresh_token=" + current_app.config['REFRESH_TOKEN'] + params = "client_id=" + current_app.config["CLIENT_ID"] + params += "&client_secret=" + current_app.config["CLIENT_SECRET"] + params += "&refresh_token=" + current_app.config["REFRESH_TOKEN"] params += "&grant_type=refresh_token" try: r = requests.post(url, params, headers=headers) - access_token = r.json()['access_token'] + access_token = r.json()["access_token"] return access_token except Exception as e: - print "Failed to obtain access token " + str(e) + print(("Failed to obtain access token " + str(e))) def send_mail_gmail(user_name, to_email, password): - print "getting access_token" + print("getting access_token") access_token = get_access_token() if access_token is None: return - sender = current_app.config['EMAIL_ID'] - print access_token - print sender + sender = current_app.config["EMAIL_ID"] + print(access_token) + print(sender) try: - smtp_conn = smtplib.SMTP('smtp.gmail.com', 587) - smtp_conn.ehlo('test') + smtp_conn = smtplib.SMTP("smtp.gmail.com", 587) + smtp_conn.ehlo("test") smtp_conn.starttls() - smtp_conn.docmd('AUTH', - 'XOAUTH2 ' + base64.b64encode(GenerateOAuth2String(sender, access_token, base64_encode=False))) - msg = responses.registration_email % (sender, user_name, to_email, to_email, password, '/'.join(request.base_url.split('/')[:3])) + smtp_conn.docmd( + "AUTH", + "XOAUTH2 " + + base64.b64encode( + GenerateOAuth2String(sender, access_token, base64_encode=False) + ), + ) + msg = responses.registration_email % ( + sender, + user_name, + to_email, + to_email, + password, + "/".join(request.base_url.split("/")[:3]), + ) smtp_conn.sendmail(sender, to_email, msg) except Exception as e: - print "Failed to send registration email to " + to_email + " " + str(e) + print(("Failed to send registration email to " + to_email + " " + str(e))) def get_email(): - """ Returns the email address of the user making the request + """Returns the email address of the user making the request based on the OAuth token Args as data: None - Get's OAuth token from the request context @@ -135,8 +158,8 @@ def get_email(): E-mail id of the user making the request """ headers = request.headers - token = headers['Authorization'][7:] - user = r.get(''.join(['oauth:', token])) + token = headers["Authorization"][7:] + user = r.get("".join(["oauth:", token])) if user: return user token = Token.objects(access_token=token).first() @@ -146,10 +169,10 @@ def get_email(): def check_if_super(email=None): if email is None: email = get_email() - if r.sismember('superusers', email): + if r.sismember("superusers", email): return True - if User.objects(email=email).first().role == 'super': - r.sadd('superusers', email) + if User.objects(email=email).first().role == "super": + r.sadd("superusers", email) return True return False @@ -160,38 +183,40 @@ def get_building_choices(call_type=None): buildings_list = [] for dataservice in dataservices: for building in dataservice.buildings: - print building + print(building) if building not in buildings_list: buildings_list.append(building) if not call_type: - return zip(buildings_list, buildings_list) + return list(zip(buildings_list, buildings_list)) else: return buildings_list def get_building_tags(building): """Get all the tags that this building has associated with it""" - tags = Building._get_collection().find({'name': building}, {'tags.name': 1, 'tags.value': 1, '_id': 0})[0]['tags'] + tags = Building._get_collection().find( + {"name": building}, {"tags.name": 1, "tags.value": 1, "_id": 0} + )[0]["tags"] res = {} for tag in tags: - if tag['name'] in res: - res[tag['name']]['values'].append(tag['value']) + if tag["name"] in res: + res[tag["name"]]["values"].append(tag["value"]) else: tagtype_dict = {} - tagtype_dict['values'] = [tag['value']] - tagtype_dict['acl_tag'] = TagType.objects(name=tag['name']).first().acl_tag - res[tag['name']] = tagtype_dict + tagtype_dict["values"] = [tag["value"]] + tagtype_dict["acl_tag"] = TagType.objects(name=tag["name"]).first().acl_tag + res[tag["name"]] = tagtype_dict return res def form_query(param, values, args, operation): res = [] - if param == 'tags': + if param == "tags": for tag in values: key_value = tag.split(":", 1) current_tag = {"tags.name": key_value[0], "tags.value": key_value[1]} res.append(current_tag) - elif param == 'metadata': + elif param == "metadata": for meta in values: key_value = meta.split(":", 1) current_meta = {"metadata." + key_value[0]: key_value[1]} @@ -214,13 +239,14 @@ def create_json(sensor): Formatted sensor object as below } """ - json_object = {'building': sensor.get('building'), - 'name': sensor.get('name'), - 'tags': sensor.get('tags'), - 'metadata': sensor.get('metadata'), - 'source_identifier': sensor.get('source_identifier'), - 'source_name': sensor.get('source_name') - } + json_object = { + "building": sensor.get("building"), + "name": sensor.get("name"), + "tags": sensor.get("tags"), + "metadata": sensor.get("metadata"), + "source_identifier": sensor.get("source_identifier"), + "source_name": sensor.get("source_name"), + } return json_object @@ -244,7 +270,7 @@ def validate_users(emails, list_format=False): """Check if user exists""" for email in emails: if not list_format: - if User.objects(email=email['user_id']).first() is None: + if User.objects(email=email["user_id"]).first() is None: return False else: if User.objects(email=email).first() is None: @@ -263,16 +289,18 @@ def get_admins(name): def add_delete_users(old, now): user_old, user_new = [], [] for user in old: - user_old.append(user['user_id']) + user_old.append(user["user_id"]) for user in now: - user_new.append(user['user_id']) + user_new.append(user["user_id"]) old, now = set(user_old), set(user_new) return now - old, old - now def get_ds(sensor, building=None): args = {} - args['buildings__all'] = [building if building else Sensor.objects(name=sensor).first().building] + args["buildings__all"] = [ + building if building else Sensor.objects(name=sensor).first().building + ] dataservices = DataService.objects(**args) return dataservices.first().name @@ -280,7 +308,7 @@ def get_ds(sensor, building=None): def get_sg_ds(sensor_group): args = {} sg = SensorGroup.objects(name=sensor_group).first() - args['buildings__all'] = [sg.building] + args["buildings__all"] = [sg.building] dataservices = DataService.objects(**args) return dataservices.first().name # sg = SensorGroup.objects(name=sensor_group).first() @@ -294,7 +322,7 @@ def secure(*args, **kwargs): abort(401) access_token = request.headers.get("Authorization")[7:] if request.headers.get("Authorization") is not None: - user = r.get(''.join(['oauth:', access_token])) + user = r.get("".join(["oauth:", access_token])) if user is not None: return f(*args, **kwargs) else: @@ -305,10 +333,15 @@ def secure(*args, **kwargs): expires_in = (token.expires - datetime.now()).total_seconds() if expires_in > 0: # Still valid, adding to redis - r.setex(''.join(['oauth:', access_token]), token.user, int(expires_in)) + r.setex( + "".join(["oauth:", access_token]), + int(expires_in), + token.user, + ) return f(*args, **kwargs) else: # Invalid, deleting token.delete() abort(401) + return secure diff --git a/buildingdepot/CentralService/app/rest_api/notifications/notification.py b/buildingdepot/CentralService/app/rest_api/notifications/notification.py index b042fd0e..c16d1128 100644 --- a/buildingdepot/CentralService/app/rest_api/notifications/notification.py +++ b/buildingdepot/CentralService/app/rest_api/notifications/notification.py @@ -4,15 +4,16 @@ This module handles setting up and managing notifications for a given ID. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import request, jsonify, Flask from flask.views import MethodView -from ...auth.views import Client, Token + from .. import responses from ..helper import check_oauth, get_email +from ...auth.views import Client, Token from ...models.cs_models import NotificationClientId @@ -28,15 +29,15 @@ def get_notification_client_id(client_email): class NotificationClientIdService(MethodView): @check_oauth def post(self): - data = request.get_json()['data'] - print 'We got the request' - print 'Want to save the ID ' + data['id'] - if data is None or data['id'] is None: + data = request.get_json()["data"] + print("We got the request") + print("Want to save the ID " + data["id"]) + if data is None or data["id"] is None: return jsonify(responses.missing_parameters) - print 'No params missing' + print("No params missing") if get_notification_client_id(get_email()) is None: - print 'Saving the ID for client ' + str(get_email()) - NotificationClientId(email=get_email(), client_id=data['id']).save() + print("Saving the ID for client " + str(get_email())) + NotificationClientId(email=get_email(), client_id=data["id"]).save() else: return jsonify(responses.client_id_already_exists) @@ -44,14 +45,16 @@ def post(self): @check_oauth def put(self): - data = request.get_json()['data'] + data = request.get_json()["data"] - if data is None or data['id'] is None: + if data is None or data["id"] is None: return jsonify(responses.missing_parameters) try: - notifications_collection = NotificationClientId.objects(email=get_email()).first() - notifications_collection.update(set__client_id=data['id']) + notifications_collection = NotificationClientId.objects( + email=get_email() + ).first() + notifications_collection.update_one(set__client_id=data["id"]) except RuntimeError as error: print("Error", error) - return jsonify(responses.success_true) \ No newline at end of file + return jsonify(responses.success_true) diff --git a/buildingdepot/CentralService/app/rest_api/notifications/push_notifications.py b/buildingdepot/CentralService/app/rest_api/notifications/push_notifications.py index 34a9eaac..be34a2a1 100644 --- a/buildingdepot/CentralService/app/rest_api/notifications/push_notifications.py +++ b/buildingdepot/CentralService/app/rest_api/notifications/push_notifications.py @@ -8,17 +8,19 @@ then this class must be extended in another file. After extending this class and implementing 'send', you must update cs_config to point to the implementation that should be loaded. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ -import pika import firebase_admin -from firebase_admin import messaging +import pika from firebase_admin import credentials +from firebase_admin import messaging + from ... import notification_type, firebase_credentials -class FirebaseNotification(): + +class FirebaseNotification: def __init__(self): try: self.cred = credentials.Certificate(firebase_credentials) @@ -26,56 +28,64 @@ def __init__(self): except ValueError: pass - def send(self, recipient_id, message, destination=''): - notification = messaging.Message(data={'notification_message': message}, token=recipient_id) + def send(self, recipient_id, message, destination=""): + notification = messaging.Message( + data={"notification_message": message}, token=recipient_id + ) response = messaging.send(notification) -class RabbitMQNotification(): + +class RabbitMQNotification: def connect_broker(self, destination): """ Args: destination: - Returns: + Returns: pubsub: object corresponding to the connection with the broker """ try: - pubsub = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) + pubsub = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost") + ) channel = pubsub.channel() - channel.exchange_declare(exchange=destination, type='direct') + channel.exchange_declare(exchange=destination, type="direct") channel.close() return pubsub except Exception as e: - print "Failed to open connection to broker " + str(e) + print("Failed to open connection to broker " + str(e)) return None - def send(self, recipient_id, message, destination=''): + def send(self, recipient_id, message, destination=""): pubsub = self.connect_broker(destination) channel = pubsub.channel() - channel.basic_publish(exchange=destination, routing_key=recipient_id, body=message) + channel.basic_publish( + exchange=destination, routing_key=recipient_id, body=message + ) if pubsub: try: channel.close() pubsub.close() except Exception as e: - print "Failed to end RabbitMQ session " + str(e) + print("Failed to end RabbitMQ session " + str(e)) + class PushNotification: firebase_instance = FirebaseNotification() rabbitmq_instance = RabbitMQNotification() - + @staticmethod def get_instance(): - if notification_type == "FIREBASE": - return PushNotification.firebase_instance - else: - return PushNotification.rabbitmq_instance + if notification_type == "FIREBASE": + return PushNotification.firebase_instance + else: + return PushNotification.rabbitmq_instance - def send(self, recipient_id, message, destination=''): + def send(self, recipient_id, message, destination=""): """ Sends a message to the push notification system selected during install. recipient_id (str) : message (str) : destination (str) : """ - raise NotImplementedError('Must override this method in a subclass') + raise NotImplementedError("Must override this method in a subclass") diff --git a/buildingdepot/CentralService/app/rest_api/permissions/permission.py b/buildingdepot/CentralService/app/rest_api/permissions/permission.py index d916e972..5d77a19e 100755 --- a/buildingdepot/CentralService/app/rest_api/permissions/permission.py +++ b/buildingdepot/CentralService/app/rest_api/permissions/permission.py @@ -5,22 +5,23 @@ This module handles interacting with the underlying permission models. It handles the required CRUD operations for permissions. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ import sys +from flask import request, jsonify from flask.views import MethodView -from ..helper import get_email, check_oauth + from .. import responses -from flask import request,jsonify -from ...models.cs_models import UserGroup,SensorGroup,Permission -from ... import r,oauth,permissions -from ...auth.acl_cache import invalidate_permission +from ..helper import get_email, check_oauth +from ... import r, oauth, permissions from ...auth.access_control import permission_allowed +from ...auth.acl_cache import invalidate_permission +from ...models.cs_models import UserGroup, SensorGroup, Permission from ...rpc import defs -class PermissionService(MethodView): +class PermissionService(MethodView): @check_oauth def get(self): """ @@ -34,17 +35,19 @@ def get(self): "error" : } """ - user_group = request.args.get('user_group') - sensor_group = request.args.get('sensor_group') + user_group = request.args.get("user_group") + sensor_group = request.args.get("sensor_group") if not all([user_group, sensor_group]): return jsonify(responses.missing_parameters) else: - permission = Permission.objects(user_group=user_group, sensor_group=sensor_group).first() + permission = Permission.objects( + user_group=user_group, sensor_group=sensor_group + ).first() if permission is None: return jsonify(responses.no_permission) else: response = dict(responses.success_true) - response['permission'] = permission.permission + response["permission"] = permission.permission return jsonify(response) @check_oauth @@ -61,13 +64,13 @@ def post(self): } """ try: - data = request.get_json()['data'] + data = request.get_json()["data"] except: return jsonify(responses.missing_data) try: - sensor_group = data['sensor_group'] - user_group = data['user_group'] - permission = data['permission'] + sensor_group = data["sensor_group"] + user_group = data["user_group"] + permission = data["permission"] except KeyError: return jsonify(responses.missing_parameters) @@ -82,22 +85,35 @@ def post(self): return jsonify(responses.no_sensorgroup_tags) email = get_email() if permission_allowed(sensor_group, email): - if defs.create_permission(user_group, sensor_group, email, permissions.get(permission)): - curr_permission = Permission.objects(user_group=user_group, sensor_group=sensor_group).first() + if defs.create_permission( + user_group, sensor_group, email, permissions.get(permission) + ): + curr_permission = Permission.objects( + user_group=user_group, sensor_group=sensor_group + ).first() if curr_permission is not None: - if email == curr_permission['owner']: - Permission.objects(user_group=user_group, - sensor_group=sensor_group).first().update( - set__permission=permissions.get(permission)) + if email == curr_permission["owner"]: + Permission.objects( + user_group=user_group, sensor_group=sensor_group + ).first().update(set__permission=permissions.get(permission)) else: return jsonify(responses.permission_authorization) else: - Permission(user_group=user_group, sensor_group=sensor_group, - permission=permissions.get(permission), - owner=email).save() + Permission( + user_group=user_group, + sensor_group=sensor_group, + permission=permissions.get(permission), + owner=email, + ).save() invalidate_permission(sensor_group) - r.hset('permission:{}:{}'.format(user_group, sensor_group), "permission", permissions.get(permission)) - r.hset('permission:{}:{}'.format(user_group, sensor_group), "owner", email) + r.hset( + "permission:{}:{}".format(user_group, sensor_group), + "permission", + permissions.get(permission), + ) + r.hset( + "permission:{}:{}".format(user_group, sensor_group), "owner", email + ) else: return jsonify(responses.ds_error) else: @@ -116,19 +132,21 @@ def delete(self): "error":
} """ - user_group = request.args.get('user_group') - sensor_group = request.args.get('sensor_group') + user_group = request.args.get("user_group") + sensor_group = request.args.get("sensor_group") if not all([user_group, sensor_group]): return jsonify(responses.missing_parameters) else: - permission = Permission.objects(user_group=user_group, sensor_group=sensor_group).first() + permission = Permission.objects( + user_group=user_group, sensor_group=sensor_group + ).first() if permission is None: return jsonify(responses.permission_not_defined) else: - if permission['owner'] == get_email(): - if defs.delete_permission(user_group,sensor_group): + if permission["owner"] == get_email(): + if defs.delete_permission(user_group, sensor_group): permission.delete() - r.delete('permission:{}:{}'.format(user_group, sensor_group)) + r.delete("permission:{}:{}".format(user_group, sensor_group)) invalidate_permission(sensor_group) else: return jsonify(responses.ds_error) @@ -149,22 +167,31 @@ def put(self): "error":
} """ - user_group = request.args.get('user_group') - sensor_group = request.args.get('sensor_group') - permission_setting = request.args.get('permission') + user_group = request.args.get("user_group") + sensor_group = request.args.get("sensor_group") + permission_setting = request.args.get("permission") email = get_email() if not all([user_group, sensor_group, permission_setting]): return jsonify(responses.missing_parameters) else: - permission = Permission.objects(user_group=user_group, sensor_group=sensor_group).first() + permission = Permission.objects( + user_group=user_group, sensor_group=sensor_group + ).first() if permission is None: return jsonify(responses.permission_not_defined) - elif not(self.permission_is_valid(permission_setting)): + elif not (self.permission_is_valid(permission_setting)): return jsonify(responses.permission_invalid_setting) else: - if permission['owner'] == email: + if permission["owner"] == email: if permission_allowed(sensor_group, email): - if defs.delete_permission(user_group,sensor_group) and defs.create_permission(user_group, sensor_group, email, permissions.get(permission_setting)): + if defs.delete_permission( + user_group, sensor_group + ) and defs.create_permission( + user_group, + sensor_group, + email, + permissions.get(permission_setting), + ): permission.permission = permissions.get(permission_setting) permission.save() return jsonify(responses.success_true) @@ -176,4 +203,9 @@ def put(self): return jsonify(responses.permission_modify_authorization) def permission_is_valid(self, permission_setting): - return permission_setting == 'r' or permission_setting == 'rw' or permission_setting == 'dr' or permission_setting == 'rwp' + return ( + permission_setting == "r" + or permission_setting == "rw" + or permission_setting == "dr" + or permission_setting == "rwp" + ) diff --git a/buildingdepot/CentralService/app/rest_api/permissions/permission_request.py b/buildingdepot/CentralService/app/rest_api/permissions/permission_request.py index e907aa0b..4229a7a9 100644 --- a/buildingdepot/CentralService/app/rest_api/permissions/permission_request.py +++ b/buildingdepot/CentralService/app/rest_api/permissions/permission_request.py @@ -5,21 +5,27 @@ This module handles interacting with the underlying permission models. It handles the required CRUD operations for permissions. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ +import hashlib +import json +import os +import pika +import traceback from flask import request, jsonify, Flask from flask.views import MethodView -from .. import responses from pymongo import MongoClient -from ...models.cs_models import Sensor, User, PermissionRequest -from ...auth.views import Client, Token + +from .. import responses from ..helper import form_query, create_response, check_oauth, get_email -from ... import oauth, app -import traceback, json, hashlib, pika, os from ..notifications.notification import get_notification_client_id from ..notifications.push_notifications import PushNotification +from ... import oauth, app +from ...auth.views import Client, Token +from ...models.cs_models import Sensor, User, PermissionRequest + class PermissionRequestService(MethodView): @check_oauth @@ -41,18 +47,20 @@ def get(self): email = get_email() timestamp_filter = 0 - if request.args.get('timestamp_filter') is not None: - timestamp_filter = int(request.args.get('timestamp_filter')) + if request.args.get("timestamp_filter") is not None: + timestamp_filter = int(request.args.get("timestamp_filter")) permission_request_history = PermissionRequest.objects(email=email) - request_results = { "permission_requests": [] } + request_results = {"permission_requests": []} if permission_request_history is not None: for permission_request in permission_request_history: timestamp = int(permission_request["timestamp"]) - + if timestamp >= timestamp_filter: - request_results["permission_requests"].append(permission_request["requests"]) + request_results["permission_requests"].append( + permission_request["requests"] + ) response = dict(responses.success_true) response.update(request_results) @@ -77,14 +85,14 @@ def map_sensors_to_owner(self, target_sensors): if tag.name == "parent": parent_sensor_uuid = tag.value parent_sensor = Sensor.objects(name=parent_sensor_uuid).first() - + sensor_owner = self.get_user_who_claimed(parent_sensor.tags) if sensor_owner is not None: if sensor_owner not in user_sensor_map: user_sensor_map[sensor_owner] = [] - user_sensor_map[sensor_owner].append(sensor_uuid) + user_sensor_map[sensor_owner].append(sensor_uuid) return user_sensor_map @@ -102,10 +110,18 @@ def get_sensor_objects_from_uuids(self, uuids): for uuid in uuids: sensor = Sensor.objects(name=uuid).first() - sensor_json = {"name": sensor.name.encode("ascii"), "source_name": sensor.source_name.encode("ascii"), "source_identifier": sensor.source_identifier.encode("ascii"), "building": sensor.building.encode("ascii"), "tags": {}} + sensor_json = { + "name": sensor.name.encode("ascii"), + "source_name": sensor.source_name.encode("ascii"), + "source_identifier": sensor.source_identifier.encode("ascii"), + "building": sensor.building.encode("ascii"), + "tags": {}, + } for tags in sensor.tags: - sensor_json["tags"][tags.name.encode("ascii")] = tags.value.encode("ascii") + sensor_json["tags"][tags.name.encode("ascii")] = tags.value.encode( + "ascii" + ) sensor_objects.append(sensor_json) @@ -120,9 +136,9 @@ def post(self): Returns: Success if the operation could be completed """ - data = request.get_json()['data'] - target_sensors = data['target_sensors'] - timestamp = data['timestamp'] + data = request.get_json()["data"] + target_sensors = data["target_sensors"] + timestamp = data["timestamp"] if not all([target_sensors, timestamp]): return jsonify(responses.missing_parameters) @@ -133,18 +149,31 @@ def post(self): for user in user_sensor_map: sensors = self.get_sensor_objects_from_uuids(user_sensor_map[user]) - print str(user) - permission_request_data = { "requester_name": str(requester.first_name) + " " + str(requester.last_name), "requester_email": str(get_email()), "requested_sensors": sensors } - PermissionRequest(email=user, timestamp=str(timestamp), requests=permission_request_data).save() + permission_request_data = { + "requester_name": str(requester.first_name) + + " " + + str(requester.last_name), + "requester_email": str(get_email()), + "requested_sensors": sensors, + } + PermissionRequest( + email=user, + timestamp=str(timestamp), + requests=permission_request_data, + ).save() try: for user_db in User._get_collection().find({"email": user}): permission_request_json = json.dumps(permission_request_data) - PushNotification.get_instance().send(get_notification_client_id(user), permission_request_json, destination="permission_requests") + PushNotification.get_instance().send( + get_notification_client_id(user), + permission_request_json, + destination="permission_requests", + ) except Exception as e: - traceback.print_exc() - print str(repr(e)) - return jsonify(responses.rabbit_mq_bind_error) - + traceback.print_exc() + print(str(repr(e))) + return jsonify(responses.rabbit_mq_bind_error) + return jsonify(responses.success_true) diff --git a/buildingdepot/CentralService/app/rest_api/permissions/permission_uuid.py b/buildingdepot/CentralService/app/rest_api/permissions/permission_uuid.py index 96eb2bf4..56c3efb8 100644 --- a/buildingdepot/CentralService/app/rest_api/permissions/permission_uuid.py +++ b/buildingdepot/CentralService/app/rest_api/permissions/permission_uuid.py @@ -5,17 +5,22 @@ This module handles interacting with the underlying permission models. It handles the required CRUD operations for permissions. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ +import hashlib +import json +import traceback +import uuid from flask import request, jsonify, Flask from flask.views import MethodView + from .. import responses -from ...models.cs_models import User from ..helper import form_query, create_response, check_oauth, get_email from ... import oauth -import traceback, json, hashlib, uuid +from ...models.cs_models import User + class PermissionRequestUUIDService(MethodView): @check_oauth @@ -26,6 +31,6 @@ def get(self): response = dict(responses.success_true) for user in results: - response.update({'id': str(user['_id'])}) + response.update({"id": str(user["_id"])}) return jsonify(response) diff --git a/buildingdepot/CentralService/app/rest_api/register.py b/buildingdepot/CentralService/app/rest_api/register.py index c1dabefd..c756e1a6 100755 --- a/buildingdepot/CentralService/app/rest_api/register.py +++ b/buildingdepot/CentralService/app/rest_api/register.py @@ -1,150 +1,232 @@ -from .tagtype import tagtype -from .buildingtemplate import buildingtemplate, buildingtemplate_tagtypes from .buildings import building, building_tags +from .buildingtemplate import buildingtemplate, buildingtemplate_tagtypes from .dataservices import dataservice, ds_buildings, ds_admins -from .users import user +from .notifications import notification +from .permissions import permission, permission_request, permission_uuid from .sensorgroups import sensorgroup, sg_tags -from .usergroups import ug_users, usergroup from .sensors import sensor, search, sensor_tags, sensor_views -from .permissions import permission, permission_request, permission_uuid -from .notifications import notification +from .tagtype import tagtype +from .usergroups import ug_users, usergroup +from .users import user def register_view(app_obj): - tagtype_view = tagtype.TagTypeService.as_view('tagtype_api') + tagtype_view = tagtype.TagTypeService.as_view("tagtype_api") # add/change tagtypes - app_obj.add_url_rule('/api/tagtype', view_func=tagtype_view, methods=['POST']) + app_obj.add_url_rule("/api/tagtype", view_func=tagtype_view, methods=["POST"]) # get info regarding a tagtype, delete a tag type - app_obj.add_url_rule('/api/tagtype/', view_func=tagtype_view, methods=['GET', 'DELETE']) + app_obj.add_url_rule( + "/api/tagtype/", view_func=tagtype_view, methods=["GET", "DELETE"] + ) - template_view = buildingtemplate.BuildingTemplateService.as_view('template_api') + template_view = buildingtemplate.BuildingTemplateService.as_view("template_api") # post creates/modifies building templates # get returns information on a specified building template. # delete deletes the template - app_obj.add_url_rule('/api/template', view_func=template_view, methods=['POST']) - app_obj.add_url_rule('/api/template/', view_func=template_view, methods=['GET', 'DELETE']) - - template_tagtypes_view = buildingtemplate_tagtypes.BuildingTemplateTagtypeService.as_view('template_tagtypes_api') + app_obj.add_url_rule("/api/template", view_func=template_view, methods=["POST"]) + app_obj.add_url_rule( + "/api/template/", view_func=template_view, methods=["GET", "DELETE"] + ) + + template_tagtypes_view = buildingtemplate_tagtypes.BuildingTemplateTagtypeService.as_view( + "template_tagtypes_api" + ) # post creates/modifies building templates # get returns information on a specified building template. # delete deletes the template - app_obj.add_url_rule('/api/template//tags', view_func=template_tagtypes_view, - methods=['GET', 'DELETE', 'POST', 'PUT']) + app_obj.add_url_rule( + "/api/template//tags", + view_func=template_tagtypes_view, + methods=["GET", "DELETE", "POST", "PUT"], + ) - building_view = building.BuildingService.as_view('building_api') + building_view = building.BuildingService.as_view("building_api") # post creates/modifies buildings # get returns information on a specified building. # delete deletes the building. - app_obj.add_url_rule('/api/building', view_func=building_view, methods=['POST']) - app_obj.add_url_rule('/api/building/', view_func=building_view, methods=['GET', 'DELETE']) + app_obj.add_url_rule("/api/building", view_func=building_view, methods=["POST"]) + app_obj.add_url_rule( + "/api/building/", view_func=building_view, methods=["GET", "DELETE"] + ) - building_tags_view = building_tags.BuildingTagsService.as_view('building_tags_api') + building_tags_view = building_tags.BuildingTagsService.as_view("building_tags_api") # get a list of tags associated with a specified building # post changes/adds tags to a specified building # delete removes a tag from a specified building - app_obj.add_url_rule('/api/building//tags', view_func=building_tags_view, - methods=['POST', 'GET', 'DELETE']) + app_obj.add_url_rule( + "/api/building//tags", + view_func=building_tags_view, + methods=["POST", "GET", "DELETE"], + ) - dataservice_view = dataservice.DataserviceService.as_view('dataservice_api') + dataservice_view = dataservice.DataserviceService.as_view("dataservice_api") # post changes/adds a data service to a central service # get returns info on the specified data service # delete unregisters a data service from a central service - app_obj.add_url_rule('/api/dataservice', view_func=dataservice_view, methods=['POST']) - app_obj.add_url_rule('/api/dataservice/', view_func=dataservice_view, methods=['GET', 'DELETE']) - - ds_buildings_view = ds_buildings.DataserviceBuildingsService.as_view('ds_buildings_api') + app_obj.add_url_rule( + "/api/dataservice", view_func=dataservice_view, methods=["POST"] + ) + app_obj.add_url_rule( + "/api/dataservice/", view_func=dataservice_view, methods=["GET", "DELETE"] + ) + + ds_buildings_view = ds_buildings.DataserviceBuildingsService.as_view( + "ds_buildings_api" + ) # post changes/adds a building to a data service # get returns info on buildings registered to the specified data service # delete removes a building from a data service - app_obj.add_url_rule('/api/dataservice//buildings', view_func=ds_buildings_view, - methods=['POST', 'GET', 'DELETE']) + app_obj.add_url_rule( + "/api/dataservice//buildings", + view_func=ds_buildings_view, + methods=["POST", "GET", "DELETE"], + ) - ds_admins_view = ds_admins.DataserviceAdminService.as_view('ds_admins_api') + ds_admins_view = ds_admins.DataserviceAdminService.as_view("ds_admins_api") # handles interactions between admins in data services - app_obj.add_url_rule('/api/dataservice//admins', view_func=ds_admins_view, methods=['POST', 'GET', 'DELETE']) + app_obj.add_url_rule( + "/api/dataservice//admins", + view_func=ds_admins_view, + methods=["POST", "GET", "DELETE"], + ) - sensor_view = sensor.SensorService.as_view('sensor_api') + sensor_view = sensor.SensorService.as_view("sensor_api") # get sensor by name - app_obj.add_url_rule('/api/sensor/', view_func=sensor_view, methods=['GET', 'DELETE']) + app_obj.add_url_rule( + "/api/sensor/", view_func=sensor_view, methods=["GET", "DELETE"] + ) # create a new sensor - app_obj.add_url_rule('/api/sensor', view_func=sensor_view, methods=['POST']) + app_obj.add_url_rule("/api/sensor", view_func=sensor_view, methods=["POST"]) - sensor_owned_view = sensor.SensorOwnedService.as_view('sensor_owned_api') + sensor_owned_view = sensor.SensorOwnedService.as_view("sensor_owned_api") # get all sensors owned and have permission to - app_obj.add_url_rule('/api/sensor', view_func=sensor_owned_view, methods=['GET']) + app_obj.add_url_rule("/api/sensor", view_func=sensor_owned_view, methods=["GET"]) - sensor_view_view = sensor_views.SensorViewService.as_view('sensor_view_api') + sensor_view_view = sensor_views.SensorViewService.as_view("sensor_view_api") # get views of sensor by name - app_obj.add_url_rule('/api/sensor//views', view_func=sensor_view_view, methods=['GET']) + app_obj.add_url_rule( + "/api/sensor//views", view_func=sensor_view_view, methods=["GET"] + ) # delete views of sensor by name - app_obj.add_url_rule('/api/sensor//views/', view_func=sensor_view_view, methods=['DELETE']) + app_obj.add_url_rule( + "/api/sensor//views/", + view_func=sensor_view_view, + methods=["DELETE"], + ) # create a new views for sensor - app_obj.add_url_rule('/api/sensor//views', view_func=sensor_view_view, methods=['POST']) + app_obj.add_url_rule( + "/api/sensor//views", view_func=sensor_view_view, methods=["POST"] + ) - sensortags_view = sensor_tags.SensorTagsService.as_view('sensortags_api') + sensortags_view = sensor_tags.SensorTagsService.as_view("sensortags_api") # get gets a sensor's tags # post changes/adds to a sensor's tags - app_obj.add_url_rule('/api/sensor//tags', view_func=sensortags_view, methods=['GET', 'POST']) + app_obj.add_url_rule( + "/api/sensor//tags", view_func=sensortags_view, methods=["GET", "POST"] + ) - sensorgroup_view = sensorgroup.SensorGroupService.as_view('sensorgroup_api') + sensorgroup_view = sensorgroup.SensorGroupService.as_view("sensorgroup_api") # post creates an new sensor group - app_obj.add_url_rule('/api/sensor_group', view_func=sensorgroup_view, methods=['POST']) + app_obj.add_url_rule( + "/api/sensor_group", view_func=sensorgroup_view, methods=["POST"] + ) # get a list of sensors in a specified sensor group - app_obj.add_url_rule('/api/sensor_group/', view_func=sensorgroup_view, methods=['GET']) + app_obj.add_url_rule( + "/api/sensor_group/", view_func=sensorgroup_view, methods=["GET"] + ) # delete a sensor_group - app_obj.add_url_rule('/api/sensor_group/', view_func=sensorgroup_view, methods=['DELETE']) + app_obj.add_url_rule( + "/api/sensor_group/", view_func=sensorgroup_view, methods=["DELETE"] + ) - sensorgroup_owned_view = sensorgroup.SensorGroupOwnedService.as_view('sensorgroup_owned_api') + sensorgroup_owned_view = sensorgroup.SensorGroupOwnedService.as_view( + "sensorgroup_owned_api" + ) # get all sensor groups owned by the requesting user - app_obj.add_url_rule('/api/sensor_group', view_func=sensorgroup_owned_view, methods=['GET']) + app_obj.add_url_rule( + "/api/sensor_group", view_func=sensorgroup_owned_view, methods=["GET"] + ) - sgtags_view = sg_tags.SensorGroupTagsService.as_view('sgtags_api') + sgtags_view = sg_tags.SensorGroupTagsService.as_view("sgtags_api") # get a list of tags in a specified sensor group # post changes/adds tags to a specified sensor group - app_obj.add_url_rule('/api/sensor_group//tags', view_func=sgtags_view, methods=['GET', 'POST']) + app_obj.add_url_rule( + "/api/sensor_group//tags", view_func=sgtags_view, methods=["GET", "POST"] + ) - usergroup_view = usergroup.UserGroupService.as_view('usergroup_api') + usergroup_view = usergroup.UserGroupService.as_view("usergroup_api") # post creates a new user group - app_obj.add_url_rule('/api/user_group', view_func=usergroup_view, methods=['POST']) - app_obj.add_url_rule('/api/user_group/', view_func=usergroup_view, methods=['GET']) + app_obj.add_url_rule("/api/user_group", view_func=usergroup_view, methods=["POST"]) + app_obj.add_url_rule( + "/api/user_group/", view_func=usergroup_view, methods=["GET"] + ) # delete a user group - app_obj.add_url_rule('/api/user_group/', view_func=usergroup_view, methods=['DELETE']) + app_obj.add_url_rule( + "/api/user_group/", view_func=usergroup_view, methods=["DELETE"] + ) - usergroup_owned_view = usergroup.UserGroupOwnedService.as_view('usergroup_owned_api') + usergroup_owned_view = usergroup.UserGroupOwnedService.as_view( + "usergroup_owned_api" + ) # get all user groups owned by the requester - app_obj.add_url_rule('/api/user_group', view_func=usergroup_owned_view, methods=['GET']) + app_obj.add_url_rule( + "/api/user_group", view_func=usergroup_owned_view, methods=["GET"] + ) - ugusers_view = ug_users.UserGroupUsersService.as_view('ugusers_api') + ugusers_view = ug_users.UserGroupUsersService.as_view("ugusers_api") # get a list of users in the user group # post add/change users in a usergroup - app_obj.add_url_rule('/api/user_group//users', view_func=ugusers_view, methods=['GET', 'POST']) + app_obj.add_url_rule( + "/api/user_group//users", view_func=ugusers_view, methods=["GET", "POST"] + ) - permission_view = permission.PermissionService.as_view('permission_service') + permission_view = permission.PermissionService.as_view("permission_service") # get the permission of a permission-pair (user_group/sensor_group pair) # post sets the value of the permission-pair to an eligable value: r r/w r/w/p d/w - app_obj.add_url_rule('/api/permission', view_func=permission_view, methods=['GET', 'POST', 'DELETE', 'PUT']) - - permission_request_view = permission_request.PermissionRequestService.as_view('permission_request_service') + app_obj.add_url_rule( + "/api/permission", + view_func=permission_view, + methods=["GET", "POST", "DELETE", "PUT"], + ) + + permission_request_view = permission_request.PermissionRequestService.as_view( + "permission_request_service" + ) # Push a notification to a mite owner that the user making this request wants permission to their mites - app_obj.add_url_rule('/api/permission/request', view_func=permission_request_view, methods=['GET', 'POST']) + app_obj.add_url_rule( + "/api/permission/request", + view_func=permission_request_view, + methods=["GET", "POST"], + ) permission_uuid_request_view = permission_uuid.PermissionRequestUUIDService.as_view( - 'permission_request_uuid_service') + "permission_request_uuid_service" + ) # Get or create UUIDs for permission request RabbitMQ queues - app_obj.add_url_rule('/api/permission/request/uuid', view_func=permission_uuid_request_view, methods=['GET']) + app_obj.add_url_rule( + "/api/permission/request/uuid", + view_func=permission_uuid_request_view, + methods=["GET"], + ) - search_view = search.SearchService.as_view('search_service') + search_view = search.SearchService.as_view("search_service") # sensor search - search for sensors using the following keywords # 'Building', 'SourceName', 'SourceIdentifier', 'ID', 'Tags', 'MetaData' - app_obj.add_url_rule('/api/sensor/search', view_func=search_view, methods=['POST']) + app_obj.add_url_rule("/api/sensor/search", view_func=search_view, methods=["POST"]) - users_view = user.UserService.as_view('users_api') + users_view = user.UserService.as_view("users_api") # get returns info on the specified user # post changes/adds information to a specified user - if the user is new, an email will be sent # delete removes a user - app_obj.add_url_rule('/api/user', view_func=users_view, methods=['POST']) - app_obj.add_url_rule('/api/user/', view_func=users_view, methods=['GET', 'DELETE']) - - notifications_view = notification.NotificationClientIdService.as_view('notification_client_id_service') + app_obj.add_url_rule("/api/user", view_func=users_view, methods=["POST"]) + app_obj.add_url_rule( + "/api/user/", view_func=users_view, methods=["GET", "DELETE"] + ) + + notifications_view = notification.NotificationClientIdService.as_view( + "notification_client_id_service" + ) # Create, get or modify an ID used in the notification system - app_obj.add_url_rule('/api/notification/id', view_func=notifications_view, methods=['POST', 'PUT']) + app_obj.add_url_rule( + "/api/notification/id", view_func=notifications_view, methods=["POST", "PUT"] + ) diff --git a/buildingdepot/CentralService/app/rest_api/responses.py b/buildingdepot/CentralService/app/rest_api/responses.py index d09cf64e..5e0fca2e 100755 --- a/buildingdepot/CentralService/app/rest_api/responses.py +++ b/buildingdepot/CentralService/app/rest_api/responses.py @@ -6,100 +6,174 @@ to the user under certain failure and success conditions are defined here in this file -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ # common responses -success_true = {'success': 'True'} -missing_data = {'success': 'False', 'error': 'Missing data'} -missing_parameters = {'success': 'False', 'error': 'Missing parameters'} -invalid_building = {'success': 'False', 'error': 'Building does not exist'} -ds_error = {'success': 'False', 'error': 'Communication failure with DataService'} +success_true = {"success": "True"} +missing_data = {"success": "False", "error": "Missing data"} +missing_parameters = {"success": "False", "error": "Missing parameters"} +invalid_building = {"success": "False", "error": "Building does not exist"} +ds_error = {"success": "False", "error": "Communication failure with DataService"} # user-related responses: -inactive_user = {'success': 'False', 'error': 'The user you are attempting to interact with is not active'} +inactive_user = { + "success": "False", + "error": "The user you are attempting to interact with is not active", +} # building API common responses: -invalid_tagtype = {'success': 'False', 'error': 'TagType does not exist'} -invalid_template = {'success': 'False', 'error': 'BuildingTemplate does not exist'} -tagtype_referenced = {'success': 'False', 'error': 'This tag value is being referenced, cannot delete'} +invalid_tagtype = {"success": "False", "error": "TagType does not exist"} +invalid_template = {"success": "False", "error": "BuildingTemplate does not exist"} +tagtype_referenced = { + "success": "False", + "error": "This tag value is being referenced, cannot delete", +} # building API specific responses: # building post/get/deletion API responses -building_in_use = {'success': 'False', 'error': 'Building is in use'} +building_in_use = {"success": "False", "error": "Building is in use"} # building-tags post/get/deletion API responses -invalid_parents = {'success': 'False', 'error': 'One of the parent tags specified does not exist'} -invalid_tag_value = {'success': 'False', 'error': 'Tag value does not exist'} +invalid_parents = { + "success": "False", + "error": "One of the parent tags specified does not exist", +} +invalid_tag_value = {"success": "False", "error": "Tag value does not exist"} # building-template post/get/deletion API responses -invalid_tagtypes = {'success': 'False', 'error': 'One of the tagtypes does not exist'} -tagtype_in_use = {'success': 'False', 'error': 'Tagtype is in use, cannot be removed from template'} -template_in_use = {'success': 'False', 'error': 'BuildingTemplate is in use'} +invalid_tagtypes = {"success": "False", "error": "One of the tagtypes does not exist"} +tagtype_in_use = { + "success": "False", + "error": "Tagtype is in use, cannot be removed from template", +} +template_in_use = {"success": "False", "error": "BuildingTemplate is in use"} # building-tagtype post/get/deletion API responses -invalid_parent_tags = {'success': 'False', 'error': 'List of parents tags not valid'} +invalid_parent_tags = {"success": "False", "error": "List of parents tags not valid"} # data-service API common responses -invalid_dataservice = {'success': 'False', 'error': 'DataService does not exist'} +invalid_dataservice = {"success": "False", "error": "DataService does not exist"} # dataservice API specific responses: # data-service post/get/deletion API responses -dataservice_in_use = {'success': 'False', 'error': 'Cannot delete DataService, contains buildings'} +dataservice_in_use = { + "success": "False", + "error": "Cannot delete DataService, contains buildings", +} # ds-admin post/get/deletion API responses -ds_invalid_admin = {'success': 'False', 'error': 'One of the users does not exist'} +ds_invalid_admin = {"success": "False", "error": "One of the users does not exist"} # ds-building post/get/deletion API responses -ds_invalid_building = {'success': 'False', 'error': 'One of the buildings does not exist'} +ds_invalid_building = { + "success": "False", + "error": "One of the buildings does not exist", +} # ACL permissions API responses: -permission_not_allowed = {'success': 'False', 'error': 'Not authorized to create permissions for the specified SensorGroup tags'} -no_permission = {'success': 'False', 'error': 'Permission does not exist'} -no_usergroup = {'success': 'False', 'error': 'User group does not exist'} -no_sensorgroup = {'success': 'False', 'error': 'Sensor group does not exist'} -no_permission_val = {'success': 'False', 'error': 'Permission value does not exist'} -permission_authorization = {'success': 'False', 'error': 'Not authorized to modify this permission'} -permission_not_defined = {'success': 'False', 'error': 'Permission is not defined'} -permission_del_authorization = {'success': 'False', 'error': 'Not authorized to delete this permission'} -permission_modify_authorization = {'success': 'False', 'error': 'Not authorized to modify this permission'} -permission_invalid_setting = {'success': 'False', 'error': 'Invalid permission setting value'} -no_permission_id = {'success': 'False', 'error': 'User does not have an ID to begin listening for permission requests'} +permission_not_allowed = { + "success": "False", + "error": "Not authorized to create permissions for the specified SensorGroup tags", +} +no_permission = {"success": "False", "error": "Permission does not exist"} +no_usergroup = {"success": "False", "error": "User group does not exist"} +no_sensorgroup = {"success": "False", "error": "Sensor group does not exist"} +no_permission_val = {"success": "False", "error": "Permission value does not exist"} +permission_authorization = { + "success": "False", + "error": "Not authorized to modify this permission", +} +permission_not_defined = {"success": "False", "error": "Permission is not defined"} +permission_del_authorization = { + "success": "False", + "error": "Not authorized to delete this permission", +} +permission_modify_authorization = { + "success": "False", + "error": "Not authorized to modify this permission", +} +permission_invalid_setting = { + "success": "False", + "error": "Invalid permission setting value", +} +no_permission_id = { + "success": "False", + "error": "User does not have an ID to begin listening for permission requests", +} # SensorGroup API responses -sensorgroup_exists = {'success': 'False', 'error': 'Sensorgroup already exists'} -no_sensorgroup_tags = {'success': 'False', 'error': 'Cannot create permissions for a SensorGroup with no tags'} -invalid_sensorgroup = {'success': 'False', 'error': 'Sensorgroup does not exist'} -sensorgroup_used = {'success': 'False', 'error': 'Sensor group tags cannot be edited. Already being used for ' - 'permissions'} -invalid_tag_permission = {'success': 'False', 'error': 'Tag value cannot be used for permissions'} -sensorgroup_delete_authorization = {'success': 'False', 'error': 'Not authorized to delete Sensorgroup'} +sensorgroup_exists = {"success": "False", "error": "Sensorgroup already exists"} +no_sensorgroup_tags = { + "success": "False", + "error": "Cannot create permissions for a SensorGroup with no tags", +} +invalid_sensorgroup = {"success": "False", "error": "Sensorgroup does not exist"} +sensorgroup_used = { + "success": "False", + "error": "Sensor group tags cannot be edited. Already being used for " + "permissions", +} +invalid_tag_permission = { + "success": "False", + "error": "Tag value cannot be used for permissions", +} +sensorgroup_delete_authorization = { + "success": "False", + "error": "Not authorized to delete Sensorgroup", +} # Sensors API responses -no_search_parameters = {'success': 'False', 'error': 'No search parameters specified'} -invalid_uuid = {'success': 'False', 'error': 'Sensor does not exist'} +no_search_parameters = {"success": "False", "error": "No search parameters specified"} +invalid_uuid = {"success": "False", "error": "Sensor does not exist"} # UserGroup API responses -invalid_usergroup = {'success': 'False', 'error': 'Usergroup does not exist'} -usergroup_add_authorization = {'success': 'False', 'error': 'Not authorized for adding users to user group'} -user_not_registered = {'success': 'False', 'error': 'One or more users not registered'} -usergroup_exists = {'success': 'False', 'error': 'Usergroup already exists'} -usergroup_delete_authorization = {'success': 'False', 'error': 'Not authorized to delete user group'} +invalid_usergroup = {"success": "False", "error": "Usergroup does not exist"} +usergroup_add_authorization = { + "success": "False", + "error": "Not authorized for adding users to user group", +} +user_not_registered = {"success": "False", "error": "One or more users not registered"} +usergroup_exists = {"success": "False", "error": "Usergroup already exists"} +usergroup_delete_authorization = { + "success": "False", + "error": "Not authorized to delete user group", +} # User creation API responses -unauthorized_user_lookup = {'success': 'False', 'error': 'Not authorized to get information on this user'} -invalid_user_role = {'success': 'False', 'error': 'User role should be either default or super'} -invalid_user = {'success': 'False', 'error': 'User does not exist'} -user_exists = {'success': 'False', 'error': 'User already exists'} -super_user_required = {'success': 'False', 'error': 'Super user privileges required. Not authorized for this ' - 'operation'} +unauthorized_user_lookup = { + "success": "False", + "error": "Not authorized to get information on this user", +} +invalid_user_role = { + "success": "False", + "error": "User role should be either default or super", +} +invalid_user = {"success": "False", "error": "User does not exist"} +user_exists = {"success": "False", "error": "User already exists"} +super_user_required = { + "success": "False", + "error": "Super user privileges required. Not authorized for this " "operation", +} # RabbitMQ error responses -rabbit_mq_bind_error = {'success': 'False', 'error': 'Unable to bind rabbitmq queue. Check params are valid'} +rabbit_mq_bind_error = { + "success": "False", + "error": "Unable to bind rabbitmq queue. Check params are valid", +} # Notification error responses -client_id_already_exists = { 'success': 'False', 'error': 'The user already has a client notification ID' } +client_id_already_exists = { + "success": "False", + "error": "The user already has a client notification ID", +} # not used -missing_template = {'success': 'False', 'error': 'Template is not specified'} -tagtype_name_change_invalid = {'success': 'False', 'error': 'Cannot change name as TagType is already in use'} -acl_tag_superuser = {'success': 'False', 'error': 'Super user privileges required. Not authorized to change ' - 'the usage of this tag'} -registration_email = '''From: Admin <%s> +missing_template = {"success": "False", "error": "Template is not specified"} +tagtype_name_change_invalid = { + "success": "False", + "error": "Cannot change name as TagType is already in use", +} +acl_tag_superuser = { + "success": "False", + "error": "Super user privileges required. Not authorized to change " + "the usage of this tag", +} +registration_email = """From: Admin <%s> To: %s <%s> Subject: Password for BuildingDepot account @@ -109,4 +183,4 @@ Hostname: %s Please register and change your password immediately. -''' +""" diff --git a/buildingdepot/CentralService/app/rest_api/sensorgroups/sensorgroup.py b/buildingdepot/CentralService/app/rest_api/sensorgroups/sensorgroup.py index 230a5ce4..ab0472b6 100755 --- a/buildingdepot/CentralService/app/rest_api/sensorgroups/sensorgroup.py +++ b/buildingdepot/CentralService/app/rest_api/sensorgroups/sensorgroup.py @@ -8,21 +8,21 @@ list of the sensors that fall in this group. This list is further used for acl's and other purposes. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ +import sys +from flask import request, jsonify, current_app from flask.views import MethodView -from flask import request,jsonify,current_app -from ...rpc import defs from .. import responses -from ...models.cs_models import SensorGroup -from ... import r,oauth from ..helper import xstr, get_building_choices, get_email, check_oauth -import sys +from ... import r, oauth +from ...models.cs_models import SensorGroup +from ...rpc import defs -class SensorGroupService(MethodView): +class SensorGroupService(MethodView): @check_oauth def post(self): """ @@ -38,10 +38,10 @@ def post(self): } """ try: - data = request.get_json()['data'] - name = data['name'] - building = data['building'] - description = data['description'] + data = request.get_json()["data"] + name = data["name"] + building = data["building"] + description = data["description"] except KeyError: return jsonify(responses.missing_parameters) @@ -54,17 +54,21 @@ def post(self): # Get the list of buildings and verify that the one specified in the # request exists - buildings_list = get_building_choices('rest_api') + buildings_list = get_building_choices("rest_api") for item in buildings_list: if building in item: - SensorGroup(name=xstr(name), building=xstr(building), - description=xstr(description), owner=xstr(owner)).save() + SensorGroup( + name=xstr(name), + building=xstr(building), + description=xstr(description), + owner=xstr(owner), + ).save() return jsonify(responses.success_true) return jsonify(responses.invalid_building) @check_oauth - def get(self,name): + def get(self, name): """ Args as data: name = @@ -82,13 +86,17 @@ def get(self,name): return jsonify(responses.invalid_sensorgroup) response = dict(responses.success_true) - response.update({"name":sensor_group['name'], - "building":sensor_group['building'], - "description":sensor_group['description']}) + response.update( + { + "name": sensor_group["name"], + "building": sensor_group["building"], + "description": sensor_group["description"], + } + ) return jsonify(response) @check_oauth - def delete(self,name): + def delete(self, name): """ Args as data: name = @@ -103,12 +111,12 @@ def delete(self,name): if sensor_group is None: return jsonify(responses.invalid_usergroup) if email == sensor_group.owner and defs.invalidate_permission(name): - SensorGroup._get_collection().remove({"name":sensor_group['name']}) + SensorGroup._get_collection().delete_one({"name": sensor_group["name"]}) response = dict(responses.success_true) else: response = dict(responses.sensorgroup_delete_authorization) return jsonify(response) - + class SensorGroupOwnedService(MethodView): @check_oauth @@ -132,7 +140,17 @@ def get(self): response = dict(responses.success_true) for sensor_group in sensor_groups: - result.append({"name":sensor_group['name'], "building":sensor_group['building'], "description":sensor_group['description'], "tags":[{'name': tag.name, 'value': tag.value} for tag in sensor_group.tags]}) - - response.update({"result":result}) + result.append( + { + "name": sensor_group["name"], + "building": sensor_group["building"], + "description": sensor_group["description"], + "tags": [ + {"name": tag.name, "value": tag.value} + for tag in sensor_group.tags + ], + } + ) + + response.update({"result": result}) return jsonify(response) diff --git a/buildingdepot/CentralService/app/rest_api/sensorgroups/sg_tags.py b/buildingdepot/CentralService/app/rest_api/sensorgroups/sg_tags.py index eab88ff6..451dfda1 100755 --- a/buildingdepot/CentralService/app/rest_api/sensorgroups/sg_tags.py +++ b/buildingdepot/CentralService/app/rest_api/sensorgroups/sg_tags.py @@ -7,24 +7,25 @@ it updates the cache where a list is maintained of the sensors that fall in each group. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ import sys +from flask import request, jsonify from flask.views import MethodView -from flask import request,jsonify + from .. import responses -from ..helper import add_delete,get_building_tags, check_oauth -from ...models.cs_models import SensorGroup,Permission -from ... import r,oauth +from ..helper import add_delete, get_building_tags, check_oauth +from ... import r, oauth from ...auth.access_control import authenticate_acl +from ...models.cs_models import SensorGroup, Permission from ...rpc import defs -class SensorGroupTagsService(MethodView): +class SensorGroupTagsService(MethodView): @check_oauth - def get(self,name): + def get(self, name): """ Args as data: "name" : @@ -50,14 +51,14 @@ def get(self,name): obj = SensorGroup.objects(name=name).first() if obj is None: return jsonify(responses.invalid_sensorgroup) - tags_owned = [{'name': tag.name, 'value': tag.value} for tag in obj.tags] + tags_owned = [{"name": tag.name, "value": tag.value} for tag in obj.tags] tags = get_building_tags(obj.building) response = dict(responses.success_true) - response.update({'tags': tags, 'tags_owned': tags_owned}) + response.update({"tags": tags, "tags_owned": tags_owned}) return jsonify(response) @check_oauth - def post(self,name): + def post(self, name): """ Args as data: "name" : @@ -82,13 +83,13 @@ def post(self,name): if Permission.objects(sensor_group=name).first() is not None: return jsonify(responses.sensorgroup_used) try: - tags = request.get_json()['data']['tags'] + tags = request.get_json()["data"]["tags"] except KeyError: return jsonify(responses.missing_data) sensorgroup = SensorGroup.objects(name=name).first() if sensorgroup is None: return jsonify(responses.invalid_sensorgroup) - validate_tags = self.check_tags(tags,sensorgroup.building) + validate_tags = self.check_tags(tags, sensorgroup.building) if validate_tags is not None: return validate_tags if defs.invalidate_permission(name): @@ -97,22 +98,19 @@ def post(self,name): return jsonify(responses.ds_error) return jsonify(responses.success_true) - def check_tags(self,tags,building): + def check_tags(self, tags, building): building_tags = get_building_tags(building) - print building_tags - print tags + print(building_tags) + print(tags) for tag in tags: - tagtype = building_tags.get(tag.get('name')) - print tagtype + tagtype = building_tags.get(tag.get("name")) + print(tagtype) if tagtype is None: return jsonify(responses.invalid_tagtype) - print tag.get('value') - tag_values = tagtype.get('values') - if tag.get('value') not in tag_values: + print((tag.get("value"))) + tag_values = tagtype.get("values") + if tag.get("value") not in tag_values: return jsonify(responses.invalid_tag_value) - if tagtype.get('acl_tag') is False: + if tagtype.get("acl_tag") is False: return jsonify(responses.invalid_tag_permission) return None - - - diff --git a/buildingdepot/CentralService/app/rest_api/sensors/search.py b/buildingdepot/CentralService/app/rest_api/sensors/search.py index 832ae3ec..0137d27f 100755 --- a/buildingdepot/CentralService/app/rest_api/sensors/search.py +++ b/buildingdepot/CentralService/app/rest_api/sensors/search.py @@ -6,42 +6,43 @@ search for Sensors based on a combination of the parameters that a sensor contains such as Tags,Building,Source identifier,uuid etc. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import request, jsonify from flask.views import MethodView + from .. import responses -from ...models.cs_models import Sensor from ..helper import form_query, create_response, check_oauth from ... import oauth +from ...models.cs_models import Sensor class SearchService(MethodView): @check_oauth def post(self): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) args = {} - for key, values in data.iteritems(): - if key == 'Building': - form_query('building', values, args, "$and") - elif key == 'SourceName': - form_query('source_name', values, args, "$and") - elif key == 'SourceIdentifier': - form_query('source_identifier', values, args, "$and") - elif key == 'Owner': - form_query('owner', values, args, "$and") - elif key == 'ID': - form_query('name', values, args, "$and") - elif key == 'Tags': - form_query('tags', values, args, "$and") - elif key == 'MetaData': - form_query('metadata', values, args, "$and") + for key, values in list(data.items()): + if key == "Building": + form_query("building", values, args, "$and") + elif key == "SourceName": + form_query("source_name", values, args, "$and") + elif key == "SourceIdentifier": + form_query("source_identifier", values, args, "$and") + elif key == "Owner": + form_query("owner", values, args, "$and") + elif key == "ID": + form_query("name", values, args, "$and") + elif key == "Tags": + form_query("tags", values, args, "$and") + elif key == "MetaData": + form_query("metadata", values, args, "$and") if not args: return jsonify(responses.no_search_parameters) collection = Sensor._get_collection().find(args) diff --git a/buildingdepot/CentralService/app/rest_api/sensors/sensor.py b/buildingdepot/CentralService/app/rest_api/sensors/sensor.py index 31111220..c488c13e 100755 --- a/buildingdepot/CentralService/app/rest_api/sensors/sensor.py +++ b/buildingdepot/CentralService/app/rest_api/sensors/sensor.py @@ -7,22 +7,22 @@ retrieving sensor details. It manages the underlying cache, and will ensure that the cache gets updated as needed. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import request, jsonify from flask.views import MethodView -from .. import responses -from ...models.cs_models import Sensor, UserGroup, Permission, SensorGroup from uuid import uuid4 -from ... import r, oauth + +from .. import responses from ..helper import get_email, xstr, get_building_choices, check_oauth +from ... import r, oauth +from ...models.cs_models import Sensor, UserGroup, Permission, SensorGroup from ...rpc import defs class SensorService(MethodView): - @check_oauth def get(self, name): """ @@ -47,17 +47,22 @@ def get(self, name): sensor = Sensor.objects(name=name).first() if sensor is None: return jsonify(responses.invalid_uuid) - tags_owned = [{'name': tag.name, 'value': tag.value} for tag in sensor.tags] - metadata = Sensor._get_collection().find({'name': name}, {'metadata': 1, '_id': 0})[0]['metadata'] - metadata = [{'name': key, 'value': val} for key, val in metadata.iteritems()] + tags_owned = [{"name": tag.name, "value": tag.value} for tag in sensor.tags] + metadata = Sensor._get_collection().find( + {"name": name}, {"metadata": 1, "_id": 0} + )[0]["metadata"] + metadata = [{"name": key, "value": val} for key, val in list(metadata.items())] response = dict(responses.success_true) - response.update({'building': str(sensor.building), - 'name': str(sensor.name), - 'tags': tags_owned, - 'metadata': metadata, - 'source_identifier': str(sensor.source_identifier), - 'source_name': str(sensor.source_name) - }) + response.update( + { + "building": str(sensor.building), + "name": str(sensor.name), + "tags": tags_owned, + "metadata": metadata, + "source_identifier": str(sensor.source_identifier), + "source_name": str(sensor.source_name), + } + ) return jsonify(response) @check_oauth @@ -77,19 +82,19 @@ def post(self): "error":
} """ - data = request.get_json()['data'] + data = request.get_json()["data"] try: - building = data['building'] + building = data["building"] except KeyError: return jsonify(responses.missing_parameters) - sensor_name = data.get('name') - identifier = data.get('identifier') - uuid = data.get('uuid') + sensor_name = data.get("name") + identifier = data.get("identifier") + uuid = data.get("uuid") email = get_email() - fields = data.get('fields') + fields = data.get("fields") try: - tags = data.get('tags') + tags = data.get("tags") except: tags = [] @@ -102,15 +107,17 @@ def post(self): if not uuid: uuid = str(uuid4()) if defs.create_sensor(uuid, email, building): - Sensor(name=uuid, - source_name=xstr(sensor_name), - source_identifier=xstr(identifier), - building=building, - owner=email, - tags=tags).save() - r.set('owner:{}'.format(uuid), email) + Sensor( + name=uuid, + source_name=xstr(sensor_name), + source_identifier=xstr(identifier), + building=building, + owner=email, + tags=tags, + ).save() + r.set("owner:{}".format(uuid), email) response = dict(responses.success_true) - response.update({'uuid': uuid}) + response.update({"uuid": uuid}) return jsonify(response) else: return jsonify(responses.ds_error) @@ -121,27 +128,28 @@ def delete(self, name): if name is None: return jsonify(responses.missing_parameters) sensor = Sensor.objects(name=name).first() - if r.get('parent:{}'.format(name)): - return jsonify({'success': 'False', 'error': 'Sensor view can\'t be deleted.'}) - views = r.smembers('views:{}'.format(sensor.name)) + if r.get("parent:{}".format(name)): + return jsonify( + {"success": "False", "error": "Sensor view can't be deleted."} + ) + views = r.smembers("views:{}".format(sensor.name)) for view in views: if defs.delete_sensor(view): - r.delete('sensor:{}'.format(view)) - r.delete('owner:{}'.format(view)) + r.delete("sensor:{}".format(view)) + r.delete("owner:{}".format(view)) # cache process done Sensor.objects(name=view).delete() # cache process if defs.delete_sensor(name): - r.delete('sensor:{}'.format(sensor.name)) - r.delete('owner:{}'.format(sensor.name)) + r.delete("sensor:{}".format(sensor.name)) + r.delete("owner:{}".format(sensor.name)) # cache process done Sensor.objects(name=sensor.name).delete() response = dict(responses.success_true) else: response = dict(responses.ds_error) return jsonify(response) - - + class SensorOwnedService(MethodView): @check_oauth @@ -170,8 +178,8 @@ def get(self): """ email = get_email() - owned_sensors = Sensor._get_collection().find({'owner':email}) - user_groups = UserGroup._get_collection().find({'users':[email]}) + owned_sensors = Sensor._get_collection().find({"owner": email}) + user_groups = UserGroup._get_collection().find({"users": [email]}) owned = [] permitted = [] @@ -180,15 +188,15 @@ def get(self): user_group_names = [] for user_group in user_groups: - user_group_names.append(user_group['name']) + user_group_names.append(user_group["name"]) - user_group_clause = {'user_group':{'$in':user_group_names}} - r_permission = {'permission':{'$eq':'r'}} - rw_permission = {'permission':{'$eq':'r/w'}} - rwp_permission = {'permission':{'$eq':'r/w/p'}} + user_group_clause = {"user_group": {"$in": user_group_names}} + r_permission = {"permission": {"$eq": "r"}} + rw_permission = {"permission": {"$eq": "r/w"}} + rwp_permission = {"permission": {"$eq": "r/w/p"}} - permission_clause = {'$or':[r_permission, rw_permission, rwp_permission]} - search_clause = {'$and':[user_group_clause, permission_clause]} + permission_clause = {"$or": [r_permission, rw_permission, rwp_permission]} + search_clause = {"$and": [user_group_clause, permission_clause]} permissions = Permission._get_collection().find(search_clause) @@ -198,38 +206,60 @@ def get(self): for permission in permissions: sensor_group_names.append(permission.sensor_group) - sensor_groups = SensorGroup._get_collection().find({'name':{'$in':sensor_group_names}}) + sensor_groups = SensorGroup._get_collection().find( + {"name": {"$in": sensor_group_names}} + ) if sensor_groups is not None: for sensor_group in sensor_groups: - sensor = Sensor._get_collection().find({'tags':sensor_group.tags}) + sensor = Sensor._get_collection().find( + {"tags": sensor_group.tags} + ) if sensor is not None: - tags_owned = [{'name': tag['name'], 'value': tag['value']} for tag in sensor['tags']] - metadata = Sensor._get_collection().find({'name': sensor['name']}, {'metadata': 1, '_id': 0})[0]['metadata'] - metadata = [{'name': key, 'value': val} for key, val in metadata.iteritems()] - - permitted.append({'building':str(sensor.building), - 'name':str(sensor.name), - 'tags':tags_owned, - 'metadata':metadata, - 'source_identifier':str(sensor.source_identifier), - 'source_name':str(sensor.source_name) - }) + tags_owned = [ + {"name": tag["name"], "value": tag["value"]} + for tag in sensor["tags"] + ] + metadata = Sensor._get_collection().find( + {"name": sensor["name"]}, {"metadata": 1, "_id": 0} + )[0]["metadata"] + metadata = [ + {"name": key, "value": val} + for key, val in metadata.items() + ] + + permitted.append( + { + "building": str(sensor.building), + "name": str(sensor.name), + "tags": tags_owned, + "metadata": metadata, + "source_identifier": str(sensor.source_identifier), + "source_name": str(sensor.source_name), + } + ) for sensor in owned_sensors: - tags_owned = [{'name': tag['name'], 'value': tag['value']} for tag in sensor['tags']] - metadata = Sensor._get_collection().find({'name': sensor['name']}, {'metadata': 1, '_id': 0})[0]['metadata'] - metadata = [{'name': key, 'value': val} for key, val in metadata.iteritems()] - - owned.append({'building':str(sensor['building']), - 'name':str(sensor['name']), - 'tags':tags_owned, - 'metadata':metadata, - 'source_identifier':str(sensor['source_identifier']), - 'source_name':str(sensor['source_name']) - }) + tags_owned = [ + {"name": tag["name"], "value": tag["value"]} for tag in sensor["tags"] + ] + metadata = Sensor._get_collection().find( + {"name": sensor["name"]}, {"metadata": 1, "_id": 0} + )[0]["metadata"] + metadata = [{"name": key, "value": val} for key, val in metadata.items()] + + owned.append( + { + "building": str(sensor["building"]), + "name": str(sensor["name"]), + "tags": tags_owned, + "metadata": metadata, + "source_identifier": str(sensor["source_identifier"]), + "source_name": str(sensor["source_name"]), + } + ) response = dict(responses.success_true) - response.update({'owned':owned, 'granted_access':permitted}) + response.update({"owned": owned, "granted_access": permitted}) return jsonify(response) diff --git a/buildingdepot/CentralService/app/rest_api/sensors/sensor_tags.py b/buildingdepot/CentralService/app/rest_api/sensors/sensor_tags.py index 419c5825..19e7804d 100755 --- a/buildingdepot/CentralService/app/rest_api/sensors/sensor_tags.py +++ b/buildingdepot/CentralService/app/rest_api/sensors/sensor_tags.py @@ -8,23 +8,24 @@ update/remove tags from a sensor -@copyright: (c) 2016 SynergyLabs +@copyright: (c) 2021 SynergyLabs @license: See License file for details. """ import sys +from flask import request, jsonify from flask.views import MethodView -from flask import request,jsonify -from ..import responses -from ...models.cs_models import Sensor,SensorGroup + +from .. import responses from ..helper import get_building_tags, check_oauth -from ... import r,oauth -from ...rpc import defs +from ... import r, oauth from ...auth.access_control import authenticate_acl +from ...models.cs_models import Sensor, SensorGroup +from ...rpc import defs -class SensorTagsService(MethodView): +class SensorTagsService(MethodView): @check_oauth - def get(self,name): + def get(self, name): """ Args as data: "name" : @@ -46,19 +47,19 @@ def get(self,name): . . ] (These are the list of tags owned by this sensor) - } """ + }""" obj = Sensor.objects(name=name).first() if obj is None: return jsonify(responses.invalid_uuid) - tags_owned = [{'name': tag.name, 'value': tag.value} for tag in obj.tags] + tags_owned = [{"name": tag.name, "value": tag.value} for tag in obj.tags] tags = get_building_tags(obj.building) response = dict(responses.success_true) - response.update({'tags': tags, 'tags_owned': tags_owned}) + response.update({"tags": tags, "tags_owned": tags_owned}) return jsonify(response) @check_oauth - @authenticate_acl('r/w/p') - def post(self,name): + @authenticate_acl("r/w/p") + def post(self, name): """ Args as data: "name" : @@ -82,10 +83,10 @@ def post(self,name): } """ - tags = request.get_json()['data']['tags'] + tags = request.get_json()["data"]["tags"] sensor = Sensor.objects(name=name).first() old_tags = set([(tag.name, tag.value) for tag in sensor.tags]) - new_tags = set([(tag['name'], tag['value']) for tag in tags]) + new_tags = set([(tag["name"], tag["value"]) for tag in tags]) tags_added = new_tags - old_tags tags_removed = old_tags - new_tags if defs.invalidate_sensor(name): @@ -94,22 +95,39 @@ def post(self,name): views = Sensor.objects(tags__all=[{"name": "parent", "value": name}]) for view in views: if defs.invalidate_sensor(view.name): - view.update(add_to_set__tags=[{'name': tag[0], 'value': tag[1]} for tag in tags_added]) - view.update(pull_all__tags=[{'name': tag[0], 'value': tag[1]} for tag in tags_removed]) - emails = list(r.hgetall(view.name).keys()) - r.hdel(view.name, emails) - if sensor.source_identifier == 'SensorView': + view.update( + add_to_set__tags=[ + {"name": tag[0], "value": tag[1]} for tag in tags_added + ] + ) + view.update( + pull_all__tags=[ + {"name": tag[0], "value": tag[1]} for tag in tags_removed + ] + ) + r.delete(view.name) + if sensor.source_identifier == "SensorView": for tag in tags: - if tag['name'] == 'parent': - parent = Sensor.objects(name=tag['value']).first() - parent_tags = set([(tag['name'], tag['value']) for tag in parent.tags]) + if tag["name"] == "parent": + parent = Sensor.objects(name=tag["value"]).first() + parent_tags = set( + [(tag["name"], tag["value"]) for tag in parent.tags] + ) if len(tags_removed.intersection(parent_tags)): - return jsonify({'success': 'False', 'error': 'Cannot delete inherited tags', 'inherited_tags':[{'name':tag, 'value':value} for tag, value in parent_tags]}) + return jsonify( + { + "success": "False", + "error": "Cannot delete inherited tags", + "inherited_tags": [ + {"name": tag, "value": value} + for tag, value in parent_tags + ], + } + ) break Sensor.objects(name=name).update(set__tags=tags) - emails = list(r.hgetall(name).keys()) - r.hdel(name, emails) + r.delete(name) else: return jsonify(responses.ds_error) return jsonify(responses.success_true) diff --git a/buildingdepot/CentralService/app/rest_api/sensors/sensor_views.py b/buildingdepot/CentralService/app/rest_api/sensors/sensor_views.py index b1415e9f..20d11819 100755 --- a/buildingdepot/CentralService/app/rest_api/sensors/sensor_views.py +++ b/buildingdepot/CentralService/app/rest_api/sensors/sensor_views.py @@ -7,17 +7,18 @@ retrieving sensor view details. It manages the underlying cache, and will ensure that the cache gets updated as needed. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import request, jsonify from flask.views import MethodView -from .. import responses -from ...models.cs_models import Sensor, Building from uuid import uuid4 -from ... import r, oauth + +from .. import responses from ..helper import get_email, xstr, get_building_choices, check_oauth +from ... import r, oauth +from ...models.cs_models import Sensor, Building from ...rpc import defs @@ -48,22 +49,26 @@ def get(self, name): views = Sensor.objects(tags__all=[{"name": "parent", "value": name}]) available_fields = [] for tag in sensor.tags: - if tag['name'] == 'fields': - available_fields += [field.strip() for field in tag['value'].split(",")] + if tag["name"] == "fields": + available_fields += [field.strip() for field in tag["value"].split(",")] all_views = [] for view in views: - tags_owned = [{'name': tag.name, 'value': tag.value} for tag in view.tags] + tags_owned = [{"name": tag.name, "value": tag.value} for tag in view.tags] fields = [] for tag in tags_owned: - if tag['name'] == 'field': - fields.append(tag['value']) - all_views.append({ - 'id': str(view.name), - 'fields': ', '.join(sorted(fields)), - 'source_name': xstr(view.source_name) - }) + if tag["name"] == "field": + fields.append(tag["value"]) + all_views.append( + { + "id": str(view.name), + "fields": ", ".join(sorted(fields)), + "source_name": xstr(view.source_name), + } + ) response = dict(responses.success_true) - response.update({"views_owned": all_views, 'available_fields': available_fields}) + response.update( + {"views_owned": all_views, "available_fields": available_fields} + ) return jsonify(response) @check_oauth @@ -85,54 +90,77 @@ def post(self, name): sensor = Sensor.objects(name=name).first() if sensor is None: return jsonify(responses.invalid_uuid) - if r.get('parent:{}'.format(name)): - return jsonify({"success": "False", "error": "Sensor views can't have sub-views."}) - data = request.get_json()['data'] + if r.get("parent:{}".format(name)): + return jsonify( + {"success": "False", "error": "Sensor views can't have sub-views."} + ) + data = request.get_json()["data"] try: - view_name = data.get('source_name') - fields = data.get('fields') + view_name = data.get("source_name") + fields = data.get("fields") except KeyError: return jsonify(responses.missing_parameters) email = get_email() building = sensor.building try: - tags = data.get('tags') + tags = data.get("tags") if not tags: tags = [] except: tags = [] - fields_list = fields.split(',') - field_tags = [{"name": "field", "value": field.strip()} for field in fields_list] + fields_list = fields.split(",") + field_tags = [ + {"name": "field", "value": field.strip()} for field in fields_list + ] available_tags = Building.objects(name=building).first().tags - available_tags = [{"name": tag.name, "value": tag.value} for tag in available_tags] - tags = tags + [{"name": tag.name, "value": tag.value} for tag in sensor.tags] + [{"name": "parent", "value": sensor.name}] + field_tags + available_tags = [ + {"name": tag.name, "value": tag.value} for tag in available_tags + ] + tags = ( + tags + + [{"name": tag.name, "value": tag.value} for tag in sensor.tags] + + [{"name": "parent", "value": sensor.name}] + + field_tags + ) if any(tag not in available_tags for tag in tags): - missing_tags = set([(tag['name'], tag['value']) for tag in tags]) - set([(tag['name'], tag['value']) for tag in available_tags]) - missing_tags = [{'name': name, 'value': value} for name, value in missing_tags] - return jsonify({'success': 'False', 'building_tags_required': missing_tags, 'error': 'Cannot create ' - 'sensor views ' - 'without the ' - 'required building ' - 'tags.'}) + missing_tags = set([(tag["name"], tag["value"]) for tag in tags]) - set( + [(tag["name"], tag["value"]) for tag in available_tags] + ) + missing_tags = [ + {"name": name, "value": value} for name, value in missing_tags + ] + return jsonify( + { + "success": "False", + "building_tags_required": missing_tags, + "error": "Cannot create " + "sensor views " + "without the " + "required building " + "tags.", + } + ) uuid = str(uuid4()) if defs.create_sensor(uuid, email, building, fields, name): - Sensor(name=uuid, - source_name=xstr(view_name), - source_identifier="SensorView", - building=building, - owner=email, - tags=tags).save() - r.set('owner:{}'.format(uuid), email) - r.sadd('views:{}'.format(name), uuid) - r.set('fields:{}'.format(uuid), fields) - r.set('parent:{}'.format(uuid), name) + Sensor( + name=uuid, + source_name=xstr(view_name), + source_identifier="SensorView", + building=building, + owner=email, + tags=tags, + ).save() + r.set("owner:{}".format(uuid), email) + r.sadd("views:{}".format(name), uuid) + r.set("fields:{}".format(uuid), fields) + r.set("parent:{}".format(uuid), name) fields = [field.strip() for field in fields.split(",")] for field in fields: - r.sadd('{}:{}'.format(name, field), uuid) - r.sadd('views', uuid) + r.sadd("{}:{}".format(name, field), uuid) + r.sadd("views", uuid) response = dict(responses.success_true) - response.update({'id': uuid}) + response.update({"id": uuid}) return jsonify(response) else: return jsonify(responses.ds_error) @@ -145,19 +173,19 @@ def delete(self, name, uuid): # cache process if get_email() == sensor.owner: if defs.delete_sensor(uuid, name): - fields = r.get('fields:{}'.format(uuid)) + fields = r.get("fields:{}".format(uuid)) if fields: fields = fields.split(",") fields = [field.strip() for field in fields] for field in fields: - r.srem('{}:{}'.format(name, field), uuid) - r.delete('fields:{}'.format(uuid)) - r.delete('parent:{}'.format(uuid)) + r.srem("{}:{}".format(name, field), uuid) + r.delete("fields:{}".format(uuid)) + r.delete("parent:{}".format(uuid)) r.delete(uuid) - r.srem('views', uuid) + r.srem("views", uuid) # cache process done Sensor.objects(name=uuid).delete() - r.srem('views:{}'.format(sensor.name), uuid) + r.srem("views:{}".format(sensor.name), uuid) response = dict(responses.success_true) else: response = dict(responses.ds_error) diff --git a/buildingdepot/CentralService/app/rest_api/tagtype/tagtype.py b/buildingdepot/CentralService/app/rest_api/tagtype/tagtype.py index 1891e170..7b61951f 100755 --- a/buildingdepot/CentralService/app/rest_api/tagtype/tagtype.py +++ b/buildingdepot/CentralService/app/rest_api/tagtype/tagtype.py @@ -6,34 +6,35 @@ of all the CRUD operations on the tagtypes. Each tagtype can have parent and children tagtypes specifed for it. -@copyright: (c) 2016 SynergyLabs +@copyright: (c) 2021 SynergyLabs @license: See License file for details. """ -from flask.views import MethodView from flask import request, jsonify -from ...models.cs_models import TagType, BuildingTemplate -from ..helper import add_delete, gen_update, get_email, check_if_super, check_oauth +from flask.views import MethodView + from .. import responses +from ..helper import add_delete, gen_update, get_email, check_if_super, check_oauth from ... import oauth from ...auth.access_control import super_required +from ...models.cs_models import TagType, BuildingTemplate class TagTypeService(MethodView): - params = ['name', 'description', 'parents', 'acl_tag'] + params = ["name", "description", "parents", "acl_tag"] @check_oauth def post(self): try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) - parents = data.get('parents') - name = data.get('name') - acl_tag = data.get('acl_tag') + parents = data.get("parents") + name = data.get("name") + acl_tag = data.get("acl_tag") if not name: - return jsonify({'success':'False', 'error': 'Invalid TagType name.'}) - tagtype = TagType.objects(name=data.get('name')).first() + return jsonify({"success": "False", "error": "Invalid TagType name."}) + tagtype = TagType.objects(name=data.get("name")).first() if not tagtype: if name is None: return jsonify(responses.missing_parameters) @@ -41,34 +42,42 @@ def post(self): for parent in parents: if TagType.objects(name=parent).first() is None: return jsonify(responses.invalid_parent_tags) - data['acl_tag'] = check_if_super(get_email()) + data["acl_tag"] = check_if_super(get_email()) TagType(**gen_update(self.params, data)).save() if parents: self.add_children(parents, name) else: collection = TagType._get_collection() if parents: - added, deleted = add_delete([str(child_tag) for child_tag in tagtype['parents']], data['parents']) + added, deleted = add_delete( + [str(child_tag) for child_tag in tagtype["parents"]], + data["parents"], + ) self.add_children(added, name) for tag in deleted: collection = TagType._get_collection() - collection.update({'name': tag}, - {'$pull': {'children': name}}, - multi=True) + collection.update_many( + {"name": tag}, {"$pull": {"children": name}} + ) user_check = check_if_super(get_email()) if (acl_tag is None) or (acl_tag is not None and not user_check): - data['acl_tag'] = user_check - collection.update({'name': name}, {'$set': gen_update(self.params, data)}) + data["acl_tag"] = user_check + collection.update_one({"name": name}, {"$set": gen_update(self.params, data)}) return jsonify(responses.success_true) @check_oauth def get(self, name): tagtype = TagType.objects(name=name).first() if tagtype: - return jsonify({'success': 'True', 'name': tagtype['name'], - 'description': tagtype['description'], - 'parents': tagtype['parents'], - 'children': tagtype['children']}) + return jsonify( + { + "success": "True", + "name": tagtype["name"], + "description": tagtype["description"], + "parents": tagtype["parents"], + "children": tagtype["children"], + } + ) else: return jsonify(responses.invalid_tagtype) @@ -79,12 +88,16 @@ def delete(self, name): if not tagtype: return jsonify(responses.invalid_tagtype) - if not tagtype.children and BuildingTemplate._get_collection().find({'tag_types': tagtype.name}).count() == 0: + if ( + not tagtype.children + and BuildingTemplate.objects(tag_types=tagtype.name).count() + == 0 + ): tagtype.delete() collection = TagType._get_collection() - collection.update({'children': name}, - {'$pull': {'children': name}}, - multi=True) + collection.update_many( + {"children": name}, {"$pull": {"children": name}} + ) return jsonify(responses.success_true) else: return jsonify(responses.tagtype_referenced) @@ -92,7 +105,4 @@ def delete(self, name): def add_children(self, parents, name): for parent in parents: collection = TagType._get_collection() - collection.update( - {'name': parent}, - {'$addToSet': {'children': str(name)}} - ) + collection.update_one({"name": parent}, {"$addToSet": {"children": str(name)}}) diff --git a/buildingdepot/CentralService/app/rest_api/usergroups/ug_users.py b/buildingdepot/CentralService/app/rest_api/usergroups/ug_users.py index 69a68a21..ab68b23d 100755 --- a/buildingdepot/CentralService/app/rest_api/usergroups/ug_users.py +++ b/buildingdepot/CentralService/app/rest_api/usergroups/ug_users.py @@ -7,23 +7,24 @@ it updates the cache where a list is maintained of the users that fall in each user group. This list is further used for acl's and other purposes. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ import sys +from flask import request, jsonify from flask.views import MethodView -from flask import request,jsonify + from .. import responses -from ...models.cs_models import UserGroup -from ..helper import add_delete_users,get_email,validate_users, check_oauth -from ... import r,oauth -from ...auth.access_control import authenticate_acl,authorize_addition +from ..helper import add_delete_users, get_email, validate_users, check_oauth +from ... import r, oauth +from ...auth.access_control import authenticate_acl, authorize_addition from ...auth.acl_cache import invalidate_user +from ...models.cs_models import UserGroup -class UserGroupUsersService(MethodView): +class UserGroupUsersService(MethodView): @check_oauth - def get(self,name): + def get(self, name): """ Args as data: name = @@ -36,11 +37,18 @@ def get(self,name): if obj is None: return jsonify(responses.invalid_usergroup) response = dict(responses.success_true) - response.update({'users':[{'user_id': user.user_id, 'manager': user.manager} for user in obj.users]}) + response.update( + { + "users": [ + {"user_id": user.user_id, "manager": user.manager} + for user in obj.users + ] + } + ) return jsonify(response) @check_oauth - def post(self,name): + def post(self, name): """ Args as data: name = @@ -54,17 +62,16 @@ def post(self,name): } """ try: - emails = request.get_json()['data']['users'] + emails = request.get_json()["data"]["users"] except: return jsonify(responses.missing_data) if UserGroup.objects(name=name).first() is None: return jsonify(responses.invalid_usergroup) if validate_users(emails): - if authorize_addition(name,get_email()): + if authorize_addition(name, get_email()): user_group = UserGroup.objects(name=name).first() UserGroup.objects(name=name).update(set__users=emails) return jsonify(responses.success_true) else: return jsonify(responses.usergroup_add_authorization) return jsonify(responses.user_not_registered) - diff --git a/buildingdepot/CentralService/app/rest_api/usergroups/usergroup.py b/buildingdepot/CentralService/app/rest_api/usergroups/usergroup.py index 1ebb04bb..33e28314 100755 --- a/buildingdepot/CentralService/app/rest_api/usergroups/usergroup.py +++ b/buildingdepot/CentralService/app/rest_api/usergroups/usergroup.py @@ -6,21 +6,21 @@ It handles the common services for user groups, such as making a new one or deleting an existing one. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ import sys +from flask import request, jsonify from flask.views import MethodView -from flask import request,jsonify -from ...auth.access_control import authorize_addition from .. import responses +from ..helper import xstr, get_email, get_building_choices, check_oauth +from ... import r, oauth +from ...auth.access_control import authorize_addition from ...models.cs_models import UserGroup -from ... import r,oauth -from ..helper import xstr,get_email,get_building_choices, check_oauth -class UserGroupService(MethodView): +class UserGroupService(MethodView): @check_oauth def post(self): """ @@ -35,9 +35,9 @@ def post(self): } """ try: - data = request.get_json()['data'] - name = data['name'] - description = data['description'] + data = request.get_json()["data"] + name = data["name"] + description = data["description"] except KeyError: return jsonify(responses.missing_parameters) @@ -45,13 +45,13 @@ def post(self): if user_group: return jsonify(responses.usergroup_exists) - UserGroup(name=xstr(name), - description=xstr(description), - owner = get_email()).save() + UserGroup( + name=xstr(name), description=xstr(description), owner=get_email() + ).save() return jsonify(responses.success_true) @check_oauth - def get(self,name): + def get(self, name): """ Args as data: name = @@ -68,12 +68,13 @@ def get(self,name): return jsonify(responses.invalid_usergroup) response = dict(responses.success_true) - response.update({"name":user_group['name'], - "description":user_group['description']}) + response.update( + {"name": user_group["name"], "description": user_group["description"]} + ) return jsonify(response) @check_oauth - def delete(self,name): + def delete(self, name): """ Args as data: name = @@ -87,13 +88,13 @@ def delete(self,name): if user_group is None: return jsonify(responses.invalid_usergroup) if authorize_addition(name, get_email()): - UserGroup._get_collection().remove({"name":user_group['name']}) + UserGroup._get_collection().delete_one({"name": user_group["name"]}) response = dict(responses.success_true) else: response = dict(responses.usergroup_delete_authorization) return jsonify(response) - + class UserGroupOwnedService(MethodView): @check_oauth def get(self): @@ -115,6 +116,15 @@ def get(self): result = [] for user_group in user_groups: - result.append({"name":user_group['name'], "description":user_group['description'], "users":[{'user_id': user.user_id, 'manager': user.manager} for user in user_group.users]}) - response.update({"result":result}) + result.append( + { + "name": user_group["name"], + "description": user_group["description"], + "users": [ + {"user_id": user.user_id, "manager": user.manager} + for user in user_group.users + ], + } + ) + response.update({"result": result}) return jsonify(response) diff --git a/buildingdepot/CentralService/app/rest_api/users/user.py b/buildingdepot/CentralService/app/rest_api/users/user.py index 86842eb9..b1cda738 100755 --- a/buildingdepot/CentralService/app/rest_api/users/user.py +++ b/buildingdepot/CentralService/app/rest_api/users/user.py @@ -7,20 +7,21 @@ an email is sent out to the specified id with a temporary password that will have to be changed on first login. -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ import uuid from flask import request, jsonify, current_app from flask.views import MethodView from werkzeug.security import generate_password_hash -from ...models.cs_models import User -from ..helper import get_email, send_mail_gmail, send_local_smtp, check_oauth + from .. import responses +from ..helper import get_email, send_mail_gmail, send_local_smtp, check_oauth from ... import oauth, r from ...auth.access_control import super_required from ...auth.views import Client, Token +from ...models.cs_models import User class UserService(MethodView): @@ -51,17 +52,17 @@ def post(self): # check whether the received request has data try: - data = request.get_json()['data'] + data = request.get_json()["data"] except KeyError: return jsonify(responses.missing_data) # extract all parameters contained in the data - first_name = data.get('first_name') - last_name = data.get('last_name') - email = data.get('email') - role = data.get('role') + first_name = data.get("first_name") + last_name = data.get("last_name") + email = data.get("email") + role = data.get("role") - # check whether all the parameters are appropriately received + # check whether all the parameters are appropriately received if not all((email, first_name, last_name, role)): return jsonify(responses.missing_parameters) @@ -70,15 +71,15 @@ def post(self): return jsonify(responses.user_exists) # check whether ther role is well formed - if role not in ['default', 'super']: + if role not in ["default", "super"]: return jsonify(responses.invalid_user_role) # register the user finally self.register_user(first_name, last_name, email, role) # cache superuser in redis - if role == 'super': - r.sadd('superusers', email) + if role == "super": + r.sadd("superusers", email) return jsonify(responses.success_true) @@ -118,15 +119,19 @@ def get(self, email): return jsonify(responses.invalid_user) # check whether the target user is a super user - if user.role == 'super': + if user.role == "super": return jsonify(responses.unauthorized_user_lookup) # send out information response = dict(responses.success_true) - response.update({'email': user.email, - 'first_name': user.first_name, - 'last_name': user.last_name, - 'role': user.role}) + response.update( + { + "email": user.email, + "first_name": user.first_name, + "last_name": user.last_name, + "role": user.role, + } + ) return jsonify(response) @@ -160,18 +165,18 @@ def delete(self, email): return jsonify(responses.invalid_user) # Remove client object - Client._get_collection().remove({'user':email}) + Client._get_collection().delete_many({"user": email}) # Find token objects - tokens = Token._get_collection().find({'email':email}) + tokens = Token._get_collection().find({"email": email}) p = r.pipeline() - if user.role == 'super': - p.srem('superusers', email) + if user.role == "super": + p.srem("superusers", email) # Remove tokens for token in tokens: - p.delete(''.join(['oauth:', token.access_token])) + p.delete("".join(["oauth:", token.access_token])) token.delete() p.execute() @@ -194,12 +199,17 @@ def register_user(self, first_name, last_name, email, role): password = str(uuid.uuid4()) # create a user with given information - User(email=email, first_name=first_name, - last_name=last_name, role=role, - password=generate_password_hash(password)).save() + User( + email=email, + first_name=first_name, + last_name=last_name, + role=role, + password=generate_password_hash(password), + ).save() # if email client is specified, send an email to the new user - if current_app.config['EMAIL'] == 'GMAIL': + if current_app.config["EMAIL"] == "GMAIL": send_mail_gmail(first_name + " " + last_name, email, password) - elif current_app.config['EMAIL'] == 'LOCAL': + elif current_app.config["EMAIL"] == "LOCAL": send_local_smtp(first_name + " " + last_name, email, password) + return jsonify(responses.success_true) diff --git a/buildingdepot/CentralService/app/rest_api/views.py b/buildingdepot/CentralService/app/rest_api/views.py index 8d5939cf..c447b8c3 100755 --- a/buildingdepot/CentralService/app/rest_api/views.py +++ b/buildingdepot/CentralService/app/rest_api/views.py @@ -7,29 +7,30 @@ requests will also have an additional check where the ACL's are referenced to see if the user has access to the specific sensor -@copyright: (c) 2016 SynergyLabs -@license: UCSD License. See License file for details. +@copyright: (c) 2021 SynergyLabs +@license: CMU License. See License file for details. """ from flask import request, jsonify + from . import api from ..models.cs_models import * -@api.route('/buildingtemplate//edit', methods=['POST']) +@api.route("/buildingtemplate//edit", methods=["POST"]) def buildingtemplate_tag_edit(name): - data = map(str,request.get_json()['data']) + data = list(map(str, request.get_json()["data"])) buildingtemplate = BuildingTemplate.objects(name=name).first() if buildingtemplate.update(set__tag_types=data): - return jsonify({'success': 'True'}) + return jsonify({"success": "True"}) else: - return jsonify({'success': 'False'}) + return jsonify({"success": "False"}) -@api.route('/tagtype/list', methods=['GET']) +@api.route("/tagtype/list", methods=["GET"]) def api(): # collection = TagType._get_collection().find() all_tags = [] collection = TagType.objects for i in collection: all_tags.append(i.name) - return jsonify({'success': 'True', 'tags': all_tags}) + return jsonify({"success": "True", "tags": all_tags}) diff --git a/buildingdepot/CentralService/app/rpc/defs.py b/buildingdepot/CentralService/app/rpc/defs.py index ed3854f9..9dbf99a5 100755 --- a/buildingdepot/CentralService/app/rpc/defs.py +++ b/buildingdepot/CentralService/app/rpc/defs.py @@ -1,10 +1,11 @@ +from xmlrpc.client import ServerProxy + from .. import svr -from ..rest_api.helper import get_ds, get_sg_ds from ..models.cs_models import DataService -from xmlrpclib import ServerProxy +from ..rest_api.helper import get_ds, get_sg_ds -def create_sensor(sensor_id, email, building, fields = None, parent = None): +def create_sensor(sensor_id, email, building, fields=None, parent=None): if not parent: svr = get_remote(get_ds(sensor_id, building)) try: @@ -30,7 +31,7 @@ def invalidate_sensor(sensor_id): return True -def delete_sensor(sensor_id, parent = None): +def delete_sensor(sensor_id, parent=None): if not parent: svr = get_remote(get_ds(sensor_id)) try: @@ -68,10 +69,10 @@ def delete_permission(user_group, sensor_group): def invalidate_permission(sensor_group): svr = get_remote(get_sg_ds(sensor_group)) try: - print "Invalidating permission" + print("Invalidating permission") svr.invalidate_permission(sensor_group) except Exception as e: - print e + print(e) return False return True diff --git a/buildingdepot/CentralService/app/static/app.js b/buildingdepot/CentralService/app/static/app.js index 5c66a978..bd456f4e 100755 --- a/buildingdepot/CentralService/app/static/app.js +++ b/buildingdepot/CentralService/app/static/app.js @@ -1,13 +1,13 @@ -(function() { +(function () { var app = angular.module('myApp', []); - app.config(['$interpolateProvider', function($interpolateProvider) { - $interpolateProvider.startSymbol('{['); - $interpolateProvider.endSymbol(']}'); + app.config(['$interpolateProvider', function ($interpolateProvider) { + $interpolateProvider.startSymbol('{['); + $interpolateProvider.endSymbol(']}'); }]); - app.controller = ('metadataController', function($scope, $http) { - $scope.ctl.select = function(name) { + app.controller = ('metadataController', function ($scope, $http) { + $scope.ctl.select = function (name) { this.selected = name; }; }); diff --git a/buildingdepot/CentralService/app/static/notify.js b/buildingdepot/CentralService/app/static/notify.js index aed93e67..21bb3a09 100755 --- a/buildingdepot/CentralService/app/static/notify.js +++ b/buildingdepot/CentralService/app/static/notify.js @@ -1,615 +1,616 @@ /* Notify.js - http://notifyjs.com/ Copyright (c) 2015 MIT */ (function (factory) { - // UMD start - // https://github.com/umdjs/umd/blob/master/jqueryPluginCommonjs.js - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else if (typeof module === 'object' && module.exports) { - // Node/CommonJS - module.exports = function( root, jQuery ) { - if ( jQuery === undefined ) { - // require('jQuery') returns a factory that requires window to - // build a jQuery instance, we normalize how we use modules - // that require this pattern but the window provided is a noop - // if it's defined (how jquery works) - if ( typeof window !== 'undefined' ) { - jQuery = require('jquery'); - } - else { - jQuery = require('jquery')(root); - } - } - factory(jQuery); - return jQuery; - }; - } else { - // Browser globals - factory(jQuery); - } + // UMD start + // https://github.com/umdjs/umd/blob/master/jqueryPluginCommonjs.js + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = function (root, jQuery) { + if (jQuery === undefined) { + // require('jQuery') returns a factory that requires window to + // build a jQuery instance, we normalize how we use modules + // that require this pattern but the window provided is a noop + // if it's defined (how jquery works) + if (typeof window !== 'undefined') { + jQuery = require('jquery'); + } else { + jQuery = require('jquery')(root); + } + } + factory(jQuery); + return jQuery; + }; + } else { + // Browser globals + factory(jQuery); + } }(function ($) { - //IE8 indexOf polyfill - var indexOf = [].indexOf || function(item) { - for (var i = 0, l = this.length; i < l; i++) { - if (i in this && this[i] === item) { - return i; - } - } - return -1; - }; - - var pluginName = "notify"; - var pluginClassName = pluginName + "js"; - var blankFieldName = pluginName + "!blank"; - - var positions = { - t: "top", - m: "middle", - b: "bottom", - l: "left", - c: "center", - r: "right" - }; - var hAligns = ["l", "c", "r"]; - var vAligns = ["t", "m", "b"]; - var mainPositions = ["t", "b", "l", "r"]; - var opposites = { - t: "b", - m: null, - b: "t", - l: "r", - c: null, - r: "l" - }; - - var parsePosition = function(str) { - var pos; - pos = []; - $.each(str.split(/\W+/), function(i, word) { - var w; - w = word.toLowerCase().charAt(0); - if (positions[w]) { - return pos.push(w); - } - }); - return pos; - }; - - var styles = {}; - - var coreStyle = { - name: "core", - html: "
\n
\n
\n
", - css: "." + pluginClassName + "-corner {\n position: fixed;\n margin: 5px;\n z-index: 1050;\n}\n\n." + pluginClassName + "-corner ." + pluginClassName + "-wrapper,\n." + pluginClassName + "-corner ." + pluginClassName + "-container {\n position: relative;\n display: block;\n height: inherit;\n width: inherit;\n margin: 3px;\n}\n\n." + pluginClassName + "-wrapper {\n z-index: 1;\n position: absolute;\n display: inline-block;\n height: 0;\n width: 0;\n}\n\n." + pluginClassName + "-container {\n display: none;\n z-index: 1;\n position: absolute;\n}\n\n." + pluginClassName + "-hidable {\n cursor: pointer;\n}\n\n[data-notify-text],[data-notify-html] {\n position: relative;\n}\n\n." + pluginClassName + "-arrow {\n position: absolute;\n z-index: 2;\n width: 0;\n height: 0;\n}" - }; - - var stylePrefixes = { - "border-radius": ["-webkit-", "-moz-"] - }; - - var getStyle = function(name) { - return styles[name]; - }; - - var addStyle = function(name, def) { - if (!name) { - throw "Missing Style name"; - } - if (!def) { - throw "Missing Style definition"; - } - if (!def.html) { - throw "Missing Style HTML"; - } - //remove existing style - var existing = styles[name]; - if (existing && existing.cssElem) { - if (window.console) { - console.warn(pluginName + ": overwriting style '" + name + "'"); - } - styles[name].cssElem.remove(); - } - def.name = name; - styles[name] = def; - var cssText = ""; - if (def.classes) { - $.each(def.classes, function(className, props) { - cssText += "." + pluginClassName + "-" + def.name + "-" + className + " {\n"; - $.each(props, function(name, val) { - if (stylePrefixes[name]) { - $.each(stylePrefixes[name], function(i, prefix) { - return cssText += " " + prefix + name + ": " + val + ";\n"; - }); - } - return cssText += " " + name + ": " + val + ";\n"; - }); - return cssText += "}\n"; - }); - } - if (def.css) { - cssText += "/* styles for " + def.name + " */\n" + def.css; - } - if (cssText) { - def.cssElem = insertCSS(cssText); - def.cssElem.attr("id", "notify-" + def.name); - } - var fields = {}; - var elem = $(def.html); - findFields("html", elem, fields); - findFields("text", elem, fields); - def.fields = fields; - }; - - var insertCSS = function(cssText) { - var e, elem, error; - elem = createElem("style"); - elem.attr("type", 'text/css'); - $("head").append(elem); - try { - elem.html(cssText); - } catch (_) { - elem[0].styleSheet.cssText = cssText; - } - return elem; - }; - - var findFields = function(type, elem, fields) { - var attr; - if (type !== "html") { - type = "text"; - } - attr = "data-notify-" + type; - return find(elem, "[" + attr + "]").each(function() { - var name; - name = $(this).attr(attr); - if (!name) { - name = blankFieldName; - } - fields[name] = type; - }); - }; - - var find = function(elem, selector) { - if (elem.is(selector)) { - return elem; - } else { - return elem.find(selector); - } - }; - - var pluginOptions = { - clickToHide: true, - autoHide: true, - autoHideDelay: 5000, - arrowShow: true, - arrowSize: 5, - breakNewLines: true, - elementPosition: "bottom", - globalPosition: "top right", - style: "bootstrap", - className: "error", - showAnimation: "slideDown", - showDuration: 400, - hideAnimation: "slideUp", - hideDuration: 200, - gap: 5 - }; - - var inherit = function(a, b) { - var F; - F = function() {}; - F.prototype = a; - return $.extend(true, new F(), b); - }; - - var defaults = function(opts) { - return $.extend(pluginOptions, opts); - }; - - var createElem = function(tag) { - return $("<" + tag + ">"); - }; - - var globalAnchors = {}; - - var getAnchorElement = function(element) { - var radios; - if (element.is('[type=radio]')) { - radios = element.parents('form:first').find('[type=radio]').filter(function(i, e) { - return $(e).attr("name") === element.attr("name"); - }); - element = radios.first(); - } - return element; - }; - - var incr = function(obj, pos, val) { - var opp, temp; - if (typeof val === "string") { - val = parseInt(val, 10); - } else if (typeof val !== "number") { - return; - } - if (isNaN(val)) { - return; - } - opp = positions[opposites[pos.charAt(0)]]; - temp = pos; - if (obj[opp] !== undefined) { - pos = positions[opp.charAt(0)]; - val = -val; - } - if (obj[pos] === undefined) { - obj[pos] = val; - } else { - obj[pos] += val; - } - return null; - }; - - var realign = function(alignment, inner, outer) { - if (alignment === "l" || alignment === "t") { - return 0; - } else if (alignment === "c" || alignment === "m") { - return outer / 2 - inner / 2; - } else if (alignment === "r" || alignment === "b") { - return outer - inner; - } - throw "Invalid alignment"; - }; - - var encode = function(text) { - encode.e = encode.e || createElem("div"); - return encode.e.text(text).html(); - }; - - function Notification(elem, data, options) { - if (typeof options === "string") { - options = { - className: options - }; - } - this.options = inherit(pluginOptions, $.isPlainObject(options) ? options : {}); - this.loadHTML(); - this.wrapper = $(coreStyle.html); - if (this.options.clickToHide) { - this.wrapper.addClass(pluginClassName + "-hidable"); - } - this.wrapper.data(pluginClassName, this); - this.arrow = this.wrapper.find("." + pluginClassName + "-arrow"); - this.container = this.wrapper.find("." + pluginClassName + "-container"); - this.container.append(this.userContainer); - if (elem && elem.length) { - this.elementType = elem.attr("type"); - this.originalElement = elem; - this.elem = getAnchorElement(elem); - this.elem.data(pluginClassName, this); - this.elem.before(this.wrapper); - } - this.container.hide(); - this.run(data); - } - - Notification.prototype.loadHTML = function() { - var style; - style = this.getStyle(); - this.userContainer = $(style.html); - this.userFields = style.fields; - }; - - Notification.prototype.show = function(show, userCallback) { - var args, callback, elems, fn, hidden; - callback = (function(_this) { - return function() { - if (!show && !_this.elem) { - _this.destroy(); - } - if (userCallback) { - return userCallback(); - } - }; - })(this); - hidden = this.container.parent().parents(':hidden').length > 0; - elems = this.container.add(this.arrow); - args = []; - if (hidden && show) { - fn = "show"; - } else if (hidden && !show) { - fn = "hide"; - } else if (!hidden && show) { - fn = this.options.showAnimation; - args.push(this.options.showDuration); - } else if (!hidden && !show) { - fn = this.options.hideAnimation; - args.push(this.options.hideDuration); - } else { - return callback(); - } - args.push(callback); - return elems[fn].apply(elems, args); - }; - - Notification.prototype.setGlobalPosition = function() { - var p = this.getPosition(); - var pMain = p[0]; - var pAlign = p[1]; - var main = positions[pMain]; - var align = positions[pAlign]; - var key = pMain + "|" + pAlign; - var anchor = globalAnchors[key]; - if (!anchor) { - anchor = globalAnchors[key] = createElem("div"); - var css = {}; - css[main] = 0; - if (align === "middle") { - css.top = '45%'; - } else if (align === "center") { - css.left = '45%'; - } else { - css[align] = 0; - } - anchor.css(css).addClass(pluginClassName + "-corner"); - $("body").append(anchor); - } - return anchor.prepend(this.wrapper); - }; - - Notification.prototype.setElementPosition = function() { - var arrowColor, arrowCss, arrowSize, color, contH, contW, css, elemH, elemIH, elemIW, elemPos, elemW, gap, j, k, len, len1, mainFull, margin, opp, oppFull, pAlign, pArrow, pMain, pos, posFull, position, ref, wrapPos; - position = this.getPosition(); - pMain = position[0]; - pAlign = position[1]; - pArrow = position[2]; - elemPos = this.elem.position(); - elemH = this.elem.outerHeight(); - elemW = this.elem.outerWidth(); - elemIH = this.elem.innerHeight(); - elemIW = this.elem.innerWidth(); - wrapPos = this.wrapper.position(); - contH = this.container.height(); - contW = this.container.width(); - mainFull = positions[pMain]; - opp = opposites[pMain]; - oppFull = positions[opp]; - css = {}; - css[oppFull] = pMain === "b" ? elemH : pMain === "r" ? elemW : 0; - incr(css, "top", elemPos.top - wrapPos.top); - incr(css, "left", elemPos.left - wrapPos.left); - ref = ["top", "left"]; - for (j = 0, len = ref.length; j < len; j++) { - pos = ref[j]; - margin = parseInt(this.elem.css("margin-" + pos), 10); - if (margin) { - incr(css, pos, margin); - } - } - gap = Math.max(0, this.options.gap - (this.options.arrowShow ? arrowSize : 0)); - incr(css, oppFull, gap); - if (!this.options.arrowShow) { - this.arrow.hide(); - } else { - arrowSize = this.options.arrowSize; - arrowCss = $.extend({}, css); - arrowColor = this.userContainer.css("border-color") || this.userContainer.css("border-top-color") || this.userContainer.css("background-color") || "white"; - for (k = 0, len1 = mainPositions.length; k < len1; k++) { - pos = mainPositions[k]; - posFull = positions[pos]; - if (pos === opp) { - continue; - } - color = posFull === mainFull ? arrowColor : "transparent"; - arrowCss["border-" + posFull] = arrowSize + "px solid " + color; - } - incr(css, positions[opp], arrowSize); - if (indexOf.call(mainPositions, pAlign) >= 0) { - incr(arrowCss, positions[pAlign], arrowSize * 2); - } - } - if (indexOf.call(vAligns, pMain) >= 0) { - incr(css, "left", realign(pAlign, contW, elemW)); - if (arrowCss) { - incr(arrowCss, "left", realign(pAlign, arrowSize, elemIW)); - } - } else if (indexOf.call(hAligns, pMain) >= 0) { - incr(css, "top", realign(pAlign, contH, elemH)); - if (arrowCss) { - incr(arrowCss, "top", realign(pAlign, arrowSize, elemIH)); - } - } - if (this.container.is(":visible")) { - css.display = "block"; - } - this.container.removeAttr("style").css(css); - if (arrowCss) { - return this.arrow.removeAttr("style").css(arrowCss); - } - }; - - Notification.prototype.getPosition = function() { - var pos, ref, ref1, ref2, ref3, ref4, ref5, text; - text = this.options.position || (this.elem ? this.options.elementPosition : this.options.globalPosition); - pos = parsePosition(text); - if (pos.length === 0) { - pos[0] = "b"; - } - if (ref = pos[0], indexOf.call(mainPositions, ref) < 0) { - throw "Must be one of [" + mainPositions + "]"; - } - if (pos.length === 1 || ((ref1 = pos[0], indexOf.call(vAligns, ref1) >= 0) && (ref2 = pos[1], indexOf.call(hAligns, ref2) < 0)) || ((ref3 = pos[0], indexOf.call(hAligns, ref3) >= 0) && (ref4 = pos[1], indexOf.call(vAligns, ref4) < 0))) { - pos[1] = (ref5 = pos[0], indexOf.call(hAligns, ref5) >= 0) ? "m" : "l"; - } - if (pos.length === 2) { - pos[2] = pos[1]; - } - return pos; - }; - - Notification.prototype.getStyle = function(name) { - var style; - if (!name) { - name = this.options.style; - } - if (!name) { - name = "default"; - } - style = styles[name]; - if (!style) { - throw "Missing style: " + name; - } - return style; - }; - - Notification.prototype.updateClasses = function() { - var classes, style; - classes = ["base"]; - if ($.isArray(this.options.className)) { - classes = classes.concat(this.options.className); - } else if (this.options.className) { - classes.push(this.options.className); - } - style = this.getStyle(); - classes = $.map(classes, function(n) { - return pluginClassName + "-" + style.name + "-" + n; - }).join(" "); - return this.userContainer.attr("class", classes); - }; - - Notification.prototype.run = function(data, options) { - var d, datas, name, type, value; - if ($.isPlainObject(options)) { - $.extend(this.options, options); - } else if ($.type(options) === "string") { - this.options.className = options; - } - if (this.container && !data) { - this.show(false); - return; - } else if (!this.container && !data) { - return; - } - datas = {}; - if ($.isPlainObject(data)) { - datas = data; - } else { - datas[blankFieldName] = data; - } - for (name in datas) { - d = datas[name]; - type = this.userFields[name]; - if (!type) { - continue; - } - if (type === "text") { - d = encode(d); - if (this.options.breakNewLines) { - d = d.replace(/\n/g, '
'); - } - } - value = name === blankFieldName ? '' : '=' + name; - find(this.userContainer, "[data-notify-" + type + value + "]").html(d); - } - this.updateClasses(); - if (this.elem) { - this.setElementPosition(); - } else { - this.setGlobalPosition(); - } - this.show(true); - if (this.options.autoHide) { - clearTimeout(this.autohideTimer); - this.autohideTimer = setTimeout(this.show.bind(this, false), this.options.autoHideDelay); - } - }; - - Notification.prototype.destroy = function() { - this.wrapper.data(pluginClassName, null); - this.wrapper.remove(); - }; - - $[pluginName] = function(elem, data, options) { - if ((elem && elem.nodeName) || elem.jquery) { - $(elem)[pluginName](data, options); - } else { - options = data; - data = elem; - new Notification(null, data, options); - } - return elem; - }; - - $.fn[pluginName] = function(data, options) { - $(this).each(function() { - var prev = getAnchorElement($(this)).data(pluginClassName); - if (prev) { - prev.destroy(); - } - var curr = new Notification($(this), data, options); - }); - return this; - }; - - $.extend($[pluginName], { - defaults: defaults, - addStyle: addStyle, - pluginOptions: pluginOptions, - getStyle: getStyle, - insertCSS: insertCSS - }); - - //always include the default bootstrap style - addStyle("bootstrap", { - html: "
\n\n
", - classes: { - base: { - "font-weight": "bold", - "padding": "8px 15px 8px 14px", - "text-shadow": "0 1px 0 rgba(255, 255, 255, 0.5)", - "background-color": "#fcf8e3", - "border": "1px solid #fbeed5", - "border-radius": "4px", - "white-space": "nowrap", - "padding-left": "25px", - "background-repeat": "no-repeat", - "background-position": "3px 7px" - }, - error: { - "color": "#B94A48", - "background-color": "#F2DEDE", - "border-color": "#EED3D7", - "background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtRJREFUeNqkVc1u00AQHq+dOD+0poIQfkIjalW0SEGqRMuRnHos3DjwAH0ArlyQeANOOSMeAA5VjyBxKBQhgSpVUKKQNGloFdw4cWw2jtfMOna6JOUArDTazXi/b3dm55socPqQhFka++aHBsI8GsopRJERNFlY88FCEk9Yiwf8RhgRyaHFQpPHCDmZG5oX2ui2yilkcTT1AcDsbYC1NMAyOi7zTX2Agx7A9luAl88BauiiQ/cJaZQfIpAlngDcvZZMrl8vFPK5+XktrWlx3/ehZ5r9+t6e+WVnp1pxnNIjgBe4/6dAysQc8dsmHwPcW9C0h3fW1hans1ltwJhy0GxK7XZbUlMp5Ww2eyan6+ft/f2FAqXGK4CvQk5HueFz7D6GOZtIrK+srupdx1GRBBqNBtzc2AiMr7nPplRdKhb1q6q6zjFhrklEFOUutoQ50xcX86ZlqaZpQrfbBdu2R6/G19zX6XSgh6RX5ubyHCM8nqSID6ICrGiZjGYYxojEsiw4PDwMSL5VKsC8Yf4VRYFzMzMaxwjlJSlCyAQ9l0CW44PBADzXhe7xMdi9HtTrdYjFYkDQL0cn4Xdq2/EAE+InCnvADTf2eah4Sx9vExQjkqXT6aAERICMewd/UAp/IeYANM2joxt+q5VI+ieq2i0Wg3l6DNzHwTERPgo1ko7XBXj3vdlsT2F+UuhIhYkp7u7CarkcrFOCtR3H5JiwbAIeImjT/YQKKBtGjRFCU5IUgFRe7fF4cCNVIPMYo3VKqxwjyNAXNepuopyqnld602qVsfRpEkkz+GFL1wPj6ySXBpJtWVa5xlhpcyhBNwpZHmtX8AGgfIExo0ZpzkWVTBGiXCSEaHh62/PoR0p/vHaczxXGnj4bSo+G78lELU80h1uogBwWLf5YlsPmgDEd4M236xjm+8nm4IuE/9u+/PH2JXZfbwz4zw1WbO+SQPpXfwG/BBgAhCNZiSb/pOQAAAAASUVORK5CYII=)" - }, - success: { - "color": "#468847", - "background-color": "#DFF0D8", - "border-color": "#D6E9C6", - "background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAutJREFUeNq0lctPE0Ecx38zu/RFS1EryqtgJFA08YCiMZIAQQ4eRG8eDGdPJiYeTIwHTfwPiAcvXIwXLwoXPaDxkWgQ6islKlJLSQWLUraPLTv7Gme32zoF9KSTfLO7v53vZ3d/M7/fIth+IO6INt2jjoA7bjHCJoAlzCRw59YwHYjBnfMPqAKWQYKjGkfCJqAF0xwZjipQtA3MxeSG87VhOOYegVrUCy7UZM9S6TLIdAamySTclZdYhFhRHloGYg7mgZv1Zzztvgud7V1tbQ2twYA34LJmF4p5dXF1KTufnE+SxeJtuCZNsLDCQU0+RyKTF27Unw101l8e6hns3u0PBalORVVVkcaEKBJDgV3+cGM4tKKmI+ohlIGnygKX00rSBfszz/n2uXv81wd6+rt1orsZCHRdr1Imk2F2Kob3hutSxW8thsd8AXNaln9D7CTfA6O+0UgkMuwVvEFFUbbAcrkcTA8+AtOk8E6KiQiDmMFSDqZItAzEVQviRkdDdaFgPp8HSZKAEAL5Qh7Sq2lIJBJwv2scUqkUnKoZgNhcDKhKg5aH+1IkcouCAdFGAQsuWZYhOjwFHQ96oagWgRoUov1T9kRBEODAwxM2QtEUl+Wp+Ln9VRo6BcMw4ErHRYjH4/B26AlQoQQTRdHWwcd9AH57+UAXddvDD37DmrBBV34WfqiXPl61g+vr6xA9zsGeM9gOdsNXkgpEtTwVvwOklXLKm6+/p5ezwk4B+j6droBs2CsGa/gNs6RIxazl4Tc25mpTgw/apPR1LYlNRFAzgsOxkyXYLIM1V8NMwyAkJSctD1eGVKiq5wWjSPdjmeTkiKvVW4f2YPHWl3GAVq6ymcyCTgovM3FzyRiDe2TaKcEKsLpJvNHjZgPNqEtyi6mZIm4SRFyLMUsONSSdkPeFtY1n0mczoY3BHTLhwPRy9/lzcziCw9ACI+yql0VLzcGAZbYSM5CCSZg1/9oc/nn7+i8N9p/8An4JMADxhH+xHfuiKwAAAABJRU5ErkJggg==)" - }, - info: { - "color": "#3A87AD", - "background-color": "#D9EDF7", - "border-color": "#BCE8F1", - "background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=)" - }, - warn: { - "color": "#C09853", - "background-color": "#FCF8E3", - "border-color": "#FBEED5", - "background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABJlBMVEXr6eb/2oD/wi7/xjr/0mP/ykf/tQD/vBj/3o7/uQ//vyL/twebhgD/4pzX1K3z8e349vK6tHCilCWbiQymn0jGworr6dXQza3HxcKkn1vWvV/5uRfk4dXZ1bD18+/52YebiAmyr5S9mhCzrWq5t6ufjRH54aLs0oS+qD751XqPhAybhwXsujG3sm+Zk0PTwG6Shg+PhhObhwOPgQL4zV2nlyrf27uLfgCPhRHu7OmLgAafkyiWkD3l49ibiAfTs0C+lgCniwD4sgDJxqOilzDWowWFfAH08uebig6qpFHBvH/aw26FfQTQzsvy8OyEfz20r3jAvaKbhgG9q0nc2LbZxXanoUu/u5WSggCtp1anpJKdmFz/zlX/1nGJiYmuq5Dx7+sAAADoPUZSAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfdBgUBGhh4aah5AAAAlklEQVQY02NgoBIIE8EUcwn1FkIXM1Tj5dDUQhPU502Mi7XXQxGz5uVIjGOJUUUW81HnYEyMi2HVcUOICQZzMMYmxrEyMylJwgUt5BljWRLjmJm4pI1hYp5SQLGYxDgmLnZOVxuooClIDKgXKMbN5ggV1ACLJcaBxNgcoiGCBiZwdWxOETBDrTyEFey0jYJ4eHjMGWgEAIpRFRCUt08qAAAAAElFTkSuQmCC)" - } - } - }); - - $(function() { - insertCSS(coreStyle.css).attr("id", "core-notify"); - $(document).on("click", "." + pluginClassName + "-hidable", function(e) { - $(this).trigger("notify-hide"); - }); - $(document).on("notify-hide", "." + pluginClassName + "-wrapper", function(e) { - var elem = $(this).data(pluginClassName); - if(elem) { - elem.show(false); - } - }); - }); + //IE8 indexOf polyfill + var indexOf = [].indexOf || function (item) { + for (var i = 0, l = this.length; i < l; i++) { + if (i in this && this[i] === item) { + return i; + } + } + return -1; + }; + + var pluginName = "notify"; + var pluginClassName = pluginName + "js"; + var blankFieldName = pluginName + "!blank"; + + var positions = { + t: "top", + m: "middle", + b: "bottom", + l: "left", + c: "center", + r: "right" + }; + var hAligns = ["l", "c", "r"]; + var vAligns = ["t", "m", "b"]; + var mainPositions = ["t", "b", "l", "r"]; + var opposites = { + t: "b", + m: null, + b: "t", + l: "r", + c: null, + r: "l" + }; + + var parsePosition = function (str) { + var pos; + pos = []; + $.each(str.split(/\W+/), function (i, word) { + var w; + w = word.toLowerCase().charAt(0); + if (positions[w]) { + return pos.push(w); + } + }); + return pos; + }; + + var styles = {}; + + var coreStyle = { + name: "core", + html: "
\n
\n
\n
", + css: "." + pluginClassName + "-corner {\n position: fixed;\n margin: 5px;\n z-index: 1050;\n}\n\n." + pluginClassName + "-corner ." + pluginClassName + "-wrapper,\n." + pluginClassName + "-corner ." + pluginClassName + "-container {\n position: relative;\n display: block;\n height: inherit;\n width: inherit;\n margin: 3px;\n}\n\n." + pluginClassName + "-wrapper {\n z-index: 1;\n position: absolute;\n display: inline-block;\n height: 0;\n width: 0;\n}\n\n." + pluginClassName + "-container {\n display: none;\n z-index: 1;\n position: absolute;\n}\n\n." + pluginClassName + "-hidable {\n cursor: pointer;\n}\n\n[data-notify-text],[data-notify-html] {\n position: relative;\n}\n\n." + pluginClassName + "-arrow {\n position: absolute;\n z-index: 2;\n width: 0;\n height: 0;\n}" + }; + + var stylePrefixes = { + "border-radius": ["-webkit-", "-moz-"] + }; + + var getStyle = function (name) { + return styles[name]; + }; + + var addStyle = function (name, def) { + if (!name) { + throw "Missing Style name"; + } + if (!def) { + throw "Missing Style definition"; + } + if (!def.html) { + throw "Missing Style HTML"; + } + //remove existing style + var existing = styles[name]; + if (existing && existing.cssElem) { + if (window.console) { + console.warn(pluginName + ": overwriting style '" + name + "'"); + } + styles[name].cssElem.remove(); + } + def.name = name; + styles[name] = def; + var cssText = ""; + if (def.classes) { + $.each(def.classes, function (className, props) { + cssText += "." + pluginClassName + "-" + def.name + "-" + className + " {\n"; + $.each(props, function (name, val) { + if (stylePrefixes[name]) { + $.each(stylePrefixes[name], function (i, prefix) { + return cssText += " " + prefix + name + ": " + val + ";\n"; + }); + } + return cssText += " " + name + ": " + val + ";\n"; + }); + return cssText += "}\n"; + }); + } + if (def.css) { + cssText += "/* styles for " + def.name + " */\n" + def.css; + } + if (cssText) { + def.cssElem = insertCSS(cssText); + def.cssElem.attr("id", "notify-" + def.name); + } + var fields = {}; + var elem = $(def.html); + findFields("html", elem, fields); + findFields("text", elem, fields); + def.fields = fields; + }; + + var insertCSS = function (cssText) { + var e, elem, error; + elem = createElem("style"); + elem.attr("type", 'text/css'); + $("head").append(elem); + try { + elem.html(cssText); + } catch (_) { + elem[0].styleSheet.cssText = cssText; + } + return elem; + }; + + var findFields = function (type, elem, fields) { + var attr; + if (type !== "html") { + type = "text"; + } + attr = "data-notify-" + type; + return find(elem, "[" + attr + "]").each(function () { + var name; + name = $(this).attr(attr); + if (!name) { + name = blankFieldName; + } + fields[name] = type; + }); + }; + + var find = function (elem, selector) { + if (elem.is(selector)) { + return elem; + } else { + return elem.find(selector); + } + }; + + var pluginOptions = { + clickToHide: true, + autoHide: true, + autoHideDelay: 5000, + arrowShow: true, + arrowSize: 5, + breakNewLines: true, + elementPosition: "bottom", + globalPosition: "top right", + style: "bootstrap", + className: "error", + showAnimation: "slideDown", + showDuration: 400, + hideAnimation: "slideUp", + hideDuration: 200, + gap: 5 + }; + + var inherit = function (a, b) { + var F; + F = function () { + }; + F.prototype = a; + return $.extend(true, new F(), b); + }; + + var defaults = function (opts) { + return $.extend(pluginOptions, opts); + }; + + var createElem = function (tag) { + return $("<" + tag + ">"); + }; + + var globalAnchors = {}; + + var getAnchorElement = function (element) { + var radios; + if (element.is('[type=radio]')) { + radios = element.parents('form:first').find('[type=radio]').filter(function (i, e) { + return $(e).attr("name") === element.attr("name"); + }); + element = radios.first(); + } + return element; + }; + + var incr = function (obj, pos, val) { + var opp, temp; + if (typeof val === "string") { + val = parseInt(val, 10); + } else if (typeof val !== "number") { + return; + } + if (isNaN(val)) { + return; + } + opp = positions[opposites[pos.charAt(0)]]; + temp = pos; + if (obj[opp] !== undefined) { + pos = positions[opp.charAt(0)]; + val = -val; + } + if (obj[pos] === undefined) { + obj[pos] = val; + } else { + obj[pos] += val; + } + return null; + }; + + var realign = function (alignment, inner, outer) { + if (alignment === "l" || alignment === "t") { + return 0; + } else if (alignment === "c" || alignment === "m") { + return outer / 2 - inner / 2; + } else if (alignment === "r" || alignment === "b") { + return outer - inner; + } + throw "Invalid alignment"; + }; + + var encode = function (text) { + encode.e = encode.e || createElem("div"); + return encode.e.text(text).html(); + }; + + function Notification(elem, data, options) { + if (typeof options === "string") { + options = { + className: options + }; + } + this.options = inherit(pluginOptions, $.isPlainObject(options) ? options : {}); + this.loadHTML(); + this.wrapper = $(coreStyle.html); + if (this.options.clickToHide) { + this.wrapper.addClass(pluginClassName + "-hidable"); + } + this.wrapper.data(pluginClassName, this); + this.arrow = this.wrapper.find("." + pluginClassName + "-arrow"); + this.container = this.wrapper.find("." + pluginClassName + "-container"); + this.container.append(this.userContainer); + if (elem && elem.length) { + this.elementType = elem.attr("type"); + this.originalElement = elem; + this.elem = getAnchorElement(elem); + this.elem.data(pluginClassName, this); + this.elem.before(this.wrapper); + } + this.container.hide(); + this.run(data); + } + + Notification.prototype.loadHTML = function () { + var style; + style = this.getStyle(); + this.userContainer = $(style.html); + this.userFields = style.fields; + }; + + Notification.prototype.show = function (show, userCallback) { + var args, callback, elems, fn, hidden; + callback = (function (_this) { + return function () { + if (!show && !_this.elem) { + _this.destroy(); + } + if (userCallback) { + return userCallback(); + } + }; + })(this); + hidden = this.container.parent().parents(':hidden').length > 0; + elems = this.container.add(this.arrow); + args = []; + if (hidden && show) { + fn = "show"; + } else if (hidden && !show) { + fn = "hide"; + } else if (!hidden && show) { + fn = this.options.showAnimation; + args.push(this.options.showDuration); + } else if (!hidden && !show) { + fn = this.options.hideAnimation; + args.push(this.options.hideDuration); + } else { + return callback(); + } + args.push(callback); + return elems[fn].apply(elems, args); + }; + + Notification.prototype.setGlobalPosition = function () { + var p = this.getPosition(); + var pMain = p[0]; + var pAlign = p[1]; + var main = positions[pMain]; + var align = positions[pAlign]; + var key = pMain + "|" + pAlign; + var anchor = globalAnchors[key]; + if (!anchor) { + anchor = globalAnchors[key] = createElem("div"); + var css = {}; + css[main] = 0; + if (align === "middle") { + css.top = '45%'; + } else if (align === "center") { + css.left = '45%'; + } else { + css[align] = 0; + } + anchor.css(css).addClass(pluginClassName + "-corner"); + $("body").append(anchor); + } + return anchor.prepend(this.wrapper); + }; + + Notification.prototype.setElementPosition = function () { + var arrowColor, arrowCss, arrowSize, color, contH, contW, css, elemH, elemIH, elemIW, elemPos, elemW, gap, j, k, + len, len1, mainFull, margin, opp, oppFull, pAlign, pArrow, pMain, pos, posFull, position, ref, wrapPos; + position = this.getPosition(); + pMain = position[0]; + pAlign = position[1]; + pArrow = position[2]; + elemPos = this.elem.position(); + elemH = this.elem.outerHeight(); + elemW = this.elem.outerWidth(); + elemIH = this.elem.innerHeight(); + elemIW = this.elem.innerWidth(); + wrapPos = this.wrapper.position(); + contH = this.container.height(); + contW = this.container.width(); + mainFull = positions[pMain]; + opp = opposites[pMain]; + oppFull = positions[opp]; + css = {}; + css[oppFull] = pMain === "b" ? elemH : pMain === "r" ? elemW : 0; + incr(css, "top", elemPos.top - wrapPos.top); + incr(css, "left", elemPos.left - wrapPos.left); + ref = ["top", "left"]; + for (j = 0, len = ref.length; j < len; j++) { + pos = ref[j]; + margin = parseInt(this.elem.css("margin-" + pos), 10); + if (margin) { + incr(css, pos, margin); + } + } + gap = Math.max(0, this.options.gap - (this.options.arrowShow ? arrowSize : 0)); + incr(css, oppFull, gap); + if (!this.options.arrowShow) { + this.arrow.hide(); + } else { + arrowSize = this.options.arrowSize; + arrowCss = $.extend({}, css); + arrowColor = this.userContainer.css("border-color") || this.userContainer.css("border-top-color") || this.userContainer.css("background-color") || "white"; + for (k = 0, len1 = mainPositions.length; k < len1; k++) { + pos = mainPositions[k]; + posFull = positions[pos]; + if (pos === opp) { + continue; + } + color = posFull === mainFull ? arrowColor : "transparent"; + arrowCss["border-" + posFull] = arrowSize + "px solid " + color; + } + incr(css, positions[opp], arrowSize); + if (indexOf.call(mainPositions, pAlign) >= 0) { + incr(arrowCss, positions[pAlign], arrowSize * 2); + } + } + if (indexOf.call(vAligns, pMain) >= 0) { + incr(css, "left", realign(pAlign, contW, elemW)); + if (arrowCss) { + incr(arrowCss, "left", realign(pAlign, arrowSize, elemIW)); + } + } else if (indexOf.call(hAligns, pMain) >= 0) { + incr(css, "top", realign(pAlign, contH, elemH)); + if (arrowCss) { + incr(arrowCss, "top", realign(pAlign, arrowSize, elemIH)); + } + } + if (this.container.is(":visible")) { + css.display = "block"; + } + this.container.removeAttr("style").css(css); + if (arrowCss) { + return this.arrow.removeAttr("style").css(arrowCss); + } + }; + + Notification.prototype.getPosition = function () { + var pos, ref, ref1, ref2, ref3, ref4, ref5, text; + text = this.options.position || (this.elem ? this.options.elementPosition : this.options.globalPosition); + pos = parsePosition(text); + if (pos.length === 0) { + pos[0] = "b"; + } + if (ref = pos[0], indexOf.call(mainPositions, ref) < 0) { + throw "Must be one of [" + mainPositions + "]"; + } + if (pos.length === 1 || ((ref1 = pos[0], indexOf.call(vAligns, ref1) >= 0) && (ref2 = pos[1], indexOf.call(hAligns, ref2) < 0)) || ((ref3 = pos[0], indexOf.call(hAligns, ref3) >= 0) && (ref4 = pos[1], indexOf.call(vAligns, ref4) < 0))) { + pos[1] = (ref5 = pos[0], indexOf.call(hAligns, ref5) >= 0) ? "m" : "l"; + } + if (pos.length === 2) { + pos[2] = pos[1]; + } + return pos; + }; + + Notification.prototype.getStyle = function (name) { + var style; + if (!name) { + name = this.options.style; + } + if (!name) { + name = "default"; + } + style = styles[name]; + if (!style) { + throw "Missing style: " + name; + } + return style; + }; + + Notification.prototype.updateClasses = function () { + var classes, style; + classes = ["base"]; + if ($.isArray(this.options.className)) { + classes = classes.concat(this.options.className); + } else if (this.options.className) { + classes.push(this.options.className); + } + style = this.getStyle(); + classes = $.map(classes, function (n) { + return pluginClassName + "-" + style.name + "-" + n; + }).join(" "); + return this.userContainer.attr("class", classes); + }; + + Notification.prototype.run = function (data, options) { + var d, datas, name, type, value; + if ($.isPlainObject(options)) { + $.extend(this.options, options); + } else if ($.type(options) === "string") { + this.options.className = options; + } + if (this.container && !data) { + this.show(false); + return; + } else if (!this.container && !data) { + return; + } + datas = {}; + if ($.isPlainObject(data)) { + datas = data; + } else { + datas[blankFieldName] = data; + } + for (name in datas) { + d = datas[name]; + type = this.userFields[name]; + if (!type) { + continue; + } + if (type === "text") { + d = encode(d); + if (this.options.breakNewLines) { + d = d.replace(/\n/g, '
'); + } + } + value = name === blankFieldName ? '' : '=' + name; + find(this.userContainer, "[data-notify-" + type + value + "]").html(d); + } + this.updateClasses(); + if (this.elem) { + this.setElementPosition(); + } else { + this.setGlobalPosition(); + } + this.show(true); + if (this.options.autoHide) { + clearTimeout(this.autohideTimer); + this.autohideTimer = setTimeout(this.show.bind(this, false), this.options.autoHideDelay); + } + }; + + Notification.prototype.destroy = function () { + this.wrapper.data(pluginClassName, null); + this.wrapper.remove(); + }; + + $[pluginName] = function (elem, data, options) { + if ((elem && elem.nodeName) || elem.jquery) { + $(elem)[pluginName](data, options); + } else { + options = data; + data = elem; + new Notification(null, data, options); + } + return elem; + }; + + $.fn[pluginName] = function (data, options) { + $(this).each(function () { + var prev = getAnchorElement($(this)).data(pluginClassName); + if (prev) { + prev.destroy(); + } + var curr = new Notification($(this), data, options); + }); + return this; + }; + + $.extend($[pluginName], { + defaults: defaults, + addStyle: addStyle, + pluginOptions: pluginOptions, + getStyle: getStyle, + insertCSS: insertCSS + }); + + //always include the default bootstrap style + addStyle("bootstrap", { + html: "
\n\n
", + classes: { + base: { + "font-weight": "bold", + "padding": "8px 15px 8px 14px", + "text-shadow": "0 1px 0 rgba(255, 255, 255, 0.5)", + "background-color": "#fcf8e3", + "border": "1px solid #fbeed5", + "border-radius": "4px", + "white-space": "nowrap", + "padding-left": "25px", + "background-repeat": "no-repeat", + "background-position": "3px 7px" + }, + error: { + "color": "#B94A48", + "background-color": "#F2DEDE", + "border-color": "#EED3D7", + "background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtRJREFUeNqkVc1u00AQHq+dOD+0poIQfkIjalW0SEGqRMuRnHos3DjwAH0ArlyQeANOOSMeAA5VjyBxKBQhgSpVUKKQNGloFdw4cWw2jtfMOna6JOUArDTazXi/b3dm55socPqQhFka++aHBsI8GsopRJERNFlY88FCEk9Yiwf8RhgRyaHFQpPHCDmZG5oX2ui2yilkcTT1AcDsbYC1NMAyOi7zTX2Agx7A9luAl88BauiiQ/cJaZQfIpAlngDcvZZMrl8vFPK5+XktrWlx3/ehZ5r9+t6e+WVnp1pxnNIjgBe4/6dAysQc8dsmHwPcW9C0h3fW1hans1ltwJhy0GxK7XZbUlMp5Ww2eyan6+ft/f2FAqXGK4CvQk5HueFz7D6GOZtIrK+srupdx1GRBBqNBtzc2AiMr7nPplRdKhb1q6q6zjFhrklEFOUutoQ50xcX86ZlqaZpQrfbBdu2R6/G19zX6XSgh6RX5ubyHCM8nqSID6ICrGiZjGYYxojEsiw4PDwMSL5VKsC8Yf4VRYFzMzMaxwjlJSlCyAQ9l0CW44PBADzXhe7xMdi9HtTrdYjFYkDQL0cn4Xdq2/EAE+InCnvADTf2eah4Sx9vExQjkqXT6aAERICMewd/UAp/IeYANM2joxt+q5VI+ieq2i0Wg3l6DNzHwTERPgo1ko7XBXj3vdlsT2F+UuhIhYkp7u7CarkcrFOCtR3H5JiwbAIeImjT/YQKKBtGjRFCU5IUgFRe7fF4cCNVIPMYo3VKqxwjyNAXNepuopyqnld602qVsfRpEkkz+GFL1wPj6ySXBpJtWVa5xlhpcyhBNwpZHmtX8AGgfIExo0ZpzkWVTBGiXCSEaHh62/PoR0p/vHaczxXGnj4bSo+G78lELU80h1uogBwWLf5YlsPmgDEd4M236xjm+8nm4IuE/9u+/PH2JXZfbwz4zw1WbO+SQPpXfwG/BBgAhCNZiSb/pOQAAAAASUVORK5CYII=)" + }, + success: { + "color": "#468847", + "background-color": "#DFF0D8", + "border-color": "#D6E9C6", + "background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAutJREFUeNq0lctPE0Ecx38zu/RFS1EryqtgJFA08YCiMZIAQQ4eRG8eDGdPJiYeTIwHTfwPiAcvXIwXLwoXPaDxkWgQ6islKlJLSQWLUraPLTv7Gme32zoF9KSTfLO7v53vZ3d/M7/fIth+IO6INt2jjoA7bjHCJoAlzCRw59YwHYjBnfMPqAKWQYKjGkfCJqAF0xwZjipQtA3MxeSG87VhOOYegVrUCy7UZM9S6TLIdAamySTclZdYhFhRHloGYg7mgZv1Zzztvgud7V1tbQ2twYA34LJmF4p5dXF1KTufnE+SxeJtuCZNsLDCQU0+RyKTF27Unw101l8e6hns3u0PBalORVVVkcaEKBJDgV3+cGM4tKKmI+ohlIGnygKX00rSBfszz/n2uXv81wd6+rt1orsZCHRdr1Imk2F2Kob3hutSxW8thsd8AXNaln9D7CTfA6O+0UgkMuwVvEFFUbbAcrkcTA8+AtOk8E6KiQiDmMFSDqZItAzEVQviRkdDdaFgPp8HSZKAEAL5Qh7Sq2lIJBJwv2scUqkUnKoZgNhcDKhKg5aH+1IkcouCAdFGAQsuWZYhOjwFHQ96oagWgRoUov1T9kRBEODAwxM2QtEUl+Wp+Ln9VRo6BcMw4ErHRYjH4/B26AlQoQQTRdHWwcd9AH57+UAXddvDD37DmrBBV34WfqiXPl61g+vr6xA9zsGeM9gOdsNXkgpEtTwVvwOklXLKm6+/p5ezwk4B+j6droBs2CsGa/gNs6RIxazl4Tc25mpTgw/apPR1LYlNRFAzgsOxkyXYLIM1V8NMwyAkJSctD1eGVKiq5wWjSPdjmeTkiKvVW4f2YPHWl3GAVq6ymcyCTgovM3FzyRiDe2TaKcEKsLpJvNHjZgPNqEtyi6mZIm4SRFyLMUsONSSdkPeFtY1n0mczoY3BHTLhwPRy9/lzcziCw9ACI+yql0VLzcGAZbYSM5CCSZg1/9oc/nn7+i8N9p/8An4JMADxhH+xHfuiKwAAAABJRU5ErkJggg==)" + }, + info: { + "color": "#3A87AD", + "background-color": "#D9EDF7", + "border-color": "#BCE8F1", + "background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=)" + }, + warn: { + "color": "#C09853", + "background-color": "#FCF8E3", + "border-color": "#FBEED5", + "background-image": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABJlBMVEXr6eb/2oD/wi7/xjr/0mP/ykf/tQD/vBj/3o7/uQ//vyL/twebhgD/4pzX1K3z8e349vK6tHCilCWbiQymn0jGworr6dXQza3HxcKkn1vWvV/5uRfk4dXZ1bD18+/52YebiAmyr5S9mhCzrWq5t6ufjRH54aLs0oS+qD751XqPhAybhwXsujG3sm+Zk0PTwG6Shg+PhhObhwOPgQL4zV2nlyrf27uLfgCPhRHu7OmLgAafkyiWkD3l49ibiAfTs0C+lgCniwD4sgDJxqOilzDWowWFfAH08uebig6qpFHBvH/aw26FfQTQzsvy8OyEfz20r3jAvaKbhgG9q0nc2LbZxXanoUu/u5WSggCtp1anpJKdmFz/zlX/1nGJiYmuq5Dx7+sAAADoPUZSAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfdBgUBGhh4aah5AAAAlklEQVQY02NgoBIIE8EUcwn1FkIXM1Tj5dDUQhPU502Mi7XXQxGz5uVIjGOJUUUW81HnYEyMi2HVcUOICQZzMMYmxrEyMylJwgUt5BljWRLjmJm4pI1hYp5SQLGYxDgmLnZOVxuooClIDKgXKMbN5ggV1ACLJcaBxNgcoiGCBiZwdWxOETBDrTyEFey0jYJ4eHjMGWgEAIpRFRCUt08qAAAAAElFTkSuQmCC)" + } + } + }); + + $(function () { + insertCSS(coreStyle.css).attr("id", "core-notify"); + $(document).on("click", "." + pluginClassName + "-hidable", function (e) { + $(this).trigger("notify-hide"); + }); + $(document).on("notify-hide", "." + pluginClassName + "-wrapper", function (e) { + var elem = $(this).data(pluginClassName); + if (elem) { + elem.show(false); + } + }); + }); })); diff --git a/buildingdepot/CentralService/app/templates/auth/__init__.py b/buildingdepot/CentralService/app/templates/auth/__init__.py index 96d2e319..6d236971 100755 --- a/buildingdepot/CentralService/app/templates/auth/__init__.py +++ b/buildingdepot/CentralService/app/templates/auth/__init__.py @@ -1 +1 @@ -__author__ = 'hp' +__author__ = "hp" diff --git a/buildingdepot/CentralService/app/templates/auth/login.html b/buildingdepot/CentralService/app/templates/auth/login.html index c6b92d4f..d383b3be 100755 --- a/buildingdepot/CentralService/app/templates/auth/login.html +++ b/buildingdepot/CentralService/app/templates/auth/login.html @@ -4,14 +4,14 @@ {% block title %}BuildingDepot - Login{% endblock %} {% block page_content %} -
-
-
- {% endblock %} \ No newline at end of file diff --git a/buildingdepot/CentralService/app/templates/auth/register.html b/buildingdepot/CentralService/app/templates/auth/register.html index d65de07e..b8e2e501 100755 --- a/buildingdepot/CentralService/app/templates/auth/register.html +++ b/buildingdepot/CentralService/app/templates/auth/register.html @@ -4,14 +4,14 @@ {% block title %}BuildingDepot - Reset Password{% endblock %} {% block page_content %} -
-
-
- {% endblock %} \ No newline at end of file diff --git a/buildingdepot/CentralService/app/templates/central/building.html b/buildingdepot/CentralService/app/templates/central/building.html index 3fec840e..9f5ff712 100755 --- a/buildingdepot/CentralService/app/templates/central/building.html +++ b/buildingdepot/CentralService/app/templates/central/building.html @@ -4,67 +4,69 @@ {% block title %}BuildingDepot - Building{% endblock %} {% block page_content %} -
- +
+ - {% endblock %} {% block scripts %} - {{ super() }} - - + + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/buildingtemplate.html b/buildingdepot/Documentation/build/html/api/CentralService/buildingtemplate.html index 8124e1dc..e78d25b5 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/buildingtemplate.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/buildingtemplate.html @@ -5,10 +5,11 @@ - + + - BuildingTemplate — BuildingDepot 3.2.9 documentation + BuildingTemplate — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -182,15 +187,41 @@
-
-

BuildingTemplate

+
+

BuildingTemplate

Each building within BuildingDepot has a BuildingTemplate as a foundation. The BuildingTemplate helps define the structure of the building. The user has to assign a set of tags to the BuildingTemplate on creation which can be used later on for all the sensors within that building. BuildingTemplate can be defined in the CentralService at http://www.example.com:81/api/buildingtemplate.

-
-

Create a Building Template

+
+

Create a Building Template

This request creates a Building Template with the name, description and tagtypes to be used in the buildingtemplate specified by the user.

+
+
+POST /api/template
+
+
JSON Parameters:
+
    +
  • name (string) – Name of the BuildingTemplate

  • +
  • description (string) – Description for the BuildingTemplate

  • +
  • tag_types (list) – List of TagTypes available in system

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
  • error (string) – An additional value that will be present only if the request fails specifying the cause for failure

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get BuildingTemplate Details

This request retrieves name, description and tagtypes used in the buildingtemplate specified in the request.

+
+
+GET /api/template/<name>
+
+
Parameters:
+
    +
  • name (string) – Name of the BuildingTemplate

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is retrieved successfully otherwise ‘False’

  • +
  • name (string) – Name of the BuildingTemplate

  • +
  • description (string) – Description for the BuildingTemplate

  • +
  • tag_types (list) – List of TagTypes assigned for the BuildingTemplate

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/template/Test_Building_Template HTTP/1.1
+

Example request:

+
GET /api/template/Test_Building_Template HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{   "success": "True",
-    "name": "Test_Building_Template",
-    "description":"New Building Template",
-    "tags": ["floor","room","corridor"]
-}
+{   "success": "True",
+    "name": "Test_Building_Template",
+    "description":"New Building Template",
+    "tags": ["floor","room","corridor"]
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{  "success": "False",
-  "error": " BuildingTemplate does not exist"
-  }
+{  "success": "False",
+  "error": " BuildingTemplate does not exist"
+  }
 
-
-
-

Delete Building Template

+
+
+

Delete Building Template

This request deletes the requested BuildingTemplate and the Tagtypes assigned to it.

+
+
+DELETE /api/template/<name>
+
+
Parameters:
+
    +
  • name (string) – Name of the BuildingTemplate

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the Building Template is successfully deleted otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
DELETE /api/template/Test_Building_Template  HTTP/1.1
+

Example request:

+
DELETE /api/template/Test_Building_Template  HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-}
+{
+  "success": "True"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " BuildingTemplate does not exist"
-}
+{
+  "success": "False",
+  "error": " BuildingTemplate does not exist"
+}
 
-{
-  "success": "False",
-  "error": " BuildingTemplate is in use"
-}
+{
+  "success": "False",
+  "error": " BuildingTemplate is in use"
+}
 
-
-
+
+
@@ -320,7 +400,7 @@

Delete Building Template

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -342,16 +422,19 @@

Delete Building Template var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/dataservice.html b/buildingdepot/Documentation/build/html/api/CentralService/dataservice.html index 4980eb5a..aa452571 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/dataservice.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/dataservice.html @@ -5,10 +5,11 @@ - + + - DataService — BuildingDepot 3.2.9 documentation + DataService — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -188,15 +193,42 @@
-
-

DataService

+
+

DataService

The DataService is where all the data related to sensors and the timeseries data of each sensor resides. All the access control related functionality is defined at centralservice is also enforced within the DataService. A new DataService can be defined in the CentralService at http://www.example.com:81/api/dataservice.

-
-

Create a new DataService

+
+

Create a new DataService

This request creates a new DataService with description,host and port where the datservice will function.

+
+
+POST /api/dataservice
+
+
JSON Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
  • description (string) – Description for the DataService

  • +
  • host (string) – HostName of the device where the DataService is to be installed

  • +
  • port (string) – Port number of the device where the DataService is to be installed

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
  • error (string) – An additional value that will be present only if the request fails specifying the cause for failure

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get DataService Details

This request retrieves name, description, hostname and port to used in the dataservice specified in the request.

+
+
+GET /api/dataservice/<name>
+
+
Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is retrieved successfully otherwise ‘False’

  • +
  • name (string) – Name of the DataService

  • +
  • description (string) – Description for the DataService

  • +
  • host (string) – HostName of the device where the DataService is installed

  • +
  • port (string) – Port number of the device where the DataService is installed

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/dataservice/ds3 HTTP/1.1
+

Example request:

+
GET /api/dataservice/ds3 HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{   "success": "True",
-      "name": "ds3"
-      "description":"Test_ds3",
-      "host":"127.0.0.3",
-      "port":"83"
-}
+{   "success": "True",
+      "name": "ds3"
+      "description":"Test_ds3",
+      "host":"127.0.0.3",
+      "port":"83"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " DataService does not exist"
-}
+{
+  "success": "False",
+  "error": " DataService does not exist"
+}
 
-
-
-

Delete DataService

+
+
+

Delete DataService

This request deletes the requested DataService from Building Depot.

+
+
+DELETE /api/dataservice/<name>
+
+
Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the DataService is successfully deleted otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
DELETE /api/dataservice/ds3 HTTP/1.1
+

Example request:

+
DELETE /api/dataservice/ds3 HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-}
+{
+  "success": "True"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "DataService doesn't exist"
-}
+{
+  "success": "False",
+  "error": "DataService doesn't exist"
+}
 
-{
-  "success": "False",
-  "error": "Cannot delete DataService, contains buildings."
-}
+{
+  "success": "False",
+  "error": "Cannot delete DataService, contains buildings."
+}
 
-
-
-

Assign Buildings to DataService

+
+
+

Assign Buildings to DataService

This request assigns a specific building to DataService. Once the building is assigned to a specific DataService, the DataService handles sensor datastreams from the building.

+
+
+POST /api/dataservice/<name>/building
+
+
Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
+
+
JSON Parameters:
+
    +
  • +
    data (dict) – Contains the information of the buildings to be added to DataService.
      +
    • buildings (list) – List of buildings to be added to DataService

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the building is successfully added to the DataService otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get Building Details from DataService

This request retrieves the names of buildings that the specified DataService hosts.

+
+
+GET /api/dataservice/<name>/buildings
+
+
Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is retrieved successfully otherwise ‘False’

  • +
  • buildings (list) – Contains the list of buildings that the the specified DataService hosts

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/dataservice/ds1/buildings HTTP/1.1
+

Example request:

+
GET /api/dataservice/ds1/buildings HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True",
-  "buildings": ["NSH", "GHC"]
-}
+{
+  "success": "True",
+  "buildings": ["NSH", "GHC"]
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " DataService doesn't exist"
-}
+{
+  "success": "False",
+  "error": " DataService doesn't exist"
+}
 
-
-
-

Remove Buildings from DataService

+
+
+

Remove Buildings from DataService

This request removes specified buildings from a DataService.

+
+
+DELETE /api/dataservice/<name>/buildings
+
+
Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
+
+
JSON Parameters:
+
    +
  • +
    data (dict) – Contains the information of the buildings to be deleted from DataService.
      +
    • buildings (list) – List of buildings to be deleted from DataService

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the buildings are successfully deleted otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Grant Admin Privileges on DataService

This request grants CRUD (create/read/update/delete) privileges on the DataService to the specified users.

+
+
+POST /api/dataservice/<name>/admins
+
+
Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
+
+
JSON Parameters:
+
    +
  • +
    data (dict) – Contains the information of the users to whom the CRUD privileges should be given.
      +
    • admins (list) – List of the emails(string) of the users.

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the admin privileges are successfully added to the DataService otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get List of Admins from DataService

This request retrieves the list of users who have the admin privileges on the specified DataService.

+
+
+GET /api/dataservice/<name>/admins
+
+
Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the list is retrieved successfully otherwise ‘False’

  • +
  • admins (list) – Contains the list of emails of the users who have admin privilege on the specified DataService

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/dataservice/ds1/buildings HTTP/1.1
+

Example request:

+
GET /api/dataservice/ds1/buildings HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True",
-  "admins": ["user1@buildingdepot.org", "user2@buildingdepot.org"]
-}
+{
+  "success": "True",
+  "admins": ["user1@buildingdepot.org", "user2@buildingdepot.org"]
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " DataService doesn't exist"
-}
+{
+  "success": "False",
+  "error": " DataService doesn't exist"
+}
 
-
-
-

Revoke Admin Privileges on DataService

+
+
+

Revoke Admin Privileges on DataService

This request revokes admin privileges on DataService from the specified users.

+
+
+DELETE /api/dataservice/<name>/admins
+
+
Parameters:
+
    +
  • name (string) – Name of the DataService

  • +
+
+
JSON Parameters:
+
    +
  • +
    data (dict) – Contains the information of the buildings to ba deleted from DataService.
      +
    • admins (list) – List of the emails of users whose privileges on DataService should be revoked.

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the permissions are successfully revoked otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
@@ -581,7 +847,7 @@

Revoke Admin Privileges on DataService

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -603,16 +869,19 @@

Revoke Admin Privileges on DataService var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/index.html b/buildingdepot/Documentation/build/html/api/CentralService/index.html index ba0bf9be..8d19cb58 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/index.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/index.html @@ -5,10 +5,11 @@ - + + - CentralService APIs — BuildingDepot 3.2.9 documentation + CentralService APIs — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -64,7 +69,7 @@
- 3.2.9 + 3.3
@@ -174,8 +179,8 @@
-
-

CentralService APIs

+
+

CentralService APIs

All urls in this chapter are with respect to the base url of the CentralService. For example: If the CentralService is at http://www.example.com:81 then a url such as /service/tagtype refers to http://www.example.com:81/api/tagtype account.

Note: In order to access the CentralService all users will first have to generate an OAuth Token using the Client ID and secret Client Key that they obtain from their account at the CentralService. Once logged into the CentralService users can go to the OAuth tab and generate a Client ID and Key that should never be shared with any other users.

Once the Client ID and Key have been generated the user can request BuildingDepot for the access token. The access token will be valid for the next 24 hours after which another access token will have to be generated.The OAuth token should also never be shared publicly.

@@ -266,7 +271,7 @@

CentralService APIs

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -309,16 +314,19 @@

CentralService APIs var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/oauth.html b/buildingdepot/Documentation/build/html/api/CentralService/oauth.html index 40630b23..820a88a2 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/oauth.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/oauth.html @@ -5,10 +5,11 @@ - + + - OAuth — BuildingDepot 3.2.9 documentation + OAuth — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -180,31 +185,43 @@
-
-

OAuth

+
+

OAuth

Every query to BuildingDepot has to be authenticated by an access token. The client id and secret key required to generate the access token can be obtained after logging into the CentralService. After these have been obtained the access token can be generated by using the following request.

-
-

Generating access tokens

+
+

Generating access tokens

Generate an access token using the client id and secret key obtained from the CentralService. Each access token is valid for 24 hours from the time of generation.

+
+
+GET /oauth/access_token/client_id=<client_id>/client_secret=<client_secret>
+
+
Returns:
+
    +
  • access token

  • +
+
+
+
+
-

Example request:

-
GET /oauth/access_token/client_id=BOCWEJSnwJ8UJ4mfPiP8CqCX0QGHink6PFbmTnx0/
+

Example request:

+
GET /oauth/access_token/client_id=BOCWEJSnwJ8UJ4mfPiP8CqCX0QGHink6PFbmTnx0/
 client_secret=1gk1pBQHiK6vHQULOndEucULq0Tf5H9vKjAUbIBVX0qMjsC9uQ HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "access_token": "528d58481bc728a5eb57e73a49ba4539"
-}
+{
+  "access_token": "528d58481bc728a5eb57e73a49ba4539"
+}
 
-
-
+
+
@@ -225,7 +242,7 @@

Generating access tokens

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -247,16 +264,19 @@

Generating access tokens var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/permission.html b/buildingdepot/Documentation/build/html/api/CentralService/permission.html index d7a4b447..06ce2cb8 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/permission.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/permission.html @@ -5,10 +5,11 @@ - + + - Permission — BuildingDepot 3.2.9 documentation + Permission — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -182,30 +187,30 @@
-
-

Permission

+
+

Permission

The Permissions are created between SensorGroups and UserGroups in Building Depot which come together to form the access control lists.Here we select a User Group and a Sensor Group and a permission value with which we want to associate these both. There are three levels of permission defined in BuildingDepot which are ‘d/r’ (deny read) ,’r’ (read), ‘r/w’ (read write) and ‘r/w/p’ (read write permission). If there are multiple permission mappings between a user and a sensor then the one that is most restrictive is chosen. Permissions can be defined in the CentralService at http://www.example.com:81/api/permission.

-
-

Create Permission

+
+

Create Permission

This request creates a new Permission link between a UserGroup and a SensorGroup.

- --- - - - - -
JSON Parameters:
 
+ +
+
+

Read Permission

This request retrieves Permission level assigned for existing SensorGroup and UserGroup.

+
+
+GET /api/permission?user_group=<user_group>&sensor_group=<sensor_group>
+
+
Parameters:
+
    +
  • user_group (string) – Name of UserGroup (compulsory)

  • +
  • sensor_group (string) – Name of SensorGroup (compulsory)

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if a permission exists between the sensor and user group otherwise ‘False’

  • +
  • permission (string) – Contains the permission level that are attached to this SensorGroup and UserGroup

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/permission?user_group=Test_User_Group&sensor_group=Test_Sensor_Group HTTP/1.1
+

Example request:

+
GET /api/permission?user_group=Test_User_Group&sensor_group=Test_Sensor_Group HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-  "permission": "r"
-}
+{
+  "success": "True"
+  "permission": "r"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " Permission does not exist"
-}
+{
+  "success": "False",
+  "error": " Permission does not exist"
+}
 
-
-
-

Delete Permission

+
+
+

Delete Permission

This request deletes the permission link between the UserGroup and SensorGroup

+
+
+DELETE /api/permission?user_group=<user_group>&sensor_group=<sensor_group>
+
+
Parameters:
+
    +
  • user_group (string) – Name of UserGroup (compulsory)

  • +
  • sensor_group (string) – Name of SensorGroup (compulsory)

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if a permission is deleted between the sensor and user group otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
DELETE /api/permission?user_group=Test_User_Group&sensor_group=Test_Sensor_Group HTTP/1.1
+

Example request:

+
DELETE /api/permission?user_group=Test_User_Group&sensor_group=Test_Sensor_Group HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-}
+{
+  "success": "True"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " Permission does not exist"
-}
+{
+  "success": "False",
+  "error": " Permission does not exist"
+}
 
-{
-  "success": "False",
-  "error": " You are not authorized to delete this permission"
-}
+{
+  "success": "False",
+  "error": " You are not authorized to delete this permission"
+}
 
-{
-  "success": "False",
-  "error": "Missing parameters"
-}
+{
+  "success": "False",
+  "error": "Missing parameters"
+}
 
-
-
+
+
@@ -376,7 +424,7 @@

Delete Permission

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -398,16 +446,19 @@

Delete Permission var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/permissionRequests.html b/buildingdepot/Documentation/build/html/api/CentralService/permissionRequests.html index 94fb8acb..32b7ef36 100644 --- a/buildingdepot/Documentation/build/html/api/CentralService/permissionRequests.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/permissionRequests.html @@ -5,10 +5,11 @@ - + + - Permission Requests — BuildingDepot 3.2.9 documentation + Permission Requests — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -181,8 +186,8 @@
-
-

Permission Requests

+
+

Permission Requests

A Permission Request can be sent to obtain access to a particular sensor entity owned by a user of BuildingDepot. Once the permission request is sent to the user, on approval the Permission APIs should be used to create a permission pair between the user and the sensor resources. @@ -192,27 +197,27 @@

Permission Requestshttp://www.example.com:81/api/permission. Note: Firebase or RabbitMQ needs to be installed during the BD installation for this to work.

-
-

Create Permission Requests

+
+

Create Permission Requests

This request creates a new Permission Request for a sensor entity owned by a user.

- --- - - - - -
JSON Parameters:
 
+ +
+
+

Read Permission Requests

This request retrieves Permission Request for a user.

+
+
+GET /api/permission/request
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if a permission exists between the sensor and user group otherwise ‘False’

  • +
  • permission_requests (string) – Contains the permission level that are attached to this SensorGroup and UserGroup

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/permission/request HTTP/1.1
+

Example request:

+
GET /api/permission/request HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
+

@@ -343,7 +361,7 @@

Read Permission Requests

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -365,16 +383,19 @@

Read Permission Requests var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/sensor.html b/buildingdepot/Documentation/build/html/api/CentralService/sensor.html index 47f66ed6..2aebe39a 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/sensor.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/sensor.html @@ -5,10 +5,11 @@ - + + - Sensor — BuildingDepot 3.2.9 documentation + Sensor — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -189,16 +194,42 @@
-
-

Sensor

+
+

Sensor

The Sensor collection manages Sensors for Locations associated with the CentralService.Sensors can be defined in the CentralService at http://www.example.com:81/api/sensor. Sensor access is restricted to Central Service Users with permissions for the Sensor and to the Admin who owns the Sensor.

-
-

Create a Sensor

+
+

Create a Sensor

Creates a new Sensor point in BuildingDepot and returns the UUID.

+
+
+POST /api/sensor
+
+
JSON Parameters:
+
    +
  • name (string) – Name of the sensor

  • +
  • identifier (string) – An identifier that will be associated with the sensor

  • +
  • building (string) – Building in which the sensor is located

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
  • uuid (string) – Returns the uuid of the sensor on successful creation

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get Sensor details

Retrieves all the details of the sensor based on the uuid specified

+
+
+GET /api/sensor/<name>
+
+
Parameters:
+
    +
  • name (string) – uuid of the sensor

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is retrieved successfully otherwise ‘False’

  • +
  • building (string) – Building in which the sensor is located

  • +
  • name (string) – UUID name of the sensor

  • +
  • tags (list) – List of tags owned by the sensor

  • +
  • metadata (list) – List of metadata owned by the sensor

  • +
  • source_identifier (dictionary) – Source identifier of the sensor

  • +
  • source_name (dictionary) – Source name of the sensor

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/sensor/8aac1048-aa9f-41c9-9c20-6dd81339c7de HTTP/1.1
+

Example request:

+
GET /api/sensor/8aac1048-aa9f-41c9-9c20-6dd81339c7de HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-    "success": "True",
-    "building": "NSH",
-    "name": "8aac1048-aa9f-41c9-9c20-6dd81339c7de",
-    "source_identifier": "Sensor_Tag",
-    "source_name": "Test_Sensor",
-    "views_owned": [
-      {
-        "fields": "EMI-0",
-        "id": "280bc754-a963-4740-89f4-bdae9ede76f6",
-        "source_name": "Averages"
-      },
-      {
-        "fields": "EMI-2, EMI-3",
-        "id": "6336a9d7-6f65-4572-9e52-78fa3808c92f",
-        "source_name": "Min, Max EMI"
-      }
-    ]
-}
+{
+    "success": "True",
+    "building": "NSH",
+    "name": "8aac1048-aa9f-41c9-9c20-6dd81339c7de",
+    "source_identifier": "Sensor_Tag",
+    "source_name": "Test_Sensor",
+    "views_owned": [
+      {
+        "fields": "EMI-0",
+        "id": "280bc754-a963-4740-89f4-bdae9ede76f6",
+        "source_name": "Averages"
+      },
+      {
+        "fields": "EMI-2, EMI-3",
+        "id": "6336a9d7-6f65-4572-9e52-78fa3808c92f",
+        "source_name": "Min, Max EMI"
+      }
+    ]
+}
 
-
-
-

Delete a Sensor

+
+
+

Delete a Sensor

Delete the Sensor associated with sensor_uuid.

-

Attention

+

Attention

Restricted to Admins only

-

Currently can only be done through the GUI

-
+

Currently can only be done through the GUI

-
-

Search Sensors

+
+
+

Search Sensors

The Search API is used search sensors based on uuid,source_name,source_identifier, building, Tag and MetaData. Multiple search queries can be sent in a single request.

- --- - - - - -
JSON Parameters:
 
    -
  • -
    data (dictionary) – Contains the list of Search Query key-value pairs
    -
      -
    • ID (list) – UUID of the Sensor
    • -
    • Building (list) – Building in which the sensor is located
    • -
    • Tags (list) – List of tags owned by the sensor. The are given as key,value pairs.
    • -
    • Metadata (list) – List of metadata owned by the sensor.The are given as key,value pairs.
    • -
    • Source_Identifier (list) – Source identifier of the sensor
    • -
    • Source_Name (list) – Source name of the sensor
    • +
      +
      +POST /api/sensor/search
      +
      + +
      +
      JSON Parameters:
      +
        +
      • +
        data (dictionary) – Contains the list of Search Query key-value pairs
          +
        • ID (list) – UUID of the Sensor

        • +
        • Building (list) – Building in which the sensor is located

        • +
        • Tags (list) – List of tags owned by the sensor. The are given as key,value pairs.

        • +
        • Metadata (list) – List of metadata owned by the sensor.The are given as key,value pairs.

        • +
        • Source_Identifier (list) – Source identifier of the sensor

        • +
        • Source_Name (list) – Source name of the sensor

      -
+ +
+
+

Add Tags to a Sensor

This request adds tags (key-value pairs) to a particular sensor. To get the available tags that can be added to the sensor, please use the get tags of a sensor API to fetch the list of available tags.

+
+
+POST /api/sensor/<name>/tags
+
+
Parameters:
+
    +
  • id (string) – UUID associated with Sensor (required)

  • +
+
+
JSON Parameters:
+
    +
  • +
    data (dictionary) – Contains the information of the tag to be added to the sensor.
      +
    • +
      tags (list) – List of tags
        +
      • name (string) – Name of the Tag Type

      • +
      • value (string) – Value for the Tag Type

      • +
      +
      +
      +
    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (array) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get Tags of a Sensor

This request retrieves two lists of key-value pairs, one list contains the array of eligible tags that can be attached to this sensor and the other list contains the array of tags that are currently attached to this sensor.

+
+
+GET /api/sensor/<name>/tags
+
+
Parameters:
+
    +
  • id (string) – UUID associated with Sensor (required)

  • +
+
+
Returns:
+
    +
  • +
    tags (list) – Contains the list of tag key-value pairs that are available for the building in which this sensor is located
      +
    • name (string) – Name of the tag point

    • +
    • value (list) – List of eligible values for this certain tag

    • +
    +
    +
    +
  • +
  • +
    tags_owned (list) – Contains the list of tag key-value pairs that are attached to this sensor
      +
    • name (string) – Name of the tag point

    • +
    • value (string) – Value for this tag

    • +
    +
    +
    +
  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/sensor/26da099a-3fe0-4966-b068-14f51bcedb6e/tags HTTP/1.1
+

Example request:

+
GET /api/sensor/26da099a-3fe0-4966-b068-14f51bcedb6e/tags HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "tags": {
-           "Corridor": [
-                        "3600",
-                        "3700"
-                       ],
-           "Floor": [
-                     "3"
-                    ],
-           "Room": [
-                    "3606"
-                   ]
-          },
-  "tags_owned": [
-                  {
-                   "name": "Corridor",
-                   "value": "3600"
-                  },
-                  {
-                   "name": "Floor",
-                   "value": "3"
-                  },
-                  {
-                   "name": "Room",
-                   "value": "3606"
-                  }
-                  ]
-  }
+{
+  "tags": {
+           "Corridor": [
+                        "3600",
+                        "3700"
+                       ],
+           "Floor": [
+                     "3"
+                    ],
+           "Room": [
+                    "3606"
+                   ]
+          },
+  "tags_owned": [
+                  {
+                   "name": "Corridor",
+                   "value": "3600"
+                  },
+                  {
+                   "name": "Floor",
+                   "value": "3"
+                  },
+                  {
+                   "name": "Room",
+                   "value": "3606"
+                  }
+                  ]
+  }
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " Sensor does not exist"
-}
+{
+  "success": "False",
+  "error": " Sensor does not exist"
+}
 
-
-
-

Create a Sensor View

+
+
+

Create a Sensor View

Creates a new Sensor View for a sensor point in BuildingDepot. Each sensor may have multiple fields of data, however, the user may only want to receive or request a subset of data. This API creates a view for the sensor and returns the UUID for the sensor.

+
+
+POST /api/sensor/<name>/view
+
+
Parameters:
+
    +
  • id (string) – UUID associated with Sensor (required)

  • +
+
+
JSON Parameters:
+
    +
  • +
    data (dictionary) – Contains the information of the sensor view.
      +
    • fields (string) – The data fields of the sensor that is being posted to BD.

    • +
    • source_name (string) – Name of the sensor view

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (dictionary) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get Sensor View details

Retrieves all the views of the sensor based on the sensor uuid specified

+
+
+GET /api/sensor/<name>/views
+
+
Parameters:
+
    +
  • name (string) – uuid of the sensor

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is retrieved successfully otherwise ‘False’

  • +
  • available_fields (list) – List of available fields that the sensor can have.

  • +
  • views_owned (list) – List of views for the sensor. +* fields (string) – A subset of fields in the sensor +* id (string) – UUID name of the views of the sensor +* source_name (string) – Source name of the views of the sensor

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/sensor/6cf53d24-e3a3-41bd-b2b5-8f109694f628 HTTP/1.1
+

Example request:

+
GET /api/sensor/6cf53d24-e3a3-41bd-b2b5-8f109694f628 HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-    "available_fields": [
-      "Temp-0",
-      "EMI-1",
-      "Mic-2"
-    ],
-    "success": "True",
-    "views_owned": [
-      {
-        "fields": "EMI-1, Mic-2, Temp-0",
-        "id": "3240e89b-81df-4c1c-a411-fe891ed2aaef",
-        "source_name": "All"
-      },
-      {
-        "fields": "EMI-1",
-        "id": "ce7330aa-0893-4115-842d-0b6ab92e98de",
-        "source_name": "EMI Average"
-      }
-    ]
-  }
+{
+    "available_fields": [
+      "Temp-0",
+      "EMI-1",
+      "Mic-2"
+    ],
+    "success": "True",
+    "views_owned": [
+      {
+        "fields": "EMI-1, Mic-2, Temp-0",
+        "id": "3240e89b-81df-4c1c-a411-fe891ed2aaef",
+        "source_name": "All"
+      },
+      {
+        "fields": "EMI-1",
+        "id": "ce7330aa-0893-4115-842d-0b6ab92e98de",
+        "source_name": "EMI Average"
+      }
+    ]
+  }
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " Sensor does not exist"
-}
+{
+  "success": "False",
+  "error": " Sensor does not exist"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "Missing parameters"
-}
+{
+  "success": "False",
+  "error": "Missing parameters"
+}
 
-
-
-

Delete a Sensor View

+
+
+

Delete a Sensor View

This request deletes a sensor view of a sensor.

+
+
+DELETE /api/sensor/<uuid>/views/<view_uuid>
+
+
Parameters:
+
    +
  • uuid (string) – UUID of the sensor.

  • +
  • view_uuid (string) – UUID of the view sensor.

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the UserGroup is successfully deleted otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
DELETE /api/sensor/8aac1048-aa9f-41c9-9c20-6dd81339c7de/views/22d807bf-af67-493a-80b7-2690d26c9244 HTTP/1.1
+

Example request:

+
DELETE /api/sensor/8aac1048-aa9f-41c9-9c20-6dd81339c7de/views/22d807bf-af67-493a-80b7-2690d26c9244 HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-}
+{
+  "success": "True"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "Permission does not exist"
-}
+{
+  "success": "False",
+  "error": "Permission does not exist"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "Communication failure with DataService"
-}
+{
+  "success": "False",
+  "error": "Communication failure with DataService"
+}
 
-
-
-

SensorGroups and UserGroups

-
-
BuildingDepot restricts access to sensors to users on three levels. A user can have either of these types of access to a sensor:
-
    -
  • Read
  • -
  • Read/Write
  • -
  • Deny Read
  • -
  • Read/Write/Permission
  • +
+
+

SensorGroups and UserGroups

+
+
BuildingDepot restricts access to sensors to users on three levels. A user can have either of these types of access to a sensor:
    +
  • Read

  • +
  • Read/Write

  • +
  • Deny Read

  • +
  • Read/Write/Permission

As the names suggest a user with read access to a sensor will be able to read all the datapoints of the sensors. A user with Read/Write access will be able to both read and write (if supported by the sensor) to the sensors. With Deny Read a user will not be able to read any datapoints of the sensor.

The basis of deciding these permissions is dependent on the abstraction of SensorGroups and UserGroups within BuildingDepot.

SensorGroups are created on the basis of tags that are specified at the time of creation. All sensors with the specified tags will be a part of the SensorGroup that is created. Usergroups are basically a list of users which are connected to a SensorGroup via a “Permissions” link. This link is what defines the level of access that the users in the UserGroup have to the sensors in the SensorGroup.

-
-
+ +
@@ -608,7 +832,7 @@

SensorGroups and UserGroups

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -630,16 +854,19 @@

SensorGroups and UserGroups var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/sensorgroup.html b/buildingdepot/Documentation/build/html/api/CentralService/sensorgroup.html index 3e93f5ab..629d235c 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/sensorgroup.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/sensorgroup.html @@ -5,10 +5,11 @@ - + + - SensorGroups — BuildingDepot 3.2.9 documentation + SensorGroups — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -184,15 +189,51 @@
-
-

SensorGroups

+
+

SensorGroups

SensorGroups are a virtual collection of sensors that are created based on the tags that are specified. Each sensor may consist a set of tags. The list of sensors that belong to a SensorGroup can be changed by modifying the tags attached to this SensorGroup. All sensors having the current tags will fall under this SensorGroup automatically for any subsequent operations. SensorGroups can be defined in the CentralService at http://www.example.com:81/api/sensorgroup.

-
-

Create SensorGroup

+
+

Create SensorGroup

This request creates a new SensorGroup with the name and description in the building specified by the user.

+
+
+POST /api/sensor_group
+
+
+
JSON Parameters:
+
    +
  • +
    data (dictionary) – Contains the information about the SensorGroup
      +
    • name (string) – Name of the SensorGroup

    • +
    • building (string) – Building to which this sensor group belongs

    • +
    • description (string) – Description of the SensorGroup

    • +
    +
    +
    +
  • +
+
+
+
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
  • error (string) – An additional value that will be present only if the request fails specifying the cause for failure

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get SensorGroup Details

This request retrieves the details of a SensorGroup

+
+
+GET /api/sensor_group/<name>
+
+
Parameters:
+
    +
  • name (string) – Name of Sensor group (compulsory)

  • +
+
+
Returns:
+
    +
  • name (string) – Contains the name of the SensorGroup

  • +
  • description (string) – Contains the description of the SensorGroup

  • +
  • building (string) – Contains the name of the building of the SensorGroup

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/sensor_group/Test HTTP/1.1
+

Example request:

+
GET /api/sensor_group/Test HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success" : "true"
-  "name":"Test",
-  "description": "A SensorGroup for Test"
-  "building":"NSH"
-}
+{
+  "success" : "true"
+  "name":"Test",
+  "description": "A SensorGroup for Test"
+  "building":"NSH"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "SensorGroup does not exist"
-}
+{
+  "success": "False",
+  "error": "SensorGroup does not exist"
+}
 
-
-
-

Delete SensorGroup

+
+
+

Delete SensorGroup

This request deletes the SensorGroup

+
+
+DELETE /api/sensor_group/<name>
+
+
Parameters:
+
    +
  • email (string) – Name of the SensorGroup

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the SensorGroup is successfully deleted otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
DELETE /api/sensor_group/Test HTTP/1.1
+

Example request:

+
DELETE /api/sensor_group/Test HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-}
+{
+  "success": "True"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "SensorGroup does not exist"
-}
+{
+  "success": "False",
+  "error": "SensorGroup does not exist"
+}
 
-
-
-

Add tags to SensorGroup

+
+
+

Add tags to SensorGroup

This request adds the tags specified in the request to the SensorGroup

Note: The list of tags sent in this request will overwrite the previous list.

+
+
+POST /api/sensor_group/<name>/tags
+
+
Parameters:
+
    +
  • name (string) – Name of SensorGroup

  • +
+
+
JSON Parameters:
+
    +
  • +
    data (dictionary) – Contains the information of the tag to be added to the SensorGroup.
      +
    • +
      tags (list) – Contains the list of tag key-value pairs
        +
      • name (string) – Name of the tag point

      • +
      • value (string) – Value of the tag point

      • +
      +
      +
      +
    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
POST /api/sensor_group/test/tags HTTP/1.1
+

Example request:

+
POST /api/sensor_group/test/tags HTTP/1.1
 Accept: application/json; charset=utf-8
 
 
@@ -313,64 +443,102 @@ 

Add tags to SensorGroup

-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-}
+{
+  "success": "True"
+}
 
-
-
+
+

Get list of tags in SensorGroup

This request retrieves two lists of key-value pairs, one list contains the array of eligible tags that can be attached to this SensorGroup and the other list contains the array of tags that are currently attached to this SensorGroup.

+
+
+GET /api/sensor_group/<name>/tags
+
+
Parameters:
+
    +
  • name (string) – Name of SensorGroup (compulsory)

  • +
+
+
Returns:
+
    +
  • +
    tags (list) – Contains the list of tag key-value pairs that are available for the building in which this SensorGroup is located
      +
    • name (string) – Name of the tag point

    • +
    • value (list) – List of eligible values for this certain tag

    • +
    +
    +
    +
  • +
  • +
    tags_owned (list) – Contains the list of tag key-value pairs that are attached to this SensorGroup
      +
    • name (string) – Name of the tag point

    • +
    • value (string) – Value for this tag

    • +
    +
    +
    +
  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/sensor_group/test/tags HTTP/1.1
+

Example request:

+
GET /api/sensor_group/test/tags HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "tags": {
-           "Corridor": [
-                        "3600",
-                        "3700"
-                       ],
-           "Floor": [
-                     "3"
-                    ],
-           "Room": [
-                    "3606"
-                   ]
-          },
-
-  "tags_owned": [
-                  {
-                   "name": "Corridor",
-                   "value": "3600"
-                  },
-                  {
-                   "name": "Floor",
-                   "value": "3"
-                  },
-                  {
-                   "name": "Room",
-                   "value": "3606"
-                  }
-                ]
-}
+{
+  "tags": {
+           "Corridor": [
+                        "3600",
+                        "3700"
+                       ],
+           "Floor": [
+                     "3"
+                    ],
+           "Room": [
+                    "3606"
+                   ]
+          },
+
+  "tags_owned": [
+                  {
+                   "name": "Corridor",
+                   "value": "3600"
+                  },
+                  {
+                   "name": "Floor",
+                   "value": "3"
+                  },
+                  {
+                   "name": "Room",
+                   "value": "3606"
+                  }
+                ]
+}
 
-
-
+
+
@@ -391,7 +559,7 @@

Get list of tags in SensorGroup

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -413,16 +581,19 @@

Get list of tags in SensorGroup var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/tagtype.html b/buildingdepot/Documentation/build/html/api/CentralService/tagtype.html index 508772a7..f468019f 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/tagtype.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/tagtype.html @@ -5,10 +5,11 @@ - + + - TagType — BuildingDepot 3.2.9 documentation + TagType — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -181,15 +186,46 @@
-
-

TagType

+
+

TagType

Tag Types are used to describe the structure and domains of buildings and help in categorising sensors that can be easily allocated in groups to different users. TagType can be defined in the CentralService at http://www.example.com:81/api/tagtype. TagsTypes have a hierarchical structure i.e. a tag can have both parent tags and children tags. They play a very crucial role within BuildingDepot in defining permissions that restrict access to sensors based on the permissions a user has. Also, A building template is supported by a group of tag types allocated to it.

-
-

Add TagType

+
+

Add TagType

This request creates a new TagType in BuildingDepot which will be used in the Building template to define the structure of the Buildings.

+
+
+POST /api/tagtype
+
+
JSON Parameters:
+
    +
  • +
    data (list) – Contains the information of the tag type to be created.
      +
    • name (string) – Name of the Tag Type

    • +
    • description (string) (optional) – Description of the Tag Type

    • +
    • parents (string) (optional) – Parent Tag Types

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (array) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get TagType

This request fetches information about a TagType in BuildingDepot.

+
+
+GET /api/tagtype/<name>
+
+
Parameters:
+
    +
  • name (string) – Name of the TagType.

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is retrieved successfully otherwise ‘False’

  • +
  • name (string) – Name of the Tag Type

  • +
  • description (string) – Description of the Tag Type

  • +
  • parents (string) – Parents of this Tag Type.

  • +
  • children (string) – Children of this Tag Type.

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/tagtype/floor HTTP/1.1
+

Example request:

+
GET /api/tagtype/floor HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-  "children": [  "corridor"
-                  ],
-  "description": null,
-  "name": "floor",
-  "parents": [],
-  "success": "True"
+{
+  "success": "True"
+  "children": [  "corridor"
+                  ],
+  "description": null,
+  "name": "floor",
+  "parents": [],
+  "success": "True"
 
-}
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "TagType does not exist"
-}
+{
+  "success": "False",
+  "error": "TagType does not exist"
+}
 
-
-
+
+
@@ -280,7 +343,7 @@

Get TagType

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -302,16 +365,19 @@

Get TagType var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/user.html b/buildingdepot/Documentation/build/html/api/CentralService/user.html index e5eaaeeb..d3eece03 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/user.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/user.html @@ -5,10 +5,11 @@ - + + - User — BuildingDepot 3.2.9 documentation + User — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -182,15 +187,44 @@
-
-

User

+
+

User

The Users of BuildingDepot have either of the two roles SuperUser and Default User through which we maintain the access control. The Super User is the admin of BuildingDepot who has permission to add,remove,read,write,delete any entity. The Default User has limited access and does not have permission to add,remove any building and dataservice related entity.Only the SuperUser can add another user and the SuperUser by default in every Building Depot installation is admin@buildingdepot.org. A new User can be added by the SuperUser in the CentralService at http://www.example.com:81/api/user.

-
-

Add a new User

+
+

Add a new User

This request creates a new User in the Central Service. Only the SuperUser can add a new user

+
+
+POST /api/user
+
+
JSON Parameters:
+
    +
  • first_name (string) – First Name of the User

  • +
  • last_name (string) – Last Name of the User

  • +
  • email (string) – email of the User

  • +
  • role (string) – role of the User +* super (string) – super- Has access to all the entities in Building Depot +* default (string) – default - limited access to entities in Building Depot

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
  • error (string) – An additional value that will be present only if the request fails specifying the cause for failure

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get User Details

This request retrieves first name, last_name, email and role of the User specified in the request.

+
+
+GET /api/user/<email>
+
+
Parameters:
+
    +
  • email (string) – Email of the User

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is retrieved successfully otherwise ‘False’

  • +
  • first_name (string) – First Name of the User

  • +
  • last_name (string) – Last Name of the User

  • +
  • email (string) – Email of the User

  • +
  • role (string) – role of the User

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/user/newuser@gmail.com HTTP/1.1
+

Example request:

+
GET /api/user/newuser@gmail.com HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{   "success": "True",
-      "first_name": "New"
-      "last_name":"User",
-      "email":"newuser@gmail.com",
-      "role":"super"
-}
+{   "success": "True",
+      "first_name": "New"
+      "last_name":"User",
+      "email":"newuser@gmail.com",
+      "role":"super"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " User does not exist"
-}
+{
+  "success": "False",
+  "error": " User does not exist"
+}
 
-
-
-

Remove User

+
+
+

Remove User

This request deletes the requested User from Building Depot.Only the Super user can delete the User in BuildingDepot.

+
+
+DELETE /api/user/<email>
+
+
Parameters:
+
    +
  • email (string) – email of the User

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the User is successfully deleted otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
DELETE /api/User/newuser@gmail.com HTTP/1.1
+

Example request:

+
DELETE /api/User/newuser@gmail.com HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-}
+{
+  "success": "True"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": " User does not exist"
-}
+{
+  "success": "False",
+  "error": " User does not exist"
+}
 
-
-
+
+
@@ -319,7 +403,7 @@

Remove User

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -341,16 +425,19 @@

Remove User var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/CentralService/usergroup.html b/buildingdepot/Documentation/build/html/api/CentralService/usergroup.html index 71ba276a..e124ecde 100755 --- a/buildingdepot/Documentation/build/html/api/CentralService/usergroup.html +++ b/buildingdepot/Documentation/build/html/api/CentralService/usergroup.html @@ -5,10 +5,11 @@ - + + - Usergroups — BuildingDepot 3.2.9 documentation + Usergroups — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -184,15 +189,46 @@
-
-

Usergroups

+
+

Usergroups

Usergroups are as the name suggests a group of users formed using the id’s with which they are registered in BuildingDepot. Usergroups when combined with SensorGroups help in bringing about the Access Control functions that BuildingDepot provides.UserGroups can be defined in the CentralService at http://www.example.com:81/api/usergroup.

-
-

Create UserGroup

+
+

Create UserGroup

This request creates a new UserGroup with the name and description as specified by the user.

+
+
+POST /api/user_group
+
+
JSON Parameters:
+
    +
  • +
    data (dictionary) – Contains the information about the UserGroup
      +
    • name (string) – Name of the UserGroup

    • +
    • description (string) – Description of the UserGroup

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
  • error (string) – An additional value that will be present only if the request fails specifying the cause for failure

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get UserGroup Details

This request retrieves the details of a UserGroup

+
+
+GET /api/user_group/<name>
+
+
Parameters:
+
    +
  • name (string) – Name of user group (compulsory)

  • +
+
+
Returns:
+
    +
  • name (string) – Contains the name of the UserGroup

  • +
  • description (string) – Contains the description of the UserGroup

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/user_group/Test HTTP/1.1
+

Example request:

+
GET /api/user_group/Test HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success" : "true",
-  "name":"Test",
-  "description":"A UserGroup for Test"
-}
+{
+  "success" : "true",
+  "name":"Test",
+  "description":"A UserGroup for Test"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "Usergroup does not exist"
-}
+{
+  "success": "False",
+  "error": "Usergroup does not exist"
+}
 
-
-
-

Delete UserGroup

+
+
+

Delete UserGroup

This request deletes the UserGroup

+
+
+DELETE /api/user_group/Test
+
+
Parameters:
+
    +
  • email (string) – Name of the UserGroup

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the UserGroup is successfully deleted otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
DELETE /api/user_group/<name> HTTP/1.1
+

Example request:

+
DELETE /api/user_group/<name> HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "True"
-}
+{
+  "success": "True"
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success": "False",
-  "error": "Usergroup does not exist"
-}
+{
+  "success": "False",
+  "error": "Usergroup does not exist"
+}
 
-
-
-

Add users to UserGroup

+
+
+

Add users to UserGroup

This request adds the users specified in the request to the usergroup

Note: The list of users sent in this request will overwrite the previous list

+
+
+POST /api/user_group/<name>/users
+
+
Parameters:
+
    +
  • name (string) – Name of UserGroup

  • +
+
+
JSON Parameters:
+
    +
  • +
    data (dictionary) – Contains the information of the users to be added to the UserGroup.
      +
    • +
      users (list) – List of user objects
        +
      • user_id (string) – Email of the user

      • +
      • manager (boolean) – Specifies whether the user is a manager of the UserGroup

      • +
      +
      +
      +
    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
  • error (string) – An additional value that will be present only if the request fails specifying the cause for failure

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Get list of users in UserGroup

This request retrieves the list of users that are in the specified UserGroup

+
+
+GET /api/user_group/<name>/users
+
+
Parameters:
+
    +
  • name (string) – Name of user group (compulsory)

  • +
+
+
Returns:
+
    +
  • users (list) – Contains the list of users in this UserGroup

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/user_group/Test/users HTTP/1.1
+

Example request:

+
GET /api/user_group/Test/users HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "users": [
-             {
-                "user_id":"synergy@gmail.com",
-                "manager": true
-             },
-             {
-                "user_id":"test@gmail.com",
-                "manager": false
-             }
-           ]
-}
+{
+  "users": [
+             {
+                "user_id":"synergy@gmail.com",
+                "manager": true
+             },
+             {
+                "user_id":"test@gmail.com",
+                "manager": false
+             }
+           ]
+}
 
-
-
+
+
@@ -382,7 +530,7 @@

Get list of users in UserGroup

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -404,16 +552,19 @@

Get list of users in UserGroup var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/DataService/index.html b/buildingdepot/Documentation/build/html/api/DataService/index.html index 3975e707..abe31a11 100755 --- a/buildingdepot/Documentation/build/html/api/DataService/index.html +++ b/buildingdepot/Documentation/build/html/api/DataService/index.html @@ -5,10 +5,11 @@ - + + - DataService APIs — BuildingDepot 3.2.9 documentation + DataService APIs — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -64,7 +69,7 @@
- 3.2.9 + 3.3
@@ -166,8 +171,8 @@
-
-

DataService APIs

+
+

DataService APIs

All urls in this chapter are with respect to the base url of the DataService. For example: If the DataService is at http://www.example.com:82 then a url such as /service/sensor refers to http://www.example.com:82/api/sensor

Note: In order to access the DataService all users will first have to generate an OAuth Token using the Client ID and secret Client Key that they obtain from their account at the Central Service. Once logged into the CentralService users can go to the OAuth tab and generate a Client ID and Key that should never be shared with any other users.

Once the Client ID and Key have been generated the user can request BuildingDepot for the access token. The access token will be valid for the next 24 hours after which another access token will have to be generated.The OAuth token should also never be shared publicly.

@@ -193,7 +198,7 @@

DataService APIs

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -236,16 +241,19 @@

DataService APIs var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/DataService/oauth.html b/buildingdepot/Documentation/build/html/api/DataService/oauth.html index 2c9a72c8..e40fa1ac 100644 --- a/buildingdepot/Documentation/build/html/api/DataService/oauth.html +++ b/buildingdepot/Documentation/build/html/api/DataService/oauth.html @@ -5,10 +5,11 @@ - + + - OAuth — BuildingDepot 3.2.9 documentation + OAuth — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -172,31 +177,43 @@
-
-

OAuth

+
+

OAuth

Every query to BuildingDepot has to be authenticated by an access token. The client id and secret key required to generate the access token can be obtained after logging into the CentralService. After these have been obtained the access token can be generated by using the following request.

-
-

Generating access tokens

+
+

Generating access tokens

Generate an access token using the client id and secret key obtained from the CentralService. Each access token is valid for 24 hours from the time of generation.

+
+
+GET /oauth/access_token/client_id=<client_id>/client_secret=<client_secret>
+
+
Returns:
+
    +
  • access token

  • +
+
+
+
+
-

Example request:

-
GET /oauth/access_token/client_id=BOCWEJSnwJ8UJ4mfPiP8CqCX0QGHink6PFbmTnx0/
+

Example request:

+
GET /oauth/access_token/client_id=BOCWEJSnwJ8UJ4mfPiP8CqCX0QGHink6PFbmTnx0/
 client_secret=1gk1pBQHiK6vHQULOndEucULq0Tf5H9vKjAUbIBVX0qMjsC9uQ HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "access_token": "528d58481bc728a5eb57e73a49ba4539"
-}
+{
+  "access_token": "528d58481bc728a5eb57e73a49ba4539"
+}
 
-
-
+
+
@@ -217,7 +234,7 @@

Generating access tokens

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -239,16 +256,19 @@

Generating access tokens var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/DataService/pubsub.html b/buildingdepot/Documentation/build/html/api/DataService/pubsub.html index a356e011..ce5a543e 100755 --- a/buildingdepot/Documentation/build/html/api/DataService/pubsub.html +++ b/buildingdepot/Documentation/build/html/api/DataService/pubsub.html @@ -5,10 +5,11 @@ - + + - Apps — BuildingDepot 3.2.9 documentation + Apps — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -64,7 +69,7 @@
- 3.2.9 + 3.3
@@ -175,34 +180,53 @@
-
-

Apps

+
+

Apps

DataService Apps API allows users interact with the underlying app models. This API handles the registration and deletion of apps from the system.

-
-

Get List of Registered Apps

+
+

Get List of Registered Apps

This retrieves a list of applications of the current user. This API first registers the application to the system, then it opens up a rabbitMQ queue.

+
+
+GET /api/apps
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if the list of application is successfully retrieved otherwise ‘False’

  • +
  • app_list (list(string)) – The list of the current user’s application names.

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
GET /api/apps HTTP/1.1
+

Example request:

+
GET /api/apps HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response (for success):

-
HTTP/1.1 200 OK
+

Example response (for success):

+
HTTP/1.1 200 OK
 Content-Type: application/json; charset=utf-8
 
-{
-  "success": "True",
-  "app_list": [
-    "app_example_1", "app_example_2", "app_example_3"
-  ]
-}
+{
+  "success": "True",
+  "app_list": [
+    "app_example_1", "app_example_2", "app_example_3"
+  ]
+}
 
-

Example response (for failure):

-
HTTP/1.1 200 OK
+

Example response (for failure):

+
HTTP/1.1 200 OK
 Content-Type: application.json; charset=utf-8
 
 {
@@ -212,14 +236,44 @@ 

Get List of Registered Apps -

Register a New App

+

+
+

Register a New App

This stores a new app for the current user.

If there already exists an application with the given name, this API call has no effect on BuildingDepot.

+
+
+POST /api/apps
+
+
JSON Parameters:
+
    +
  • +
    data (dict) – Contains the information of the new application to be registered.
      +
    • name (string) – The name of the new application to be registered.

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if adding a new application was successful or an application with the given name already exists. Otherwise ‘False’

  • +
  • app_id (string) – If successful, contains the new RabbitMQ channel ID that corresponds to the new application.

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Delete an App

This deletes an app of the current user.

+
+
+DELETE /api/apps
+
+
JSON Parameters:
+
    +
  • +
    data (dict) – Contains the information of the application to be deleted.
      +
    • name (string) – The name of the application to be deleted.

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if adding a new application was successful or an application with the given name already exists. Otherwise ‘False’

  • +
  • error (string) – Details of an error if unsuccessful

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Subscribe to a Sensor

This is used to subscribes to the sensor data.

+
+
+POST /api/apps/subscription
+
+
JSON Parameters:
+
    +
  • +
    data (dict) – Contains the information about the subscription.
      +
    • app (string) – The name of the application.

    • +
    • sensor (string) – The name of the sensor to subscribe to.

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if subscription was successful. Otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
+

Unsubscribe from a Sensor

This is used to unsubscribes from the sensor data.

+
+
+DELETE /api/apps/subscription
+
+
JSON Parameters:
+
    +
  • +
    data (dict) – Contains the information about the unsubscription.
      +
    • app (string) – The name of the application.

    • +
    • sensor (string) – The name of the sensor to unsubscribe from.

    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if unsubscription was successful. Otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
+
@@ -430,7 +574,7 @@

Unsubscribe from a Sensor

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -452,16 +596,19 @@

Unsubscribe from a Sensor var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/api/DataService/sensordata.html b/buildingdepot/Documentation/build/html/api/DataService/sensordata.html index 7309dacd..f7990405 100755 --- a/buildingdepot/Documentation/build/html/api/DataService/sensordata.html +++ b/buildingdepot/Documentation/build/html/api/DataService/sensordata.html @@ -5,10 +5,11 @@ - + + - Timeseries — BuildingDepot 3.2.9 documentation + Timeseries — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -65,7 +70,7 @@
- 3.2.9 + 3.3
@@ -173,17 +178,55 @@
-
-

Timeseries

+
+

Timeseries

The Sensor collection manages Sensors for Locations associated with the DataService. Sensor access is restricted to Central Service Users with permissions for the Sensor and to the Admin who owns the Sensor.

-
-

Post Timeseries Datapoints

+
+

Post Timeseries Datapoints

This stores datapoints in the timeseries of the specified Sensorpoint.

The first datapoint that is posted to the uuid defines the datatype for all further timeseries datapoints e.g. if the first datapoint posted to a certain uuid is a int then all further datapoints have to be ints.

+
+
+POST /api/sensor/timeseries
+

Within a singe POST request data can be posted to multiple sensor points. The format for each sensor point in the list should be as follows.

+
+
JSON Parameters:
+
    +
  • +
    data (list of dictionaries) – Contains the information of the tag to be added to the sensor.
      +
    • sensor_id (string) – UUID of the sensor to which data has to be posted

    • +
    • +
      samples (list) – A list of the data points that have to be added to the time-series of the sensor point given by sensor_id. Each item in the list has the following information:
        +
      • time (timestamp) – Unix timestamp of a sampling

      • +
      • value (int/float) – Sensor value

      • +
      +
      +
      +
    • +
    +
    +
    +
  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is posted successfully otherwise ‘False’

  • +
+
+
Status Codes:
+
+
+
+
+
-

Example request:

-
POST /api/sensor/timeseries HTTP/1.1
+

Example request:

+
+
+

Read Timeseries Datapoints

This retreives a list of datapoints for the timeseries of the specified Sensorpoint

+
+
+GET /sensor/<sensor-uuid>/timeseries?start_time=<start_timestamp>&end_time=<end_timestamp>&resolution=<resolution_units>
+
+
Parameters:
+
    +
  • sensor-uuid (string) – UUID associated with Sensor (compulsory)

  • +
  • start_time (integer) – The starting point of time from which the timeseries data of this sensor point is desired. Has to be a UNIX timestamp. (compulsory)

  • +
  • end_time (integer) – The ending point of time till which the timeseries data of this sensor point is desired. Has to be a UNIX timestamp.(compulsory)

  • +
  • resolution (string) – The resolution of the data required. If not specified will retrieve all the datapoints over the specified interval. Has to be specified in the format time units as an integer + unit identifier e.g. 10s,1m,1h etc. (optional)

  • +
+
+
Returns:
+
    +
  • success (string) – Returns ‘True’ if data is retrieved succesfully otherwise ‘False’

  • +
  • +
    data (struct) – Contains the series
      +
    • series (list) – Contains the timeseries data, uuid of the sensor and the column names for the timeseries data

    • +
    • columns (list) – Contains the names of the columns of the data that is present in the timeseries

    • +
    • name (string) – uuid of the sensor whose data is being retrieved

    • +
    • values (list) – Contains the list of timeseries data that has been requested in the order represented by the columns.

    • +
    +
    +
    +
  • +
+
+
Status Codes:
+
+
+
+
+

Note: Both interval and resolution are specified with the time value appended by the type of the value e.g. 10s for 10 seconds or 10m for 10 minutes.

-

Example request:

-
GET /sensor/<sensor-uuid>/timeseries?start_time=1445535722&end_time=1445789516&resolution=10s HTTP/1.1
+

Example request:

+
GET /sensor/<sensor-uuid>/timeseries?start_time=1445535722&end_time=1445789516&resolution=10s HTTP/1.1
 Accept: application/json; charset=utf-8
 
-

Example response:

-
HTTP/1.1 200 OK
+

Example response:

+
HTTP/1.1 200 OK
 Content-Type: application/json
 
-{
-  "success":"True",
-  "data": {
-    "series": [
-      {
-        "columns": [
-          "time",
-          "inserted_at",
-          "value"
-        ],
-        "name": "35b137b2-c7c6-4608-8489-1c3f0ee7e2d5",
-        "values": [
-          [
-            "2015-10-22T17:41:44.762495917Z",
-            1445535722.0,
-            22.11
-          ],
-          [
-            "2015-10-22T17:43:19.48927063Z",
-            1445535818.0,
-            22.23
-          ],
-                    [
-            "2015-10-22T22:44:53.066248715Z",
-            1445553913.0,
-            24.56
-          ]
-        ]
-      }
-    ]
-  }
-}
+{
+  "success":"True",
+  "data": {
+    "series": [
+      {
+        "columns": [
+          "time",
+          "inserted_at",
+          "value"
+        ],
+        "name": "35b137b2-c7c6-4608-8489-1c3f0ee7e2d5",
+        "values": [
+          [
+            "2015-10-22T17:41:44.762495917Z",
+            1445535722.0,
+            22.11
+          ],
+          [
+            "2015-10-22T17:43:19.48927063Z",
+            1445535818.0,
+            22.23
+          ],
+                    [
+            "2015-10-22T22:44:53.066248715Z",
+            1445553913.0,
+            24.56
+          ]
+        ]
+      }
+    ]
+  }
+}
 
-
-
+
+
@@ -299,7 +378,7 @@

Read Timeseries Datapoints

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -321,16 +400,19 @@

Read Timeseries Datapoints var DOCUMENTATION_OPTIONS = { URL_ROOT:'../../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/centralservice.html b/buildingdepot/Documentation/build/html/centralservice.html index 0366c712..4ccc61e5 100755 --- a/buildingdepot/Documentation/build/html/centralservice.html +++ b/buildingdepot/Documentation/build/html/centralservice.html @@ -5,10 +5,11 @@ - + + - Central Service — BuildingDepot 3.2.9 documentation + Central Service — BuildingDepot 3.3 documentation @@ -28,14 +29,18 @@ + + + + - + - + @@ -64,7 +69,7 @@
- 3.2.9 + 3.3
@@ -173,8 +178,8 @@
_images/BuildingDepot.svg -
-

Central Service

+
+

Central Service

The Central Service within BuildingDepot is the part of the system that holds all the core metadata. Starting from the structures of the Buildings to the DataServices that lie in an installation the CentralService plays a very important role in organising the data within BuildingDepot in a meaningful way. A brief explanation will be provided here of each of the options available in the CentralService.

We define any entity to which a timeseries can be associated with as a Point. A Point can be measurements from sensors, or commands to turn a light on/off, or a configuration parameter @@ -215,19 +220,17 @@

Central Service -
Different evels of permission access:
-

+
+

TagType

Tags are an integral part of BuildingDepot and play an important role in organising and categorising the sensors and their data. Users can create new tags here which will be used in various places throughout BuildingDepot. When creating each tag parent tag(s) can be specified for each tag enabling us to create a tag hierarchy that proves to be very useful when defining structures such as Buildings. Here only the tag names are specified and the values for these tags are specified later on. Each tag can have multiple values if needed.

-
-
-

BuildingTemplate

+ +
+

BuildingTemplate

Each building within BuildingDepot has a BuildingTemplate as a foundation. The BuildingTemplate helps define the structure of the building. The user has to assign a set of tags to the BuildingTemplate on creation which can be used later on for all the sensors within that building.

-
-
-

Building

+ +
+

Building

All the buildings that are present within the deployment of BuildingDepot are defined here. When adding a new building a BuildingTemplate has to be selected which defines the structure of this building. The tags that are available to be assigned to this building are dependent on the BuildingTemplate. Tags can be selected and the values for each of them can be specified here. Each tag can have multiple values specified for it.

-
-
-

Data Services

+ +
+

Data Services

BuildingDepot consists of a single CentralService and if needed multiple DataServices. The number of DataServices to deploy is a decision that is completely left to the user. A DataService per building is an ideal choice that we suggest. Each DataService has to be specified within the DataService’s section in the CentralService. For each DataService all the buildings that belong to it also have to be selected and added. The admins for each DataService who will have complete administrative control over this DataService also have to be specified here.

Note: The first DataService has to be called “ds1”.

-
-
-

Sensor

+ +
+

Sensor

Individual sensor points are defined here. After adding a sensor a UUID is generated which will be the unique identifier used in all further transactions with BuildingDepot whether it be reading a datapoint from a sensor or posting a bunch of datapoints to a sensor. Each sensor can also have a set of tags attached to it that not only help in categorising them in a meaningful way but also are critical for defining the access control lists later on. The option to attach metadata that is specific to this sensor is also provided. Sensors can be searched for using either the tags or metadata as a filter.

-
-
-

Sensor Group

+ +
+

Sensor Group

Sensor groups are as the name suggests a set of sensors that have been grouped together on the basis of the tags that the user selected while creating the group. While creating a Sensor groups each individual sensor that the user wants to put in the group do not have to be manual added. Simply selecting the tag will automatically add at the backend all the sensors containing that tag into this group.

-
-
-

User Group

+ +
+

User Group

Similar to Sensor groups, User groups are a list of users that have been categorised into one group. Groups are created using the user email id that was used during registration.

-
-
-

Permission

+ +
+

Permission

In the permissions section Sensor groups and User groups come together to form the access control lists. Here we select a User Group and a Sensor Group and a permission value with which we want to associate these both. There are three levels of permission defined in BuildingDepot which are ‘d/r’ (deny read) ,’r’ (read), ‘r/w’ (read write) and ‘r/w/p’ (read write permission). If there are multiple permission mappings between a user and a sensor then the one that is most restrictive is chosen.

-
-
+ +

@@ -287,7 +290,7 @@

PermissionNext - Previous + Previous

@@ -296,7 +299,7 @@

Permission

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -318,16 +321,19 @@

Permission var DOCUMENTATION_OPTIONS = { URL_ROOT:'./', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/dataservice.html b/buildingdepot/Documentation/build/html/dataservice.html index ce06b999..d0fa9837 100755 --- a/buildingdepot/Documentation/build/html/dataservice.html +++ b/buildingdepot/Documentation/build/html/dataservice.html @@ -5,10 +5,11 @@ - + + - Data Service — BuildingDepot 3.2.9 documentation + Data Service — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -64,7 +69,7 @@
- 3.2.9 + 3.3
@@ -166,8 +171,8 @@
_images/BuildingDepot.svg -
-

Data Service

+
+

Data Service

The DataService is the service with which users will be interacting directly most of the time. It’s within the DataService that all the data related to sensors and the timeseries data of each sensor resides. All the access control related functionality is defined and enforced within the DataService. OAuth access tokens which are required by every query that is sent to BuildingDepot are also generated via the DataService. A brief explanation will be provided here of each of the options available in the DataService.

DataService stores point time series data points from the underlying point networks. The DataService manages the points and time series data points of points allocated to it. A @@ -183,15 +188,15 @@

Data Service_images/DSComponents.svg -
-

Sensor

+
+

Sensor

The time-series data of all the points in buildings associated with the Data Service is stored here. The sensor UUID(s) are used in all further time-series transactions with BuildingDepot whether it be reading a datapoint from a sensor or posting a bunch of datapoints to a sensor.The time-series data of all the points in buildings associated with a certain Data Service is stored here. The sensor UUID(s) are used in all further time-series transactions with BuildingDepot whether it be reading a datapoint from a sensor or posting a bunch of datapoints to a sensor.

-
-
-

Apps

+

+
+

Apps

BuildingDepot allows users to subscribe to sensors’ time-series data. This can be done by registering a new app and associating it with the desired sensor UUIDs. All the information about a user’s apps is located here.

-
-
+ +

@@ -212,7 +217,7 @@

Apps

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -234,16 +239,19 @@

Apps + + + diff --git a/buildingdepot/Documentation/build/html/genindex.html b/buildingdepot/Documentation/build/html/genindex.html index 81ec3bcb..e423fb9e 100755 --- a/buildingdepot/Documentation/build/html/genindex.html +++ b/buildingdepot/Documentation/build/html/genindex.html @@ -1,6 +1,5 @@ - @@ -9,7 +8,7 @@ - Index — BuildingDepot 3.2.9 documentation + Index — BuildingDepot 3.3 documentation @@ -29,12 +28,16 @@ + + + + - + @@ -63,7 +66,7 @@
- 3.2.9 + 3.3
@@ -175,7 +178,7 @@

Index

- © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

@@ -197,16 +200,19 @@

Index

+ + + diff --git a/buildingdepot/Documentation/build/html/http-routingtable.html b/buildingdepot/Documentation/build/html/http-routingtable.html index f669d974..35aa3336 100755 --- a/buildingdepot/Documentation/build/html/http-routingtable.html +++ b/buildingdepot/Documentation/build/html/http-routingtable.html @@ -1,42 +1,172 @@ - - - - - - - HTTP Routing Table — BuildingDepot 3.2.8 documentation - - - - - - - - - - + + + + + + + + + HTTP Routing Table — BuildingDepot 3.3 documentation + + - + - - + + + + + + -
-
-
+ +
+ + + + +
+ + + + + + +
+
+ + + + + + + + + + + + + + + + +
+ +
    + +
  • Docs »
  • + +
  • HTTP Routing Table
  • + + +
  • + + + +
  • + +
+ + +
+
+
+

HTTP Routing Table

@@ -81,6 +211,11 @@

HTTP Routing Table

GET /api/dataservice/<name>/buildings + + + + GET /api/permission/request + @@ -141,6 +276,11 @@

HTTP Routing Table

POST /api/apps + + + + POST /api/apps/subscription + @@ -171,6 +311,11 @@

HTTP Routing Table

POST /api/permission + + + + POST /api/permission/request + @@ -236,6 +381,11 @@

HTTP Routing Table

DELETE /api/apps + + + + DELETE /api/apps/subscription + @@ -297,7 +447,7 @@

HTTP Routing Table

- GET /oauth/access_token/client_id=<client_id>/client_secret=<client_secret> + GET /oauth/access_token/client_id=<client_id>/client_secret=<client_secret>   @@ -310,45 +460,66 @@

HTTP Routing Table

+
- -
-
- -
-
- - + - - +
+ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/buildingdepot/Documentation/build/html/index.html b/buildingdepot/Documentation/build/html/index.html index 23cade40..ec2dd5af 100755 --- a/buildingdepot/Documentation/build/html/index.html +++ b/buildingdepot/Documentation/build/html/index.html @@ -5,10 +5,11 @@ - + + - Building Depot v3.2.9 — BuildingDepot 3.2.9 documentation + Building Depot v3.3 — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -63,7 +68,7 @@
- 3.2.9 + 3.3
@@ -141,7 +146,7 @@
  • Docs »
  • -
  • Building Depot v3.2.9
  • +
  • Building Depot v3.3
  • @@ -160,18 +165,18 @@
    -
    -

    Building Depot v3.2.9

    -
    -

    Overview

    -

    This is the official documentation of BuildingDepot v3.2.9. BuildingDepot is essentialy an Extensible and Distributed Architecture for Sensor Data Storage, Access and Sharing.

    +
    +

    Building Depot v3.3

    +
    +

    Overview

    +

    This is the official documentation of BuildingDepot v3.3. BuildingDepot is essentialy an Extensible and Distributed Architecture for Sensor Data Storage, Access and Sharing.

    Building Depot has two essential components a Central Service and a Data Service :

    Central Service - A central directory containing details of all the buildings,users

    Data Service - The core component of BD and is responsible for handling the actual sensor data. Exposes a RESTful API for retrieving data and inserting data into the system.

    An institution has a single CentralService that houses data of all buildings and user accounts on campus, and multiple DataServices, each of which may contain sensor data of several buildings.

    A DataService may belong to any single administrative group that requires sole control over who can access the underlying sensor data points. Different buildings on a campus might desire their own DataService.

    -
    -

    CentralService

    +
    +

    CentralService

    Read more about the CentralService

    +
    +

    DataService

    Read more about the DataService

    -
    -

    Install

    +
    +
    +
    +

    Download

    +

    You can get the latest BuildingDepot package (tar.gz) from buildingdepot.org

    +
    +
    +

    Install

    -
    -
    -

    Getting Started

    + +
    +

    Getting Started

    The Getting started document will help you guide through the initial setup after installing BuildingDepot.

    -
    -
    -

    API Documentation

    + +
    +

    API Documentation

    -
    -
    + +
    @@ -249,7 +254,7 @@

    API Documentation

    - © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

    @@ -271,16 +276,19 @@

    API Documentation var DOCUMENTATION_OPTIONS = { URL_ROOT:'./', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/install.html b/buildingdepot/Documentation/build/html/install.html index 8ffb41b2..a24b3fc4 100755 --- a/buildingdepot/Documentation/build/html/install.html +++ b/buildingdepot/Documentation/build/html/install.html @@ -5,10 +5,11 @@ - + + - Installation — BuildingDepot 3.2.9 documentation + Installation — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -64,7 +69,7 @@
    - 3.2.9 + 3.3
    @@ -169,93 +174,92 @@
    -
    -

    Installation

    -
    -

    Using install.sh

    +
    +

    Installation

    +
    +

    Using install.sh

    -

    Note

    -

    This installer installs the BD DataService, CentralService, MongoDB, InfluxDB +

    Note

    +

    This installer installs the BD DataService, CentralService, MongoDB, InfluxDB and Redis on the same machine.

      -
    1. Extract the package and cd into the folder**:

      +
    2. Extract the package and cd into the folder**:

      $ tar -xzf buildingdepot-3.#.#.tar.gz

      $ cd buildingdepot-3.#.#/

    3. -
    4. Run the installer

      +
    5. Run the installer

      $ ./install.sh

      This will install BuildingDepot in the default installation location /srv/buildingdepot with the following directory structure:

      -
      buildingdepot
      -|-- CentralService - CentralService
      -|-- DataService - DataService
      -|-- CentralReplica - The central replica that is present at every DataService
      -+-- venv - Python Virtual Environment
      +
      buildingdepot
      +|-- CentralService - CentralService
      +|-- DataService - DataService
      +|-- CentralReplica - The central replica that is present at every DataService
      ++-- venv - Python Virtual Environment
       
    6. -
    7. After installation please go the CentralService on port 81 of your installation and create a DataService to start off with called “ds1”. If you would like to use another name for your DataService do ensure that the name is accordingly changed in the config.py file in the DataService folder.

      -
    8. +
    9. After installation please go the CentralService on port 81 of your installation and create a DataService to start off with called “ds1”. If you would like to use another name for your DataService do ensure that the name is accordingly changed in the config.py file in the DataService folder.

    -
    -
    -

    What’s installed

    + +
    +

    What’s installed

      -
    • The following packages are installed using apt-get

      -
      openssl
      -python-setuptools
      -python-dev
      -build-essential
      -python-software-properties
      -mongodb
      -python-pip
      -nginx
      -supervisor
      -redis-server
      -influxdb
      +
    • The following packages are installed using apt-get

      +
      openssl
      +python-setuptools
      +python-dev
      +build-essential
      +python-software-properties
      +mongodb
      +python-pip
      +nginx
      +supervisor
      +redis-server
      +influxdb
       
    • -
    • The following packages are installed in the python virtual environment

      -
      Flask
      -mongoengine
      -flask-restful
      -Flask-HTTPAuth
      -flask-login
      -validate_email
      -requests
      -Flask-Script
      -Flask-WTF
      -flask-bootstrap
      -redis
      -influxdb
      -pymongo
      +
    • The following packages are installed in the python virtual environment

      +
      Flask
      +mongoengine
      +flask-restful
      +Flask-HTTPAuth
      +flask-login
      +validate_email
      +requests
      +Flask-Script
      +Flask-WTF
      +flask-bootstrap
      +redis
      +influxdb
      +pymongo
       
    -
    -
    -
    -

    Configuration

    + + +
    +

    Configuration

    The BD Installer configures BD with some default values.

    The CentralService can be accessed on port 81 and the DataService on port 82.

    -
    -

    DataService

    +
    +

    DataService

    To access the DataService, go to

    -
    URL - http://<host>:82
    +
    URL - http://<host>:82
     
    -
    -
    -

    CentralService

    +
    +
    +

    CentralService

    To access the CentralService, go to

    -
    URL - http://<host>:81
    +
    URL - http://<host>:81
     
    -
    -
    +
    +
    @@ -276,7 +280,7 @@

    What’s installed

    - © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

    @@ -298,16 +302,19 @@

    What’s installed var DOCUMENTATION_OPTIONS = { URL_ROOT:'./', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/build/html/objects.inv b/buildingdepot/Documentation/build/html/objects.inv index 5b2d798d..0263809d 100755 Binary files a/buildingdepot/Documentation/build/html/objects.inv and b/buildingdepot/Documentation/build/html/objects.inv differ diff --git a/buildingdepot/Documentation/build/html/search.html b/buildingdepot/Documentation/build/html/search.html index 15045efe..292fb4a6 100755 --- a/buildingdepot/Documentation/build/html/search.html +++ b/buildingdepot/Documentation/build/html/search.html @@ -8,7 +8,7 @@ - Search — BuildingDepot 3.2.9 documentation + Search — BuildingDepot 3.3 documentation @@ -28,12 +28,16 @@ + + + + - + @@ -62,7 +66,7 @@
    - 3.2.9 + 3.3
    @@ -178,7 +182,7 @@

    - © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

    @@ -200,16 +204,19 @@ + + + diff --git a/buildingdepot/Documentation/build/html/searchindex.js b/buildingdepot/Documentation/build/html/searchindex.js index e2b90b6a..f368b35f 100644 --- a/buildingdepot/Documentation/build/html/searchindex.js +++ b/buildingdepot/Documentation/build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["api/CentralService/building","api/CentralService/buildingtemplate","api/CentralService/dataservice","api/CentralService/index","api/CentralService/oauth","api/CentralService/permission","api/CentralService/permissionRequests","api/CentralService/sensor","api/CentralService/sensorgroup","api/CentralService/tagtype","api/CentralService/user","api/CentralService/usergroup","api/DataService/index","api/DataService/oauth","api/DataService/pubsub","api/DataService/sensordata","centralservice","dataservice","index","install","source/index"],envversion:52,filenames:["api/CentralService/building.rst","api/CentralService/buildingtemplate.rst","api/CentralService/dataservice.rst","api/CentralService/index.rst","api/CentralService/oauth.rst","api/CentralService/permission.rst","api/CentralService/permissionRequests.rst","api/CentralService/sensor.rst","api/CentralService/sensorgroup.rst","api/CentralService/tagtype.rst","api/CentralService/user.rst","api/CentralService/usergroup.rst","api/DataService/index.rst","api/DataService/oauth.rst","api/DataService/pubsub.rst","api/DataService/sensordata.rst","centralservice.rst","dataservice.rst","index.rst","install.rst","source/index.rst"],objects:{},objnames:{},objtypes:{},terms:{"066248715z":15,"0a6505b4f5a6":15,"0b6ab92e98d":7,"10m":15,"10s":15,"14f51bcedb6":7,"1c3f0ee7e2d5":15,"1gk1pbqhik6vhqulondeuculq0tf5h9vkjaubibvx0qmjsc9uq":[4,13],"20c501ca2afa":15,"22d807bf":7,"22t17":15,"22t22":15,"2690d26c9244":7,"26da099a":7,"272c37e7e38f":7,"280bc754":7,"3240e89b":7,"35b137b2":15,"3fe0":7,"41bd":[6,7],"41c9":7,"48927063z":15,"493a":7,"49d2":15,"4b51":15,"4c1c":7,"4c67":7,"4ef6":7,"528d58481bc728a5eb57e73a49ba4539":[4,13],"6336a9d7":7,"6cf53d24":[6,7],"6dd81339c7de":7,"6f65":7,"72e5":15,"762495917z":15,"78fa3808c92f":7,"80b7":7,"81df":7,"842d":7,"886e":7,"89f4":7,"8aac1048":7,"8f109694f628":[6,7],"8f109694f629":6,"94f1":15,"9c20":7,"9e52":7,"abstract":[7,16],"default":[10,19],"function":[2,11,17],"import":[7,16],"int":15,"new":[1,3,5,6,7,8,9,11,12,16,17],"null":9,"return":[5,6,7,16],"super":[10,16],"true":[0,1,2,5,6,7,8,9,10,11,14,15],"while":16,For:[3,12,16],One:[0,1,2,11],The:[0,1,2,3,4,5,6,7,8,10,11,12,13,15,16,17,18,19],There:[5,6,16],These:16,Using:16,Will:[5,6,16],With:7,a411:7,a5d6277:15,a963:7,aa9f:7,abl:[5,6,7],about:[9,11,17,18],abov:16,accept:[0,1,2,4,5,6,7,8,9,10,11,13,14,15,17],access:[2,3,5,6,7,9,10,11,12,15,16,17,18,19],access_token:[4,13],accordingli:19,account:[3,12,16,17,18],act:16,actual:18,actuat:16,add:[3,5,6,16],added:[7,10,16],adding:[0,16],addit:[5,6,16],addition:16,admin:[3,6,7,10,15,16],administr:[16,17,18],af67:7,after:[3,4,12,13,16,18,19],all:[0,1,2,3,7,8,12,15,16,17,18],alloc:[9,17],allow:[14,16,17],almost:17,alreadi:[10,14],also:[2,3,9,12,16,17],alter:16,ani:[3,5,6,7,8,10,12,16,17,18],anoth:[3,10,12,19],api:[0,1,2,5,6,7,8,9,10,11,14,15,16],app:[12,18],app_example_1:14,app_example_2:14,app_example_3:14,app_id:14,app_list:14,app_nam:14,append:15,applic:[0,1,2,4,5,6,7,8,9,10,11,13,14,15],approv:6,apt:19,architectur:18,area:16,arrai:[7,8],assign:[0,1,3,5,6,16],associ:[5,6,7,15,16,17],attach:[7,8,16],authent:[4,13,17],author:5,automat:[8,16],avail:[0,7,16,17],available_field:7,averag:[7,16],b068:7,b2b5:[6,7],b9fd:15,backend:16,balanc:17,base:[3,7,8,9,12,16],basi:[7,16],basic:7,bdae9ede76f6:7,be2a09a5:7,been:[3,4,12,13,16],being:0,belong:[8,16,17,18],between:[5,6,16],bind:14,bocwejsnwj8uj4mfpip8cqcx0qghink6pfbmtnx0:[4,13],bootstrap:19,both:[5,6,7,9,15,16],bottleneck:17,brief:[16,17],bring:11,broker:14,build:[3,5,6,7,8,9,10,17,19],buildingdepot:[0,1,2,3,4,5,6,7,9,10,11,12,13,14,16,17,18,19],buildingtempl:[0,3,18],bunch:[16,17],c7c6:15,call:[14,16,19],campu:[17,18],can:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,16,17,18,19],can_delet:0,cannot:[0,2,16],categoris:[9,16],caus:[5,6],ce7330aa:7,cee06227:15,central:[7,10,12,15,18,19],centralreplica:19,centralservic:[0,1,2,4,5,6,7,8,9,10,11,12,13,16,17],certain:[15,16,17],chang:[8,16,19],chapter:[3,12],charset:[0,1,2,4,5,6,7,8,9,10,11,13,14,15],check:17,children:9,choic:16,choos:16,chosen:[5,6,16],circumst:16,client:[3,4,12,13,16],client_id:[4,13],client_secret:[4,13],collect:[7,8,15],column:15,com:[0,1,2,3,5,6,7,8,9,10,11,12],combin:[11,16],come:[5,6,16],command:16,commun:[7,17],complet:16,complex:16,compon:18,config:19,configur:[16,18],connect:[7,14],connector:16,consist:[8,16],contain:[2,5,6,7,8,16,18],content:[0,1,2,4,5,6,7,8,9,10,11,13,14,15],context:16,control:[2,5,6,10,11,16,17,18],convent:16,core:[16,18],corridor:[0,1,7,8,9],creat:[3,9,10,14,16,19],creation:[1,7,16],creator:16,credenti:[5,6,16],critic:16,crucial:9,crud:2,current:[7,8,14],cycl:16,data:[0,1,2,5,6,7,8,9,10,11,14,15,18],datapoint:[7,12,16,17],dataservic:[3,7,10,14,15,16,17],datastream:2,datatyp:15,datservic:2,decid:7,decis:16,defin:[0,1,2,5,6,7,8,9,11,15,16,17],delet:[3,10,12],deni:[5,6,7,16],depend:[0,7,16,17],deploi:16,deploy:[0,16],depot:[2,5,6,10],describ:9,descript:[0,1,2,8,9,11],desir:[17,18],detail:[3,18],determin:[16,17],dev:19,dictionari:[5,6,7],differ:[9,16,17,18],directli:17,directori:[18,19],distribut:18,document:16,doe:[0,1,2,5,6,7,8,9,10,11],doesn:[1,2,14],domain:9,done:[7,17],ds1:[2,16,19],ds3:2,dure:[6,16],e3a3:[6,7],each:[0,1,2,4,7,8,13,16,17,18],easili:9,effect:14,either:[7,10,16],element:16,elig:[7,8],email:[10,16],emi:7,enabl:16,end_tim:15,enforc:[2,17],enjoi:16,ensur:19,entiti:[6,10,16],environ:19,error:[0,1,2,5,6,7,8,9,10,11,14],essenti:[18,19],essentiali:18,evel:16,even:16,everi:[4,10,13,17,19],exampl:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],example_app_nam:14,example_build:16,exist:[0,1,2,5,6,7,8,9,10,11,14,17],explan:[16,17],expos:18,extens:18,extract:19,facilit:16,fahrenheit:16,fail:[5,6,14],failur:[0,1,2,5,6,7,8,9,10,11,14],fall:8,fals:[0,1,2,5,6,7,8,9,10,11,14],fault:16,fe891ed2aaef:7,fetch:[7,9],field:7,file:19,filter:16,firebas:6,first:[3,10,12,14,15,16],first_nam:10,flask:19,floor:[0,1,7,8,9],folder:19,follow:[4,13,19],form:[5,6,11,16],foundat:[1,16],four:16,frequent:17,from:[3,4,5,6,10,12,13,16,17,18],further:[15,16,17],gain:16,gener:[3,12,16,17],get:[3,4,5,6,12,13,15,16,19],ghc:2,give:[5,6,16],given:[7,14,16],gmail:[10,11],grant:3,group:[5,6,8,9,11,17,18],gui:[7,16],guid:18,handl:[2,14,18],has:[0,1,4,9,10,13,14,16,18],have:[0,2,3,4,7,8,9,10,12,13,15,16],haystack2:16,header:[3,12],help:[1,9,11,16,18],here:[0,5,6,16,17],hierarch:9,hierarchi:16,highest:[5,6,16],hold:16,home:16,home_usergroup:16,host:[2,19],hostnam:2,hour:[3,4,12,13],hous:18,how:[16,17],howev:7,http:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,19],httpauth:19,ideal:16,ident:16,identifi:[7,16],immens:17,includ:16,index:20,indic:16,individu:16,influxdb:19,inform:[9,17],initi:18,insert:18,inserted_at:15,instal:[6,10,16],institut:[17,18],integr:16,interact:[14,17],interfac:16,interv:15,issu:17,its:[16,17],json:[0,1,2,4,5,6,7,8,9,10,11,13,14,15,16],keep:17,kei:[3,4,7,8,12,13,16],last_nam:10,later:[1,16],latest:18,left:16,level:[5,6,7,16],lie:16,light:16,like:[16,19],limit:10,link:[5,7,16],list:[3,5,6,7,9,12,15,16],load:17,locat:[7,15,17,19],log:[3,4,12,13,16],login:19,machin:[16,19],made:[3,12],mai:[7,8,17,18],maintain:[10,16],manag:[7,11,15,17],mani:17,manual:16,map:[5,6,16],master:17,max:7,meaning:16,measur:16,mechan:16,meet:16,metadata:[7,16],mic:7,might:[17,18],min:7,minut:15,miss:[0,1,2,5,6,7,10,14],mode:17,model:14,modifi:8,modul:20,mongodb:19,mongoengin:19,more:[11,16,18],most:[5,6,16,17],multipl:[0,5,6,7,16,18],name:[0,1,2,5,6,7,8,9,10,11,14,15,16,19],need:[6,16,17],network:17,never:[3,12],new_app_nam:14,newus:10,next:[3,12],nginx:19,note:[3,6,8,11,12,15,16],nsh:[2,7,8],number:16,oauth:[3,12,17,18],object:16,obtain:[3,4,6,12,13,16],occup:16,occur:16,off:[16,19],offic:16,offici:18,onc:[2,3,6,12],one:[5,6,7,8,16],onli:[5,6,7,10,16,17],open:14,openssl:19,oper:8,option:[16,17],order:[3,12],org:[2,6,10,18],organis:16,other:[3,7,8,12,16],otherwis:[5,6],over:[16,17,18],overwrit:[8,11],own:[6,7,15,17,18],ownership:16,packag:[18,19],page:20,pair:[0,6,7,8,16],paramet:[0,1,2,5,6,7,10,14,16],parent:[0,9,16],part:[7,16],particular:[6,7,16],passag:9,per:16,perform:17,permiss:[3,7,9,10,15,17,18],permission_request:6,pip:19,place:16,plai:[9,16],pleas:[7,19],point:[7,16,17,18],port:[2,19],post:[0,1,2,5,6,7,8,9,10,11,12,14,16,17],pre:16,present:[0,5,6,16,19],previou:[8,11],privaci:16,privileg:[3,16],process:16,project:16,properti:19,prove:16,provid:[11,16,17],publicli:[3,12],put:16,pymongo:19,python:19,queri:[4,7,13,16,17],queue:14,rabbitmq:[6,14],read:[2,3,7,10,12,16,17,18],real:16,realworld:16,receiv:7,redi:19,refer:[3,12,16],referenc:0,regener:16,regist:[11,12,17],registr:[14,16],relat:[2,10,17],remov:[3,5,6],replica:[17,19],request:[0,1,2,3,4,5,7,8,9,10,11,12,13,14,15,17,19],requested_sensor:6,requester_email:6,requester_nam:6,requir:[4,13,16,17,18],resid:[2,17],resolut:15,resolv:17,resourc:6,respect:[3,12],respons:[0,1,2,4,5,6,7,8,9,10,11,13,14,15,18],rest:[16,18,19],restrict:[5,6,7,9,15,16],retreiv:15,retriev:[0,1,2,5,6,7,8,10,11,14,18],revok:[3,16],role:[9,10,16],room:[0,1,7,8,16],run:19,rwp:[5,6],same:19,sampl:15,script:19,search:[3,16,20],second:15,secret:[3,4,12,13,16],section:16,secur:16,see:16,select:[0,5,6,16],sensor:[1,2,3,5,6,8,9,12,15,18],sensor_group:[5,8],sensor_id:15,sensor_tag:7,sensor_uuid:[7,14],sensorgroup:[3,5,6,11,16],sensorpoint:15,sent:[3,6,7,8,11,12,17],seri:[15,17],server:19,servic:[3,7,10,12,15,18],set:[1,8,16,17],setup:18,setuptool:19,sever:18,share:[3,12,18],should:[3,6,12],similar:16,simpli:16,simultan:16,singl:[7,16,17,18],slave:17,softwar:19,sole:[17,18],some:19,sourc:7,source_identifi:7,source_nam:7,specif:[2,16,17],specifi:[0,1,2,5,6,7,8,10,11,15,16],srv:19,standard:16,start:[16,19],start_tim:15,statu:[5,6],storag:18,store:[14,15,17],string:[5,6],structur:[0,1,9,16,19],subscrib:[12,17],subsequ:8,subset:7,success:[0,1,2,5,6,7,8,9,10,11,14,15],successfulli:[5,6],suggest:[7,11,16],superus:10,supervisor:19,support:[7,9,16],synergi:11,system:[14,16,18],tab:[3,12],tag:[1,3,5,6,9,16,17],tag_typ:1,tags_own:[7,8],tagstyp:9,tagtyp:[1,3,18],tar:[18,19],target:6,target_sensor:6,temp:7,temperatur:16,templat:[0,3,9,16],test:[6,7,8,11],test_build:0,test_building_templ:[0,1],test_ds3:2,test_sensor:7,test_sensor_group:5,test_user_group:5,thei:[3,9,11,12,16],them:[0,16],themselv:16,therefor:17,thi:[0,1,2,3,5,6,7,8,9,10,11,12,14,15,16,17,18,19],three:[5,6,7,16],through:[7,10,16,18],throughout:16,thu:17,time:[4,7,13,15,17],timeseri:[2,12,16,17],timestamp:6,togeth:[5,6,16],token:[3,12,16,17],traffic:17,transact:[16,17],treat:16,turn:16,two:[7,8,10,18],type:[0,1,2,4,5,6,7,8,9,10,11,13,14,15,16],unauthor:[5,6],under:[8,16],underli:[14,17,18],undertak:17,uniqu:16,unit:16,univers:16,unsubscrib:12,until:16,updat:2,url:[3,12,19],usag:16,use:[0,1,7,19],used:[0,1,2,6,7,9,14,16,17],useful:16,user1:2,user2:2,user:[0,1,2,3,5,6,7,8,9,12,14,15,17,18],user_group:[5,6,11],user_id:11,usergroup:[3,5,6,16],using:[3,4,11,12,13,16,19],utf:[0,1,2,4,5,6,7,8,9,10,11,13,14,15],util:16,uuid:[7,15,16,17],valid:[3,4,9,12,13,16],validate_email:19,valu:[0,5,6,7,8,15,16,19],variou:16,venv:19,veri:[9,16],via:[7,17],view:3,views_own:7,virtual:[8,16,19],wai:[16,17],want:[5,6,7,16],wash:16,weanhal:0,well:16,what:7,when:[0,11,16],where:2,whether:[16,17],which:[0,1,3,5,6,7,9,10,11,12,16,17,18],who:[2,7,10,15,16,17,18],within:[0,1,2,7,9,16,17],without:16,work:6,world:16,would:[16,17,19],write:[5,6,7,10,16,17],wtf:19,www:[0,1,2,3,5,6,7,8,9,10,11,12],xzf:19,you:[5,18,19],your:19},titles:["Buildings","BuildingTemplate","DataService","CentralService APIs","OAuth","Permission","Permission Requests","Sensor","SensorGroups","TagType","User","Usergroups","DataService APIs","OAuth","Apps","Timeseries","Central Service","Data Service","Building Depot v3.2.9","Installation","Welcome to BuildingDepot v3.2.9\u2019s documentation!"],titleterms:{"new":[0,2,10,14],Using:19,access:[4,13],add:[7,8,9,10,11],admin:2,api:[3,12,18],app:[14,17],assign:2,build:[0,1,2,16,18],buildingdepot:20,buildingtempl:[1,16],central:16,centralservic:[3,18,19],configur:19,creat:[0,1,2,5,6,7,8,11],data:[16,17],datapoint:15,dataservic:[2,12,18,19],delet:[0,1,2,5,7,8,11,14],depot:18,detail:[0,1,2,7,8,10,11],document:[18,20],download:18,from:[2,14],gener:[4,13],get:[0,1,2,7,8,9,10,11,14,18],grant:2,group:16,indic:20,instal:[18,19],list:[2,8,11,14],oauth:[4,13,16],overview:18,permiss:[5,6,16],post:15,privileg:2,read:[5,6,15],regist:14,remov:[2,10],request:6,revok:2,search:7,sensor:[7,14,16,17],sensorgroup:[7,8],servic:[16,17],start:18,subscrib:14,tabl:20,tag:[0,7,8],tagtyp:[9,16],templat:1,timeseri:15,token:[4,13],unsubscrib:14,user:[10,11,16],usergroup:[7,11],view:7,welcom:20,what:19}}) \ No newline at end of file +Search.setIndex({"docnames": ["api/CentralService/building", "api/CentralService/buildingtemplate", "api/CentralService/dataservice", "api/CentralService/index", "api/CentralService/oauth", "api/CentralService/permission", "api/CentralService/permissionRequests", "api/CentralService/sensor", "api/CentralService/sensorgroup", "api/CentralService/tagtype", "api/CentralService/user", "api/CentralService/usergroup", "api/DataService/index", "api/DataService/oauth", "api/DataService/pubsub", "api/DataService/sensordata", "centralservice", "dataservice", "index", "install", "source/index"], "filenames": ["api/CentralService/building.rst", "api/CentralService/buildingtemplate.rst", "api/CentralService/dataservice.rst", "api/CentralService/index.rst", "api/CentralService/oauth.rst", "api/CentralService/permission.rst", "api/CentralService/permissionRequests.rst", "api/CentralService/sensor.rst", "api/CentralService/sensorgroup.rst", "api/CentralService/tagtype.rst", "api/CentralService/user.rst", "api/CentralService/usergroup.rst", "api/DataService/index.rst", "api/DataService/oauth.rst", "api/DataService/pubsub.rst", "api/DataService/sensordata.rst", "centralservice.rst", "dataservice.rst", "index.rst", "install.rst", "source/index.rst"], "titles": ["Buildings", "BuildingTemplate", "DataService", "CentralService APIs", "OAuth", "Permission", "Permission Requests", "Sensor", "SensorGroups", "TagType", "User", "Usergroups", "DataService APIs", "OAuth", "Apps", "Timeseries", "Central Service", "Data Service", "Building Depot v3.3", "Installation", "Welcome to BuildingDepot v3.2.9\u2019s documentation!"], "terms": {"all": [0, 1, 2, 3, 7, 8, 10, 12, 15, 16, 17, 18], "ar": [0, 2, 3, 5, 6, 7, 8, 9, 11, 12, 15, 16, 17, 19], "present": [0, 1, 2, 5, 6, 8, 10, 11, 15, 16, 19], "within": [0, 1, 2, 7, 9, 15, 16, 17], "deploy": [0, 16], "buildingdepot": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19], "defin": [0, 1, 2, 5, 6, 7, 8, 9, 11, 15, 16, 17], "here": [0, 5, 6, 16, 17], "when": [0, 11, 16], "ad": [0, 2, 7, 8, 10, 11, 14, 15, 16], "buildingtempl": [0, 3, 18], "ha": [0, 1, 4, 9, 10, 13, 14, 15, 16, 18], "select": [0, 5, 6, 16], "which": [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18], "structur": [0, 1, 9, 16, 19], "thi": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20], "The": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "avail": [0, 1, 7, 8, 16, 17], "assign": [0, 1, 3, 5, 6, 16], "depend": [0, 7, 16, 17], "can": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20], "valu": [0, 1, 2, 5, 6, 7, 8, 10, 11, 15, 16, 19], "each": [0, 1, 2, 4, 7, 8, 13, 15, 16, 17, 18], "them": [0, 16], "specifi": [0, 1, 2, 5, 6, 7, 8, 10, 11, 15, 16], "have": [0, 2, 3, 4, 7, 8, 9, 10, 12, 13, 15, 16], "multipl": [0, 5, 6, 7, 15, 16, 18], "centralservic": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16, 17], "http": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 19], "www": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12], "exampl": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "com": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12], "81": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 19], "api": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "request": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 19], "name": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 19], "descript": [0, 1, 2, 8, 9, 11], "templat": [0, 3, 9, 16], "us": [0, 1, 2, 3, 4, 6, 7, 9, 11, 12, 13, 14, 16, 17], "user": [0, 1, 2, 3, 5, 6, 7, 8, 9, 12, 14, 15, 17, 18], "post": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 17], "1": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "accept": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 17], "applic": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "json": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16], "charset": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "utf": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "8": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "data": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15, 18], "test_build": 0, "test_building_templ": [0, 1], "respons": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 18], "success": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "200": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "ok": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "content": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "type": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16], "true": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "failur": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14], "fals": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "error": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14], "doe": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11], "exist": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 17], "miss": [0, 1, 2, 5, 6, 7, 10, 14], "paramet": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "retriev": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15, 18], "i": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "pair": [0, 6, 7, 8, 16], "weanhal": 0, "corridor": [0, 1, 7, 8, 9], "10": [0, 15], "parent": [0, 9, 16], "floor": [0, 1, 7, 8, 9], "One": [0, 1, 2, 11], "room": [0, 1, 7, 8, 16], "can_delet": 0, "being": [0, 7, 15], "referenc": 0, "cannot": [0, 2, 16], "foundat": [1, 16], "help": [1, 9, 11, 16, 18], "set": [1, 8, 16, 17], "tag": [1, 3, 5, 6, 9, 15, 16, 17], "creation": [1, 7, 16], "later": [1, 16], "sensor": [0, 1, 2, 3, 5, 6, 8, 9, 12, 15, 18], "tagtyp": [0, 1, 3, 18], "new": [1, 3, 5, 6, 7, 8, 9, 11, 12, 16, 17], "tag_typ": 1, "doesn": [1, 2, 14], "t": [1, 2, 14], "where": 2, "relat": [2, 10, 17], "timeseri": [2, 12, 16, 17], "resid": [2, 17], "access": [2, 3, 5, 6, 7, 9, 10, 11, 12, 15, 16, 17, 18, 19], "control": [2, 5, 6, 10, 11, 16, 17, 18], "function": [2, 11, 17], "also": [2, 3, 9, 12, 16, 17], "enforc": [2, 17], "A": [2, 6, 7, 8, 9, 10, 11, 15, 16, 17, 18], "host": [2, 19], "port": [2, 19], "datservic": 2, "ds3": 2, "test_ds3": 2, "127": 2, "0": [2, 7, 15], "3": [2, 7, 8, 19], "83": 2, "hostnam": 2, "depot": [2, 5, 6, 10], "contain": [0, 2, 5, 6, 7, 8, 9, 11, 14, 15, 16, 18, 20], "specif": [2, 16, 17], "onc": [2, 3, 6, 12], "handl": [2, 14, 18], "datastream": 2, "ds1": [2, 16, 19], "nsh": [2, 7, 8], "ghc": 2, "crud": 2, "read": [2, 3, 7, 10, 12, 16, 17, 18], "updat": 2, "user1": 2, "org": [2, 6, 10, 18], "user2": 2, "who": [2, 7, 10, 15, 16, 17, 18], "url": [3, 12, 19], "chapter": [3, 12], "respect": [3, 12], "base": [3, 7, 8, 9, 12, 16], "For": [3, 12, 16], "If": [3, 5, 6, 12, 14, 15, 16, 19], "servic": [3, 7, 10, 12, 15, 18], "refer": [3, 12, 16], "account": [3, 12, 16, 17, 18], "note": [3, 6, 8, 11, 12, 15, 16], "In": [3, 5, 6, 12, 16, 17], "order": [3, 12, 15], "first": [3, 10, 12, 14, 15, 16], "gener": [3, 12, 16, 17], "an": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 18], "oauth": [3, 12, 17, 18], "token": [3, 12, 16, 17], "client": [3, 4, 12, 13, 16], "id": [3, 4, 7, 11, 12, 13, 14, 16], "secret": [3, 4, 12, 13, 16], "kei": [3, 4, 7, 8, 12, 13, 16], "thei": [3, 9, 11, 12, 16], "obtain": [3, 4, 6, 12, 13, 16], "from": [3, 4, 5, 6, 10, 12, 13, 15, 16, 17, 18], "log": [3, 4, 12, 13, 16], "go": [3, 12, 19], "tab": [3, 12], "should": [2, 3, 6, 12, 15, 20], "never": [3, 12], "share": [3, 12, 18], "ani": [3, 5, 6, 7, 8, 10, 12, 16, 17, 18], "other": [3, 7, 8, 12, 16], "been": [3, 4, 12, 13, 15, 16], "valid": [3, 4, 9, 12, 13, 16], "next": [3, 12], "24": [3, 4, 12, 13, 15, 20], "hour": [3, 4, 12, 13], "after": [3, 4, 12, 13, 16, 18, 19], "anoth": [3, 10, 12, 19], "publicli": [3, 12], "sent": [3, 6, 7, 8, 11, 12, 17], "header": [3, 12], "made": [3, 12], "add": [3, 5, 6, 16], "get": [3, 4, 5, 6, 12, 13, 15, 16, 19], "creat": [3, 9, 10, 14, 16, 19], "build": [3, 5, 6, 7, 8, 9, 10, 17, 19], "detail": [3, 14, 18], "delet": [3, 10, 12], "dataservic": [3, 7, 10, 14, 15, 16, 17], "remov": [3, 5, 6], "grant": 3, "admin": [3, 6, 7, 10, 15, 16], "privileg": [3, 16], "list": [0, 1, 3, 5, 6, 7, 9, 12, 15, 16], "revok": [3, 16], "search": [3, 16, 20], "view": 3, "sensorgroup": [3, 5, 6, 11, 16], "usergroup": [3, 5, 6, 16], "permiss": [2, 3, 7, 9, 10, 15, 17, 18], "everi": [4, 10, 13, 17, 19], "queri": [4, 7, 13, 16, 17], "authent": [4, 13, 17], "requir": [4, 7, 13, 15, 16, 17, 18], "follow": [4, 13, 15, 19], "time": [4, 7, 13, 15, 17], "access_token": [4, 13], "client_id": [4, 13], "bocwejsnwj8uj4mfpip8cqcx0qghink6pfbmtnx0": [4, 13], "client_secret": [4, 13], "1gk1pbqhik6vhqulondeuculq0tf5h9vkjaubibvx0qmjsc9uq": [4, 13], "528d58481bc728a5eb57e73a49ba4539": [4, 13], "between": [5, 6, 16], "come": [5, 6, 16], "togeth": [5, 6, 16], "form": [5, 6, 11, 16], "we": [5, 6, 10, 16, 17], "group": [5, 6, 8, 9, 11, 17, 18], "want": [5, 6, 7, 16], "associ": [5, 6, 7, 15, 16, 17], "both": [5, 6, 7, 9, 15, 16], "There": [5, 6, 16], "three": [5, 6, 7, 16], "level": [5, 6, 7, 16], "d": [5, 6, 16], "r": [5, 6, 16], "deni": [5, 6, 7, 16], "w": [5, 6, 16], "write": [5, 6, 7, 10, 16, 17], "p": [5, 6, 16], "map": [5, 6, 16], "one": [5, 6, 7, 8, 16], "most": [5, 6, 16, 17], "restrict": [5, 6, 7, 9, 15, 16], "chosen": [5, 6, 16], "link": [5, 7, 16], "dictionari": [0, 5, 6, 7, 8, 11, 15], "sensor_group": [5, 8], "string": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "user_group": [5, 6, 11], "Will": [5, 6, 16], "give": [5, 6, 16], "onli": [0, 1, 2, 5, 6, 7, 8, 10, 11, 16, 17], "rw": [5, 6], "dr": [5, 6], "rwp": [5, 6], "highest": [5, 6, 16], "addit": [0, 1, 2, 5, 6, 8, 10, 11, 16], "abl": [5, 6, 7], "return": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16], "successfulli": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "otherwis": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "fail": [0, 1, 2, 5, 6, 8, 10, 11, 14], "caus": [0, 1, 2, 5, 6, 8, 10, 11], "statu": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "401": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "unauthor": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "credenti": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "test_sensor_group": 5, "test_user_group": 5, "you": [5, 18, 19, 20], "author": 5, "particular": [6, 7, 16], "entiti": [6, 10, 16], "own": [6, 7, 15, 17, 18], "approv": 6, "resourc": 6, "firebas": 6, "rabbitmq": [6, 14], "need": [6, 16, 17], "instal": [2, 6, 10, 16], "dure": [6, 16], "bd": [6, 7, 18, 19], "work": 6, "target_sensor": 6, "target": 6, "timestamp": [6, 15], "6cf53d24": [6, 7], "e3a3": [6, 7], "41bd": [6, 7], "b2b5": [6, 7], "8f109694f628": [6, 7], "8f109694f629": 6, "1626105964": 6, "permission_request": 6, "requester_nam": 6, "requester_email": 6, "test": [6, 7, 8, 11], "requested_sensor": 6, "collect": [7, 8, 15], "manag": [7, 11, 15, 17], "locat": [7, 8, 15, 17, 19], "central": [7, 10, 12, 15, 18, 19], "point": [7, 8, 15, 16, 17, 18], "uuid": [7, 15, 16, 17], "test_sensor": 7, "identifi": [7, 15, 16], "sensor_tag": 7, "8aac1048": 7, "aa9f": 7, "41c9": 7, "9c20": 7, "6dd81339c7de": 7, "source_identifi": 7, "source_nam": 7, "views_own": 7, "field": 7, "emi": 7, "280bc754": 7, "a963": 7, "4740": 7, "89f4": 7, "bdae9ede76f6": 7, "averag": [7, 16], "2": 7, "6336a9d7": 7, "6f65": 7, "4572": 7, "9e52": 7, "78fa3808c92f": 7, "min": 7, "max": 7, "sensor_uuid": [7, 14], "current": [7, 8, 14], "done": [7, 17], "through": [7, 10, 16, 18], "gui": [7, 16], "metadata": [7, 16], "singl": [7, 16, 17, 18], "given": [2, 7, 14, 15, 16], "sourc": 7, "4200": 7, "To": [7, 16, 17, 19], "pleas": [7, 19], "fetch": [7, 9], "be2a09a5": 7, "4ef6": 7, "4c67": 7, "886e": 7, "272c37e7e38f": 7, "4120": 7, "two": [7, 8, 10, 18], "arrai": [7, 8, 9], "elig": [7, 8], "attach": [0, 5, 6, 7, 8, 16], "26da099a": 7, "3fe0": 7, "4966": 7, "b068": 7, "14f51bcedb6": 7, "3600": [7, 8], "3700": [7, 8], "3606": [7, 8], "tags_own": [7, 8], "mai": [7, 8, 17, 18], "howev": 7, "receiv": 7, "subset": 7, "temp": 7, "import": [7, 16], "commun": [7, 17], "available_field": 7, "mic": 7, "3240e89b": 7, "81df": 7, "4c1c": 7, "a411": 7, "fe891ed2aaef": 7, "ce7330aa": 7, "0893": 7, "4115": 7, "842d": 7, "0b6ab92e98d": 7, "22d807bf": 7, "af67": 7, "493a": 7, "80b7": 7, "2690d26c9244": 7, "either": [7, 10, 16], "As": 7, "suggest": [7, 11, 16], "datapoint": [7, 12, 16, 17], "support": [7, 9, 16], "With": 7, "basi": [7, 16], "decid": 7, "abstract": [7, 16], "part": [7, 16], "basic": 7, "connect": [7, 14], "via": [7, 17], "what": 7, "virtual": [8, 16, 19], "consist": [8, 16], "belong": [8, 16, 17, 18], "chang": [8, 16, 19], "modifi": 8, "fall": 8, "under": [8, 16], "automat": [8, 16], "subsequ": 8, "oper": 8, "overwrit": [8, 11], "previou": [8, 11], "describ": 9, "domain": 9, "categoris": [9, 16], "easili": 9, "alloc": [9, 17], "differ": [9, 16, 17, 18], "tagstyp": 9, "hierarch": 9, "e": [9, 15, 16], "children": 9, "plai": [9, 16], "veri": [9, 16], "crucial": 9, "role": [9, 10, 16], "passag": 9, "inform": [0, 2, 7, 8, 9, 11, 14, 15, 17], "about": [8, 9, 11, 14, 17, 18], "null": 9, "superus": 10, "default": [10, 19], "maintain": [10, 16], "super": [10, 16], "limit": 10, "first_nam": 10, "last_nam": 10, "email": [2, 8, 10, 11, 16], "newus": 10, "gmail": [10, 11], "alreadi": [10, 14], "": [11, 14, 16, 17], "regist": [11, 12, 17], "combin": [11, 16], "bring": 11, "provid": [11, 16, 17], "No": 11, "user_id": 11, "synergi": 11, "more": [11, 16, 18], "82": [12, 19], "app": [12, 18], "subscrib": [12, 17], "unsubscrib": 12, "allow": [14, 16, 17], "interact": [14, 17], "underli": [14, 17, 18], "model": 14, "registr": [14, 16], "system": [1, 14, 16, 18], "open": 14, "up": [14, 17], "queue": 14, "app_list": 14, "app_example_1": 14, "app_example_2": 14, "app_example_3": 14, "store": [14, 15, 17], "call": [14, 16, 19], "effect": 14, "new_app_nam": 14, "app_id": 14, "broker": 14, "example_app_nam": 14, "app_nam": 14, "bind": 14, "sensorpoint": 15, "datatyp": 15, "further": [15, 16, 17], "g": [15, 16], "certain": [7, 8, 15, 16, 17], "int": 15, "sensor_id": 15, "a5d6277": 15, "4b51": 15, "4056": 15, "b9fd": 15, "0a6505b4f5a6": 15, "sampl": 15, "56": 15, "1225865462": 15, "23": 15, "12": 15, "1225865500": 15, "cee06227": 15, "72e5": 15, "49d2": 15, "94f1": 15, "20c501ca2afa": 15, "retreiv": 15, "interv": 15, "resolut": 15, "append": 15, "second": 15, "10m": 15, "minut": 15, "start_tim": 15, "1445535722": 15, "end_tim": 15, "1445789516": 15, "seri": [15, 17], "column": 15, "inserted_at": 15, "35b137b2": 15, "c7c6": 15, "4608": 15, "8489": 15, "1c3f0ee7e2d5": 15, "2015": 15, "22t17": 15, "41": 15, "44": 15, "762495917z": 15, "22": 15, "11": 15, "43": 15, "19": 15, "48927063z": 15, "1445535818": 15, "22t22": 15, "53": 15, "066248715z": 15, "1445553913": 15, "hold": 16, "core": [16, 18], "start": [15, 16, 19], "lie": 16, "organis": 16, "meaning": 16, "wai": [16, 17], "brief": [16, 17], "explan": [16, 17], "option": [9, 15, 16, 17], "measur": 16, "command": 16, "turn": 16, "light": 16, "off": [16, 19], "configur": [16, 18], "cycl": 16, "wash": 16, "machin": [16, 19], "even": 16, "fault": 16, "indic": 16, "includ": 16, "real": 16, "world": 16, "actuat": 16, "well": 16, "realworld": 16, "temperatur": 16, "treat": 16, "ident": 16, "occur": 16, "connector": 16, "see": 16, "document": 16, "interfac": 16, "univers": 16, "uniqu": 16, "offic": 16, "would": [16, 17, 19], "like": [16, 19, 20], "300": 16, "unit": [15, 16], "fahrenheit": 16, "so": 16, "themselv": 16, "complex": 16, "its": [16, 17], "area": 16, "usag": 16, "pre": 16, "act": 16, "These": 16, "standard": 16, "convent": 16, "project": 16, "haystack2": 16, "mechan": 16, "rest": [16, 18, 19], "individu": 16, "occup": 16, "example_build": 16, "meet": 16, "object": [11, 16], "util": 16, "context": 16, "how": [16, 17], "without": 16, "determin": [16, 17], "enjoi": 16, "while": 16, "put": 16, "do": [16, 19], "manual": 16, "simpli": 16, "backend": 16, "element": 16, "facilit": 16, "privaci": 16, "secur": 16, "choos": 16, "four": 16, "variou": 16, "simultan": 16, "evel": 16, "alter": 16, "abov": 16, "ownership": 16, "It": [16, 17], "addition": 16, "circumst": 16, "home_usergroup": 16, "home": 16, "creator": 16, "gain": 16, "until": 16, "regener": 16, "process": 16, "integr": 16, "place": 16, "throughout": 16, "enabl": 16, "u": 16, "hierarchi": 16, "prove": 16, "number": [2, 16], "deploi": 16, "decis": 16, "complet": [16, 20], "left": 16, "per": 16, "ideal": 16, "choic": 16, "section": 16, "administr": [16, 17, 18], "over": [15, 16, 17, 18], "transact": [16, 17], "whether": [11, 16, 17], "bunch": [16, 17], "critic": 16, "filter": 16, "similar": 16, "wa": [14, 16], "directli": 17, "network": 17, "sole": [17, 18], "campu": [17, 18], "might": [17, 18], "desir": [15, 17, 18], "thu": 17, "institut": [17, 18], "mani": 17, "immens": 17, "frequent": 17, "almost": 17, "check": 17, "therefor": 17, "keep": 17, "perform": 17, "bottleneck": 17, "resolv": 17, "issu": 17, "master": 17, "slave": 17, "mode": 17, "replica": [17, 19], "undertak": 17, "traffic": 17, "load": 17, "balanc": 17, "offici": 18, "essentiali": 18, "extens": 18, "distribut": 18, "architectur": 18, "storag": 18, "essenti": [18, 19], "compon": 18, "directori": [18, 19], "actual": 18, "expos": 18, "insert": 18, "hous": 18, "sever": 18, "latest": 18, "packag": [18, 19], "tar": [18, 19], "gz": [18, 19], "guid": 18, "initi": 18, "setup": 18, "mongodb": 19, "influxdb": 19, "redi": 19, "same": 19, "extract": 19, "cd": 19, "folder": 19, "xzf": 19, "run": 19, "srv": 19, "centralreplica": 19, "venv": 19, "python": 19, "environ": 19, "your": [19, 20], "ensur": 19, "accordingli": 19, "config": 19, "py": 19, "file": [19, 20], "apt": 19, "openssl": 19, "setuptool": 19, "dev": 19, "softwar": 19, "properti": 19, "pip": 19, "nginx": 19, "supervisor": 19, "server": 19, "flask": 19, "mongoengin": 19, "httpauth": 19, "login": 19, "validate_email": 19, "script": 19, "wtf": 19, "bootstrap": 19, "pymongo": 19, "some": 19, "sphinx": 20, "quickstart": 20, "mon": 20, "feb": 20, "20": 20, "13": 20, "25": 20, "2021": 20, "adapt": 20, "least": 20, "root": 20, "toctre": 20, "direct": 20, "index": 20, "modul": 20, "page": 20, "code": [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 14, 15], "building_nam": 0, "devic": 2, "dict": [2, 14], "whom": 2, "ba": 2, "whose": [2, 15], "compulsori": [5, 8, 11, 15], "view_uuid": 7, "last": 10, "boolean": 11, "channel": 14, "correspond": 14, "unsuccess": 14, "subscript": 14, "unsubscript": 14, "sing": 15, "format": 15, "item": 15, "unix": 15, "float": 15, "start_timestamp": 15, "end_timestamp": 15, "resolution_unit": 15, "integ": 15, "end": 15, "till": 15, "1m": 15, "1h": 15, "etc": 15, "succesfulli": 15, "struct": 15, "repres": 15}, "objects": {"": [[14, 0, 1, "delete--api-apps", "/api/apps"], [14, 1, 1, "get--api-apps", "/api/apps"], [14, 2, 1, "post--api-apps", "/api/apps"], [14, 0, 1, "delete--api-apps-subscription", "/api/apps/subscription"], [14, 2, 1, "post--api-apps-subscription", "/api/apps/subscription"], [0, 2, 1, "post--api-building", "/api/building"], [0, 0, 1, "delete--api-building--building_name--tags", "/api/building/<building_name>/tags"], [0, 1, 1, "get--api-building--building_name--tags", "/api/building/<building_name>/tags"], [0, 2, 1, "post--api-building--building_name--tags", "/api/building/<building_name>/tags"], [0, 0, 1, "delete--api-building--name-", "/api/building/<name>"], [0, 1, 1, "get--api-building--name-", "/api/building/<name>"], [2, 2, 1, "post--api-dataservice", "/api/dataservice"], [2, 0, 1, "delete--api-dataservice--name-", "/api/dataservice/<name>"], [2, 1, 1, "get--api-dataservice--name-", "/api/dataservice/<name>"], [2, 0, 1, "delete--api-dataservice--name--admins", "/api/dataservice/<name>/admins"], [2, 1, 1, "get--api-dataservice--name--admins", "/api/dataservice/<name>/admins"], [2, 2, 1, "post--api-dataservice--name--admins", "/api/dataservice/<name>/admins"], [2, 2, 1, "post--api-dataservice--name--building", "/api/dataservice/<name>/building"], [2, 0, 1, "delete--api-dataservice--name--buildings", "/api/dataservice/<name>/buildings"], [2, 1, 1, "get--api-dataservice--name--buildings", "/api/dataservice/<name>/buildings"], [5, 2, 1, "post--api-permission", "/api/permission"], [6, 1, 1, "get--api-permission-request", "/api/permission/request"], [6, 2, 1, "post--api-permission-request", "/api/permission/request"], [5, 0, 1, "delete--api-permission?user_group=-user_group-&sensor_group=-sensor_group-", "/api/permission?user_group=<user_group>&sensor_group=<sensor_group>"], [5, 1, 1, "get--api-permission?user_group=-user_group-&sensor_group=-sensor_group-", "/api/permission?user_group=<user_group>&sensor_group=<sensor_group>"], [7, 2, 1, "post--api-sensor", "/api/sensor"], [7, 1, 1, "get--api-sensor--name-", "/api/sensor/<name>"], [7, 1, 1, "get--api-sensor--name--tags", "/api/sensor/<name>/tags"], [7, 2, 1, "post--api-sensor--name--tags", "/api/sensor/<name>/tags"], [7, 2, 1, "post--api-sensor--name--view", "/api/sensor/<name>/view"], [7, 1, 1, "get--api-sensor--name--views", "/api/sensor/<name>/views"], [7, 0, 1, "delete--api-sensor--uuid--views--view_uuid-", "/api/sensor/<uuid>/views/<view_uuid>"], [7, 2, 1, "post--api-sensor-search", "/api/sensor/search"], [15, 2, 1, "post--api-sensor-timeseries", "/api/sensor/timeseries"], [8, 2, 1, "post--api-sensor_group", "/api/sensor_group"], [8, 0, 1, "delete--api-sensor_group--name-", "/api/sensor_group/<name>"], [8, 1, 1, "get--api-sensor_group--name-", "/api/sensor_group/<name>"], [8, 1, 1, "get--api-sensor_group--name--tags", "/api/sensor_group/<name>/tags"], [8, 2, 1, "post--api-sensor_group--name--tags", "/api/sensor_group/<name>/tags"], [9, 2, 1, "post--api-tagtype", "/api/tagtype"], [9, 1, 1, "get--api-tagtype--name-", "/api/tagtype/<name>"], [1, 2, 1, "post--api-template", "/api/template"], [1, 0, 1, "delete--api-template--name-", "/api/template/<name>"], [1, 1, 1, "get--api-template--name-", "/api/template/<name>"], [10, 2, 1, "post--api-user", "/api/user"], [10, 0, 1, "delete--api-user--email-", "/api/user/<email>"], [10, 1, 1, "get--api-user--email-", "/api/user/<email>"], [11, 2, 1, "post--api-user_group", "/api/user_group"], [11, 1, 1, "get--api-user_group--name-", "/api/user_group/<name>"], [11, 1, 1, "get--api-user_group--name--users", "/api/user_group/<name>/users"], [11, 2, 1, "post--api-user_group--name--users", "/api/user_group/<name>/users"], [11, 0, 1, "delete--api-user_group-Test", "/api/user_group/Test"], [13, 1, 1, "get--oauth-access_token-client_id=-client_id--client_secret=-client_secret-", "/oauth/access_token/client_id=<client_id>/client_secret=<client_secret>"], [15, 1, 1, "get--sensor--sensor-uuid--timeseries?start_time=-start_timestamp-&end_time=-end_timestamp-&resolution=-resolution_units-", "/sensor/<sensor-uuid>/timeseries?start_time=<start_timestamp>&end_time=<end_timestamp>&resolution=<resolution_units>"]]}, "objtypes": {"0": "http:delete", "1": "http:get", "2": "http:post"}, "objnames": {"0": ["http", "delete", "HTTP delete"], "1": ["http", "get", "HTTP get"], "2": ["http", "post", "HTTP post"]}, "titleterms": {"build": [0, 1, 2, 16, 18], "creat": [0, 1, 2, 5, 6, 7, 8, 11], "new": [0, 2, 10, 14], "get": [0, 1, 2, 7, 8, 9, 10, 11, 14, 18], "detail": [0, 1, 2, 7, 8, 10, 11], "delet": [0, 1, 2, 5, 7, 8, 11, 14], "tag": [0, 7, 8], "buildingtempl": [1, 16], "templat": 1, "dataservic": [2, 12, 18, 19], "assign": 2, "from": [2, 14], "remov": [2, 10], "grant": 2, "admin": 2, "privileg": 2, "list": [2, 8, 11, 14], "revok": 2, "centralservic": [3, 18, 19], "api": [3, 12, 18], "oauth": [4, 13, 16], "gener": [4, 13], "access": [4, 13], "token": [4, 13], "permiss": [5, 6, 16], "read": [5, 6, 15], "request": 6, "sensor": [7, 14, 16, 17], "search": 7, "add": [7, 8, 9, 10, 11], "view": 7, "sensorgroup": [7, 8], "usergroup": [7, 11], "tagtyp": [9, 16], "user": [10, 11, 16], "app": [14, 17], "regist": 14, "an": 14, "subscrib": 14, "unsubscrib": 14, "timeseri": 15, "post": 15, "datapoint": 15, "central": 16, "servic": [16, 17], "data": [16, 17], "group": 16, "depot": 18, "v3": [18, 20], "2": 20, "9": 20, "overview": 18, "download": 18, "instal": [18, 19], "start": 18, "document": [18, 20], "us": 19, "sh": 19, "what": 19, "": [19, 20], "configur": 19, "welcom": 20, "buildingdepot": 20, "indic": 20, "tabl": 20, "3": 18}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx": 57}, "alltitles": {"Buildings": [[0, "buildings"]], "Create a new Building": [[0, "create-a-new-building"]], "Get Building Details": [[0, "get-building-details"]], "Delete Building": [[0, "delete-building"]], "Create a new Building Tag": [[0, "create-a-new-building-tag"]], "Get Building Tag Details": [[0, "get-building-tag-details"]], "Delete Building Tags": [[0, "delete-building-tags"]], "BuildingTemplate": [[1, "buildingtemplate"], [16, "buildingtemplate"]], "Create a Building Template": [[1, "create-a-building-template"]], "Get BuildingTemplate Details": [[1, "get-buildingtemplate-details"]], "Delete Building Template": [[1, "delete-building-template"]], "DataService": [[2, "dataservice"], [19, "dataservice"], [18, "dataservice"]], "Create a new DataService": [[2, "create-a-new-dataservice"]], "Get DataService Details": [[2, "get-dataservice-details"]], "Delete DataService": [[2, "delete-dataservice"]], "Assign Buildings to DataService": [[2, "assign-buildings-to-dataservice"]], "Get Building Details from DataService": [[2, "get-building-details-from-dataservice"]], "Remove Buildings from DataService": [[2, "remove-buildings-from-dataservice"]], "Grant Admin Privileges on DataService": [[2, "grant-admin-privileges-on-dataservice"]], "Get List of Admins from DataService": [[2, "get-list-of-admins-from-dataservice"]], "Revoke Admin Privileges on DataService": [[2, "revoke-admin-privileges-on-dataservice"]], "CentralService APIs": [[3, "centralservice-apis"]], "OAuth": [[4, "oauth"], [13, "oauth"], [16, "oauth"]], "Generating access tokens": [[4, "generating-access-tokens"], [13, "generating-access-tokens"]], "Permission": [[5, "permission"], [16, "permission"]], "Create Permission": [[5, "create-permission"]], "Read Permission": [[5, "read-permission"]], "Delete Permission": [[5, "delete-permission"]], "Permission Requests": [[6, "permission-requests"]], "Create Permission Requests": [[6, "create-permission-requests"]], "Read Permission Requests": [[6, "read-permission-requests"]], "Sensor": [[7, "sensor"], [16, "sensor"], [17, "sensor"]], "Create a Sensor": [[7, "create-a-sensor"]], "Get Sensor details": [[7, "get-sensor-details"]], "Delete a Sensor": [[7, "delete-a-sensor"]], "Search Sensors": [[7, "search-sensors"]], "Add Tags to a Sensor": [[7, "add-tags-to-a-sensor"]], "Get Tags of a Sensor": [[7, "get-tags-of-a-sensor"]], "Create a Sensor View": [[7, "create-a-sensor-view"]], "Get Sensor View details": [[7, "get-sensor-view-details"]], "Delete a Sensor View": [[7, "delete-a-sensor-view"]], "SensorGroups and UserGroups": [[7, "sensorgroups-and-usergroups"]], "SensorGroups": [[8, "sensorgroups"]], "Create SensorGroup": [[8, "create-sensorgroup"]], "Get SensorGroup Details": [[8, "get-sensorgroup-details"]], "Delete SensorGroup": [[8, "delete-sensorgroup"]], "Add tags to SensorGroup": [[8, "add-tags-to-sensorgroup"]], "Get list of tags in SensorGroup": [[8, "get-list-of-tags-in-sensorgroup"]], "TagType": [[9, "tagtype"], [16, "tagtype"]], "Add TagType": [[9, "add-tagtype"]], "Get TagType": [[9, "get-tagtype"]], "User": [[10, "user"]], "Add a new User": [[10, "add-a-new-user"]], "Get User Details": [[10, "get-user-details"]], "Remove User": [[10, "remove-user"]], "Usergroups": [[11, "usergroups"]], "Create UserGroup": [[11, "create-usergroup"]], "Get UserGroup Details": [[11, "get-usergroup-details"]], "Delete UserGroup": [[11, "delete-usergroup"]], "Add users to UserGroup": [[11, "add-users-to-usergroup"]], "Get list of users in UserGroup": [[11, "get-list-of-users-in-usergroup"]], "DataService APIs": [[12, "dataservice-apis"]], "Apps": [[14, "apps"], [17, "apps"]], "Get List of Registered Apps": [[14, "get-list-of-registered-apps"]], "Register a New App": [[14, "register-a-new-app"]], "Delete an App": [[14, "delete-an-app"]], "Subscribe to a Sensor": [[14, "subscribe-to-a-sensor"]], "Unsubscribe from a Sensor": [[14, "unsubscribe-from-a-sensor"]], "Timeseries": [[15, "timeseries"]], "Post Timeseries Datapoints": [[15, "post-timeseries-datapoints"]], "Read Timeseries Datapoints": [[15, "read-timeseries-datapoints"]], "Central Service": [[16, "central-service"]], "Building": [[16, "building"]], "Data Services": [[16, "data-services"]], "Sensor Group": [[16, "sensor-group"]], "User Group": [[16, "user-group"]], "Data Service": [[17, "data-service"]], "Installation": [[19, "installation"]], "Using install.sh": [[19, "using-install-sh"]], "What\u2019s installed": [[19, "what-s-installed"]], "Configuration": [[19, "configuration"]], "CentralService": [[19, "centralservice"], [18, "centralservice"]], "Welcome to BuildingDepot v3.2.9\u2019s documentation!": [[20, "welcome-to-buildingdepot-v3-2-9-s-documentation"]], "Indices and tables": [[20, "indices-and-tables"]], "Building Depot v3.3": [[18, "building-depot-v3-3"]], "Overview": [[18, "overview"]], "Download": [[18, "download"]], "Install": [[18, "install"]], "Getting Started": [[18, "getting-started"]], "API Documentation": [[18, "api-documentation"]]}, "indexentries": {}}) \ No newline at end of file diff --git a/buildingdepot/Documentation/build/html/source/index.html b/buildingdepot/Documentation/build/html/source/index.html index 345e4b7e..2b15aa91 100644 --- a/buildingdepot/Documentation/build/html/source/index.html +++ b/buildingdepot/Documentation/build/html/source/index.html @@ -5,10 +5,11 @@ - + + - Welcome to BuildingDepot v3.2.9’s documentation! — BuildingDepot 3.2.9 documentation + Welcome to BuildingDepot v3.2.9’s documentation! — BuildingDepot 3.3 documentation @@ -28,12 +29,16 @@ + + + + - + @@ -62,7 +67,7 @@
    - 3.2.9 + 3.3
    @@ -159,19 +164,22 @@
    -
    -

    Welcome to BuildingDepot v3.2.9’s documentation!

    +

    sphinx-quickstart on Mon Feb 24 20:13:25 2021. +You can adapt this file completely to your liking, but it should at least +contain the root toctree directive.

    +
    +

    Welcome to BuildingDepot v3.2.9’s documentation!

    -
    -
    -

    Indices and tables

    + +
    +

    Indices and tables

    -
    +
    @@ -183,7 +191,7 @@

    Indices and tables

    - © Copyright 2020, SynergyLabs. + © Copyright 2022, SynergyLabs, Sudershan Boovaraghavan.

    @@ -205,16 +213,19 @@

    Indices and tables var DOCUMENTATION_OPTIONS = { URL_ROOT:'../', - VERSION:'3.2.9', + VERSION:'3.3', COLLAPSE_INDEX:false, FILE_SUFFIX:'.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt' }; + + + diff --git a/buildingdepot/Documentation/source/conf.py b/buildingdepot/Documentation/source/conf.py index ed9fdf49..bb48c338 100755 --- a/buildingdepot/Documentation/source/conf.py +++ b/buildingdepot/Documentation/source/conf.py @@ -7,15 +7,14 @@ # -- Project information ----------------------------------------------------- -project = 'BuildingDepot' -copyright = '2020, SynergyLabs' -author = 'SynergyLabs' +project = u"BuildingDepot" +copyright = u"2022, SynergyLabs, Sudershan Boovaraghavan" +author = u"Sudershan Boovaraghavan" # The short X.Y version -version = '3.2.9' +version = "3.3" # The full version, including alpha/beta/rc tags -release = '3.2.9' - +release = "3.3" # -- General configuration --------------------------------------------------- @@ -27,37 +26,37 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.todo', - 'sphinx.ext.githubpages', + "sphinx.ext.todo", + "sphinx.ext.githubpages", + "sphinxcontrib.httpdomain" ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - +pygments_style = "sphinx" # -- Options for HTML output ------------------------------------------------- @@ -74,7 +73,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -85,7 +84,8 @@ # 'searchbox.html']``. # # html_sidebars = {} -#---sphinx-themes----- -html_theme = 'neo_rtd_theme' +# ---sphinx-themes----- +html_theme = "neo_rtd_theme" import sphinx_theme + html_theme_path = [sphinx_theme.get_html_theme_path()] diff --git a/buildingdepot/Documentation/source/images/BuildingDepot.svg b/buildingdepot/Documentation/source/images/BuildingDepot.svg index 75024beb..b3087e2b 100644 --- a/buildingdepot/Documentation/source/images/BuildingDepot.svg +++ b/buildingdepot/Documentation/source/images/BuildingDepot.svg @@ -1 +1,392 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/buildingdepot/Documentation/source/images/CSComponents.svg b/buildingdepot/Documentation/source/images/CSComponents.svg index 6d99d886..ef24e5c7 100644 --- a/buildingdepot/Documentation/source/images/CSComponents.svg +++ b/buildingdepot/Documentation/source/images/CSComponents.svg @@ -1 +1,70 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/buildingdepot/Documentation/source/images/DSComponents.svg b/buildingdepot/Documentation/source/images/DSComponents.svg index a17b8174..cc3e5949 100644 --- a/buildingdepot/Documentation/source/images/DSComponents.svg +++ b/buildingdepot/Documentation/source/images/DSComponents.svg @@ -1 +1,28 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/buildingdepot/Documentation/source/index.rst b/buildingdepot/Documentation/source/index.rst index 00626236..29fe974f 100755 --- a/buildingdepot/Documentation/source/index.rst +++ b/buildingdepot/Documentation/source/index.rst @@ -1,13 +1,13 @@ -.. BuildingDepot 3.2.9 documentation master file. +.. BuildingDepot 3.3 documentation master file. ===================== -Building Depot v3.2.9 +Building Depot v3.3 ===================== Overview ######## -This is the official documentation of BuildingDepot v3.2.9. BuildingDepot is essentialy an Extensible and Distributed Architecture for Sensor Data Storage, Access and Sharing. +This is the official documentation of BuildingDepot v3.3. BuildingDepot is essentialy an Extensible and Distributed Architecture for Sensor Data Storage, Access and Sharing. Building Depot has two essential components a Central Service and a Data Service : @@ -44,7 +44,7 @@ Read more about the DataService Download ######## -You can get the latest BuildingDepot package (tar.gz) from `buildingdepot.org `_ +You can get the latest BuildingDepot package (tar.gz) from `buildingdepot.org `_ Install ####### diff --git a/buildingdepot/Documentation/source/source/conf.py b/buildingdepot/Documentation/source/source/conf.py index dd351556..0c37ded1 100644 --- a/buildingdepot/Documentation/source/source/conf.py +++ b/buildingdepot/Documentation/source/source/conf.py @@ -19,15 +19,14 @@ # -- Project information ----------------------------------------------------- -project = u"BuildingDepot v3.2.9" +project = u"BuildingDepot v3.3.0" copyright = u"2021, Sudershan Boovaraghavan, Shreyas Nagare" author = u"Sudershan Boovaraghavan, Shreyas Nagare" # The short X.Y version -version = u"" +version = "" # The full version, including alpha/beta/rc tags -release = u"" - +release = "" # -- General configuration --------------------------------------------------- @@ -67,7 +66,6 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -100,8 +98,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = "BuildingDepotv329doc" - +htmlhelp_basename = "BuildingDepotv330doc" # -- Options for LaTeX output ------------------------------------------------ @@ -126,14 +123,13 @@ latex_documents = [ ( master_doc, - "BuildingDepotv329.tex", - u"BuildingDepot v3.2.9 Documentation", + "BuildingDepotv330.tex", + u"BuildingDepot v3.3.0 Documentation", u"Synergy Labs", "manual", ), ] - # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples @@ -141,14 +137,13 @@ man_pages = [ ( master_doc, - "buildingdepotv329", - u"BuildingDepot v3.2.9 Documentation", + "buildingdepotv330", + "BuildingDepot v3.3.0 Documentation", [author], 1, ) ] - # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples @@ -157,10 +152,10 @@ texinfo_documents = [ ( master_doc, - "BuildingDepotv329", - u"BuildingDepot v3.2.9 Documentation", + "BuildingDepotv330", + "BuildingDepot v3.3.0 Documentation", author, - "BuildingDepotv329", + "BuildingDepotv330", "One line description of project.", "Miscellaneous", ), diff --git a/buildingdepot/Documentation/source/source/index.rst b/buildingdepot/Documentation/source/source/index.rst index e9f61510..da94d05a 100644 --- a/buildingdepot/Documentation/source/source/index.rst +++ b/buildingdepot/Documentation/source/source/index.rst @@ -1,7 +1,7 @@ .. BuildingDepot v3.2.9 documentation master file, created by - sphinx-quickstart on Mon Feb 24 20:13:25 2020. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +sphinx-quickstart on Mon Feb 24 20:13:25 2021. +You can adapt this file completely to your liking, but it should at least +contain the root `toctree` directive. Welcome to BuildingDepot v3.2.9's documentation! ================================================ diff --git a/buildingdepot/Instrumentation/comment.py b/buildingdepot/Instrumentation/comment.py index 70a5640d..8601428b 100644 --- a/buildingdepot/Instrumentation/comment.py +++ b/buildingdepot/Instrumentation/comment.py @@ -7,10 +7,10 @@ filename = sys.argv[1] -f = open(filename, 'rb') +f = open(filename, "rb") for line in f: - if line == '@instrument\n': - print "# " + line, - else: - print line, \ No newline at end of file + if line == "@instrument\n": + print("# " + line, end=" ") + else: + print(line, end=" ") diff --git a/buildingdepot/Instrumentation/instrument.py b/buildingdepot/Instrumentation/instrument.py index e942d88c..2285708c 100644 --- a/buildingdepot/Instrumentation/instrument.py +++ b/buildingdepot/Instrumentation/instrument.py @@ -14,12 +14,12 @@ 185618305,1,write_timeseries_data,41105.2598953,41124.270916,19.0110206604 """ -import thread -import time -from functools import wraps -import traceback +import _thread import os import random +import time +import traceback +from functools import wraps global process_unique_id global seq_dict @@ -30,7 +30,7 @@ beginningOfTime = time.time() # Location of file where the instrumentation logs will be written to -logfile = '/srv/buildingdepot/Instrumentation/instrument.csv' +logfile = "/srv/buildingdepot/Instrumentation/instrument.csv" # Toggle for enabling/disabling instrumentation enable_instrumentation = True @@ -39,17 +39,18 @@ # that only 70% of the logs are collected (uniformly randomly) and rest are dropped. sampling_rate = 1 - """A unique ID needs to be generated for every request so that all the calls made by it can be tracked sequentially. The unique ID is generated using a Cantor pairing function. """ + + def compute_request_id(): global process_unique_id pid = os.getpid() # Using Cantor pairing function val = process_unique_id + pid - request_id = process_unique_id + (((val)*(val + 1))/2) + request_id = process_unique_id + (((val) * (val + 1)) / 2) return str(request_id) @@ -75,25 +76,26 @@ def func_wrapper(*args, **kwargs): seq_dict[request_id] = seq + 1 # Time the function execution - start = (time.time() - beginningOfTime)*1000 + start = (time.time() - beginningOfTime) * 1000 ret = func(*args, **kwargs) - end = (time.time() - beginningOfTime)*1000 - + end = (time.time() - beginningOfTime) * 1000 + # Format the log string logString = request_id + "," + str(seq) + "," + func.__name__ + "," - for i in range(0,seq): + for i in range(0, seq): logString = "\t" + logString - logString = logString + "%0.2f,%0.2f,%0.2f\n" % (start, end, end-start) + logString = logString + "%0.2f,%0.2f,%0.2f\n" % (start, end, end - start) logs.append(logString) # Flush logs to file if seq == 0: logs = sorted(logs, key=lambda str: str.strip()) - with open(logfile, 'a') as file: + with open(logfile, "a") as file: for item in logs: file.write(item) del logs[:] return ret + return func_wrapper diff --git a/buildingdepot/Instrumentation/logSummarizer.py b/buildingdepot/Instrumentation/logSummarizer.py index 0e549e32..dd2ec06f 100644 --- a/buildingdepot/Instrumentation/logSummarizer.py +++ b/buildingdepot/Instrumentation/logSummarizer.py @@ -7,7 +7,7 @@ import sys filename = sys.argv[1] -averages = {} # Mapping between function and total time taken + elements seen +averages = {} # Mapping between function and total time taken + elements seen """ LOG FORMAT (comma separated): @@ -18,21 +18,28 @@ """ -f = open(filename, 'rb') +f = open(filename, "rb") reader = csv.reader(f) logs = list(reader) for log in logs: - if log[2] in averages.keys(): - averages[log[2]][0] += float(log[5]) - averages[log[2]][1] += 1 - else: - averages[log[2]] = [float(log[5]), 1] - -print "\nAVERAGES FOR TIME TAKEN (Function wise): " -for key, value in averages.iteritems(): - print " " + key + "(): " + str(value[0]/value[1]) + "ms (" + str(value[1]) + " calls)" - -print - - - + if log[2] in list(averages.keys()): + averages[log[2]][0] += float(log[5]) + averages[log[2]][1] += 1 + else: + averages[log[2]] = [float(log[5]), 1] + +print("\nAVERAGES FOR TIME TAKEN (Function wise): ") +for key, value in list(averages.items()): + print( + ( + " " + + key + + "(): " + + str(value[0] / value[1]) + + "ms (" + + str(value[1]) + + " calls)" + ) + ) + +print() diff --git a/buildingdepot/Instrumentation/uncomment.py b/buildingdepot/Instrumentation/uncomment.py index 3f882005..eae34367 100644 --- a/buildingdepot/Instrumentation/uncomment.py +++ b/buildingdepot/Instrumentation/uncomment.py @@ -7,10 +7,10 @@ filename = sys.argv[1] -f = open(filename, 'rb') +f = open(filename, "rb") for line in f: - if line == '# @instrument\n': - print "@instrument\n", - else: - print line, \ No newline at end of file + if line == "# @instrument\n": + print("@instrument\n", end=" ") + else: + print(line, end=" ") diff --git a/buildingdepot/pip_packages.list b/buildingdepot/pip_packages.list index 9256bc57..22821077 100755 --- a/buildingdepot/pip_packages.list +++ b/buildingdepot/pip_packages.list @@ -1,14 +1,20 @@ -Flask==0.10.1 -mongoengine==0.8.7 -flask-restful==0.3.1 -Flask-HTTPAuth==2.3.0 -flask-login==0.2.11 -validate_email==1.2 -requests==2.5.0 -Flask-Script==0.6.6 -Flask-WTF==0.9.5 -flask-bootstrap==3.3.0.1 -redis==2.10.3 -influxdb==2.6.0 -pymongo==2.8.0 -pika==0.10.0 +aniso8601==8.0.0 +email-validator==1.1.1 +Werkzeug==2.0.3 +Jinja2==3.0.3 +Flask-Bootstrap==3.3.7.1 +Flask-Login==0.5.0 +Flask-OAuthlib==0.9.6 +Flask-Script==2.0.6 +Flask-WTF==0.14.3 +influxdb==5.3.1 +jsonschema==3.2.0 +mongoengine==0.27.0 +pika==1.1.0 +redis==5.0.1 +Sphinx==3.0.4 +sphinx-theme==1.0 +sphinxcontrib-httpdomain==1.8.1 +wheel==0.42.0 +itsdangerous==2.0.1 +uWSGI==2.0.23 diff --git a/configs/certbot_post_hook.sh b/configs/certbot_post_hook.sh new file mode 100644 index 00000000..ea72e953 --- /dev/null +++ b/configs/certbot_post_hook.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +mkdir /etc/rabbitmq/ssl/ +cp -rL --remove-destination /etc/rabbitmq/ssl/fullchain.pem +cp -rL --remove-destination /etc/rabbitmq/ssl/cert.pem +cp -rL --remove-destination /etc/rabbitmq/ssl/privkey.pem +chmod -R 555 /etc/rabbitmq/ssl/ +service rabbitmq-server restart \ No newline at end of file diff --git a/configs/rabbitmq.conf b/configs/rabbitmq.conf new file mode 100644 index 00000000..861d80c8 --- /dev/null +++ b/configs/rabbitmq.conf @@ -0,0 +1,48 @@ +listeners.ssl.default = 5671 + +ssl_options.cacertfile = /etc/rabbitmq/ssl/fullchain.pem +ssl_options.certfile = /etc/rabbitmq/ssl/cert.pem +ssl_options.keyfile = /etc/rabbitmq/ssl/privkey.pem +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = true + +# Configuring the RabbitMQ STOMP Plugin to use SSL +stomp.ssl_cert_login = true +stomp.listeners.tcp = none +stomp.listeners.ssl.default = 61613 +stomp.tcp_listen_options.backlog = 4096 + +# Configuring the RabbitMQ web_STOMP Plugin to use SSL +web_stomp.ssl.port = 15675 +web_stomp.ssl.backlog = 1024 +web_stomp.ssl.cacertfile = /etc/rabbitmq/ssl/fullchain.pem +web_stomp.ssl.certfile = /etc/rabbitmq/ssl/cert.pem +web_stomp.ssl.keyfile = /etc/rabbitmq/ssl/privkey.pem + +# Configuring the RabbitMQ Management Plugin to use SSL +management.ssl.port = 15671 +management.ssl.cacertfile = /etc/rabbitmq/ssl/fullchain.pem +management.ssl.certfile = /etc/rabbitmq/ssl/cert.pem +management.ssl.keyfile = /etc/rabbitmq/ssl/privkey.pem + +# For RabbitMQ 3.7.10 and later versions +management.ssl.honor_cipher_order = true +management.ssl.honor_ecc_order = true +management.ssl.client_renegotiation = false +management.ssl.secure_renegotiate = true + +management.ssl.versions.1 = tlsv1.2 +management.ssl.versions.2 = tlsv1.1 + +management.ssl.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384 +management.ssl.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384 +management.ssl.ciphers.3 = ECDHE-ECDSA-AES256-SHA384 +management.ssl.ciphers.4 = ECDHE-RSA-AES256-SHA384 +management.ssl.ciphers.5 = ECDH-ECDSA-AES256-GCM-SHA384 +management.ssl.ciphers.6 = ECDH-RSA-AES256-GCM-SHA384 +management.ssl.ciphers.7 = ECDH-ECDSA-AES256-SHA384 +management.ssl.ciphers.8 = ECDH-RSA-AES256-SHA384 +management.ssl.ciphers.9 = DHE-RSA-AES256-GCM-SHA384 + +management.ssl.verify = verify_none +management.ssl.fail_if_no_peer_cert = false \ No newline at end of file diff --git a/configs/supervisor-ds.conf b/configs/supervisor-ds.conf index 5d89ef47..6eaa9a0b 100755 --- a/configs/supervisor-ds.conf +++ b/configs/supervisor-ds.conf @@ -1,5 +1,5 @@ [program:ds] -command= bash -c 'sleep 5 && /srv/buildingdepot/venv/bin/uwsgi /etc/uwsgi/apps-available/ds.ini' +command=/srv/buildingdepot/venv/bin/uwsgi /etc/uwsgi/apps-available/ds.ini directory=/srv/buildingdepot autostart=true diff --git a/configs/uwsgi_cs.ini b/configs/uwsgi_cs.ini index ed8e1bf2..bcf3267c 100755 --- a/configs/uwsgi_cs.ini +++ b/configs/uwsgi_cs.ini @@ -1,20 +1,20 @@ [uwsgi] -socket=/var/sockets/cs.sock -pyhome=/srv/buildingdepot/venv -pythonpath=/srv/buildingdepot -pythonpath=/srv/buildingdepot/CentralService -module=CentralService:app -workers=32 +socket = /var/sockets/cs.sock +pyhome = /srv/buildingdepot/venv +pythonpath = /srv/buildingdepot +pythonpath = /srv/buildingdepot/CentralService +module = CentralService:app +workers = 32 cheaper = 4 cheaper-step = 4 -master=true -chmod=666 -harakiri=30 -post-buffering=4096 +master = true +chmod = 666 +harakiri = 30 +post-buffering = 4096 -logto=/var/log/buildingdepot/CentralService/uwsgi-app.log -log-maxsize=1000000000 -disable-logging=false +logto = /var/log/buildingdepot/CentralService/uwsgi-app.log +log-maxsize = 1000000000 +disable-logging = false -env=CS_SETTINGS=/srv/buildingdepot/CentralService/cs_config -env=BD=/srv/buildingdepot/ +env = CS_SETTINGS=/srv/buildingdepot/CentralService/cs_config +env = BD=/srv/buildingdepot/ diff --git a/configs/uwsgi_ds.ini b/configs/uwsgi_ds.ini index 28033ce6..87e59ff9 100755 --- a/configs/uwsgi_ds.ini +++ b/configs/uwsgi_ds.ini @@ -1,20 +1,20 @@ [uwsgi] -socket=/var/sockets/ds.sock -pyhome=/srv/buildingdepot/venv -pythonpath=/srv/buildingdepot -pythonpath=/srv/buildingdepot/DataService -module=DataService:app -workers=32 +socket = /var/sockets/ds.sock +pyhome = /srv/buildingdepot/venv +pythonpath = /srv/buildingdepot +pythonpath = /srv/buildingdepot/DataService +module = DataService:app +workers = 32 cheaper = 4 cheaper-step = 4 -master=true -chmod=666 -harakiri=30 -post-buffering=4096 +master = true +chmod = 666 +harakiri = 30 +post-buffering = 4096 -logto=/var/log/buildingdepot/DataService/uwsgi-app.log -log-maxsize=1000000000 -disable-logging=false +logto = /var/log/buildingdepot/DataService/uwsgi-app.log +log-maxsize = 1000000000 +disable-logging = false -env=DS_SETTINGS=/srv/buildingdepot/DataService/ds_config -env=BD=/srv/buildingdepot/ +env = DS_SETTINGS=/srv/buildingdepot/DataService/ds_config +env = BD=/srv/buildingdepot/ diff --git a/install.sh b/install.sh index 603a448e..073d547a 100755 --- a/install.sh +++ b/install.sh @@ -8,8 +8,8 @@ DEPLOY_DS=true # Check and make sure we are running as root or sudo (?) ################################################################################ if [[ $UID -ne 0 ]]; then - echo -e "\n$0 must be run as root. Most functions require super-user priviledges!\n" - exit 1 + echo -e "\n$0 must be run as root. Most functions require super-user priviledges!\n" + exit 1 fi BD=/srv/buildingdepot/ @@ -22,426 +22,456 @@ mkdir -p /var/log/buildingdepot/CentralService mkdir -p /var/log/buildingdepot/DataService mkdir -p /var/sockets +function setup_venv() { + cp pip_packages.list $1 + cd $1 -function setup_venv { - cp pip_packages.list $1 - cd $1 + virtualenv ./venv + source venv/bin/activate - virtualenv ./venv - source venv/bin/activate + pip3 install --upgrade pip + pip3 install --upgrade setuptools + if [ $DISTRIB_CODENAME == "bionic" ]; then + pip3 install "Flask==2.0.3" + else + pip3 install "Flask==2.1.3" + fi + pip3 install --upgrade -r pip_packages.list + pip3 install "firebase-admin" - pip install --upgrade pip - pip install --upgrade setuptools - pip install --upgrade -r pip_packages.list + pip3 install --upgrade uWSGI + mkdir -p /etc/uwsgi/apps-available/ - pip install --upgrade uWSGI - mkdir -p /etc/uwsgi/apps-available/ - - deactivate - cd - + deactivate + cd - } # Deploy apps -function deploy_centralservice { - setup_venv /srv/buildingdepot/ - - #copy and untar new dataservice tarball - cp -r buildingdepot/CentralService /srv/buildingdepot/ - cp -r buildingdepot/DataService /srv/buildingdepot/ - cp -r buildingdepot/CentralReplica /srv/buildingdepot/ - cp -r buildingdepot/OAuth2Server /srv/buildingdepot/ - cp -r buildingdepot/Documentation /srv/buildingdepot/ - cd /srv/buildingdepot - # copy uwsgi files - cp configs/uwsgi_cs.ini /etc/uwsgi/apps-available/cs.ini - - # Create supervisor config - cp configs/supervisor-cs.conf /etc/supervisor/conf.d/ - - # Create supervisor config for central replica - cp configs/supervisor-replica.conf /etc/supervisor/conf.d/ - - # Create nginx config - rm -f /etc/nginx/sites-enabled/default +function deploy_centralservice() { + setup_venv /srv/buildingdepot/ + + #copy and untar new dataservice tarball + cp -r buildingdepot/CentralService /srv/buildingdepot/ + cp -r buildingdepot/DataService /srv/buildingdepot/ + cp -r buildingdepot/CentralReplica /srv/buildingdepot/ + cp -r buildingdepot/OAuth2Server /srv/buildingdepot/ + cp -r buildingdepot/Documentation /srv/buildingdepot/ + cd /srv/buildingdepot + # copy uwsgi files + cp configs/uwsgi_cs.ini /etc/uwsgi/apps-available/cs.ini + + # Create supervisor config + cp configs/supervisor-cs.conf /etc/supervisor/conf.d/ + + # Create supervisor config for central replica + cp configs/supervisor-replica.conf /etc/supervisor/conf.d/ + + # Create nginx config + rm -f /etc/nginx/sites-enabled/default } -function deploy_dataservice { - setup_venv /srv/buildingdepot/ +function deploy_dataservice() { + setup_venv /srv/buildingdepot/ - cd /srv/buildingdepot + cd /srv/buildingdepot - # copy uwsgi files - cp configs/uwsgi_ds.ini /etc/uwsgi/apps-available/ds.ini + # copy uwsgi files + cp configs/uwsgi_ds.ini /etc/uwsgi/apps-available/ds.ini - # Create supervisor config - cp configs/supervisor-ds.conf /etc/supervisor/conf.d/ + # Create supervisor config + cp configs/supervisor-ds.conf /etc/supervisor/conf.d/ - # Create nginx config - rm -f /etc/nginx/sites-enabled/default + # Create nginx config + rm -f /etc/nginx/sites-enabled/default } - -function joint_deployment_fix { - # Create join nginx config - rm -f /etc/nginx/sites-enabled/default - cd /srv/buildingdepot - #Setting up SSL - echo "Do you have a SSL certificate and key that you would like to use? Please enter [y/n]" - read response - #If user already has certificate - if [ "$response" == "Y" ] || [ "$response" == "y" ]; then - echo "Please enter the path to the certificate:" - read cert_path - sed -i "s||$cert_path|g" /srv/buildingdepot/configs/together_ssl.conf - echo "Please enter the path to the key:" - read key_path - sed -i "s||$key_path|g" /srv/buildingdepot/configs/together_ssl.conf - echo "Please enter the ip address or the domain name of this installation" - read domain - sed -i "s||$domain|g" /srv/buildingdepot/configs/together_ssl.conf - cp configs/together_ssl.conf /etc/nginx/sites-available/together.conf - else - cp configs/together.conf /etc/nginx/sites-available/together.conf +function joint_deployment_fix() { + # Create join nginx config + rm -f /etc/nginx/sites-enabled/default + cd /srv/buildingdepot + + #Setting up SSL + echo "Do you have a SSL certificate and key that you would like to use? Please enter [y/n]" + read response + + #If user already has certificate + if [ "$response" == "Y" ] || [ "$response" == "y" ]; then + echo "Please enter the path to the CA certificate (fullchain.pem):" + read ca_cert_path + sed -i "s||$ca_cert_path|g" /srv/buildingdepot/configs/together_ssl.conf + echo "Please enter the path to the server certificate (cert.pem):" + read cert_path + echo "Please enter the path to the server key (privkey.pem):" + read key_path + sed -i "s||$key_path|g" /srv/buildingdepot/configs/together_ssl.conf + echo "Please enter the ip address or the domain name of this installation" + read domain + sed -i "s||$domain|g" /srv/buildingdepot/configs/together_ssl.conf + cp configs/together_ssl.conf /etc/nginx/sites-available/together.conf + + #Setting up SSL for packages + echo "Would you like to use these SSL certificates for BD packages (RabbitMQ)? [y/n]" + read response_packages + if [ "$response_packages" == "Y" ] || [ "$response_packages" == "y" ]; then + mkdir -p /etc/rabbitmq/ssl + cp -rL --remove-destination "$ca_cert_path" /etc/rabbitmq/ssl/fullchain.pem + cp -rL --remove-destination "$cert_path" /etc/rabbitmq/ssl/cert.pem + cp -rL --remove-destination "$key_path" /etc/rabbitmq/ssl/privkey.pem + chmod -R 555 /etc/rabbitmq/ssl/ + cp configs/rabbitmq.conf /etc/rabbitmq/rabbitmq.conf + service rabbitmq-server restart + + #Setting up certbot post hook + echo "Have you installed certbot to automatically renew the SSL certificates? [y/n]" + read response_certbot + + if [ "$response_certbot" == "Y" ] || [ "$response_certbot" == "y" ]; then + sed -i "s||$ca_cert_path|g" /srv/buildingdepot/configs/certbot_post_hook.sh + sed -i "s||$cert_path|g" /srv/buildingdepot/configs/certbot_post_hook.sh + sed -i "s||$key_path|g" /srv/buildingdepot/configs/certbot_post_hook.sh + cp configs/certbot_post_hook.sh /etc/letsencrypt/renewal-hooks/deploy/ + chmod +x /etc/letsencrypt/renewal-hooks/deploy/certbot_post_hook.sh + fi fi - ln -sf /etc/nginx/sites-available/together.conf /etc/nginx/sites-enabled/together.conf + else + cp configs/together.conf /etc/nginx/sites-available/together.conf + fi + ln -sf /etc/nginx/sites-available/together.conf /etc/nginx/sites-enabled/together.conf } -function deploy_config { - cp -r configs/ /srv/buildingdepot - mkdir /var/sockets +function deploy_config() { + cp -r configs/ /srv/buildingdepot + mkdir /var/sockets } -function install_packages { - apt-get install -y curl - apt-get install -y apt-transport-https - source /etc/lsb-release - - # Add keys for Rabbitmq - curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.deb.sh | bash - # Adds Launchpad PPA that provides modern Erlang releases - sudo apt-key adv --keyserver "keyserver.ubuntu.com" --recv-keys "F77F1EDA57EBB1CC" - echo "deb http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu ${DISTRIB_CODENAME} main" | tee /etc/apt/sources.list.d/rabbitmq-erlang.list - echo "deb-src http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu ${DISTRIB_CODENAME} main" | tee -a /etc/apt/sources.list.d/rabbitmq-erlang.list - - # Add keys to install influxdb - curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add - - echo "deb https://repos.influxdata.com/${DISTRIB_ID,,} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list - - # Add keys to install mongodb - wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add - - if [ $DISTRIB_CODENAME == "focal" ]; then - echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list - elif [ $DISTRIB_CODENAME == "bionic" ]; then - echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list - elif [ $DISTRIB_CODENAME == "xenial" ]; then - echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list - elif [ $DISTRIB_CODENAME == "trusty" ]; then - wget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add - - echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/${DISTRIB_ID,,} ${DISTRIB_CODENAME}/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list - fi - - apt-get update -y - apt-get install - apt-get -y install python-pip +function install_packages() { + apt-get install -y curl + apt-get install -y apt-transport-https + apt-get install -y gnupg + source /etc/lsb-release + + # Add keys for Rabbitmq + curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.deb.sh | bash + # Adds Launchpad PPA that provides modern Erlang releases + curl -1sLf "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xf77f1eda57ebb1cc" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg > /dev/null + echo "deb [ signed-by=/usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg ] http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu ${DISTRIB_CODENAME} main" | tee /etc/apt/sources.list.d/rabbitmq-erlang.list + echo "deb-src [ signed-by=/usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg ] http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu ${DISTRIB_CODENAME} main" | tee -a /etc/apt/sources.list.d/rabbitmq-erlang.list + + # Add keys to install influxdb + curl -s https://repos.influxdata.com/influxdata-archive_compat.key > influxdata-archive_compat.key + echo '393e8779c89ac8d958f81f942f9ad7fb82a25e133faddaf92e15b16e6ac9ce4c influxdata-archive_compat.key' | sha256sum -c && cat influxdata-archive_compat.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg > /dev/null + echo 'deb [ signed-by=/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg ] https://repos.influxdata.com/debian stable main' | sudo tee /etc/apt/sources.list.d/influxdata.list + + # Add keys to install mongodb + if [ $DISTRIB_CODENAME == "bionic" ]; then + curl -fsSL https://pgp.mongodb.com/server-6.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-6.0.gpg --dearmor + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list + else + curl -fsSL https://pgp.mongodb.com/server-7.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list + fi + + apt-get update -y + apt-get install + apt-get -y install python3-pip + + if [ $DISTRIB_CODENAME == "bionic" ]; then + apt-get install -y mongodb-org=6.0.10 mongodb-org-database=6.0.10 mongodb-org-server=6.0.10 mongodb-org-mongos=6.0.10 mongodb-org-tools=6.0.10 + else + apt-get install -y mongodb-org + fi + + apt-get install -y openssl python3-setuptools python3-dev build-essential software-properties-common + apt-get install -y nginx + apt-get install -y supervisor + apt-get install -y redis-server + pip3 install --upgrade virtualenv + apt-get install -y wget + apt-get install -y influxdb + service influxdb start + systemctl start mongod + systemctl enable mongod.service + apt-get install -y erlang-base \ + erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \ + erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \ + erlang-runtime-tools erlang-snmp erlang-ssl \ + erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl + apt-get install -y rabbitmq-server --fix-missing + curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - + apt-get install -y nodejs + apt-get install -y npm + sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf + service postfix restart +} - if [ $DISTRIB_CODENAME == "trusty" ]; then - apt-get install -y mongodb-org=4.0.25 mongodb-org-server=4.0.25 mongodb-org-shell=4.0.25 mongodb-org-mongos=4.0.25 mongodb-org-tools=4.0.25 - else - apt-get install -y mongodb-org=4.4.6 mongodb-org-server=4.4.6 mongodb-org-shell=4.4.6 mongodb-org-mongos=4.4.6 mongodb-org-tools=4.4.6 - fi +function setup_gmail() { + echo "Please register an app in your Google API manager, generate an OAuth token and refresh token" + echo "For more details refer to this url: https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough" + echo "Please enter your Gmail id" + read email_id + echo "Please enter your client id" + read client_id + echo "Please enter your client secret" + read client_secret + echo "Please enter access token" + read access_token + echo "Please enter refresh token" + read refresh_token + echo "EMAIL = 'GMAIL'" >>$BD/CentralService/cs_config + echo "EMAIL_ID = '$email_id'" >>$BD/CentralService/cs_config + echo "ACCESS_TOKEN = '$access_token'" >>$BD/CentralService/cs_config + echo "REFRESH_TOKEN = '$refresh_token'" >>$BD/CentralService/cs_config + echo "CLIENT_ID = '$client_id'" >>$BD/CentralService/cs_config + echo "CLIENT_SECRET = '$client_secret'" >>$BD/CentralService/cs_config +} - apt-get install -y openssl python-setuptools python-dev build-essential software-properties-common - apt-get install -y nginx - apt-get install -y supervisor - apt-get install -y redis-server - pip install --upgrade virtualenv - apt-get install -y wget - apt-get install -y influxdb - service influxdb start - service mongod start - apt-get install -y erlang-base \ - erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \ - erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \ - erlang-runtime-tools erlang-snmp erlang-ssl \ - erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl - apt-get install -y rabbitmq-server - apt-get install -y nodejs - apt-get install -y npm +function setup_email() { + echo "BuildingDepot requires a Mail Transfer Agent. Would you like to install one or use your gmail account?" + echo "Note: If you use GMail, it is advised to create a new account for this purpose." + echo "Enter Y to install an MTA and N to use your GMail account." + read response + if [ "$response" == "Y" ] || [ "$response" == "y" ]; then + sudo apt-get install -y mailutils sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf service postfix restart - systemctl enable mongod.service -} - -function setup_gmail { - echo "Please register an app in your Google API manager, generate an OAuth token and refresh token" - echo "For more details refer to this url: https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough" - echo "Please enter your Gmail id" - read email_id - echo "Please enter your client id" - read client_id - echo "Please enter your client secret" - read client_secret - echo "Please enter access token" - read access_token - echo "Please enter refresh token" - read refresh_token - echo "EMAIL = 'GMAIL'" >> $BD/CentralService/cs_config - echo "EMAIL_ID = '$email_id'" >> $BD/CentralService/cs_config - echo "ACCESS_TOKEN = '$access_token'" >> $BD/CentralService/cs_config - echo "REFRESH_TOKEN = '$refresh_token'" >> $BD/CentralService/cs_config - echo "CLIENT_ID = '$client_id'" >> $BD/CentralService/cs_config - echo "CLIENT_SECRET = '$client_secret'" >> $BD/CentralService/cs_config + while true; do + echo "Please enter email address to send test mail:" + read email_id + n=$(od -An -N2 -i /dev/random) + echo $n | mail -s "Test email from BuildingDepot" $email_id + echo "Please enter the number you received in the mail" + read input + if [ $input == $n ]; then + echo "EMAIL = 'LOCAL'" >>$BD/CentralService/cs_config + echo "EMAIL_ID = 'admin@buildingdepot.org'" >>$BD/CentralService/cs_config + break + else + echo "Verification failed. Enter R to retry, Y to use GMail" + read response + if [ "$response" == "R" ] || [ "$response" == "r" ]; then + continue + elif [ "$response" == 'Y' ] || [ "$response" == 'y' ]; then + setup_gmail + break + else + echo "Invalid input! Exiting!" + exit + fi + fi + done + else + setup_gmail + fi } -function setup_email { - echo "BuildingDepot requires a Mail Transfer Agent. Would you like to install one or use your gmail account?" - echo "Note: If you use GMail, it is advised to create a new account for this purpose." - echo "Enter Y to install an MTA and N to use your GMail account." +function setup_notifications() { + echo "BuildingDepot uses notifications to alert users or systems of events in real-time. By default, " + echo "BuildingDepot uses RabbitMQ to deliver messages and we also supports Google Firebase Cloud Messaging (FCM), " + echo "which allows BuildingDepot to send push notifications to mobile users." + echo "Enter Y to select Google FCM and N to select RabbitMQ: " + read response + if [ "$response" == "Y" ] || [ "$response" == "y" ]; then + echo "Please provide the absolute path of where your Google Service Account JSON file is, which contains the keys for your FCM project." read response - if [ "$response" == "Y" ] || [ "$response" == "y" ]; then - sudo apt-get install -y mailutils - sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf - service postfix restart - while true; do - echo "Please enter email address to send test mail:" - read email_id - n=$(od -An -N2 -i /dev/random) - echo $n | mail -s "Test email from BuildingDepot" $email_id - echo "Please enter the number you received in the mail" - read input - if [ $input == $n ]; then - echo "EMAIL = 'LOCAL'" >> $BD/CentralService/cs_config - echo "EMAIL_ID = 'admin@buildingdepot.org'" >> $BD/CentralService/cs_config - break - else - echo "Verification failed. Enter R to retry, Y to use GMail" - read response - if [ "$response" == "R" ] || [ "$response" == "r" ]; then - continue - elif [ "$response" == 'Y' ] || [ "$response" == 'y' ]; then - setup_gmail - break - else - echo "Invalid input! Exiting!" - exit - fi - fi - done - else - setup_gmail + if [ ! -z "$response" ]; then + echo "NOTIFICATION_TYPE = 'FIREBASE'" >>$BD/CentralService/cs_config + echo "FIREBASE_CREDENTIALS = '$response'" >>$BD/CentralService/cs_config fi + elif [ "$response" == "N" ] || [ "$response" == "n" ]; then + echo "NOTIFICATION_TYPE = 'RabbitMQ'" >>$BD/CentralService/cs_config + fi } -function setup_notifications { - echo "BuildingDepot uses notifications to alert users or systems of events in real-time. By default, " - echo "BuildingDepot uses RabbitMQ to deliver messages and we also supports Google Firebase Cloud Messaging (FCM), " - echo "which allows BuildingDepot to send push notifications to mobile users." - echo "Enter Y to select Google FCM and N to select RabbitMQ: " +function setup_packages() { + + echo + echo "Securing BD Packages" + echo "--------------------" + echo "Enter Y to auto-generate credentials for packages (MongoDB,InfluxDB & Redis). Credentials are stored in cs_config file (or) Enter N to input individual package credentials" + read response + if [ "$response" == "Y" ] || [ "$response" == "y" ]; then + ## Add MongoDB Admin user + mongoUsername="user$(openssl rand -hex 16)" + mongoPassword=$(openssl rand -hex 32) + echo "MONGODB_USERNAME = '$mongoUsername'" >>$BD/CentralService/cs_config + echo "MONGODB_PWD = '$mongoPassword'" >>$BD/CentralService/cs_config + echo "MONGODB_USERNAME = '$mongoUsername'" >>$BD/DataService/ds_config + echo "MONGODB_PWD = '$mongoPassword'" >>$BD/DataService/ds_config + echo " MONGODB_USERNAME = '$mongoUsername'" >>$BD/CentralReplica/config.py + echo " MONGODB_PWD = '$mongoPassword'" >>$BD/CentralReplica/config.py + mongosh --eval "db.getSiblingDB('admin').createUser({user:'$mongoUsername',pwd:'$mongoPassword',roles:['userAdminAnyDatabase','dbAdminAnyDatabase','readWriteAnyDatabase']})" + # Enable MongoDB authorization + echo "security:" >>/etc/mongod.conf + echo " authorization: \"enabled\"" >>/etc/mongod.conf + service mongod restart + + sleep 2 + + ## Add InfluxDB Admin user + influxUsername="user$(openssl rand -hex 16)" + influxPassword=$(openssl rand -hex 32) + echo "INFLUXDB_USERNAME = '$influxUsername'" >>$BD/DataService/ds_config + echo "INFLUXDB_PWD = '$influxPassword'" >>$BD/DataService/ds_config + sleep 1 + curl -d "q=CREATE USER $influxUsername WITH PASSWORD '$influxPassword' WITH ALL PRIVILEGES" -X POST http://localhost:8086/query + sed -ir 's/# auth-enabled = false/auth-enabled = true/g' /etc/influxdb/influxdb.conf + service influxdb restart + + sleep 2 + + ## Add Redis Admin user + redisPassword=$(openssl rand -hex 64) + echo "REDIS_PWD = '$redisPassword'" >>$BD/CentralService/cs_config + echo "REDIS_PWD = '$redisPassword'" >>$BD/DataService/ds_config + echo " REDIS_PWD = '$redisPassword'" >>$BD/CentralReplica/config.py + sed -i -e '/#.* requirepass / s/.*/ requirepass '$redisPassword'/' /etc/redis/redis.conf + service redis restart + + sleep 2 + + ## Add RabbitMQ Admin user + rabbitmqUsername="user$(openssl rand -hex 16)" + rabbitmqPassword=$(openssl rand -hex 32) + rabbitmqUsername_endUser="user$(openssl rand -hex 16)" + rabbitmqPassword_endUser=$(openssl rand -hex 32) + echo "RABBITMQ_ADMIN_USERNAME = '$rabbitmqUsername'" >>$BD/DataService/ds_config + echo "RABBITMQ_ADMIN_PWD = '$rabbitmqPassword'" >>$BD/DataService/ds_config + echo "RABBITMQ_ENDUSER_USERNAME = '$rabbitmqUsername_endUser'" >>$BD/DataService/ds_config + echo "RABBITMQ_ENDUSER_PWD = '$rabbitmqPassword_endUser'" >>$BD/DataService/ds_config + # Create a Admin user. + rabbitmqctl add_user "$rabbitmqUsername" "$rabbitmqPassword" + # Add Administrative Rights + rabbitmqctl set_user_tags "$rabbitmqUsername" administrator + # Grant necessary permissions + rabbitmqctl set_permissions -p / "$rabbitmqUsername" ".*" ".*" ".*" + # Create a End User. + rabbitmqctl add_user "$rabbitmqUsername_endUser" "$rabbitmqPassword_endUser" + # Add Permissions + rabbitmqctl set_user_tags "$rabbitmqUsername_endUser" + # Grant necessary permissions + rabbitmqctl set_permissions -p / "$rabbitmqUsername_endUser" "" "" ".*" + + echo "BuildingDepot uses RabbitMQ Queues for Publishing and Subscribing to Sensor data. " + echo "Some web front-end use RabbitMQ Queues use rabbitmq_web_stomp plugin" + echo "Enter Y to install rabbitmq_web_stomp plugin: " read response if [ "$response" == "Y" ] || [ "$response" == "y" ]; then - pip install "firebase-admin==2.18.0" - echo "Please provide the absolute path of where your Google Service Account JSON file is, which contains the keys for your FCM project." - read response - if [ ! -z "$response" ]; then - echo "NOTIFICATION_TYPE = 'FIREBASE'" >> $BD/CentralService/cs_config - echo "FIREBASE_CREDENTIALS = '$response'" >> $BD/CentralService/cs_config - fi - elif [ "$response" == "N" ] || [ "$response" == "n" ]; then - echo "NOTIFICATION_TYPE = 'RabbitMQ'" >> $BD/CentralService/cs_config + rabbitmq-plugins enable rabbitmq_web_stomp fi -} -function setup_packages { + sleep 1 echo - echo "Securing BD Packages" - echo "--------------------" - echo "Enter Y to auto-generate credentials for packages (MongoDB,InfluxDB & Redis). Credentials are stored in cs_config file (or) Enter N to input individual package credentials" - read response + echo "Auto-Generated User Credentials for BuildingDepot Packages [MongoDB,InfluxDB & Redis]" + echo + elif [ "$response" == 'N' ] || [ "$response" == 'n' ]; then + ## Add MongoDB Admin user + echo "Enter MongoDB Username: " + read mongoUsername + echo "Enter MongoDB Password: " + read -s mongoPassword + echo "MONGODB_USERNAME = '$mongoUsername'" >>$BD/CentralService/cs_config + echo "MONGODB_PWD = '$mongoPassword'" >>$BD/CentralService/cs_config + echo "MONGODB_USERNAME = '$mongoUsername'" >>$BD/DataService/ds_config + echo "MONGODB_PWD = '$mongoPassword'" >>$BD/DataService/ds_config + echo " MONGODB_USERNAME = '$mongoUsername'" >>$BD/CentralReplica/config.py + echo " MONGODB_PWD = '$mongoPassword'" >>$BD/CentralReplica/config.py + mongosh --eval "db.getSiblingDB('admin').createUser({user:'$mongoUsername',pwd:'$mongoPassword',roles:['userAdminAnyDatabase','dbAdminAnyDatabase','readWriteAnyDatabase']})" + # Enable MongoDB authorization + echo "security:" >>/etc/mongod.conf + echo " authorization: \"enabled\"" >>/etc/mongod.conf + service mongod restart + + sleep 2 + + ## Add InfluxDB Admin user + echo + echo "Enter InfluxDB Username: " + read influxUsername + echo "Enter InfluxDB Password: " + read -s influxPassword + echo "INFLUXDB_USERNAME = '$influxUsername'" >>$BD/DataService/ds_config + echo "INFLUXDB_PWD = '$influxPassword'" >>$BD/DataService/ds_config + sleep 1 + curl -d "q=CREATE USER $influxUsername WITH PASSWORD '$influxPassword' WITH ALL PRIVILEGES" -X POST http://localhost:8086/query + sed -ir 's/# auth-enabled = false/auth-enabled = true/g' /etc/influxdb/influxdb.conf + service influxdb restart + + sleep 2 + + ## Add Redis Admin user + echo + echo "Enter Redis Username: " + read redisUsername + echo "Enter Redis Password: " + read -s redisPassword + echo "REDIS_PWD = '$redisPassword'" >>$BD/CentralService/cs_config + echo "REDIS_PWD = '$redisPassword'" >>$BD/DataService/ds_config + echo " REDIS_PWD = '$redisPassword'" >>$BD/CentralReplica/config.py + sed -i -e '/#.* requirepass / s/.*/ requirepass '$redisPassword'/' /etc/redis/redis.conf + service redis restart + + sleep 2 + + ## Add RabbitMQ Admin user + echo + echo "Enter RabbitMQ Admin Username: " + read rabbitmqUsername + echo "Enter RabbitMQ Admin Password: " + read -s rabbitmqPassword + echo "Enter RabbitMQ EndUser Username: " + read rabbitmqUsername_endUser + echo "Enter RabbitMQ EndUser Password: " + read -s rabbitmqPassword_endUser + + #Create a new user. + rabbitmqctl add_user "$rabbitmqUsername" "$rabbitmqPassword" + # Add Administrative Rights + rabbitmqctl set_user_tags "$rabbitmqUsername" administrator + # Grant necessary permissions + rabbitmqctl set_permissions -p / "$rabbitmqUsername" ".*" ".*" ".*" + # Create a End User. + rabbitmqctl add_user "$rabbitmqUsername_endUser" "$rabbitmqPassword_endUser" + # Add Permissions + rabbitmqctl set_user_tags "$rabbitmqUsername_endUser" + # Grant necessary permissions + rabbitmqctl set_permissions -p / "$rabbitmqUsername_endUser" "" "" ".*" + + echo "RABBITMQ_ADMIN_USERNAME = '$rabbitmqUsername'" >>$BD/DataService/ds_config + echo "RABBITMQ_ADMIN_PWD = '$rabbitmqPassword'" >>$BD/DataService/ds_config + echo "RABBITMQ_ENDUSER_USERNAME = '$rabbitmqUsername_endUser'" >>$BD/DataService/ds_config + echo "RABBITMQ_ENDUSER_PWD = '$rabbitmqPassword_endUser'" >>$BD/DataService/ds_config + + echo "BuildingDepot uses RabbitMQ Queues for Publishing and Subscribing to Sensor data. " + echo "Some web front-end use RabbitMQ Queues use rabbitmq_web_stomp plugin" + echo "Enter Y to install rabbitmq_web_stomp plugin: " + read response if [ "$response" == "Y" ] || [ "$response" == "y" ]; then - ## Add MongoDB Admin user - mongoUsername="user$(openssl rand -hex 16)" - mongoPassword=$(openssl rand -hex 32) - echo "MONGODB_USERNAME = '$mongoUsername'" >> $BD/CentralService/cs_config - echo "MONGODB_PWD = '$mongoPassword'" >> $BD/CentralService/cs_config - echo "MONGODB_USERNAME = '$mongoUsername'" >> $BD/DataService/ds_config - echo "MONGODB_PWD = '$mongoPassword'" >> $BD/DataService/ds_config - echo " MONGODB_USERNAME = '$mongoUsername'" >> $BD/CentralReplica/config.py - echo " MONGODB_PWD = '$mongoPassword'" >> $BD/CentralReplica/config.py - mongo --eval "db.getSiblingDB('admin').createUser({user:'$mongoUsername',pwd:'$mongoPassword',roles:['userAdminAnyDatabase','dbAdminAnyDatabase','readWriteAnyDatabase']})" - # Enable MongoDB authorization - echo "security:" >> /etc/mongod.conf - echo " authorization: \"enabled\"">> /etc/mongod.conf - service mongod restart - - sleep 2 - - ## Add InfluxDB Admin user - influxUsername="user$(openssl rand -hex 16)" - influxPassword=$(openssl rand -hex 32) - echo "INFLUXDB_USERNAME = '$influxUsername'">> $BD/DataService/ds_config - echo "INFLUXDB_PWD = '$influxPassword'">> $BD/DataService/ds_config - sleep 1 - curl -d "q=CREATE USER $influxUsername WITH PASSWORD '$influxPassword' WITH ALL PRIVILEGES" -X POST http://localhost:8086/query - sed -ir 's/# auth-enabled = false/auth-enabled = true/g' /etc/influxdb/influxdb.conf - service influxdb restart - - sleep 2 - - ## Add Redis Admin user - redisPassword=$(openssl rand -hex 64) - echo "REDIS_PWD = '$redisPassword'">> $BD/CentralService/cs_config - echo "REDIS_PWD = '$redisPassword'">> $BD/DataService/ds_config - echo " REDIS_PWD = '$redisPassword'" >> $BD/CentralReplica/config.py - sed -i -e '/#.* requirepass / s/.*/ requirepass '$redisPassword'/' /etc/redis/redis.conf - service redis restart - - sleep 2 - - ## Add RabbitMQ Admin user - rabbitmqUsername="user$(openssl rand -hex 16)" - rabbitmqPassword=$(openssl rand -hex 32) - rabbitmqUsername_endUser="user$(openssl rand -hex 16)" - rabbitmqPassword_endUser=$(openssl rand -hex 32) - echo "RABBITMQ_ADMIN_USERNAME = '$rabbitmqUsername'">> $BD/DataService/ds_config - echo "RABBITMQ_ADMIN_PWD = '$rabbitmqPassword'">> $BD/DataService/ds_config - echo "RABBITMQ_ENDUSER_USERNAME = '$rabbitmqUsername_endUser'">> $BD/DataService/ds_config - echo "RABBITMQ_ENDUSER_PWD = '$rabbitmqPassword_endUser'">> $BD/DataService/ds_config - # Create a Admin user. - rabbitmqctl add_user "$rabbitmqUsername" "$rabbitmqPassword" - # Add Administrative Rights - rabbitmqctl set_user_tags "$rabbitmqUsername" administrator - # Grant necessary permissions - rabbitmqctl set_permissions -p / "$rabbitmqUsername" ".*" ".*" ".*" - # Create a End User. - rabbitmqctl add_user "$rabbitmqUsername_endUser" "$rabbitmqPassword_endUser" - # Add Permissions - rabbitmqctl set_user_tags "$rabbitmqUsername_endUser" - # Grant necessary permissions - rabbitmqctl set_permissions -p / "$rabbitmqUsername_endUser" "" "" ".*" - - echo "BuildingDepot uses RabbitMQ Queues for Publishing and Subscribing to Sensor data. " - echo "Some web front-end use RabbitMQ Queues use rabbitmq_web_stomp plugin" - echo "Enter Y to install rabbitmq_web_stomp plugin: " - read response - if [ "$response" == "Y" ] || [ "$response" == "y" ]; then - rabbitmq-plugins enable rabbitmq_web_stomp - fi - - sleep 1 - - echo - echo "Auto-Generated User Credentials for BuildingDepot Packages [MongoDB,InfluxDB & Redis]" - echo - - elif [ "$response" == 'N' ] || [ "$response" == 'n' ]; then - ## Add MongoDB Admin user - echo "Enter MongoDB Username: " - read mongoUsername - echo "Enter MongoDB Password: " - read -s mongoPassword - echo "MONGODB_USERNAME = '$mongoUsername'" >> $BD/CentralService/cs_config - echo "MONGODB_PWD = '$mongoPassword'" >> $BD/CentralService/cs_config - echo "MONGODB_USERNAME = '$mongoUsername'" >> $BD/DataService/ds_config - echo "MONGODB_PWD = '$mongoPassword'" >> $BD/DataService/ds_config - echo " MONGODB_USERNAME = '$mongoUsername'" >> $BD/CentralReplica/config.py - echo " MONGODB_PWD = '$mongoPassword'" >> $BD/CentralReplica/config.py - mongo --eval "db.getSiblingDB('admin').createUser({user:'$mongoUsername',pwd:'$mongoPassword',roles:['userAdminAnyDatabase','dbAdminAnyDatabase','readWriteAnyDatabase']})" - # Enable MongoDB authorization - echo "security:" >> /etc/mongod.conf - echo " authorization: \"enabled\"">> /etc/mongod.conf - service mongod restart - - sleep 2 - - ## Add InfluxDB Admin user - echo - echo "Enter InfluxDB Username: " - read influxUsername - echo "Enter InfluxDB Password: " - read -s influxPassword - echo "INFLUXDB_USERNAME = '$influxUsername'">> $BD/DataService/ds_config - echo "INFLUXDB_PWD = '$influxPassword'">> $BD/DataService/ds_config - sleep 1 - curl -d "q=CREATE USER $influxUsername WITH PASSWORD '$influxPassword' WITH ALL PRIVILEGES" -X POST http://localhost:8086/query - sed -ir 's/# auth-enabled = false/auth-enabled = true/g' /etc/influxdb/influxdb.conf - service influxdb restart - - sleep 2 - - ## Add Redis Admin user - echo - echo "Enter Redis Username: " - read redisUsername - echo "Enter Redis Password: " - read -s redisPassword - echo "REDIS_PWD = '$redisPassword'">> $BD/CentralService/cs_config - echo "REDIS_PWD = '$redisPassword'">> $BD/DataService/ds_config - echo " REDIS_PWD = '$redisPassword'" >> $BD/CentralReplica/config.py - sed -i -e '/#.* requirepass / s/.*/ requirepass '$redisPassword'/' /etc/redis/redis.conf - service redis restart - - sleep 2 - - ## Add RabbitMQ Admin user - echo - echo "Enter RabbitMQ Admin Username: " - read rabbitmqUsername - echo "Enter RabbitMQ Admin Password: " - read -s rabbitmqPassword - echo "Enter RabbitMQ EndUser Username: " - read rabbitmqUsername_endUser - echo "Enter RabbitMQ EndUser Password: " - read -s rabbitmqPassword_endUser - - #Create a new user. - rabbitmqctl add_user "$rabbitmqUsername" "$rabbitmqPassword" - # Add Administrative Rights - rabbitmqctl set_user_tags "$rabbitmqUsername" administrator - # Grant necessary permissions - rabbitmqctl set_permissions -p / "$rabbitmqUsername" ".*" ".*" ".*" - # Create a End User. - rabbitmqctl add_user "$rabbitmqUsername_endUser" "$rabbitmqPassword_endUser" - # Add Permissions - rabbitmqctl set_user_tags "$rabbitmqUsername_endUser" - # Grant necessary permissions - rabbitmqctl set_permissions -p / "$rabbitmqUsername_endUser" "" "" ".*" - - echo "RABBITMQ_ADMIN_USERNAME = '$rabbitmqUsername'">> $BD/DataService/ds_config - echo "RABBITMQ_ADMIN_PWD = '$rabbitmqPassword'">> $BD/DataService/ds_config - echo "RABBITMQ_ENDUSER_USERNAME = '$rabbitmqUsername_endUser'">> $BD/DataService/ds_config - echo "RABBITMQ_ENDUSER_PWD = '$rabbitmqPassword_endUser'">> $BD/DataService/ds_config - - echo "BuildingDepot uses RabbitMQ Queues for Publishing and Subscribing to Sensor data. " - echo "Some web front-end use RabbitMQ Queues use rabbitmq_web_stomp plugin" - echo "Enter Y to install rabbitmq_web_stomp plugin: " - read response - if [ "$response" == "Y" ] || [ "$response" == "y" ]; then - rabbitmq-plugins enable rabbitmq_web_stomp - fi + rabbitmq-plugins enable rabbitmq_web_stomp + fi - sleep 1 + sleep 1 - echo - echo "Saved User Credentials for BuildingDepot Packages" - echo + echo + echo "Saved User Credentials for BuildingDepot Packages" + echo - else - echo - echo "Invalid option please Try again." - setup_packages - fi + else + echo + echo "Invalid option please Try again." + setup_packages + fi } deploy_config install_packages if [ "$DEPLOY_CS" = true ]; then - deploy_centralservice + deploy_centralservice fi if [ "$DEPLOY_DS" = true ]; then - deploy_dataservice + deploy_dataservice fi service mongod start @@ -451,10 +481,9 @@ sleep 5 supervisorctl restart all service influxdb start - if [ "$DEPLOY_TOGETHER" = true ]; then - joint_deployment_fix - service nginx restart + joint_deployment_fix + service nginx restart fi rm -rf configs @@ -463,11 +492,10 @@ popd setup_email setup_notifications - # Create Database on InfluxDB curl -d "q=CREATE DATABASE buildingdepot" -X POST http://localhost:8086/query setup_packages -/srv/buildingdepot/venv/bin/python2.7 setup_bd.py "install" +/srv/buildingdepot/venv/bin/python setup_bd.py "install" # echo -e "\nInstallation Finished..\n" supervisorctl restart all diff --git a/license.txt b/license.txt index 6a24a904..cf76ad96 100644 --- a/license.txt +++ b/license.txt @@ -1,5 +1,5 @@ Copyright (c) 2015-2016, Carnegie Mellon University - & + & Copyright (c) 2013-2016, The Regents of the University of California All rights reserved. diff --git a/pip_packages.list b/pip_packages.list index 1a7e22f8..22821077 100644 --- a/pip_packages.list +++ b/pip_packages.list @@ -1,38 +1,20 @@ -Babel==2.8.0 -Flask-Bootstrap==3.3.0.1 -Flask-HTTPAuth==2.3.0 -Flask-Login==0.2.11 -Flask-OAuthlib==0.9.1 -Flask-RESTful==0.3.1 -Flask-Script==0.6.6 -Flask-WTF==0.9.5 -Jinja2==2.11.0 -MarkupSafe==1.1.1 -PyLD==0.7.1 -Pygments==2.5.2 -Sphinx==1.7.5 -WTForms==1.0.5 -Werkzeug==0.16.1 -imagesize==1.2.0 -influxdb==2.12.0 -itsdangerous==1.1.0 -mongoengine==0.14.3 -oauthlib==2.1.0 -packaging==20.1 -pika==0.10.0 -pymongo==3.5.0 -pyparsing==2.4.6 -python-dateutil==2.8.1 -pytz==2019.3 -redis==2.10.3 -requests-oauthlib==1.1.0 -requests==2.5.0 -six==1.14.0 -snowballstemmer==2.0.0 +aniso8601==8.0.0 +email-validator==1.1.1 +Werkzeug==2.0.3 +Jinja2==3.0.3 +Flask-Bootstrap==3.3.7.1 +Flask-Login==0.5.0 +Flask-OAuthlib==0.9.6 +Flask-Script==2.0.6 +Flask-WTF==0.14.3 +influxdb==5.3.1 +jsonschema==3.2.0 +mongoengine==0.27.0 +pika==1.1.0 +redis==5.0.1 +Sphinx==3.0.4 sphinx-theme==1.0 -sphinxcontrib-httpdomain==1.7.0 -sphinxcontrib-websupport==1.1.2 -typing==3.7.4.1 -uWSGI==2.0.18 -validate-email==1.2 -firebase-admin==2.18.0 +sphinxcontrib-httpdomain==1.8.1 +wheel==0.42.0 +itsdangerous==2.0.1 +uWSGI==2.0.23 diff --git a/script_for_github_actions.sh b/script_for_github_actions.sh index c3f28823..0b1715c6 100755 --- a/script_for_github_actions.sh +++ b/script_for_github_actions.sh @@ -5,11 +5,11 @@ DEPLOY_CS=true DEPLOY_DS=true ################################################################################ -# Check and make sure we are running as root or sudo (?) +# Check and make sure we are running as root or (?) ################################################################################ if [[ $UID -ne 0 ]]; then - echo -e "\n$0 must be run as root. Most functions require super-user priviledges!\n" - exit 1 + echo -e "\n$0 must be run as root. Most functions require super-user priviledges!\n" + exit 1 fi BD=/srv/buildingdepot/ @@ -22,261 +22,262 @@ mkdir -p /var/log/buildingdepot/CentralService mkdir -p /var/log/buildingdepot/DataService mkdir -p /var/sockets - -function setup_venv { - cp pip_packages.list $1 - cd $1 - - virtualenv ./venv - source venv/bin/activate - - pip install --upgrade pip - pip install --upgrade setuptools - pip install --upgrade -r pip_packages.list - pip install "firebase-admin==2.18.0" - - pip install --upgrade uWSGI - mkdir -p /etc/uwsgi/apps-available/ - - deactivate - cd - +function setup_venv() { + cp pip_packages.list $1 + cd $1 + + virtualenv ./venv + source venv/bin/activate + + pip3 install --upgrade pip + pip3 install --upgrade setuptools + if [ $DISTRIB_CODENAME == "bionic" ]; then + pip3 install "Flask==2.0.3" + else + pip3 install "Flask==2.1.3" + fi + pip3 install --upgrade -r pip_packages.list + pip3 install "firebase-admin" + pip3 install --upgrade uWSGI + mkdir -p /etc/uwsgi/apps-available/ + + deactivate + cd - } # Deploy apps -function deploy_centralservice { - setup_venv /srv/buildingdepot/ - - #copy and untar new dataservice tarball - cp -r buildingdepot/CentralService /srv/buildingdepot/ - cp -r buildingdepot/DataService /srv/buildingdepot/ - cp -r buildingdepot/CentralReplica /srv/buildingdepot/ - cp -r buildingdepot/Documentation /srv/buildingdepot/ - cd /srv/buildingdepot - # copy uwsgi files - cp configs/uwsgi_cs.ini /etc/uwsgi/apps-available/cs.ini - - # Create supervisor config - cp configs/supervisor-cs.conf /etc/supervisor/conf.d/ - - # Create supervisor config for central replica - cp configs/supervisor-replica.conf /etc/supervisor/conf.d/ - - # Create nginx config - rm -f /etc/nginx/sites-enabled/default +function deploy_centralservice() { + setup_venv /srv/buildingdepot/ + + #copy and untar new dataservice tarball + cp -r buildingdepot/CentralService /srv/buildingdepot/ + cp -r buildingdepot/DataService /srv/buildingdepot/ + cp -r buildingdepot/CentralReplica /srv/buildingdepot/ + cp -r buildingdepot/Documentation /srv/buildingdepot/ + cd /srv/buildingdepot + # copy uwsgi files + cp configs/uwsgi_cs.ini /etc/uwsgi/apps-available/cs.ini + + # Create supervisor config + cp configs/supervisor-cs.conf /etc/supervisor/conf.d/ + + # Create supervisor config for central replica + cp configs/supervisor-replica.conf /etc/supervisor/conf.d/ + + # Create nginx config + rm -f /etc/nginx/sites-enabled/default } -function deploy_dataservice { - setup_venv /srv/buildingdepot/ +function deploy_dataservice() { + setup_venv /srv/buildingdepot/ - cd /srv/buildingdepot + cd /srv/buildingdepot - # copy uwsgi files - cp configs/uwsgi_ds.ini /etc/uwsgi/apps-available/ds.ini + # copy uwsgi files + cp configs/uwsgi_ds.ini /etc/uwsgi/apps-available/ds.ini - # Create supervisor config - cp configs/supervisor-ds.conf /etc/supervisor/conf.d/ + # Create supervisor config + cp configs/supervisor-ds.conf /etc/supervisor/conf.d/ - # Create nginx config - rm -f /etc/nginx/sites-enabled/default + # Create nginx config + rm -f /etc/nginx/sites-enabled/default } - -function joint_deployment_fix { - # Create join nginx config - rm -f /etc/nginx/sites-enabled/default - cd /srv/buildingdepot - #Setting up SSL - echo "Skipping SSL configuration..." - #If user already has certificate - cp configs/together.conf /etc/nginx/sites-available/together.conf - ln -sf /etc/nginx/sites-available/together.conf /etc/nginx/sites-enabled/together.conf +function joint_deployment_fix() { + # Create join nginx config + rm -f /etc/nginx/sites-enabled/default + cd /srv/buildingdepot + #Setting up SSL + echo "Skipping SSL configuration..." + #If user already has certificate + cp configs/together.conf /etc/nginx/sites-available/together.conf + ln -sf /etc/nginx/sites-available/together.conf /etc/nginx/sites-enabled/together.conf } -function deploy_config { - cp -r configs/ /srv/buildingdepot - mkdir /var/sockets +function deploy_config() { + cp -r configs/ /srv/buildingdepot + mkdir /var/sockets } -function install_packages { - apt-get install -y curl - apt-get install -y apt-transport-https - apt install curl gnupg -y - source /etc/lsb-release - - # Add keys for Rabbitmq - curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.deb.sh | bash - # Adds Launchpad PPA that provides modern Erlang releases - sudo apt-key adv --keyserver "keyserver.ubuntu.com" --recv-keys "F77F1EDA57EBB1CC" - echo "deb http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu ${DISTRIB_CODENAME} main" | tee /etc/apt/sources.list.d/rabbitmq-erlang.list - echo "deb-src http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu ${DISTRIB_CODENAME} main" | tee -a /etc/apt/sources.list.d/rabbitmq-erlang.list - - # Add keys to install influxdb - curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add - - echo "deb https://repos.influxdata.com/${DISTRIB_ID,,} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list - - # Add keys to install mongodb - wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add - - if [ $DISTRIB_CODENAME == "focal" ]; then - echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list - elif [ $DISTRIB_CODENAME == "bionic" ]; then - echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list - elif [ $DISTRIB_CODENAME == "xenial" ]; then - echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list - elif [ $DISTRIB_CODENAME == "trusty" ]; then - wget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add - - echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/${DISTRIB_ID,,} ${DISTRIB_CODENAME}/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list - fi - - apt-get update -y - apt-get install - apt-get install -y python-pip - - if [ $DISTRIB_CODENAME == "trusty" ]; then - apt-get install -y mongodb-org=4.0.25 mongodb-org-server=4.0.25 mongodb-org-shell=4.0.25 mongodb-org-mongos=4.0.25 mongodb-org-tools=4.0.25 - else - apt-get install -y mongodb-org=4.4.6 mongodb-org-server=4.4.6 mongodb-org-shell=4.4.6 mongodb-org-mongos=4.4.6 mongodb-org-tools=4.4.6 - fi - - apt-get install -y openssl python-setuptools python-dev build-essential software-properties-common - apt-get install -y nginx - apt-get install -y supervisor - apt-get install -y redis-server - pip install --upgrade virtualenv - apt-get install -y wget - apt-get install -y influxdb - service influxdb start - service mongod start - apt-get install -y erlang-base \ - erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \ - erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \ - erlang-runtime-tools erlang-snmp erlang-ssl \ - erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl - apt-get install -y rabbitmq-server --fix-missing - DEBIAN_FRONTEND=noninteractive apt-get install -y postfix - apt-get install -y nodejs - apt-get install -y npm - sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf - service postfix restart - systemctl enable mongod.service +function install_packages() { + apt-get install -y curl + apt-get install -y apt-transport-https + apt-get install -y gnupg + source /etc/lsb-release + + # Add keys for Rabbitmq + curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.deb.sh | bash + # Adds Launchpad PPA that provides modern Erlang releases + curl -1sLf "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xf77f1eda57ebb1cc" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg > /dev/null + echo "deb [ signed-by=/usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg ] http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu ${DISTRIB_CODENAME} main" | tee /etc/apt/sources.list.d/rabbitmq-erlang.list + echo "deb-src [ signed-by=/usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg ] http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu ${DISTRIB_CODENAME} main" | tee -a /etc/apt/sources.list.d/rabbitmq-erlang.list + + # Add keys to install influxdb + curl -s https://repos.influxdata.com/influxdata-archive_compat.key > influxdata-archive_compat.key + echo '393e8779c89ac8d958f81f942f9ad7fb82a25e133faddaf92e15b16e6ac9ce4c influxdata-archive_compat.key' | sha256sum -c && cat influxdata-archive_compat.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg > /dev/null + echo 'deb [ signed-by=/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg ] https://repos.influxdata.com/debian stable main' | sudo tee /etc/apt/sources.list.d/influxdata.list + + # Add keys to install mongodb + if [ $DISTRIB_CODENAME == "bionic" ]; then + curl -fsSL https://pgp.mongodb.com/server-6.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-6.0.gpg --dearmor + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list + else + curl -fsSL https://pgp.mongodb.com/server-7.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu ${DISTRIB_CODENAME}/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list + fi + + apt-get update -y + apt-get install + apt-get install -y python3-pip + + if [ $DISTRIB_CODENAME == "bionic" ]; then + apt-get install -y mongodb-org=6.0.10 mongodb-org-database=6.0.10 mongodb-org-server=6.0.10 mongodb-org-mongos=6.0.10 mongodb-org-tools=6.0.10 + else + apt-get install -y mongodb-org + fi + + apt-get install -y openssl python3-setuptools python3-dev build-essential software-properties-common + apt-get install -y nginx + apt-get install -y supervisor + apt-get install -y redis-server + pip3 install --upgrade virtualenv + apt-get install -y wget + apt-get install -y influxdb + service influxdb start + systemctl start mongod + systemctl enable mongod.service + apt-get install -y erlang-base \ + erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \ + erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \ + erlang-runtime-tools erlang-snmp erlang-ssl \ + erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl + apt-get install -y rabbitmq-server --fix-missing + DEBIAN_FRONTEND=noninteractive apt-get install -y postfix + curl -fsSL https://deb.nodesource.com/setup_lts.x | -E bash - + apt-get install -y nodejs + apt-get install -y npm + sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf + service postfix restart } -function setup_gmail { - echo "Please register an app in your Google API manager, generate an OAuth token and refresh token" - echo "For more details refer to this url: https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough" - echo "Please enter your Gmail id" - read email_id - echo "Please enter your client id" - read client_id - echo "Please enter your client secret" - read client_secret - echo "Please enter access token" - read access_token - echo "Please enter refresh token" - read refresh_token - echo "EMAIL = 'GMAIL'" >> $BD/CentralService/cs_config - echo "EMAIL_ID = '$email_id'" >> $BD/CentralService/cs_config - echo "ACCESS_TOKEN = '$access_token'" >> $BD/CentralService/cs_config - echo "REFRESH_TOKEN = '$refresh_token'" >> $BD/CentralService/cs_config - echo "CLIENT_ID = '$client_id'" >> $BD/CentralService/cs_config - echo "CLIENT_SECRET = '$client_secret'" >> $BD/CentralService/cs_config +function setup_gmail() { + echo "Please register an app in your Google API manager, generate an OAuth token and refresh token" + echo "For more details refer to this url: https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough" + echo "Please enter your Gmail id" + read email_id + echo "Please enter your client id" + read client_id + echo "Please enter your client secret" + read client_secret + echo "Please enter access token" + read access_token + echo "Please enter refresh token" + read refresh_token + echo "EMAIL = 'GMAIL'" >>$BD/CentralService/cs_config + echo "EMAIL_ID = '$email_id'" >>$BD/CentralService/cs_config + echo "ACCESS_TOKEN = '$access_token'" >>$BD/CentralService/cs_config + echo "REFRESH_TOKEN = '$refresh_token'" >>$BD/CentralService/cs_config + echo "CLIENT_ID = '$client_id'" >>$BD/CentralService/cs_config + echo "CLIENT_SECRET = '$client_secret'" >>$BD/CentralService/cs_config } -function setup_email { - echo "BuildingDepot requires a Mail Transfer Agent. Would you like to install one or use your gmail account?" - echo "Note: If you use GMail, it is advised to create a new account for this purpose." - echo "Installing an MTA..." - sudo apt-get install -y mailutils - sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf - service postfix restart - echo "EMAIL = 'LOCAL'" >> $BD/CentralService/cs_config - echo "EMAIL_ID = 'admin@buildingdepot.org'" >> $BD/CentralService/cs_config +function setup_email() { + echo "BuildingDepot requires a Mail Transfer Agent. Would you like to install one or use your gmail account?" + echo "Note: If you use GMail, it is advised to create a new account for this purpose." + echo "Installing an MTA..." + apt-get install -y mailutils + sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf + service postfix restart + echo "EMAIL = 'LOCAL'" >>$BD/CentralService/cs_config + echo "EMAIL_ID = 'admin@buildingdepot.org'" >>$BD/CentralService/cs_config } -function setup_packages { - echo - echo "Securing BD Packages" - echo "--------------------" - echo "Auto-generating credentials for packages (MongoDB,InfluxDB & Redis)..." - ## Add MongoDB Admin user - mongoUsername="user$(openssl rand -hex 16)" - mongoPassword=$(openssl rand -hex 32) - echo "MONGODB_USERNAME = '$mongoUsername'" >> $BD/CentralService/cs_config - echo "MONGODB_PWD = '$mongoPassword'" >> $BD/CentralService/cs_config - echo "MONGODB_USERNAME = '$mongoUsername'" >> $BD/DataService/ds_config - echo "MONGODB_PWD = '$mongoPassword'" >> $BD/DataService/ds_config - echo " MONGODB_USERNAME = '$mongoUsername'" >> $BD/CentralReplica/config.py - echo " MONGODB_PWD = '$mongoPassword'" >> $BD/CentralReplica/config.py - mongo --eval "db.getSiblingDB('admin').createUser({user:'$mongoUsername',pwd:'$mongoPassword',roles:['userAdminAnyDatabase','dbAdminAnyDatabase','readWriteAnyDatabase']})" - # Enable MongoDB authorization - echo "security:" >> /etc/mongod.conf - echo " authorization: \"enabled\"">> /etc/mongod.conf - service mongod restart - - sleep 2 - - ## Add InfluxDB Admin user - influxUsername="user$(openssl rand -hex 16)" - influxPassword=$(openssl rand -hex 32) - echo "INFLUXDB_USERNAME = '$influxUsername'">> $BD/DataService/ds_config - echo "INFLUXDB_PWD = '$influxPassword'">> $BD/DataService/ds_config - sleep 1 - curl -d "q=CREATE USER $influxUsername WITH PASSWORD '$influxPassword' WITH ALL PRIVILEGES" -X POST http://localhost:8086/query - sed -ir 's/# auth-enabled = false/auth-enabled = true/g' /etc/influxdb/influxdb.conf - service influxdb restart - - sleep 2 - - ## Add Redis Admin user - redisPassword=$(openssl rand -hex 64) - echo "REDIS_PWD = '$redisPassword'">> $BD/CentralService/cs_config - echo "REDIS_PWD = '$redisPassword'">> $BD/DataService/ds_config - echo " REDIS_PWD = '$redisPassword'" >> $BD/CentralReplica/config.py - sed -i -e '/#.* requirepass / s/.*/ requirepass '$redisPassword'/' /etc/redis/redis.conf - service redis restart - - sleep 2 - - ## Add RabbitMQ Admin user - rabbitmqUsername="user$(openssl rand -hex 16)" - rabbitmqPassword=$(openssl rand -hex 32) - rabbitmqUsername_endUser="user$(openssl rand -hex 16)" - rabbitmqPassword_endUser=$(openssl rand -hex 32) - echo "RABBITMQ_ADMIN_USERNAME = '$rabbitmqUsername'">> $BD/DataService/ds_config - echo "RABBITMQ_ADMIN_PWD = '$rabbitmqPassword'">> $BD/DataService/ds_config - echo "RABBITMQ_ENDUSER_USERNAME = '$rabbitmqUsername_endUser'">> $BD/DataService/ds_config - echo "RABBITMQ_ENDUSER_PWD = '$rabbitmqPassword_endUser'">> $BD/DataService/ds_config - # Create a Admin user. - rabbitmqctl add_user "$rabbitmqUsername" "$rabbitmqPassword" - # Add Administrative Rights - rabbitmqctl set_user_tags "$rabbitmqUsername" administrator - # Grant necessary permissions - rabbitmqctl set_permissions -p / "$rabbitmqUsername" ".*" ".*" ".*" - # Create a End User. - rabbitmqctl add_user "$rabbitmqUsername_endUser" "$rabbitmqPassword_endUser" - # Add Permissions - rabbitmqctl set_user_tags "$rabbitmqUsername_endUser" - # Grant necessary permissions - rabbitmqctl set_permissions -p / "$rabbitmqUsername_endUser" "" "" ".*" - echo "BuildingDepot uses RabbitMQ Queues for Publishing and Subscribing to Sensor data. " - echo "Some web front-end use RabbitMQ Queues use rabbitmq_web_stomp plugin" - echo "Enter Y to install rabbitmq_web_stomp plugin: " - rabbitmq-plugins enable rabbitmq_web_stomp - - sleep 1 - - - echo - echo "Auto-Generated User Credentials for BuildingDepot Packages [MongoDB,InfluxDB & Redis]" - echo +function setup_packages() { + echo + echo "Securing BD Packages" + echo "--------------------" + echo "Auto-generating credentials for packages (MongoDB,InfluxDB & Redis)..." + ## Add MongoDB Admin user + mongoUsername="user$(openssl rand -hex 16)" + mongoPassword=$(openssl rand -hex 32) + echo "MONGODB_USERNAME = '$mongoUsername'" >>$BD/CentralService/cs_config + echo "MONGODB_PWD = '$mongoPassword'" >>$BD/CentralService/cs_config + echo "MONGODB_USERNAME = '$mongoUsername'" >>$BD/DataService/ds_config + echo "MONGODB_PWD = '$mongoPassword'" >>$BD/DataService/ds_config + echo " MONGODB_USERNAME = '$mongoUsername'" >>$BD/CentralReplica/config.py + echo " MONGODB_PWD = '$mongoPassword'" >>$BD/CentralReplica/config.py + + mongosh --eval "db.getSiblingDB('admin').createUser({user:'$mongoUsername',pwd:'$mongoPassword',roles:['userAdminAnyDatabase','dbAdminAnyDatabase','readWriteAnyDatabase']})" + + # Enable MongoDB authorization + echo "security:" >>/etc/mongod.conf + echo " authorization: \"enabled\"" >>/etc/mongod.conf + service mongod restart + + sleep 2 + + ## Add InfluxDB Admin user + influxUsername="user$(openssl rand -hex 16)" + influxPassword=$(openssl rand -hex 32) + echo "INFLUXDB_USERNAME = '$influxUsername'" >>$BD/DataService/ds_config + echo "INFLUXDB_PWD = '$influxPassword'" >>$BD/DataService/ds_config + sleep 1 + curl -d "q=CREATE USER $influxUsername WITH PASSWORD '$influxPassword' WITH ALL PRIVILEGES" -X POST http://localhost:8086/query + sed -ir 's/# auth-enabled = false/auth-enabled = true/g' /etc/influxdb/influxdb.conf + service influxdb restart + + sleep 2 + + ## Add Redis Admin user + redisPassword=$(openssl rand -hex 64) + echo "REDIS_PWD = '$redisPassword'" >>$BD/CentralService/cs_config + echo "REDIS_PWD = '$redisPassword'" >>$BD/DataService/ds_config + echo " REDIS_PWD = '$redisPassword'" >>$BD/CentralReplica/config.py + sed -i -e '/#.* requirepass / s/.*/ requirepass '$redisPassword'/' /etc/redis/redis.conf + service redis restart + + sleep 2 + + ## Add RabbitMQ Admin user + rabbitmqUsername="user$(openssl rand -hex 16)" + rabbitmqPassword=$(openssl rand -hex 32) + rabbitmqUsername_endUser="user$(openssl rand -hex 16)" + rabbitmqPassword_endUser=$(openssl rand -hex 32) + echo "RABBITMQ_ADMIN_USERNAME = '$rabbitmqUsername'" >>$BD/DataService/ds_config + echo "RABBITMQ_ADMIN_PWD = '$rabbitmqPassword'" >>$BD/DataService/ds_config + echo "RABBITMQ_ENDUSER_USERNAME = '$rabbitmqUsername_endUser'" >>$BD/DataService/ds_config + echo "RABBITMQ_ENDUSER_PWD = '$rabbitmqPassword_endUser'" >>$BD/DataService/ds_config + # Create a Admin user. + rabbitmqctl add_user "$rabbitmqUsername" "$rabbitmqPassword" + # Add Administrative Rights + rabbitmqctl set_user_tags "$rabbitmqUsername" administrator + # Grant necessary permissions + rabbitmqctl set_permissions -p / "$rabbitmqUsername" ".*" ".*" ".*" + # Create a End User. + rabbitmqctl add_user "$rabbitmqUsername_endUser" "$rabbitmqPassword_endUser" + # Add Permissions + rabbitmqctl set_user_tags "$rabbitmqUsername_endUser" + # Grant necessary permissions + rabbitmqctl set_permissions -p / "$rabbitmqUsername_endUser" "" "" ".*" + echo "BuildingDepot uses RabbitMQ Queues for Publishing and Subscribing to Sensor data. " + echo "Some web front-end use RabbitMQ Queues use rabbitmq_web_stomp plugin" + echo "Enter Y to install rabbitmq_web_stomp plugin: " + rabbitmq-plugins enable rabbitmq_web_stomp + + sleep 1 + + echo + echo "Auto-Generated User Credentials for BuildingDepot Packages [MongoDB,InfluxDB & Redis]" + echo } deploy_config install_packages if [ "$DEPLOY_CS" = true ]; then - deploy_centralservice + deploy_centralservice fi if [ "$DEPLOY_DS" = true ]; then - deploy_dataservice + deploy_dataservice fi service mongod start @@ -287,10 +288,9 @@ sleep 5 supervisorctl restart all service influxdb start - if [ "$DEPLOY_TOGETHER" = true ]; then - joint_deployment_fix - service nginx restart + joint_deployment_fix + service nginx restart fi rm -rf configs @@ -298,11 +298,14 @@ rm -rf configs popd setup_email - # Create Database on InfluxDB curl -d "q=CREATE DATABASE buildingdepot" -X POST http://localhost:8086/query setup_packages -/srv/buildingdepot/venv/bin/python2.7 setup_bd.py "test" +/srv/buildingdepot/venv/bin/python setup_bd.py "test" # echo -e "\nInstallation Finished..\n" -supervisorctl restart all \ No newline at end of file +supervisorctl restart all +cat /srv/buildingdepot/CentralService/cs_config +cat /srv/buildingdepot/DataService/ds_config +tail -500 /var/log/buildingdepot/CentralService/uwsgi-app.log +tail -500 /var/log/buildingdepot/DataService/uwsgi-app.log \ No newline at end of file diff --git a/scripts/bd_install.sh b/scripts/bd_install.sh index c95b4afa..1174feb0 100755 --- a/scripts/bd_install.sh +++ b/scripts/bd_install.sh @@ -43,8 +43,6 @@ function get_os_ver() { } get_os_ver - - DEPLOY_TOGETHER=true DEPLOY_CS=true DEPLOY_DS=true @@ -53,229 +51,223 @@ DEPLOY_DS=true # Check and make sure we are running as root or sudo (?) ################################################################################ if [[ $UID -ne 0 ]]; then - echo -e "\n$0 must be run as root. Most functions require super-user priviledges!\n" - exit 1 + echo -e "\n$0 must be run as root. Most functions require super-user priviledges!\n" + exit 1 fi BD=/srv/buildingdepot/ pushd $(pwd) - mkdir -p /srv/buildingdepot mkdir -p /var/log/buildingdepot/CentralService mkdir -p /var/log/buildingdepot/DataService mkdir -p /var/sockets || true # Deploy apps -function deploy_centralservice { - setup_venv /srv/buildingdepot/ - - #copy and untar new dataservice tarball - cp -r buildingdepot/CentralService /srv/buildingdepot/ - cp -r buildingdepot/DataService /srv/buildingdepot/ - cp -r buildingdepot/CentralReplica /srv/buildingdepot/ - #cp -r buildingdepot/OAuth2Server /srv/buildingdepot/ - cp -r buildingdepot/Documentation /srv/buildingdepot/ - cd /srv/buildingdepot - # copy uwsgi files - cp configs/uwsgi_cs.ini /etc/uwsgi/apps-available/cs.ini - - # Create supervisor config - cp configs/supervisor-cs.conf /etc/supervisor/conf.d/ - - # Create supervisor config for central replica - cp configs/supervisor-replica.conf /etc/supervisor/conf.d/ - - # Create nginx config - rm -f /etc/nginx/sites-enabled/default +function deploy_centralservice() { + setup_venv /srv/buildingdepot/ + + #copy and untar new dataservice tarball + cp -r buildingdepot/CentralService /srv/buildingdepot/ + cp -r buildingdepot/DataService /srv/buildingdepot/ + cp -r buildingdepot/CentralReplica /srv/buildingdepot/ + #cp -r buildingdepot/OAuth2Server /srv/buildingdepot/ + cp -r buildingdepot/Documentation /srv/buildingdepot/ + cd /srv/buildingdepot + # copy uwsgi files + cp configs/uwsgi_cs.ini /etc/uwsgi/apps-available/cs.ini + + # Create supervisor config + cp configs/supervisor-cs.conf /etc/supervisor/conf.d/ + + # Create supervisor config for central replica + cp configs/supervisor-replica.conf /etc/supervisor/conf.d/ + + # Create nginx config + rm -f /etc/nginx/sites-enabled/default } -function deploy_dataservice { - setup_venv /srv/buildingdepot/ +function deploy_dataservice() { + setup_venv /srv/buildingdepot/ - cd /srv/buildingdepot + cd /srv/buildingdepot - # copy uwsgi files - cp configs/uwsgi_ds.ini /etc/uwsgi/apps-available/ds.ini + # copy uwsgi files + cp configs/uwsgi_ds.ini /etc/uwsgi/apps-available/ds.ini - # Create supervisor config - cp configs/supervisor-ds.conf /etc/supervisor/conf.d/ + # Create supervisor config + cp configs/supervisor-ds.conf /etc/supervisor/conf.d/ - # Create nginx config - rm -f /etc/nginx/sites-enabled/default + # Create nginx config + rm -f /etc/nginx/sites-enabled/default } - -function joint_deployment_fix { - # Create join nginx config - rm -f /etc/nginx/sites-enabled/default - cd /srv/buildingdepot - #Setting up SSL - ssl_conf_file='configs/bd_config.json' - get_config_value 'cert_path' - cert_path=$res - get_config_value 'key_path' - key_path=$res - get_config_value 'domain' - domain=$res - sed -i "s||$cert_path|g" /srv/buildingdepot/configs/together_ssl.conf - sed -i "s||$key_path|g" /srv/buildingdepot/configs/together_ssl.conf - sed -i "s||$domain|g" /srv/buildingdepot/configs/together_ssl.conf - cp configs/together_ssl.conf /etc/nginx/sites-available/together.conf - ln -sf /etc/nginx/sites-available/together.conf /etc/nginx/sites-enabled/together.conf +function joint_deployment_fix() { + # Create join nginx config + rm -f /etc/nginx/sites-enabled/default + cd /srv/buildingdepot + #Setting up SSL + ssl_conf_file='configs/bd_config.json' + get_config_value 'cert_path' + cert_path=$res + get_config_value 'key_path' + key_path=$res + get_config_value 'domain' + domain=$res + sed -i "s||$cert_path|g" /srv/buildingdepot/configs/together_ssl.conf + sed -i "s||$key_path|g" /srv/buildingdepot/configs/together_ssl.conf + sed -i "s||$domain|g" /srv/buildingdepot/configs/together_ssl.conf + cp configs/together_ssl.conf /etc/nginx/sites-available/together.conf + ln -sf /etc/nginx/sites-available/together.conf /etc/nginx/sites-enabled/together.conf } -function deploy_config { - cp -r configs/ /srv/buildingdepot - mkdir -p /var/sockets || true +function deploy_config() { + cp -r configs/ /srv/buildingdepot + mkdir -p /var/sockets || true } -function install_packages { - apt-get install -y curl jq - apt-get install -y apt-transport-https - # Update rabbitmq key - wget -O - "https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc" | sudo apt-key add - - echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list - curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add - - source /etc/lsb-release - echo "deb https://repos.influxdata.com/${DISTRIB_ID,,} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list - sleep 10 - apt-get update - apt-get install - apt-get -y install python-pip - apt-get install -y mongodb - apt-get install -y openssl python-setuptools python-dev build-essential - if [ "$OS_VER" = "18.04" ]; - then - apt-get install -y software-properties-common - else - apt-get install -y python-software-properties - fi - apt-get install -y nginx - apt-get install -y supervisor - apt-get install -y redis-server - pip install --upgrade virtualenv - apt-get install -y wget - sudo apt-get install -y influxdb - sudo service influxdb start - sleep 10 - sudo apt-get install rabbitmq-server - #sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf - #service postfix restart +function install_packages() { + apt-get install -y curl jq + apt-get install -y apt-transport-https + # Update rabbitmq key + wget -O - "https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc" | sudo apt-key add - + echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list + curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add - + source /etc/lsb-release + echo "deb https://repos.influxdata.com/${DISTRIB_ID,,} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list + sleep 10 + apt-get update + apt-get install + apt-get -y install python3-pip + apt-get install -y mongodb + apt-get install -y openssl python3-setuptools python3-dev build-essential + if [ "$OS_VER" = "18.04" ]; then + apt-get install -y software-properties-common + else + apt-get install -y python3-software-properties + fi + apt-get install -y nginx + apt-get install -y supervisor + apt-get install -y redis-server + pip3 install --upgrade virtualenv + apt-get install -y wget + sudo apt-get install -y influxdb + sudo service influxdb start + sleep 10 + sudo apt-get install rabbitmq-server + #sed -i -e 's/"inet_interfaces = all/"inet_interfaces = loopback-only"/g' /etc/postfix/main.cf + #service postfix restart } -function setup_venv { - cp -f pip_packages.list $1 || true - cd $1 +function setup_venv() { + cp -f pip_packages.list $1 || true + cd $1 - virtualenv ./venv - source venv/bin/activate + virtualenv ./venv + source venv/bin/activate - pip install --upgrade pip - pip install --upgrade setuptools - pip install --upgrade -r pip_packages.list + pip3 install --upgrade pip + pip3 install --upgrade setuptools + pip3 install --upgrade -r pip_packages.list - pip install --upgrade uWSGI - mkdir -p /etc/uwsgi/apps-available/ + pip3 install --upgrade uWSGI + mkdir -p /etc/uwsgi/apps-available/ - deactivate - cd - + deactivate + cd - } -function get_config_value { - prefix='.["' - postfix='"]' - res=$(jq $prefix$1$postfix configs/bd_config.json) - res=${res:1:$(expr ${#res} - 2)} +function get_config_value() { + prefix='.["' + postfix='"]' + res=$(jq $prefix$1$postfix configs/bd_config.json) + res=${res:1:$(expr ${#res} - 2)} } -function set_redis_credentials { +function set_redis_credentials() { echo $BD get_config_value 'redis_pwd' redis_pwd=$res - echo "REDIS_PWD = '$redis_pwd'">> $BD/CentralService/cs_config - echo "">> $BD/DataService/ds_config - echo "REDIS_PWD = '$redis_pwd'">> $BD/DataService/ds_config - echo " REDIS_PWD = '$redis_pwd'" >> $BD/CentralReplica/config.py + echo "REDIS_PWD = '$redis_pwd'" >>$BD/CentralService/cs_config + echo "" >>$BD/DataService/ds_config + echo "REDIS_PWD = '$redis_pwd'" >>$BD/DataService/ds_config + echo " REDIS_PWD = '$redis_pwd'" >>$BD/CentralReplica/config.py sed -i -e '/#.* requirepass / s/.*/ requirepass '$redis_pwd'/' /etc/redis/redis.conf service redis restart } -function set_influxdb_credentials { +function set_influxdb_credentials() { ## Add InfluxDB Admin user get_config_value 'influx_user' influx_user=$res get_config_value 'influx_pwd' influx_pwd=$res - echo "INFLUXDB_USERNAME = '$influx_user'">> $BD/DataService/ds_config - echo "INFLUXDB_PWD = '$influx_pwd'">> $BD/DataService/ds_config + echo "INFLUXDB_USERNAME = '$influx_user'" >>$BD/DataService/ds_config + echo "INFLUXDB_PWD = '$influx_pwd'" >>$BD/DataService/ds_config sleep 1 curl -d "q=CREATE USER $influx_user WITH PASSWORD '$influx_pwd' WITH ALL PRIVILEGES" -X POST http://localhost:8086/query sed -ir 's/# auth-enabled = false/auth-enabled = true/g' /etc/influxdb/influxdb.conf - service influxdb restart + service influxdb restart } -function set_mongodb_credentials { +function set_mongodb_credentials() { get_config_value 'mongo_user' mongo_user=$res get_config_value 'mongo_pwd' mongo_pwd=$res - echo "MONGODB_USERNAME = '$mongo_user'" >> $BD/CentralService/cs_config - echo "MONGODB_PWD = '$mongo_pwd'" >> $BD/CentralService/cs_config - echo "MONGODB_USERNAME = '$mongo_user'" >> $BD/DataService/ds_config - echo "MONGODB_PWD = '$mongo_pwd'" >> $BD/DataService/ds_config - echo " MONGODB_USERNAME = '$mongo_user'" >> $BD/CentralReplica/config.py - echo " MONGODB_PWD = '$mongo_pwd'" >> $BD/CentralReplica/config.py + echo "MONGODB_USERNAME = '$mongo_user'" >>$BD/CentralService/cs_config + echo "MONGODB_PWD = '$mongo_pwd'" >>$BD/CentralService/cs_config + echo "MONGODB_USERNAME = '$mongo_user'" >>$BD/DataService/ds_config + echo "MONGODB_PWD = '$mongo_pwd'" >>$BD/DataService/ds_config + echo " MONGODB_USERNAME = '$mongo_user'" >>$BD/CentralReplica/config.py + echo " MONGODB_PWD = '$mongo_pwd'" >>$BD/CentralReplica/config.py mongo --eval "db.getSiblingDB('admin').createUser({user:'$mongo_user',pwd:'$mongo_pwd',roles:['userAdminAnyDatabase','dbAdminAnyDatabase','readWriteAnyDatabase']})" || true # Enable MongoDB authorization lastline=$(cat /etc/mongodb.conf | tail -1) auth_opt="auth = true" if ["$lastline" != "$auth_opt"]; then - echo $auth_opt >> /etc/mongodb.conf + echo $auth_opt >>/etc/mongodb.conf fi #echo "security:" >> /etc/mongod.conf #echo " authorization: \"enabled\"">> /etc/mongod.conf service mongodb restart - } - -function set_credentials { +function set_credentials() { set_redis_credentials set_influxdb_credentials set_mongodb_credentials } - -function setup_gmail { - get_config_value 'client_id' - client_id=$res - get_config_value 'client_secret' - client_secret=$res - get_config_value 'email' - email_id=$res - get_config_value 'refresh_token' - refresh_token=$res - get_config_value 'access_token' - access_token=$res - echo "EMAIL = 'GMAIL'" >> $BD/CentralService/cs_config - echo "EMAIL_ID = '$email_id'" >> $BD/CentralService/cs_config - echo "ACCESS_TOKEN = '$access_token'" >> $BD/CentralService/cs_config - echo "REFRESH_TOKEN = '$refresh_token'" >> $BD/CentralService/cs_config - echo "CLIENT_ID = '$client_id'" >> $BD/CentralService/cs_config - echo "CLIENT_SECRET = '$client_secret'" >> $BD/CentralService/cs_config +function setup_gmail() { + get_config_value 'client_id' + client_id=$res + get_config_value 'client_secret' + client_secret=$res + get_config_value 'email' + email_id=$res + get_config_value 'refresh_token' + refresh_token=$res + get_config_value 'access_token' + access_token=$res + echo "EMAIL = 'GMAIL'" >>$BD/CentralService/cs_config + echo "EMAIL_ID = '$email_id'" >>$BD/CentralService/cs_config + echo "ACCESS_TOKEN = '$access_token'" >>$BD/CentralService/cs_config + echo "REFRESH_TOKEN = '$refresh_token'" >>$BD/CentralService/cs_config + echo "CLIENT_ID = '$client_id'" >>$BD/CentralService/cs_config + echo "CLIENT_SECRET = '$client_secret'" >>$BD/CentralService/cs_config } deploy_config install_packages cp configs/nginx.conf /etc/nginx/nginx.conf if [ "$DEPLOY_CS" = true ]; then - deploy_centralservice + deploy_centralservice fi if [ "$DEPLOY_DS" = true ]; then - deploy_dataservice + deploy_dataservice fi /etc/init.d/mongodb start @@ -288,8 +280,8 @@ service influxdb start set_credentials if [ "$DEPLOY_TOGETHER" = true ]; then - joint_deployment_fix - service nginx restart + joint_deployment_fix + service nginx restart fi rm -rf configs @@ -299,7 +291,7 @@ setup_gmail #curl -G http://localhost:8086/query --data-urlencode "q=CREATE DATABASE buildingdepot" #TODO: Implement this with admin account echo -e "\nInstallation Finished..\n" -/srv/buildingdepot/venv/bin/python2.7 setup_bd.py "bd_install" +/srv/buildingdepot/venv/bin/python setup_bd.py "bd_install" echo -e "Created a super user with following credentials. Please login and change password immediately \n user id : admin@buildingdepot.org \n password: admin" supervisorctl restart cs diff --git a/scripts/bd_oauth2.py b/scripts/bd_oauth2.py index 58201e11..06389ec4 100644 --- a/scripts/bd_oauth2.py +++ b/scripts/bd_oauth2.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # # Copyright 2012 Google Inc. # @@ -6,7 +6,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # - # http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -63,294 +63,322 @@ import base64 import imaplib import json -from optparse import OptionParser import smtplib import sys -import urllib +import urllib.error +import urllib.parse +import urllib.request +from optparse import OptionParser def SetupOptionParser(): - # Usage message is the module's docstring. - parser = OptionParser(usage=__doc__) - parser.add_option('--generate_oauth2_token', - action='store_true', - dest='generate_oauth2_token', - help='generates an OAuth2 token for testing') - parser.add_option('--generate_oauth2_string', - action='store_true', - dest='generate_oauth2_string', - help='generates an initial client response string for ' - 'OAuth2') - parser.add_option('--client_id', - default=None, - help='Client ID of the application that is authenticating. ' - 'See OAuth2 documentation for details.') - parser.add_option('--client_secret', - default=None, - help='Client secret of the application that is ' - 'authenticating. See OAuth2 documentation for ' - 'details.') - parser.add_option('--access_token', - default=None, - help='OAuth2 access token') - parser.add_option('--refresh_token', - default=None, - help='OAuth2 refresh token') - parser.add_option('--scope', - default='https://mail.google.com/', - help='scope for the access token. Multiple scopes can be ' - 'listed separated by spaces with the whole argument ' - 'quoted.') - parser.add_option('--test_imap_authentication', - action='store_true', - dest='test_imap_authentication', - help='attempts to authenticate to IMAP') - parser.add_option('--test_smtp_authentication', - action='store_true', - dest='test_smtp_authentication', - help='attempts to authenticate to SMTP') - parser.add_option('--user', - default=None, - help='email address of user whose account is being ' - 'accessed') - return parser + # Usage message is the module's docstring. + parser = OptionParser(usage=__doc__) + parser.add_option( + "--generate_oauth2_token", + action="store_true", + dest="generate_oauth2_token", + help="generates an OAuth2 token for testing", + ) + parser.add_option( + "--generate_oauth2_string", + action="store_true", + dest="generate_oauth2_string", + help="generates an initial client response string for " "OAuth2", + ) + parser.add_option( + "--client_id", + default=None, + help="Client ID of the application that is authenticating. " + "See OAuth2 documentation for details.", + ) + parser.add_option( + "--client_secret", + default=None, + help="Client secret of the application that is " + "authenticating. See OAuth2 documentation for " + "details.", + ) + parser.add_option("--access_token", default=None, help="OAuth2 access token") + parser.add_option("--refresh_token", default=None, help="OAuth2 refresh token") + parser.add_option( + "--scope", + default="https://mail.google.com/", + help="scope for the access token. Multiple scopes can be " + "listed separated by spaces with the whole argument " + "quoted.", + ) + parser.add_option( + "--test_imap_authentication", + action="store_true", + dest="test_imap_authentication", + help="attempts to authenticate to IMAP", + ) + parser.add_option( + "--test_smtp_authentication", + action="store_true", + dest="test_smtp_authentication", + help="attempts to authenticate to SMTP", + ) + parser.add_option( + "--user", + default=None, + help="email address of user whose account is being " "accessed", + ) + return parser # The URL root for accessing Google Accounts. -GOOGLE_ACCOUNTS_BASE_URL = 'https://accounts.google.com' - +GOOGLE_ACCOUNTS_BASE_URL = "https://accounts.google.com" # Hardcoded dummy redirect URI for non-web apps. -REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob' +REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob" def AccountsUrl(command): - """Generates the Google Accounts URL. + """Generates the Google Accounts URL. - Args: - command: The command to execute. + Args: + command: The command to execute. - Returns: - A URL for the given command. - """ - return '%s/%s' % (GOOGLE_ACCOUNTS_BASE_URL, command) + Returns: + A URL for the given command. + """ + return "%s/%s" % (GOOGLE_ACCOUNTS_BASE_URL, command) def UrlEscape(text): - # See OAUTH 5.1 for a definition of which characters need to be escaped. - return urllib.quote(text, safe='~-._') + # See OAUTH 5.1 for a definition of which characters need to be escaped. + return urllib.parse.quote(text, safe="~-._") def UrlUnescape(text): - # See OAUTH 5.1 for a definition of which characters need to be escaped. - return urllib.unquote(text) + # See OAUTH 5.1 for a definition of which characters need to be escaped. + return urllib.parse.unquote(text) def FormatUrlParams(params): - """Formats parameters into a URL query string. + """Formats parameters into a URL query string. - Args: - params: A key-value map. + Args: + params: A key-value map. - Returns: - A URL query string version of the given parameters. - """ - param_fragments = [] - for param in sorted(params.iteritems(), key=lambda x: x[0]): - param_fragments.append('%s=%s' % (param[0], UrlEscape(param[1]))) - return '&'.join(param_fragments) + Returns: + A URL query string version of the given parameters. + """ + param_fragments = [] + for param in sorted(iter(list(params.items())), key=lambda x: x[0]): + param_fragments.append("%s=%s" % (param[0], UrlEscape(param[1]))) + return "&".join(param_fragments) -def GeneratePermissionUrl(client_id, scope='https://mail.google.com/'): - """Generates the URL for authorizing access. +def GeneratePermissionUrl(client_id, scope="https://mail.google.com/"): + """Generates the URL for authorizing access. - This uses the "OAuth2 for Installed Applications" flow described at - https://developers.google.com/accounts/docs/OAuth2InstalledApp + This uses the "OAuth2 for Installed Applications" flow described at + https://developers.google.com/accounts/docs/OAuth2InstalledApp - Args: - client_id: Client ID obtained by registering your app. - scope: scope for access token, e.g. 'https://mail.google.com' - Returns: - A URL that the user should visit in their browser. - """ - params = {} - params['client_id'] = client_id - params['redirect_uri'] = REDIRECT_URI - params['scope'] = scope - params['response_type'] = 'code' - return '%s?%s' % (AccountsUrl('o/oauth2/auth'), - FormatUrlParams(params)) + Args: + client_id: Client ID obtained by registering your app. + scope: scope for access token, e.g. 'https://mail.google.com' + Returns: + A URL that the user should visit in their browser. + """ + params = {} + params["client_id"] = client_id + params["redirect_uri"] = REDIRECT_URI + params["scope"] = scope + params["response_type"] = "code" + return "%s?%s" % (AccountsUrl("o/oauth2/auth"), FormatUrlParams(params)) def AuthorizeTokens(client_id, client_secret, authorization_code): - """Obtains OAuth access token and refresh token. - - This uses the application portion of the "OAuth2 for Installed Applications" - flow at https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse - - Args: - client_id: Client ID obtained by registering your app. - client_secret: Client secret obtained by registering your app. - authorization_code: code generated by Google Accounts after user grants - permission. - Returns: - The decoded response from the Google Accounts server, as a dict. Expected - fields include 'access_token', 'expires_in', and 'refresh_token'. - """ - params = {} - params['client_id'] = client_id - params['client_secret'] = client_secret - params['code'] = authorization_code - params['redirect_uri'] = REDIRECT_URI - params['grant_type'] = 'authorization_code' - request_url = AccountsUrl('o/oauth2/token') - - response = urllib.urlopen(request_url, urllib.urlencode(params)).read() - return json.loads(response) + """Obtains OAuth access token and refresh token. + + This uses the application portion of the "OAuth2 for Installed Applications" + flow at https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse + + Args: + client_id: Client ID obtained by registering your app. + client_secret: Client secret obtained by registering your app. + authorization_code: code generated by Google Accounts after user grants + permission. + Returns: + The decoded response from the Google Accounts server, as a dict. Expected + fields include 'access_token', 'expires_in', and 'refresh_token'. + """ + params = {} + params["client_id"] = client_id + params["client_secret"] = client_secret + params["code"] = authorization_code + params["redirect_uri"] = REDIRECT_URI + params["grant_type"] = "authorization_code" + request_url = AccountsUrl("o/oauth2/token") + + response = urllib.request.urlopen( + request_url, urllib.parse.urlencode(params) + ).read() + return json.loads(response) def RefreshToken(client_id, client_secret, refresh_token): - """Obtains a new token given a refresh token. - - See https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh - - Args: - client_id: Client ID obtained by registering your app. - client_secret: Client secret obtained by registering your app. - refresh_token: A previously-obtained refresh token. - Returns: - The decoded response from the Google Accounts server, as a dict. Expected - fields include 'access_token', 'expires_in', and 'refresh_token'. - """ - params = {} - params['client_id'] = client_id - params['client_secret'] = client_secret - params['refresh_token'] = refresh_token - params['grant_type'] = 'refresh_token' - request_url = AccountsUrl('o/oauth2/token') - - response = urllib.urlopen(request_url, urllib.urlencode(params)).read() - return json.loads(response) + """Obtains a new token given a refresh token. + + See https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh + + Args: + client_id: Client ID obtained by registering your app. + client_secret: Client secret obtained by registering your app. + refresh_token: A previously-obtained refresh token. + Returns: + The decoded response from the Google Accounts server, as a dict. Expected + fields include 'access_token', 'expires_in', and 'refresh_token'. + """ + params = {} + params["client_id"] = client_id + params["client_secret"] = client_secret + params["refresh_token"] = refresh_token + params["grant_type"] = "refresh_token" + request_url = AccountsUrl("o/oauth2/token") + + response = urllib.request.urlopen( + request_url, urllib.parse.urlencode(params) + ).read() + return json.loads(response) def GenerateOAuth2String(username, access_token, base64_encode=True): - """Generates an IMAP OAuth2 authentication string. + """Generates an IMAP OAuth2 authentication string. - See https://developers.google.com/google-apps/gmail/oauth2_overview + See https://developers.google.com/google-apps/gmail/oauth2_overview - Args: - username: the username (email address) of the account to authenticate - access_token: An OAuth2 access token. - base64_encode: Whether to base64-encode the output. + Args: + username: the username (email address) of the account to authenticate + access_token: An OAuth2 access token. + base64_encode: Whether to base64-encode the output. - Returns: - The SASL argument for the OAuth2 mechanism. - """ - auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token) - if base64_encode: - auth_string = base64.b64encode(auth_string) - return auth_string + Returns: + The SASL argument for the OAuth2 mechanism. + """ + auth_string = "user=%s\1auth=Bearer %s\1\1" % (username, access_token) + if base64_encode: + auth_string = base64.b64encode(auth_string) + return auth_string def TestImapAuthentication(user, auth_string): - """Authenticates to IMAP with the given auth_string. + """Authenticates to IMAP with the given auth_string. - Prints a debug trace of the attempted IMAP connection. + Prints a debug trace of the attempted IMAP connection. - Args: - user: The Gmail username (full email address) - auth_string: A valid OAuth2 string, as returned by GenerateOAuth2String. - Must not be base64-encoded, since imaplib does its own base64-encoding. - """ - print - imap_conn = imaplib.IMAP4_SSL('imap.gmail.com') - imap_conn.debug = 4 - imap_conn.authenticate('XOAUTH2', lambda x: auth_string) - imap_conn.select('INBOX') + Args: + user: The Gmail username (full email address) + auth_string: A valid OAuth2 string, as returned by GenerateOAuth2String. + Must not be base64-encoded, since imaplib does its own base64-encoding. + """ + print() + imap_conn = imaplib.IMAP4_SSL("imap.gmail.com") + imap_conn.debug = 4 + imap_conn.authenticate("XOAUTH2", lambda x: auth_string) + imap_conn.select("INBOX") def TestSmtpAuthentication(user, auth_string): - """Authenticates to SMTP with the given auth_string. + """Authenticates to SMTP with the given auth_string. - Args: - user: The Gmail username (full email address) - auth_string: A valid OAuth2 string, not base64-encoded, as returned by - GenerateOAuth2String. - """ - print - smtp_conn = smtplib.SMTP('smtp.gmail.com', 587) - smtp_conn.set_debuglevel(True) - smtp_conn.ehlo('test') - smtp_conn.starttls() - smtp_conn.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_string)) + Args: + user: The Gmail username (full email address) + auth_string: A valid OAuth2 string, not base64-encoded, as returned by + GenerateOAuth2String. + """ + print() + smtp_conn = smtplib.SMTP("smtp.gmail.com", 587) + smtp_conn.set_debuglevel(True) + smtp_conn.ehlo("test") + smtp_conn.starttls() + smtp_conn.docmd("AUTH", "XOAUTH2 " + base64.b64encode(auth_string)) def RequireOptions(options, *args): - missing = [arg for arg in args if getattr(options, arg) is None] - if missing: - print 'Missing options: %s' % ' '.join(missing) - sys.exit(-1) + missing = [arg for arg in args if getattr(options, arg) is None] + if missing: + print(("Missing options: %s" % " ".join(missing))) + sys.exit(-1) def main(argv): - options_parser = SetupOptionParser() - (options, args) = options_parser.parse_args() - if options.refresh_token: - RequireOptions(options, 'client_id', 'client_secret') - response = RefreshToken(options.client_id, options.client_secret, - options.refresh_token) - print 'Access Token: %s' % response['access_token'] - print 'Access Token Expiration Seconds: %s' % response['expires_in'] - elif options.generate_oauth2_string: - RequireOptions(options, 'user', 'access_token') - print ('OAuth2 argument:\n' + - GenerateOAuth2String(options.user, options.access_token)) - elif options.generate_oauth2_token: - RequireOptions(options, 'client_id', 'client_secret') - print 'To authorize token, visit this url and follow the directions:' - print ' %s' % GeneratePermissionUrl(options.client_id, options.scope) - authorization_code = raw_input('Enter verification code: ') - response = AuthorizeTokens(options.client_id, options.client_secret, - authorization_code) - print 'Refresh Token: %s' % response['refresh_token'] - print 'Access Token: %s' % response['access_token'] - print 'Access Token Expiration Seconds: %s' % response['expires_in'] - elif options.test_imap_authentication: - RequireOptions(options, 'user', 'access_token') - TestImapAuthentication(options.user, - GenerateOAuth2String(options.user, options.access_token, - base64_encode=False)) - elif options.test_smtp_authentication: - RequireOptions(options, 'user', 'access_token') - TestSmtpAuthentication(options.user, - GenerateOAuth2String(options.user, options.access_token, - base64_encode=False)) - else: - options_parser.print_help() - print 'Nothing to do, exiting.' - return + options_parser = SetupOptionParser() + (options, args) = options_parser.parse_args() + if options.refresh_token: + RequireOptions(options, "client_id", "client_secret") + response = RefreshToken( + options.client_id, options.client_secret, options.refresh_token + ) + print(("Access Token: %s" % response["access_token"])) + print(("Access Token Expiration Seconds: %s" % response["expires_in"])) + elif options.generate_oauth2_string: + RequireOptions(options, "user", "access_token") + print( + ( + "OAuth2 argument:\n" + + GenerateOAuth2String(options.user, options.access_token) + ) + ) + elif options.generate_oauth2_token: + RequireOptions(options, "client_id", "client_secret") + print("To authorize token, visit this url and follow the directions:") + print((" %s" % GeneratePermissionUrl(options.client_id, options.scope))) + authorization_code = eval(input("Enter verification code: ")) + response = AuthorizeTokens( + options.client_id, options.client_secret, authorization_code + ) + print(("Refresh Token: %s" % response["refresh_token"])) + print(("Access Token: %s" % response["access_token"])) + print(("Access Token Expiration Seconds: %s" % response["expires_in"])) + elif options.test_imap_authentication: + RequireOptions(options, "user", "access_token") + TestImapAuthentication( + options.user, + GenerateOAuth2String( + options.user, options.access_token, base64_encode=False + ), + ) + elif options.test_smtp_authentication: + RequireOptions(options, "user", "access_token") + TestSmtpAuthentication( + options.user, + GenerateOAuth2String( + options.user, options.access_token, base64_encode=False + ), + ) + else: + options_parser.print_help() + print("Nothing to do, exiting.") + return + def main_bd(): - #oauth2 --user=xxx@gmail.com \ - # --client_id=1038[...].apps.googleusercontent.com \ - # --client_secret=VWFn8LIKAMC-MsjBMhJeOplZ \ - # --generate_oauth2_token - cred_filename = 'configs/bd_config.json' - with open(cred_filename, 'r') as fp: - d = json.load(fp) - cid = d['client_id'] - csec = d['client_secret'] - print 'To authorize token, visit this url and follow the directions:' - print ' %s' % GeneratePermissionUrl(cid) - authorization_code = raw_input('Enter verification code: ') - response = AuthorizeTokens(cid, csec, authorization_code) - with open(cred_filename, 'r') as fp: - d = json.load(fp) - d['refresh_token'] = response['refresh_token'] - d['access_token'] = response['access_token'] - with open(cred_filename, 'w') as fp: - json.dump(d, fp, indent=2) - -if __name__ == '__main__': - #main(sys.argv) - main_bd() + # oauth2 --user=xxx@gmail.com \ + # --client_id=1038[...].apps.googleusercontent.com \ + # --client_secret=VWFn8LIKAMC-MsjBMhJeOplZ \ + # --generate_oauth2_token + cred_filename = "configs/bd_config.json" + with open(cred_filename, "r") as fp: + d = json.load(fp) + cid = d["client_id"] + csec = d["client_secret"] + print("To authorize token, visit this url and follow the directions:") + print((" %s" % GeneratePermissionUrl(cid))) + authorization_code = eval(input("Enter verification code: ")) + response = AuthorizeTokens(cid, csec, authorization_code) + with open(cred_filename, "r") as fp: + d = json.load(fp) + d["refresh_token"] = response["refresh_token"] + d["access_token"] = response["access_token"] + with open(cred_filename, "w") as fp: + json.dump(d, fp, indent=2) + + +if __name__ == "__main__": + # main(sys.argv) + main_bd() diff --git a/scripts/upgrade_to_latest_BD.sh b/scripts/upgrade_to_latest_BD.sh index 0a35d710..8ae7b8ea 100755 --- a/scripts/upgrade_to_latest_BD.sh +++ b/scripts/upgrade_to_latest_BD.sh @@ -1,6 +1,6 @@ #!/bin/bash -echo "Please make sure you have downloaded the latest Building Depot version and are in the BD scritps folder!" +echo "Please make sure you have downloaded the latest Building Depot version and are in the BD scripts folder!" read response cd ../ cd buildingdepot diff --git a/setup_bd.py b/setup_bd.py index 63e6cc8f..a0af0434 100644 --- a/setup_bd.py +++ b/setup_bd.py @@ -1,66 +1,88 @@ -#!/srv/buildingdepot/venv/bin/python2.7 -import json, sys -import StringIO, os, ConfigParser -import string, random +#!/srv/buildingdepot/venv/bin/python +import configparser +import io +import json +import os +import random +import string +import sys from pymongo import MongoClient from werkzeug.security import generate_password_hash -print "Setting up BuildingDepot.. " +print("Setting up BuildingDepot.. ") option = sys.argv[1:][0] # get arguments # Create a temporary password -tempPwd = ''.join(random.choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) for _ in range(16)) +tempPwd = "".join( + random.choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) + for _ in range(16) +) # Get Username and Password for MongoDB -if (option == "install"): - configBuffer = StringIO.StringIO() - configBuffer.write('[dummysection]\n') - configBuffer.write(open('/srv/buildingdepot/CentralService/cs_config').read()) +if option == "install": + configBuffer = io.StringIO() + configBuffer.write("[dummysection]\n") + configBuffer.write(open("/srv/buildingdepot/CentralService/cs_config").read()) configBuffer.seek(0, os.SEEK_SET) - config = ConfigParser.ConfigParser() - config.readfp(configBuffer) - user = config.get('dummysection', 'MONGODB_USERNAME').strip("'").strip('"') - pwd = config.get('dummysection', 'MONGODB_PWD').strip("'").strip('"') + config = configparser.ConfigParser() + config.read_file(configBuffer) + user = config.get("dummysection", "MONGODB_USERNAME").strip("'").strip('"') + pwd = config.get("dummysection", "MONGODB_PWD").strip("'").strip('"') -elif (option == "bd_install"): - configs = json.load(open('configs/bd_config.json', 'r')) - user = configs['mongo_user'] - pwd = configs['mongo_pwd'] +elif option == "bd_install": + configs = json.load(open("configs/bd_config.json", "r")) + user = configs["mongo_user"] + pwd = configs["mongo_pwd"] -elif (option == "test"): - configBuffer = StringIO.StringIO() - configBuffer.write('[dummysection]\n') - configBuffer.write(open('/srv/buildingdepot/CentralService/cs_config').read()) +elif option == "test": + configBuffer = io.StringIO() + configBuffer.write("[dummysection]\n") + configBuffer.write(open("/srv/buildingdepot/CentralService/cs_config").read()) configBuffer.seek(0, os.SEEK_SET) - config = ConfigParser.ConfigParser() - config.readfp(configBuffer) - user = config.get('dummysection', 'MONGODB_USERNAME').strip("'").strip('"') - pwd = config.get('dummysection', 'MONGODB_PWD').strip("'").strip('"') - configs = json.load(open('benchmarking-tools/functional-testing-tool/tests/config.json', 'r')) + config = configparser.ConfigParser() + config.read_file(configBuffer) + user = config.get("dummysection", "MONGODB_USERNAME").strip("'").strip('"') + pwd = config.get("dummysection", "MONGODB_PWD").strip("'").strip('"') + configs = json.load( + open("benchmarking-tools/functional-testing-tool/tests/config.json", "r") + ) test_config = dict(configs) - test_config['password'] = tempPwd - with open('benchmarking-tools/functional-testing-tool/tests/config.json', 'w') as output: + test_config["password"] = tempPwd + with open( + "benchmarking-tools/functional-testing-tool/tests/config.json", "w" + ) as output: json.dump(test_config, output) + else: exit(0) # Create BuildingDepot Database -client = MongoClient(username=user, - password=pwd, - authSource='admin') +client = MongoClient(username=user, password=pwd, authSource="admin") db = client.buildingdepot -db.user.insert({"email": "admin@buildingdepot.org", - "password": generate_password_hash(tempPwd), - "first_name": "Admin", - "first_login": True, - "role": "super"}) -db.data_service.insert({'name': 'ds1', - 'description': '', - 'host': '127.0.0.1', - 'port': 82, - 'buildings': [], - 'admins': []}) +db.user.insert_one( + { + "email": "admin@buildingdepot.org", + "password": generate_password_hash(tempPwd), + "first_name": "Admin", + "first_login": True, + "role": "super", + } +) +db.data_service.insert_one( + { + "name": "ds1", + "description": "", + "host": "127.0.0.1", + "port": 82, + "buildings": [], + "admins": [], + } +) -print "\n Created a super user with following credentials. Please login and change password immediately \n user id " \ - ": admin@buildingdepot.org \n password: " + tempPwd \ No newline at end of file +print( + ( + "\n Created a super user with following credentials. Please login and change password immediately \n user id " + ": admin@buildingdepot.org \n password: " + tempPwd + ) +)